@ticatec/omniflow-core 0.1.1 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +112 -54
- package/README_CN.md +113 -55
- package/dist/index.d.ts +5 -4
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -2
- package/dist/index.js.map +1 -1
- package/dist/plugin/PluginContext.d.ts +39 -0
- package/dist/plugin/PluginContext.d.ts.map +1 -0
- package/dist/plugin/PluginContext.js +8 -0
- package/dist/plugin/PluginContext.js.map +1 -0
- package/dist/primitives/docker.d.ts.map +1 -1
- package/dist/primitives/docker.js +9 -0
- package/dist/primitives/docker.js.map +1 -1
- package/dist/primitives/git.d.ts +58 -16
- package/dist/primitives/git.d.ts.map +1 -1
- package/dist/primitives/git.js +91 -33
- package/dist/primitives/git.js.map +1 -1
- package/dist/primitives/shell.d.ts +4 -0
- package/dist/primitives/shell.d.ts.map +1 -1
- package/dist/primitives/shell.js +20 -60
- package/dist/primitives/shell.js.map +1 -1
- package/dist/primitives/ssh.d.ts +8 -5
- package/dist/primitives/ssh.d.ts.map +1 -1
- package/dist/primitives/ssh.js +49 -14
- package/dist/primitives/ssh.js.map +1 -1
- package/dist/primitives/subprocess.d.ts +52 -0
- package/dist/primitives/subprocess.d.ts.map +1 -0
- package/dist/primitives/subprocess.js +353 -0
- package/dist/primitives/subprocess.js.map +1 -0
- package/dist/toolchain/providers/GradleToolchain.d.ts +1 -1
- package/dist/toolchain/providers/GradleToolchain.d.ts.map +1 -1
- package/dist/toolchain/providers/GradleToolchain.js +4 -3
- package/dist/toolchain/providers/GradleToolchain.js.map +1 -1
- package/dist/toolchain/providers/MavenToolchain.d.ts +1 -1
- package/dist/toolchain/providers/MavenToolchain.d.ts.map +1 -1
- package/dist/toolchain/providers/MavenToolchain.js +3 -3
- package/dist/toolchain/providers/MavenToolchain.js.map +1 -1
- package/dist/toolchain/providers/NodeToolchain.d.ts +1 -1
- package/dist/toolchain/providers/NodeToolchain.d.ts.map +1 -1
- package/dist/toolchain/providers/NodeToolchain.js +61 -0
- package/dist/toolchain/providers/NodeToolchain.js.map +1 -1
- package/dist/toolchain/providers/pom.d.ts.map +1 -1
- package/dist/toolchain/providers/pom.js +13 -0
- package/dist/toolchain/providers/pom.js.map +1 -1
- package/dist/toolchain/registry.d.ts +11 -3
- package/dist/toolchain/registry.d.ts.map +1 -1
- package/dist/toolchain/registry.js +33 -8
- package/dist/toolchain/registry.js.map +1 -1
- package/dist/utils/mask.d.ts.map +1 -1
- package/dist/utils/mask.js +30 -5
- package/dist/utils/mask.js.map +1 -1
- package/docs/toolchain-extension.md +301 -0
- package/docs/toolchain-extension_CN.md +304 -0
- package/package.json +23 -1
- package/src/context/index.ts +74 -0
- package/src/context/storage.ts +8 -0
- package/src/context/types.ts +69 -0
- package/src/index.ts +97 -0
- package/src/plugin/PluginContext.ts +57 -0
- package/src/primitives/docker.ts +164 -0
- package/src/primitives/git.ts +172 -0
- package/src/primitives/index.ts +4 -0
- package/src/primitives/shell.ts +157 -0
- package/src/primitives/ssh.ts +249 -0
- package/src/primitives/subprocess.ts +389 -0
- package/src/toolchain/index.ts +6 -0
- package/src/toolchain/providers/GradleToolchain.ts +137 -0
- package/src/toolchain/providers/MavenToolchain.ts +64 -0
- package/src/toolchain/providers/NodeToolchain.ts +172 -0
- package/src/toolchain/providers/pom.ts +145 -0
- package/src/toolchain/registry.ts +161 -0
- package/src/toolchain/types.ts +40 -0
- package/src/utils/mask.ts +73 -0
- package/src/utils/template.ts +62 -0
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
import path from 'node:path'
|
|
2
|
+
import { execa, type Options as ExecaOptions } from 'execa'
|
|
3
|
+
import { tryGetContext } from '../context/index.js'
|
|
4
|
+
import { templateToArgv } from '../utils/template.js'
|
|
5
|
+
import { maskString } from '../utils/mask.js'
|
|
6
|
+
import {
|
|
7
|
+
resolveEnv,
|
|
8
|
+
getSecretValues,
|
|
9
|
+
runSubprocess,
|
|
10
|
+
createMaskTransform
|
|
11
|
+
} from './subprocess.js'
|
|
12
|
+
|
|
13
|
+
export { createMaskTransform }
|
|
14
|
+
|
|
15
|
+
export interface RunOptions {
|
|
16
|
+
/** Working directory (defaults to context's active module or workspace) */
|
|
17
|
+
cwd?: string
|
|
18
|
+
/** Extra environment variables merged over process and context env */
|
|
19
|
+
env?: Record<string, string>
|
|
20
|
+
/** Reject promise on non-zero exit code (default: true) */
|
|
21
|
+
reject?: boolean
|
|
22
|
+
/** Capture and return stdout / stderr */
|
|
23
|
+
capture?: boolean
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export interface ShellOutputResult {
|
|
27
|
+
stdout: string
|
|
28
|
+
stderr: string
|
|
29
|
+
exitCode: number
|
|
30
|
+
failed?: boolean
|
|
31
|
+
signal?: string
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function resolveCwd(explicitCwd?: string): string {
|
|
35
|
+
if (explicitCwd) return explicitCwd
|
|
36
|
+
const ctx = tryGetContext()
|
|
37
|
+
if (ctx) {
|
|
38
|
+
if (ctx.moduleFolder) {
|
|
39
|
+
return path.resolve(ctx.workspace, ctx.moduleFolder)
|
|
40
|
+
}
|
|
41
|
+
if (ctx.workspace) {
|
|
42
|
+
return ctx.workspace
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
return process.cwd()
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Execute command via execa, piping output and handling dryRun.
|
|
50
|
+
*/
|
|
51
|
+
async function executeInternal(
|
|
52
|
+
fileOrCmd: string,
|
|
53
|
+
args: string[] | undefined,
|
|
54
|
+
options: RunOptions & { isShell?: boolean }
|
|
55
|
+
): Promise<ShellOutputResult> {
|
|
56
|
+
const ctx = tryGetContext()
|
|
57
|
+
const cwd = resolveCwd(options.cwd)
|
|
58
|
+
const env = resolveEnv(options.env)
|
|
59
|
+
const secrets = getSecretValues()
|
|
60
|
+
const cmdDisplay = args ? [fileOrCmd, ...args].join(' ') : fileOrCmd
|
|
61
|
+
const maskedCmd = maskString(cmdDisplay, secrets)
|
|
62
|
+
|
|
63
|
+
// Dry-run interception
|
|
64
|
+
if (ctx?.dryRun) {
|
|
65
|
+
ctx.logger.info(`[DRY-RUN] Executing: ${maskedCmd} (cwd: ${cwd})`)
|
|
66
|
+
return {
|
|
67
|
+
stdout: '',
|
|
68
|
+
stderr: '',
|
|
69
|
+
exitCode: 0,
|
|
70
|
+
failed: false
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
const execaOpts: ExecaOptions = {
|
|
75
|
+
cwd,
|
|
76
|
+
env,
|
|
77
|
+
reject: options.reject ?? true,
|
|
78
|
+
shell: options.isShell ?? false
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
const displayCmd = `$ ${maskedCmd}`
|
|
82
|
+
ctx?.logger?.info?.(displayCmd)
|
|
83
|
+
|
|
84
|
+
const subprocess = args
|
|
85
|
+
? execa(fileOrCmd, args, execaOpts)
|
|
86
|
+
: execa(fileOrCmd, execaOpts)
|
|
87
|
+
|
|
88
|
+
return runSubprocess({
|
|
89
|
+
subprocess,
|
|
90
|
+
displayCmd,
|
|
91
|
+
cmdText: maskedCmd,
|
|
92
|
+
errorPrefix: 'Command',
|
|
93
|
+
reject: options.reject ?? true,
|
|
94
|
+
secrets
|
|
95
|
+
})
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Tagged template runner signature.
|
|
100
|
+
*/
|
|
101
|
+
export interface TemplateRunner {
|
|
102
|
+
(template: TemplateStringsArray, ...values: unknown[]): Promise<ShellOutputResult>
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* `shell.run` runner function supporting:
|
|
107
|
+
* 1. `shell.run`docker build -t ${img} .``
|
|
108
|
+
* 2. `shell.run({ cwd: '/app' })`docker build -t ${img} .``
|
|
109
|
+
*/
|
|
110
|
+
export function run(options: RunOptions): TemplateRunner
|
|
111
|
+
export function run(template: TemplateStringsArray, ...values: unknown[]): Promise<ShellOutputResult>
|
|
112
|
+
export function run(
|
|
113
|
+
templateOrOptions: TemplateStringsArray | RunOptions,
|
|
114
|
+
...values: unknown[]
|
|
115
|
+
): Promise<ShellOutputResult> | TemplateRunner {
|
|
116
|
+
// Overload 2: options passed first, returns a TemplateRunner
|
|
117
|
+
if (!Array.isArray(templateOrOptions) || !('raw' in templateOrOptions)) {
|
|
118
|
+
const options = (templateOrOptions as RunOptions) ?? {}
|
|
119
|
+
return async (template: TemplateStringsArray, ...innerValues: unknown[]) => {
|
|
120
|
+
const argv = templateToArgv(template, innerValues)
|
|
121
|
+
if (argv.length === 0) {
|
|
122
|
+
throw new Error('Command is empty')
|
|
123
|
+
}
|
|
124
|
+
const [cmd, ...args] = argv
|
|
125
|
+
return executeInternal(cmd, args, options)
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
// Overload 1: direct tagged template
|
|
130
|
+
const template = templateOrOptions as TemplateStringsArray
|
|
131
|
+
const argv = templateToArgv(template, values)
|
|
132
|
+
if (argv.length === 0) {
|
|
133
|
+
throw new Error('Command is empty')
|
|
134
|
+
}
|
|
135
|
+
const [cmd, ...args] = argv
|
|
136
|
+
return executeInternal(cmd, args, {})
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* Run a command string via the shell (supports pipes, redirects, &&, ||).
|
|
141
|
+
*/
|
|
142
|
+
export async function sh(cmd: string, options: RunOptions = {}): Promise<ShellOutputResult> {
|
|
143
|
+
return executeInternal(cmd, undefined, { ...options, isShell: true })
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* Run a command and capture its stdout, stderr and exitCode without throwing on failure.
|
|
148
|
+
*/
|
|
149
|
+
export async function output(cmd: string, options: RunOptions = {}): Promise<ShellOutputResult> {
|
|
150
|
+
return executeInternal(cmd, undefined, { ...options, isShell: true, reject: false, capture: true })
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
export const shell = {
|
|
154
|
+
run,
|
|
155
|
+
sh,
|
|
156
|
+
output
|
|
157
|
+
}
|
|
@@ -0,0 +1,249 @@
|
|
|
1
|
+
import * as fs from 'node:fs/promises'
|
|
2
|
+
import * as path from 'node:path'
|
|
3
|
+
import { tmpdir } from 'node:os'
|
|
4
|
+
import { execa } from 'execa'
|
|
5
|
+
import { tryGetContext } from '../context/index.js'
|
|
6
|
+
import { maskString } from '../utils/mask.js'
|
|
7
|
+
import {
|
|
8
|
+
getSecretValues,
|
|
9
|
+
resolveEnv,
|
|
10
|
+
runSubprocess
|
|
11
|
+
} from './subprocess.js'
|
|
12
|
+
|
|
13
|
+
export interface SshServerConfig {
|
|
14
|
+
host?: string
|
|
15
|
+
server?: string
|
|
16
|
+
user: string
|
|
17
|
+
privateKeyFile?: string
|
|
18
|
+
privateKey?: string
|
|
19
|
+
port?: number
|
|
20
|
+
tty?: boolean
|
|
21
|
+
connectTimeout?: number
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export interface SshAuthConfig {
|
|
25
|
+
user: string
|
|
26
|
+
privateKey?: string
|
|
27
|
+
privateKeyFile?: string
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export interface SshConnectionConfig extends SshAuthConfig {
|
|
31
|
+
host: string
|
|
32
|
+
port?: number
|
|
33
|
+
tty?: boolean
|
|
34
|
+
connectTimeout?: number
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export type SshTarget = string | SshConnectionConfig
|
|
38
|
+
|
|
39
|
+
export interface SshResult {
|
|
40
|
+
stdout: string
|
|
41
|
+
stderr: string
|
|
42
|
+
exitCode: number
|
|
43
|
+
failed?: boolean
|
|
44
|
+
signal?: string
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function resolveTarget(
|
|
48
|
+
target: SshTarget,
|
|
49
|
+
configMap: Record<string, SshServerConfig> = {}
|
|
50
|
+
): SshConnectionConfig {
|
|
51
|
+
if (typeof target !== 'string') {
|
|
52
|
+
if (!target.host || !target.user) {
|
|
53
|
+
throw new Error('SSH target config requires both "host" and "user"')
|
|
54
|
+
}
|
|
55
|
+
return target
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
const config = configMap[target]
|
|
59
|
+
if (!config) {
|
|
60
|
+
const known = Object.keys(configMap)
|
|
61
|
+
throw new Error(
|
|
62
|
+
`SSH target configuration not found for key: "${target}". ` +
|
|
63
|
+
(known.length ? `Configured targets: ${known.join(', ')}` : 'No SSH targets configured.')
|
|
64
|
+
)
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
const host = config.host || config.server
|
|
68
|
+
if (!host) {
|
|
69
|
+
throw new Error(`SSH config '${target}' is missing host/server address`)
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
return {
|
|
73
|
+
host,
|
|
74
|
+
user: config.user,
|
|
75
|
+
privateKeyFile: config.privateKeyFile,
|
|
76
|
+
privateKey: config.privateKey,
|
|
77
|
+
port: config.port,
|
|
78
|
+
connectTimeout: config.connectTimeout,
|
|
79
|
+
tty: config.tty
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
async function createTempKeyFile(keyContent: string): Promise<string> {
|
|
84
|
+
const dir = await fs.mkdtemp(path.join(tmpdir(), 'omniflow-ssh-'))
|
|
85
|
+
const keyFile = path.join(dir, 'id_rsa')
|
|
86
|
+
await fs.writeFile(keyFile, keyContent, { mode: 0o600 })
|
|
87
|
+
return keyFile
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
function connectionArgs(
|
|
91
|
+
config: SshConnectionConfig,
|
|
92
|
+
keyFile: string | undefined,
|
|
93
|
+
portFlag: '-p' | '-P'
|
|
94
|
+
): string[] {
|
|
95
|
+
const args = [
|
|
96
|
+
'-o', 'BatchMode=yes',
|
|
97
|
+
'-o', 'StrictHostKeyChecking=accept-new',
|
|
98
|
+
'-o', `ConnectTimeout=${config.connectTimeout ?? 15}`,
|
|
99
|
+
portFlag, String(config.port ?? 22)
|
|
100
|
+
]
|
|
101
|
+
if (keyFile) {
|
|
102
|
+
args.push('-i', keyFile)
|
|
103
|
+
}
|
|
104
|
+
return args
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
function shellQuote(str: string): string {
|
|
108
|
+
if (/^[a-zA-Z0-9_./-]+$/.test(str)) return str
|
|
109
|
+
return `'${str.replace(/'/g, "'\\''")}'`
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
export class SshClient {
|
|
113
|
+
private readonly configMap: Record<string, SshServerConfig>
|
|
114
|
+
|
|
115
|
+
constructor(configMap: Record<string, SshServerConfig> = {}) {
|
|
116
|
+
this.configMap = configMap
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
async exec(
|
|
120
|
+
target: SshTarget,
|
|
121
|
+
remoteCommand: string,
|
|
122
|
+
remoteDir?: string
|
|
123
|
+
): Promise<SshResult> {
|
|
124
|
+
const ctx = tryGetContext()
|
|
125
|
+
const config = resolveTarget(target, this.configMap)
|
|
126
|
+
const secrets = getSecretValues()
|
|
127
|
+
const maskedCmd = maskString(remoteCommand, secrets)
|
|
128
|
+
|
|
129
|
+
if (ctx?.dryRun) {
|
|
130
|
+
ctx.logger.info(`[DRY-RUN] SSH to ${config.user}@${config.host}: ${maskedCmd} (remoteDir: ${remoteDir ?? '~'})`)
|
|
131
|
+
return { stdout: '', stderr: '', exitCode: 0, failed: false }
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
let tempKeyFile: string | undefined
|
|
135
|
+
try {
|
|
136
|
+
if (config.privateKey) {
|
|
137
|
+
tempKeyFile = await createTempKeyFile(config.privateKey)
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
const keyFile = tempKeyFile || config.privateKeyFile
|
|
141
|
+
const args = connectionArgs(config, keyFile, '-p')
|
|
142
|
+
|
|
143
|
+
if (config.tty) {
|
|
144
|
+
args.push('-tt')
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
args.push(`${config.user}@${config.host}`)
|
|
148
|
+
|
|
149
|
+
const script = remoteDir
|
|
150
|
+
? `cd ${shellQuote(remoteDir)} || exit 1\n${remoteCommand}`
|
|
151
|
+
: remoteCommand
|
|
152
|
+
|
|
153
|
+
args.push(script)
|
|
154
|
+
|
|
155
|
+
const displayCmd = `SSH (${config.user}@${config.host}) $ ${maskedCmd}`
|
|
156
|
+
ctx?.logger?.info?.(displayCmd)
|
|
157
|
+
|
|
158
|
+
const subprocess = execa('ssh', args, { reject: true, env: resolveEnv() })
|
|
159
|
+
|
|
160
|
+
return await runSubprocess({
|
|
161
|
+
subprocess,
|
|
162
|
+
displayCmd,
|
|
163
|
+
cmdText: maskedCmd,
|
|
164
|
+
errorPrefix: 'SSH command',
|
|
165
|
+
reject: true,
|
|
166
|
+
secrets
|
|
167
|
+
})
|
|
168
|
+
} finally {
|
|
169
|
+
if (tempKeyFile) {
|
|
170
|
+
await fs.rm(path.dirname(tempKeyFile), { recursive: true, force: true }).catch(() => {})
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
async cp(
|
|
176
|
+
target: SshTarget,
|
|
177
|
+
srcFile: string,
|
|
178
|
+
targetFolder: string
|
|
179
|
+
): Promise<void> {
|
|
180
|
+
const ctx = tryGetContext()
|
|
181
|
+
const config = resolveTarget(target, this.configMap)
|
|
182
|
+
const secrets = getSecretValues()
|
|
183
|
+
|
|
184
|
+
if (ctx?.dryRun) {
|
|
185
|
+
ctx.logger.info(`[DRY-RUN] SCP: Copy ${srcFile} -> ${config.user}@${config.host}:${targetFolder}`)
|
|
186
|
+
return
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
let tempKeyFile: string | undefined
|
|
190
|
+
try {
|
|
191
|
+
if (config.privateKey) {
|
|
192
|
+
tempKeyFile = await createTempKeyFile(config.privateKey)
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
const keyFile = tempKeyFile || config.privateKeyFile
|
|
196
|
+
const args = connectionArgs(config, keyFile, '-P')
|
|
197
|
+
|
|
198
|
+
args.push(srcFile)
|
|
199
|
+
args.push(`${config.user}@${config.host}:${targetFolder}`)
|
|
200
|
+
|
|
201
|
+
const displayCmd = `SCP ${srcFile} -> ${config.user}@${config.host}:${targetFolder}`
|
|
202
|
+
ctx?.logger?.info?.(displayCmd)
|
|
203
|
+
|
|
204
|
+
const subprocess = execa('scp', args, { reject: true, env: resolveEnv() })
|
|
205
|
+
|
|
206
|
+
await runSubprocess({
|
|
207
|
+
subprocess,
|
|
208
|
+
displayCmd,
|
|
209
|
+
cmdText: displayCmd,
|
|
210
|
+
errorPrefix: 'SCP',
|
|
211
|
+
reject: true,
|
|
212
|
+
secrets
|
|
213
|
+
})
|
|
214
|
+
} finally {
|
|
215
|
+
if (tempKeyFile) {
|
|
216
|
+
await fs.rm(path.dirname(tempKeyFile), { recursive: true, force: true }).catch(() => {})
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
/** Default singleton instance helper */
|
|
223
|
+
const defaultClientConfigMap: Record<string, SshServerConfig> = {}
|
|
224
|
+
const defaultClient = new SshClient(defaultClientConfigMap)
|
|
225
|
+
|
|
226
|
+
export const ssh = {
|
|
227
|
+
exec: (target: SshTarget, command: string, remoteDir?: string) =>
|
|
228
|
+
defaultClient.exec(target, command, remoteDir),
|
|
229
|
+
cp: (target: SshTarget, srcFile: string, targetFolder: string) =>
|
|
230
|
+
defaultClient.cp(target, srcFile, targetFolder),
|
|
231
|
+
createClient: (configMap: Record<string, SshServerConfig>) =>
|
|
232
|
+
new SshClient(configMap),
|
|
233
|
+
configure: (
|
|
234
|
+
nameOrMap: string | Record<string, SshServerConfig>,
|
|
235
|
+
config?: SshServerConfig
|
|
236
|
+
) => {
|
|
237
|
+
if (typeof nameOrMap === 'string' && config) {
|
|
238
|
+
defaultClientConfigMap[nameOrMap] = config
|
|
239
|
+
} else if (typeof nameOrMap === 'object' && nameOrMap !== null) {
|
|
240
|
+
Object.assign(defaultClientConfigMap, nameOrMap)
|
|
241
|
+
}
|
|
242
|
+
},
|
|
243
|
+
/** Reset/clear all configured SSH targets on the singleton instance */
|
|
244
|
+
reset: () => {
|
|
245
|
+
for (const key of Object.keys(defaultClientConfigMap)) {
|
|
246
|
+
delete defaultClientConfigMap[key]
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
}
|