mirror of
https://github.com/Eugeny/tabby.git
synced 2025-08-18 23:31:51 +00:00
Compare commits
28 Commits
Author | SHA1 | Date | |
---|---|---|---|
![]() |
891fa5770a | ||
![]() |
6b395cc2b3 | ||
![]() |
448a1a094f | ||
![]() |
788dd61a13 | ||
![]() |
467684d9ab | ||
![]() |
5069070040 | ||
![]() |
ecf5297bc3 | ||
![]() |
78bd90ac55 | ||
![]() |
712589eb93 | ||
![]() |
f103e71285 | ||
![]() |
0cf883cc4a | ||
![]() |
2b0ad0d558 | ||
![]() |
67bacb9dd3 | ||
![]() |
d0a597634d | ||
![]() |
322014c409 | ||
![]() |
c751a8725b | ||
![]() |
5417efe558 | ||
![]() |
bf356fcd19 | ||
![]() |
10ee66b9dd | ||
![]() |
763da0d80c | ||
![]() |
8d46bb2181 | ||
![]() |
fe936c7726 | ||
![]() |
2f3e32990a | ||
![]() |
22344f8d54 | ||
![]() |
f6d37a39f4 | ||
![]() |
0e4c60ad4b | ||
![]() |
e8c2171d8f | ||
![]() |
f7a5be2c67 |
4
.github/workflows/macos.yml
vendored
4
.github/workflows/macos.yml
vendored
@@ -26,6 +26,10 @@ jobs:
|
||||
cd ..
|
||||
rm app/node_modules/.yarn-integrity
|
||||
yarn
|
||||
./node_modules/.bin/patch-package
|
||||
cd app
|
||||
../node_modules/.bin/patch-package
|
||||
cd ..
|
||||
|
||||
- name: Build native deps
|
||||
run: scripts/build-native.js
|
||||
|
@@ -1,4 +1,4 @@
|
||||
****
|
||||

|
||||
|
||||
|
||||
<p align="center">
|
||||
|
@@ -75,7 +75,7 @@ export class Application {
|
||||
}
|
||||
|
||||
onGlobalHotkey (): void {
|
||||
if (this.windows.some(x => x.isFocused())) {
|
||||
if (this.windows.some(x => x.isFocused() && x.isVisible())) {
|
||||
for (const window of this.windows) {
|
||||
window.hide()
|
||||
}
|
||||
@@ -147,7 +147,7 @@ export class Application {
|
||||
|
||||
handleSecondInstance (argv: string[], cwd: string): void {
|
||||
this.presentAllWindows()
|
||||
this.windows[this.windows.length - 1].handleSecondInstance(argv, cwd)
|
||||
this.windows[this.windows.length - 1].passCliArguments(argv, cwd, true)
|
||||
}
|
||||
|
||||
private setupMenu () {
|
||||
|
@@ -53,7 +53,7 @@ if (argv.d) {
|
||||
})
|
||||
}
|
||||
|
||||
app.on('ready', () => {
|
||||
app.on('ready', async () => {
|
||||
if (process.platform === 'darwin') {
|
||||
app.dock.setMenu(Menu.buildFromTemplate([
|
||||
{
|
||||
@@ -65,5 +65,8 @@ app.on('ready', () => {
|
||||
]))
|
||||
}
|
||||
application.init()
|
||||
application.newWindow({ hidden: argv.hidden })
|
||||
|
||||
const window = await application.newWindow({ hidden: argv.hidden })
|
||||
await window.ready
|
||||
window.passCliArguments(process.argv, process.cwd(), false)
|
||||
})
|
||||
|
@@ -65,6 +65,7 @@ export class Window {
|
||||
enableRemoteModule: true,
|
||||
contextIsolation: false,
|
||||
},
|
||||
maximizable: true,
|
||||
frame: false,
|
||||
show: false,
|
||||
backgroundColor: '#00000000',
|
||||
@@ -190,6 +191,10 @@ export class Window {
|
||||
return this.window.isFocused()
|
||||
}
|
||||
|
||||
isVisible (): boolean {
|
||||
return this.window.isVisible()
|
||||
}
|
||||
|
||||
hide (): void {
|
||||
if (process.platform === 'darwin') {
|
||||
// Lose focus
|
||||
@@ -225,8 +230,8 @@ export class Window {
|
||||
}
|
||||
}
|
||||
|
||||
handleSecondInstance (argv: string[], cwd: string): void {
|
||||
this.send('host:second-instance', parseArgs(argv, cwd), cwd)
|
||||
passCliArguments (argv: string[], cwd: string, secondInstance: boolean): void {
|
||||
this.send('cli', parseArgs(argv, cwd), cwd, secondInstance)
|
||||
}
|
||||
|
||||
private setupWindowManagement () {
|
||||
|
@@ -21,7 +21,7 @@
|
||||
"@angular/platform-browser": "^9.1.9",
|
||||
"@angular/platform-browser-dynamic": "^9.1.9",
|
||||
"@ng-bootstrap/ng-bootstrap": "^6.1.0",
|
||||
"@terminus-term/node-pty": "0.10.0-beta11",
|
||||
"@terminus-term/node-pty": "0.10.0-beta10",
|
||||
"electron-config": "2.0.0",
|
||||
"electron-debug": "^3.0.1",
|
||||
"electron-is-dev": "1.1.0",
|
||||
|
12
app/patches/node-abi+2.19.3.patch
Normal file
12
app/patches/node-abi+2.19.3.patch
Normal file
@@ -0,0 +1,12 @@
|
||||
diff --git a/node_modules/node-abi/abi_registry.json b/node_modules/node-abi/abi_registry.json
|
||||
index bc1436d..630c1b7 100644
|
||||
--- a/node_modules/node-abi/abi_registry.json
|
||||
+++ b/node_modules/node-abi/abi_registry.json
|
||||
@@ -94,6 +94,6 @@
|
||||
"future": true,
|
||||
"lts": false,
|
||||
"runtime": "electron",
|
||||
- "target": "12.0.0-nightly.20201013"
|
||||
+ "target": "12.0.0-beta.16"
|
||||
}
|
||||
]
|
@@ -117,12 +117,12 @@
|
||||
dependencies:
|
||||
debug "^4.1.1"
|
||||
|
||||
"@terminus-term/node-pty@0.10.0-beta11":
|
||||
version "0.10.0-beta11"
|
||||
resolved "https://registry.yarnpkg.com/@terminus-term/node-pty/-/node-pty-0.10.0-beta11.tgz#9e83041bc0644f17a4329c41a13d09575653c544"
|
||||
integrity sha512-ZrplD84UNV8qpCNpsG8DAo8fmoRP1Ynb9QsApIleCJjbeNvUP2ZAeR6wo0aV9B7Zqjvq5O4hoUSayIDGtORH+A==
|
||||
"@terminus-term/node-pty@0.10.0-beta10":
|
||||
version "0.10.0-beta10"
|
||||
resolved "https://registry.yarnpkg.com/@terminus-term/node-pty/-/node-pty-0.10.0-beta10.tgz#de9dade3d7549d44b0906ec0d0b9e1bb411f1f21"
|
||||
integrity sha512-j9RJk7sD/es4vR6+AR5M/p3SicVxY6kZEeE0UQKhHNcaAla90/mcGeBNicAWPaAkjO1uQZVbYh5cJMMu5unQgA==
|
||||
dependencies:
|
||||
node-addon-api "^3.0.2"
|
||||
nan "^2.14.0"
|
||||
|
||||
"@types/mz@0.0.32":
|
||||
version "0.0.32"
|
||||
@@ -1405,7 +1405,7 @@ inherits@2.0.3:
|
||||
resolved "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz"
|
||||
integrity sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=
|
||||
|
||||
ini@^1.3.4, ini@^1.3.5, ini@~1.3.0:
|
||||
ini@^1.3.4, ini@^1.3.5, ini@^1.3.8, ini@~1.3.0:
|
||||
version "1.3.8"
|
||||
resolved "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz"
|
||||
integrity sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==
|
||||
@@ -2073,7 +2073,7 @@ mz@^2.7.0:
|
||||
object-assign "^4.0.1"
|
||||
thenify-all "^1.0.0"
|
||||
|
||||
nan@^2.13.2, nan@^2.14.2:
|
||||
nan@^2.13.2, nan@^2.14.0, nan@^2.14.2:
|
||||
version "2.14.2"
|
||||
resolved "https://registry.yarnpkg.com/nan/-/nan-2.14.2.tgz#f5376400695168f4cc694ac9393d0c9585eeea19"
|
||||
integrity sha512-M2ufzIiINKCuDfBSAUr1vWQ+vuVcA9kqx8JJUsbQi6yf1uGRyb7HfpdfUr5qLXf3B/t8dPvcjhKMmlfnP47EzQ==
|
||||
@@ -2270,9 +2270,9 @@ npm-user-validate@^1.0.1:
|
||||
integrity sha512-uQwcd/tY+h1jnEaze6cdX/LrhWhoBxfSknxentoqmIuStxUExxjWd3ULMLFPiFUrZKbOVMowH6Jq2FRWfmhcEw==
|
||||
|
||||
npm@6:
|
||||
version "6.14.10"
|
||||
resolved "https://registry.yarnpkg.com/npm/-/npm-6.14.10.tgz#f45c8e4244294ba793770f2ab0e9ce2d0b93fd29"
|
||||
integrity sha512-FT23Qy/JMA+qxEYReMOr1MY7642fKn8Onn+72LASPi872Owvmw0svm+/DXTHOC3yO9CheEO+EslyXEpdBdRtIA==
|
||||
version "6.14.11"
|
||||
resolved "https://registry.yarnpkg.com/npm/-/npm-6.14.11.tgz#e0b5598d7b9a42d275e61d8bd28cd7eee0074a3b"
|
||||
integrity sha512-1Zh7LjuIoEhIyjkBflSSGzfjuPQwDlghNloppjruOH5bmj9midT9qcNT0tRUZRR04shU9ekrxNy9+UTBrqeBpQ==
|
||||
dependencies:
|
||||
JSONStream "^1.3.5"
|
||||
abbrev "~1.1.1"
|
||||
@@ -2309,7 +2309,7 @@ npm@6:
|
||||
infer-owner "^1.0.4"
|
||||
inflight "~1.0.6"
|
||||
inherits "^2.0.4"
|
||||
ini "^1.3.5"
|
||||
ini "^1.3.8"
|
||||
init-package-json "^1.10.3"
|
||||
is-cidr "^3.0.0"
|
||||
json-parse-better-errors "^1.0.2"
|
||||
@@ -3025,16 +3025,16 @@ rxjs@^6.5.5:
|
||||
dependencies:
|
||||
tslib "^1.9.0"
|
||||
|
||||
safe-buffer@^5.0.1, safe-buffer@^5.1.2, safe-buffer@~5.1.0, safe-buffer@~5.1.1:
|
||||
version "5.1.2"
|
||||
resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz"
|
||||
integrity sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==
|
||||
|
||||
safe-buffer@^5.1.0, safe-buffer@^5.1.1, safe-buffer@^5.2.0:
|
||||
safe-buffer@^5.0.1, safe-buffer@^5.1.0, safe-buffer@^5.1.1, safe-buffer@^5.1.2, safe-buffer@^5.2.0:
|
||||
version "5.2.1"
|
||||
resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6"
|
||||
integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==
|
||||
|
||||
safe-buffer@~5.1.0, safe-buffer@~5.1.1:
|
||||
version "5.1.2"
|
||||
resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz"
|
||||
integrity sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==
|
||||
|
||||
"safer-buffer@>= 2.1.2 < 3.0.0", safer-buffer@^2.0.2, safer-buffer@^2.1.0, safer-buffer@~2.1.0:
|
||||
version "2.1.2"
|
||||
resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz"
|
||||
|
@@ -8,8 +8,9 @@ afterAllArtifactBuild: "./build/mac/afterBuildHook.js"
|
||||
files:
|
||||
- '**/*'
|
||||
- dist
|
||||
- '!lib'
|
||||
- '!src'
|
||||
- '!**/node_modules/*/{CHANGELOG.md,README.md,README,readme.md,readme}'
|
||||
- '!**/node_modules/*/{CHANGELOG.md,README.md,README,readme.md,readme,node.lib}'
|
||||
- '!**/node_modules/*/{test,__tests__,tests,powered-test,example,examples,docs}'
|
||||
- '!**/node_modules/@angular/common/locales'
|
||||
- '!**/node_modules/@angular/compiler/src'
|
||||
|
@@ -17,7 +17,7 @@
|
||||
"core-js": "^3.8.1",
|
||||
"cross-env": "7.0.2",
|
||||
"css-loader": "3.4.2",
|
||||
"electron": "^11.1.1",
|
||||
"electron": "12.0.0-beta.16",
|
||||
"electron-builder": "22.10.4",
|
||||
"electron-download": "^4.1.1",
|
||||
"electron-installer-snap": "^5.1.0",
|
||||
@@ -36,6 +36,7 @@
|
||||
"node-sass": "^5.0.0",
|
||||
"npmlog": "4.1.2",
|
||||
"npx": "^10.2.2",
|
||||
"patch-package": "^6.2.2",
|
||||
"pug": "^2.0.4",
|
||||
"pug-html-loader": "1.1.5",
|
||||
"pug-lint": "^2.6.0",
|
||||
|
12
patches/node-abi+2.19.3.patch
Normal file
12
patches/node-abi+2.19.3.patch
Normal file
@@ -0,0 +1,12 @@
|
||||
diff --git a/node_modules/node-abi/abi_registry.json b/node_modules/node-abi/abi_registry.json
|
||||
index bc1436d..630c1b7 100644
|
||||
--- a/node_modules/node-abi/abi_registry.json
|
||||
+++ b/node_modules/node-abi/abi_registry.json
|
||||
@@ -94,6 +94,6 @@
|
||||
"future": true,
|
||||
"lts": false,
|
||||
"runtime": "electron",
|
||||
- "target": "12.0.0-nightly.20201013"
|
||||
+ "target": "12.0.0-beta.16"
|
||||
}
|
||||
]
|
@@ -1,8 +1,6 @@
|
||||
#!/usr/bin/env node
|
||||
const builder = require('electron-builder').build
|
||||
const vars = require('./vars')
|
||||
const fs = require('fs')
|
||||
const signHook = require('../build/mac/afterSignHook')
|
||||
|
||||
const isTag = (process.env.GITHUB_REF || '').startsWith('refs/tags/')
|
||||
|
||||
@@ -16,6 +14,7 @@ builder({
|
||||
extraMetadata: {
|
||||
version: vars.version,
|
||||
},
|
||||
npmRebuild: process.env.ARCH !== 'arm64',
|
||||
},
|
||||
publish: isTag ? 'always' : 'onTag',
|
||||
}).catch(e => {
|
||||
|
@@ -1,14 +1,14 @@
|
||||
import { Injectable } from '@angular/core'
|
||||
import { TerminalColorSchemeProvider, TerminalColorScheme } from 'terminus-terminal'
|
||||
|
||||
const schemeContents = require.context('../schemes/', true, /.*/)
|
||||
const schemeContents = require.context('../schemes/', false, /.*/)
|
||||
|
||||
@Injectable()
|
||||
export class ColorSchemes extends TerminalColorSchemeProvider {
|
||||
async getSchemes (): Promise<TerminalColorScheme[]> {
|
||||
const schemes: TerminalColorScheme[] = []
|
||||
|
||||
schemeContents.keys().forEach(schemeFile => {
|
||||
schemeContents.keys().filter(x => !x.startsWith('./')).forEach(schemeFile => {
|
||||
const lines = (schemeContents(schemeFile).default as string).split('\n')
|
||||
|
||||
// process #define variables
|
||||
|
@@ -1,5 +1,6 @@
|
||||
import type { BrowserWindow, TouchBar, MenuItemConstructorOptions } from 'electron'
|
||||
import * as path from 'path'
|
||||
import * as fs from 'mz/fs'
|
||||
import shellEscape from 'shell-escape'
|
||||
import { Observable, Subject } from 'rxjs'
|
||||
import { Injectable, NgZone, EventEmitter } from '@angular/core'
|
||||
@@ -150,9 +151,10 @@ export class HostAppService {
|
||||
this.zone.run(() => this.displaysChanged.next())
|
||||
})
|
||||
|
||||
electron.ipcRenderer.on('host:second-instance', (_$event, argv: any, cwd: string) => this.zone.run(() => {
|
||||
electron.ipcRenderer.on('cli', (_$event, argv: any, cwd: string, secondInstance: boolean) => this.zone.run(async () => {
|
||||
this.logger.info('Second instance', argv)
|
||||
const op = argv._[0]
|
||||
const opAsPath = path.resolve(cwd, op)
|
||||
if (op === 'open') {
|
||||
this.cliOpenDirectory.next(path.resolve(cwd, argv.directory))
|
||||
} else if (op === 'run') {
|
||||
@@ -167,7 +169,11 @@ export class HostAppService {
|
||||
this.cliOpenProfile.next(argv.profileName)
|
||||
} else if (op === undefined) {
|
||||
this.newWindow()
|
||||
} else {
|
||||
} else if ((await fs.lstat(opAsPath)).isDirectory()) {
|
||||
this.cliOpenDirectory.next(opAsPath)
|
||||
}
|
||||
|
||||
if (secondInstance) {
|
||||
this.secondInstance.next()
|
||||
}
|
||||
}))
|
||||
|
@@ -124,9 +124,9 @@ colorspace@1.1.x:
|
||||
text-hex "1.0.x"
|
||||
|
||||
core-js@^3.1.2:
|
||||
version "3.7.0"
|
||||
resolved "https://registry.yarnpkg.com/core-js/-/core-js-3.7.0.tgz#b0a761a02488577afbf97179e4681bf49568520f"
|
||||
integrity sha512-NwS7fI5M5B85EwpWuIwJN4i/fbisQUwLwiSNUWeXlkAZ0sbBjLEvLvFLf1uzAUV66PcEPt4xCGCmOZSxVf3xzA==
|
||||
version "3.8.2"
|
||||
resolved "https://registry.yarnpkg.com/core-js/-/core-js-3.8.2.tgz#0a1fd6709246da9ca8eff5bb0cbd15fba9ac7044"
|
||||
integrity sha512-FfApuSRgrR6G5s58casCBd9M2k+4ikuu4wbW6pJyYU7bd9zvFc9qf7vr5xmrZOhT9nn+8uwlH1oRR9jTnFoA3A==
|
||||
|
||||
core-util-is@~1.0.0:
|
||||
version "1.0.2"
|
||||
|
@@ -131,6 +131,10 @@ export class SerialSession extends BaseSession {
|
||||
this.kill('TERM')
|
||||
}
|
||||
|
||||
supportsWorkingDirectory (): boolean {
|
||||
return false
|
||||
}
|
||||
|
||||
async getWorkingDirectory (): Promise<string|null> {
|
||||
return null
|
||||
}
|
||||
|
@@ -363,6 +363,10 @@ export class SSHSession extends BaseSession {
|
||||
this.kill('TERM')
|
||||
}
|
||||
|
||||
supportsWorkingDirectory (): boolean {
|
||||
return true
|
||||
}
|
||||
|
||||
async getWorkingDirectory (): Promise<string|null> {
|
||||
return null
|
||||
}
|
||||
@@ -398,3 +402,9 @@ export interface SSHConnectionGroup {
|
||||
name: string
|
||||
connections: SSHConnection[]
|
||||
}
|
||||
|
||||
export const ALGORITHM_BLACKLIST = [
|
||||
// cause native crashes in node crypto, use EC instead
|
||||
'diffie-hellman-group-exchange-sha256',
|
||||
'diffie-hellman-group-exchange-sha1',
|
||||
]
|
||||
|
@@ -103,7 +103,7 @@
|
||||
.header
|
||||
.title Jump host
|
||||
select.form-control([(ngModel)]='connection.jumpHost')
|
||||
option([ngValue]='null') None
|
||||
option(value='') None
|
||||
option([ngValue]='x.name', *ngFor='let x of config.store.ssh.connections') {{x.name}}
|
||||
|
||||
.form-line
|
||||
|
@@ -3,7 +3,7 @@ import { Component } from '@angular/core'
|
||||
import { NgbModal, NgbActiveModal } from '@ng-bootstrap/ng-bootstrap'
|
||||
import { ElectronService, HostAppService, ConfigService } from 'terminus-core'
|
||||
import { PasswordStorageService } from '../services/passwordStorage.service'
|
||||
import { SSHConnection, LoginScript, SSHAlgorithmType } from '../api'
|
||||
import { SSHConnection, LoginScript, SSHAlgorithmType, ALGORITHM_BLACKLIST } from '../api'
|
||||
import { PromptModalComponent } from './promptModal.component'
|
||||
import { ALGORITHMS } from 'ssh2-streams/lib/constants'
|
||||
|
||||
@@ -40,8 +40,8 @@ export class EditConnectionModalComponent {
|
||||
[SSHAlgorithmType.CIPHER]: 'CIPHER',
|
||||
[SSHAlgorithmType.HMAC]: 'HMAC',
|
||||
}[k]
|
||||
this.supportedAlgorithms[k] = ALGORITHMS[supportedAlg]
|
||||
this.defaultAlgorithms[k] = ALGORITHMS[defaultAlg]
|
||||
this.supportedAlgorithms[k] = ALGORITHMS[supportedAlg].filter(x => !ALGORITHM_BLACKLIST.includes(x))
|
||||
this.defaultAlgorithms[k] = ALGORITHMS[defaultAlg].filter(x => !ALGORITHM_BLACKLIST.includes(x))
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -23,6 +23,9 @@ export class SSHTabComponent extends BaseTerminalTabComponent {
|
||||
session?: SSHSession
|
||||
private sessionStack: SSHSession[] = []
|
||||
private homeEndSubscription: Subscription
|
||||
private recentInputs = ''
|
||||
private reconnectOffered = false
|
||||
private sessionHandlers: Subscription[] = []
|
||||
|
||||
constructor (
|
||||
injector: Injector,
|
||||
@@ -57,6 +60,10 @@ export class SSHTabComponent extends BaseTerminalTabComponent {
|
||||
|
||||
this.frontendReady$.pipe(first()).subscribe(() => {
|
||||
this.initializeSession()
|
||||
this.input$.subscribe(data => {
|
||||
this.recentInputs += data
|
||||
this.recentInputs = this.recentInputs.substring(this.recentInputs.length - 32)
|
||||
})
|
||||
})
|
||||
|
||||
super.ngOnInit()
|
||||
@@ -68,12 +75,19 @@ export class SSHTabComponent extends BaseTerminalTabComponent {
|
||||
|
||||
async setupOneSession (session: SSHSession): Promise<void> {
|
||||
if (session.connection.jumpHost) {
|
||||
const jumpConnection = this.config.store.ssh.connections.find(x => x.name === session.connection.jumpHost)
|
||||
const jumpConnection: SSHConnection|null = this.config.store.ssh.connections.find(x => x.name === session.connection.jumpHost)
|
||||
|
||||
if (!jumpConnection) {
|
||||
throw new Error(`${session.connection.host}: jump host "${session.connection.jumpHost}" not found in your config`)
|
||||
}
|
||||
|
||||
const jumpSession = this.ssh.createSession(jumpConnection)
|
||||
|
||||
await this.setupOneSession(jumpSession)
|
||||
|
||||
jumpSession.destroyed$.subscribe(() => session.destroy())
|
||||
this.sessionHandlers.push(
|
||||
jumpSession.destroyed$.subscribe(() => session.destroy())
|
||||
)
|
||||
|
||||
session.jumpStream = await new Promise((resolve, reject) => jumpSession.ssh.forwardOut(
|
||||
'127.0.0.1', 0, session.connection.host, session.connection.port ?? 22,
|
||||
@@ -93,15 +107,31 @@ export class SSHTabComponent extends BaseTerminalTabComponent {
|
||||
this.sessionStack.push(session)
|
||||
}
|
||||
|
||||
|
||||
session.serviceMessage$.subscribe(msg => {
|
||||
this.sessionHandlers.push(session.serviceMessage$.subscribe(msg => {
|
||||
this.write(`\r\n${colors.black.bgWhite(' SSH ')} ${msg}\r\n`)
|
||||
session.resize(this.size.columns, this.size.rows)
|
||||
})
|
||||
}))
|
||||
|
||||
session.destroyed$.subscribe(() => {
|
||||
this.write('\r\n' + colors.black.bgCyan(' SSH ') + ` ${session.connection.host}: session closed\r\n`)
|
||||
})
|
||||
this.sessionHandlers.push(session.destroyed$.subscribe(() => {
|
||||
if (
|
||||
// Ctrl-D
|
||||
this.recentInputs.charCodeAt(this.recentInputs.length - 1) === 4 ||
|
||||
this.recentInputs.endsWith('exit\r')
|
||||
) {
|
||||
// User closed the session
|
||||
this.destroy()
|
||||
} else {
|
||||
// Session was closed abruptly
|
||||
this.write('\r\n' + colors.black.bgCyan(' SSH ') + ` ${session.connection.host}: session closed\r\n`)
|
||||
if (!this.reconnectOffered) {
|
||||
this.reconnectOffered = true
|
||||
this.write('Press any key to reconnect\r\n')
|
||||
this.input$.pipe(first()).subscribe(() => {
|
||||
this.reconnect()
|
||||
})
|
||||
}
|
||||
}
|
||||
}))
|
||||
|
||||
this.write('\r\n' + colors.black.bgCyan(' SSH ') + ` Connecting to ${session.connection.host}\r\n`)
|
||||
|
||||
@@ -129,6 +159,7 @@ export class SSHTabComponent extends BaseTerminalTabComponent {
|
||||
}
|
||||
|
||||
async initializeSession (): Promise<void> {
|
||||
this.reconnectOffered = false
|
||||
if (!this.connection) {
|
||||
this.logger.error('No SSH connection info supplied')
|
||||
return
|
||||
@@ -136,7 +167,11 @@ export class SSHTabComponent extends BaseTerminalTabComponent {
|
||||
|
||||
this.session = this.ssh.createSession(this.connection)
|
||||
|
||||
await this.setupOneSession(this.session)
|
||||
try {
|
||||
await this.setupOneSession(this.session)
|
||||
} catch (e) {
|
||||
this.write(colors.black.bgRed(' X ') + ' ' + colors.red(e.message) + '\r\n')
|
||||
}
|
||||
|
||||
this.attachSessionHandlers()
|
||||
|
||||
@@ -158,6 +193,10 @@ export class SSHTabComponent extends BaseTerminalTabComponent {
|
||||
}
|
||||
|
||||
async reconnect (): Promise<void> {
|
||||
for (const s of this.sessionHandlers) {
|
||||
s.unsubscribe()
|
||||
}
|
||||
this.sessionHandlers = []
|
||||
this.session?.destroy()
|
||||
await this.initializeSession()
|
||||
this.session?.releaseInitialDataBuffer()
|
||||
|
@@ -12,7 +12,7 @@ import * as sshpk from 'sshpk'
|
||||
import { ToastrService } from 'ngx-toastr'
|
||||
import { HostAppService, Platform, Logger, LogService, ElectronService, AppService, SelectorOption, ConfigService } from 'terminus-core'
|
||||
import { SettingsTabComponent } from 'terminus-settings'
|
||||
import { SSHConnection, SSHSession } from '../api'
|
||||
import { ALGORITHM_BLACKLIST, SSHConnection, SSHSession } from '../api'
|
||||
import { PromptModalComponent } from '../components/promptModal.component'
|
||||
import { PasswordStorageService } from './passwordStorage.service'
|
||||
import { SSHTabComponent } from '../components/sshTab.component'
|
||||
@@ -147,6 +147,10 @@ export class SSHService {
|
||||
session.ssh = ssh
|
||||
let connected = false
|
||||
let savedPassword: string|null = null
|
||||
const algorithms = {}
|
||||
for (const key of Object.keys(session.connection.algorithms ?? {})) {
|
||||
algorithms[key] = session.connection.algorithms![key].filter(x => !ALGORITHM_BLACKLIST.includes(x))
|
||||
}
|
||||
await new Promise(async (resolve, reject) => {
|
||||
ssh.on('ready', () => {
|
||||
connected = true
|
||||
@@ -258,7 +262,7 @@ export class SSHService {
|
||||
tryKeyboard: true,
|
||||
agent: agent ?? undefined,
|
||||
agentForward: session.connection.agentForward && !!agent,
|
||||
keepaliveInterval: session.connection.keepaliveInterval,
|
||||
keepaliveInterval: session.connection.keepaliveInterval ?? 15000,
|
||||
keepaliveCountMax: session.connection.keepaliveCountMax,
|
||||
readyTimeout: session.connection.readyTimeout,
|
||||
hostVerifier: (digest: string) => {
|
||||
@@ -267,7 +271,7 @@ export class SSHService {
|
||||
return true
|
||||
},
|
||||
hostHash: 'sha256' as any,
|
||||
algorithms: session.connection.algorithms,
|
||||
algorithms,
|
||||
sock: session.jumpStream,
|
||||
authHandler: methodsLeft => {
|
||||
while (true) {
|
||||
|
@@ -18,6 +18,7 @@ import { TerminalDecorator } from './decorator'
|
||||
/** @hidden */
|
||||
export interface ToastrServiceProxy {
|
||||
info: (_: string) => void
|
||||
error: (_: string) => void
|
||||
}
|
||||
/**
|
||||
* A class to base your custom terminal tabs on
|
||||
@@ -140,7 +141,7 @@ export class BaseTerminalTabComponent extends BaseTabComponent implements OnInit
|
||||
this.logger = this.log.create('baseTerminalTab')
|
||||
this.setTitle('Terminal')
|
||||
|
||||
this.hotkeysSubscription = this.hotkeys.matchedHotkey.subscribe(hotkey => {
|
||||
this.hotkeysSubscription = this.hotkeys.matchedHotkey.subscribe(async hotkey => {
|
||||
if (!this.hasFocus) {
|
||||
return
|
||||
}
|
||||
@@ -207,6 +208,9 @@ export class BaseTerminalTabComponent extends BaseTabComponent implements OnInit
|
||||
case 'pane-focus-all':
|
||||
this.focusAllPanes()
|
||||
break
|
||||
case 'copy-current-path':
|
||||
this.copyCurrentPath()
|
||||
break
|
||||
}
|
||||
})
|
||||
this.bellPlayer = document.createElement('audio')
|
||||
@@ -438,6 +442,19 @@ export class BaseTerminalTabComponent extends BaseTabComponent implements OnInit
|
||||
}
|
||||
}
|
||||
|
||||
async copyCurrentPath (): Promise<void> {
|
||||
let cwd: string|null = null
|
||||
if (this.session?.supportsWorkingDirectory()) {
|
||||
cwd = await this.session.getWorkingDirectory()
|
||||
}
|
||||
if (cwd) {
|
||||
this.electron.clipboard.writeText(cwd)
|
||||
this.toastr.info('Copied')
|
||||
} else {
|
||||
this.toastr.error('Shell does not support current path detection')
|
||||
}
|
||||
}
|
||||
|
||||
/** @hidden */
|
||||
ngOnDestroy (): void {
|
||||
this.frontend?.detach(this.content.nativeElement)
|
||||
|
@@ -4,6 +4,7 @@ import { ConfigProvider, Platform } from 'terminus-core'
|
||||
export class TerminalConfigProvider extends ConfigProvider {
|
||||
defaults = {
|
||||
hotkeys: {
|
||||
'copy-current-path': [],
|
||||
shell: {
|
||||
__nonStructural: true,
|
||||
},
|
||||
|
@@ -62,6 +62,10 @@ export class TerminalHotkeyProvider extends HotkeyProvider {
|
||||
id: 'ctrl-c',
|
||||
name: 'Intelligent Ctrl-C (copy/abort)',
|
||||
},
|
||||
{
|
||||
id: 'copy-current-path',
|
||||
name: 'Copy current path',
|
||||
},
|
||||
{
|
||||
id: 'search',
|
||||
name: 'Search',
|
||||
|
@@ -194,6 +194,9 @@ export default class TerminalModule { // eslint-disable-line @typescript-eslint/
|
||||
})
|
||||
|
||||
hostApp.cliOpenDirectory$.subscribe(async directory => {
|
||||
if (directory.length > 1 && (directory.endsWith('/') || directory.endsWith('\\'))) {
|
||||
directory = directory.substring(0, directory.length - 1)
|
||||
}
|
||||
if (await fs.exists(directory)) {
|
||||
if ((await fs.stat(directory)).isDirectory()) {
|
||||
terminal.openTab(undefined, directory)
|
||||
|
@@ -87,6 +87,7 @@ export abstract class BaseSession {
|
||||
abstract kill (signal?: string): void
|
||||
abstract async getChildProcesses (): Promise<ChildProcess[]>
|
||||
abstract async gracefullyKillProcess (): Promise<void>
|
||||
abstract supportsWorkingDirectory (): boolean
|
||||
abstract async getWorkingDirectory (): Promise<string|null>
|
||||
}
|
||||
|
||||
@@ -259,6 +260,16 @@ export class Session extends BaseSession {
|
||||
}
|
||||
}
|
||||
|
||||
supportsWorkingDirectory (): boolean {
|
||||
if (this.reportedCWD || this.guessedCWD) {
|
||||
return true
|
||||
}
|
||||
if (!this.truePID) {
|
||||
return false
|
||||
}
|
||||
return process.platform !== 'win32'
|
||||
}
|
||||
|
||||
async getWorkingDirectory (): Promise<string|null> {
|
||||
if (this.reportedCWD) {
|
||||
return this.reportedCWD
|
||||
|
@@ -123,6 +123,13 @@ export class NewTabContextMenu extends TabContextMenuItemProvider {
|
||||
})
|
||||
}
|
||||
|
||||
if (tab instanceof TerminalTabComponent && tab.session?.supportsWorkingDirectory()) {
|
||||
items.push({
|
||||
label: 'Copy current path',
|
||||
click: () => this.zone.run(() => tab.copyCurrentPath()),
|
||||
})
|
||||
}
|
||||
|
||||
return items
|
||||
}
|
||||
}
|
||||
|
85
yarn.lock
85
yarn.lock
@@ -359,10 +359,10 @@
|
||||
resolved "https://registry.npmjs.org/@types/node/-/node-14.14.14.tgz"
|
||||
integrity sha512-UHnOPWVWV1z+VV8k6L1HhG7UbGBgIdghqF3l9Ny9ApPghbjICXkUJSd/b9gOgQfjM1r+37cipdw/HJ3F6ICEnQ==
|
||||
|
||||
"@types/node@^12.0.12":
|
||||
version "12.19.9"
|
||||
resolved "https://registry.npmjs.org/@types/node/-/node-12.19.9.tgz"
|
||||
integrity sha512-yj0DOaQeUrk3nJ0bd3Y5PeDRJ6W0r+kilosLA+dzF3dola/o9hxhMSg2sFvVcA2UHS5JSOsZp4S0c1OEXc4m1Q==
|
||||
"@types/node@^14.6.2":
|
||||
version "14.14.22"
|
||||
resolved "https://registry.yarnpkg.com/@types/node/-/node-14.14.22.tgz#0d29f382472c4ccf3bd96ff0ce47daf5b7b84b18"
|
||||
integrity sha512-g+f/qj/cNcqKkc3tFqlXOYjrmZA+jNBiDzbP3kH+B+otKFqAdPgVTGP1IeKRdMml/aE69as5S4FqtxAbl+LaMw==
|
||||
|
||||
"@types/plist@^3.0.1":
|
||||
version "3.0.2"
|
||||
@@ -638,6 +638,11 @@
|
||||
resolved "https://registry.npmjs.org/@xtuc/long/-/long-4.2.2.tgz"
|
||||
integrity sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ==
|
||||
|
||||
"@yarnpkg/lockfile@^1.1.0":
|
||||
version "1.1.0"
|
||||
resolved "https://registry.yarnpkg.com/@yarnpkg/lockfile/-/lockfile-1.1.0.tgz#e77a97fbd345b76d83245edcd17d393b1b41fb31"
|
||||
integrity sha512-GpSwvyXOcOOlV70vbnzjj4fW5xW/FdUF6nQEt1ENy7m4ZCczi1+/buVUPAqmGfqznsORNFzUMjctTIp8a9tuCQ==
|
||||
|
||||
JSONStream@~1.3.1:
|
||||
version "1.3.1"
|
||||
resolved "https://registry.npmjs.org/JSONStream/-/JSONStream-1.3.1.tgz"
|
||||
@@ -1966,7 +1971,7 @@ cross-spawn@^5.0.1:
|
||||
shebang-command "^1.2.0"
|
||||
which "^1.2.9"
|
||||
|
||||
cross-spawn@^6.0.0:
|
||||
cross-spawn@^6.0.0, cross-spawn@^6.0.5:
|
||||
version "6.0.5"
|
||||
resolved "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz"
|
||||
integrity sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==
|
||||
@@ -2482,13 +2487,13 @@ electron-to-chromium@^1.3.621:
|
||||
resolved "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.629.tgz"
|
||||
integrity sha512-iSPPJtPvHrMAvYOt+9cdbDmTasPqwnwz4lkP8Dn200gDNUBQOLQ96xUsWXBwXslAo5XxdoXAoQQ3RAy4uao9IQ==
|
||||
|
||||
electron@^11.1.1:
|
||||
version "11.1.1"
|
||||
resolved "https://registry.yarnpkg.com/electron/-/electron-11.1.1.tgz#188f036f8282798398dca9513e9bb3b10213e3aa"
|
||||
integrity sha512-tlbex3xosJgfileN6BAQRotevPRXB/wQIq48QeQ08tUJJrXwE72c8smsM/hbHx5eDgnbfJ2G3a60PmRjHU2NhA==
|
||||
electron@12.0.0-beta.16:
|
||||
version "12.0.0-beta.16"
|
||||
resolved "https://registry.yarnpkg.com/electron/-/electron-12.0.0-beta.16.tgz#8142fe7af8ee85a181575063db689d6da4175899"
|
||||
integrity sha512-8fsosa7PLnfheNqVK5G+NToZBh4audSVJIwff4vKorCkNK3XDMQCoHD9JKFeVRczYlb4YEY2mh7SsX4C8p9FMQ==
|
||||
dependencies:
|
||||
"@electron/get" "^1.0.1"
|
||||
"@types/node" "^12.0.12"
|
||||
"@types/node" "^14.6.2"
|
||||
extract-zip "^1.0.3"
|
||||
|
||||
emoji-regex@^7.0.1:
|
||||
@@ -3106,6 +3111,14 @@ find-up@^5.0.0:
|
||||
locate-path "^6.0.0"
|
||||
path-exists "^4.0.0"
|
||||
|
||||
find-yarn-workspace-root@^1.2.1:
|
||||
version "1.2.1"
|
||||
resolved "https://registry.yarnpkg.com/find-yarn-workspace-root/-/find-yarn-workspace-root-1.2.1.tgz#40eb8e6e7c2502ddfaa2577c176f221422f860db"
|
||||
integrity sha512-dVtfb0WuQG+8Ag2uWkbG79hOUzEsRrhBzgfn86g2sJPkzmcpGdghbNTfUKGTxymFrY/tLIodDzLoW9nOJ4FY8Q==
|
||||
dependencies:
|
||||
fs-extra "^4.0.3"
|
||||
micromatch "^3.1.4"
|
||||
|
||||
flat-cache@^3.0.4:
|
||||
version "3.0.4"
|
||||
resolved "https://registry.npmjs.org/flat-cache/-/flat-cache-3.0.4.tgz"
|
||||
@@ -3183,7 +3196,7 @@ fs-constants@^1.0.0:
|
||||
resolved "https://registry.yarnpkg.com/fs-constants/-/fs-constants-1.0.0.tgz#6be0de9be998ce16af8afc24497b9ee9b7ccd9ad"
|
||||
integrity sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==
|
||||
|
||||
fs-extra@^4.0.1:
|
||||
fs-extra@^4.0.1, fs-extra@^4.0.3:
|
||||
version "4.0.3"
|
||||
resolved "https://registry.npmjs.org/fs-extra/-/fs-extra-4.0.3.tgz"
|
||||
integrity sha512-q6rbdDd1o2mAnQreO7YADIxf/Whx4AHBiRf6d+/cVT8h44ss+lHgxf1FemcqDnQt9X3ct4McHr+JMGlYSsK7Cg==
|
||||
@@ -3192,6 +3205,15 @@ fs-extra@^4.0.1:
|
||||
jsonfile "^4.0.0"
|
||||
universalify "^0.1.0"
|
||||
|
||||
fs-extra@^7.0.1:
|
||||
version "7.0.1"
|
||||
resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-7.0.1.tgz#4f189c44aa123b895f722804f55ea23eadc348e9"
|
||||
integrity sha512-YJDaCJZEnBmcbw13fvdAM9AwNOJwOzrE4pqMqBq5nFiEqXUqHwlK4B+3pUw6JNvfSPtX05xFHtYy/1ni01eGCw==
|
||||
dependencies:
|
||||
graceful-fs "^4.1.2"
|
||||
jsonfile "^4.0.0"
|
||||
universalify "^0.1.0"
|
||||
|
||||
fs-extra@^8.1.0:
|
||||
version "8.1.0"
|
||||
resolved "https://registry.npmjs.org/fs-extra/-/fs-extra-8.1.0.tgz"
|
||||
@@ -4511,6 +4533,13 @@ kind-of@^6.0.0, kind-of@^6.0.2:
|
||||
resolved "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz"
|
||||
integrity sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==
|
||||
|
||||
klaw-sync@^6.0.0:
|
||||
version "6.0.0"
|
||||
resolved "https://registry.yarnpkg.com/klaw-sync/-/klaw-sync-6.0.0.tgz#1fd2cfd56ebb6250181114f0a581167099c2b28c"
|
||||
integrity sha512-nIeuVSzdCCs6TDPTqI8w1Yre34sSq7AkZ4B3sfOBbI2CgVSB4Du4aLQijFU2+lhAFCwt9+42Hel6lQNIv6AntQ==
|
||||
dependencies:
|
||||
graceful-fs "^4.1.11"
|
||||
|
||||
klona@^2.0.4:
|
||||
version "2.0.4"
|
||||
resolved "https://registry.npmjs.org/klona/-/klona-2.0.4.tgz"
|
||||
@@ -4919,7 +4948,7 @@ merge2@^1.3.0:
|
||||
resolved "https://registry.yarnpkg.com/merge2/-/merge2-1.4.1.tgz#4368892f885e907455a6fd7dc55c0c9d404990ae"
|
||||
integrity sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==
|
||||
|
||||
micromatch@^3.1.9:
|
||||
micromatch@^3.1.4, micromatch@^3.1.9:
|
||||
version "3.1.10"
|
||||
resolved "https://registry.npmjs.org/micromatch/-/micromatch-3.1.10.tgz"
|
||||
integrity sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg==
|
||||
@@ -5712,7 +5741,7 @@ os-locale@^3.1.0:
|
||||
lcid "^2.0.0"
|
||||
mem "^4.0.0"
|
||||
|
||||
os-tmpdir@^1.0.0:
|
||||
os-tmpdir@^1.0.0, os-tmpdir@~1.0.2:
|
||||
version "1.0.2"
|
||||
resolved "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz"
|
||||
integrity sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ=
|
||||
@@ -5906,6 +5935,24 @@ pascalcase@^0.1.1:
|
||||
resolved "https://registry.npmjs.org/pascalcase/-/pascalcase-0.1.1.tgz"
|
||||
integrity sha1-s2PlXoAGym/iF4TS2yK9FdeRfxQ=
|
||||
|
||||
patch-package@^6.2.2:
|
||||
version "6.2.2"
|
||||
resolved "https://registry.yarnpkg.com/patch-package/-/patch-package-6.2.2.tgz#71d170d650c65c26556f0d0fbbb48d92b6cc5f39"
|
||||
integrity sha512-YqScVYkVcClUY0v8fF0kWOjDYopzIM8e3bj/RU1DPeEF14+dCGm6UeOYm4jvCyxqIEQ5/eJzmbWfDWnUleFNMg==
|
||||
dependencies:
|
||||
"@yarnpkg/lockfile" "^1.1.0"
|
||||
chalk "^2.4.2"
|
||||
cross-spawn "^6.0.5"
|
||||
find-yarn-workspace-root "^1.2.1"
|
||||
fs-extra "^7.0.1"
|
||||
is-ci "^2.0.0"
|
||||
klaw-sync "^6.0.0"
|
||||
minimist "^1.2.0"
|
||||
rimraf "^2.6.3"
|
||||
semver "^5.6.0"
|
||||
slash "^2.0.0"
|
||||
tmp "^0.0.33"
|
||||
|
||||
path-exists@^2.0.0:
|
||||
version "2.1.0"
|
||||
resolved "https://registry.npmjs.org/path-exists/-/path-exists-2.1.0.tgz"
|
||||
@@ -7092,6 +7139,11 @@ single-line-log@^1.1.2:
|
||||
dependencies:
|
||||
string-width "^1.0.1"
|
||||
|
||||
slash@^2.0.0:
|
||||
version "2.0.0"
|
||||
resolved "https://registry.yarnpkg.com/slash/-/slash-2.0.0.tgz#de552851a1759df3a8f206535442f5ec4ddeab44"
|
||||
integrity sha512-ZYKh3Wh2z1PpEXWr0MpSBZ0V6mZHAQfYevttO11c51CaWjGTaadiKZ+wVt1PbMlDV5qhMFslpZCemhwOK7C89A==
|
||||
|
||||
slash@^3.0.0:
|
||||
version "3.0.0"
|
||||
resolved "https://registry.yarnpkg.com/slash/-/slash-3.0.0.tgz#6539be870c165adbd5240220dbe361f1bc4d4634"
|
||||
@@ -7764,6 +7816,13 @@ tmp@0.1.0:
|
||||
dependencies:
|
||||
rimraf "^2.6.3"
|
||||
|
||||
tmp@^0.0.33:
|
||||
version "0.0.33"
|
||||
resolved "https://registry.yarnpkg.com/tmp/-/tmp-0.0.33.tgz#6d34335889768d21b2bcda0aa277ced3b1bfadf9"
|
||||
integrity sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==
|
||||
dependencies:
|
||||
os-tmpdir "~1.0.2"
|
||||
|
||||
tmp@^0.2.0:
|
||||
version "0.2.1"
|
||||
resolved "https://registry.npmjs.org/tmp/-/tmp-0.2.1.tgz"
|
||||
|
Reference in New Issue
Block a user