@wyxos/zephyr 0.2.21 → 0.2.22

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.
@@ -1,99 +1,99 @@
1
- function escapeForDoubleQuotes(value) {
2
- return value.replace(/(["\\$`])/g, '\\$1')
3
- }
4
-
5
- function escapeForSingleQuotes(value) {
6
- return value.replace(/'/g, "'\\''")
7
- }
8
-
9
- function createProfileBootstrap() {
10
- return [
11
- 'if [ -f "$HOME/.profile" ]; then . "$HOME/.profile"; fi',
12
- 'if [ -f "$HOME/.bash_profile" ]; then . "$HOME/.bash_profile"; fi',
13
- 'if [ -f "$HOME/.bashrc" ]; then . "$HOME/.bashrc"; fi',
14
- 'if [ -f "$HOME/.zprofile" ]; then . "$HOME/.zprofile"; fi',
15
- 'if [ -f "$HOME/.zshrc" ]; then . "$HOME/.zshrc"; fi',
16
- 'if [ -s "$HOME/.nvm/nvm.sh" ]; then . "$HOME/.nvm/nvm.sh"; fi',
17
- 'if [ -s "$HOME/.config/nvm/nvm.sh" ]; then . "$HOME/.config/nvm/nvm.sh"; fi',
18
- 'if [ -s "/usr/local/opt/nvm/nvm.sh" ]; then . "/usr/local/opt/nvm/nvm.sh"; fi',
19
- 'if command -v npm >/dev/null 2>&1; then :',
20
- 'elif [ -d "$HOME/.nvm/versions/node" ]; then NODE_VERSION=$(ls -1 "$HOME/.nvm/versions/node" | tail -1) && export PATH="$HOME/.nvm/versions/node/$NODE_VERSION/bin:$PATH"',
21
- 'elif [ -d "/usr/local/lib/node_modules/npm/bin" ]; then export PATH="/usr/local/lib/node_modules/npm/bin:$PATH"',
22
- 'elif [ -d "/opt/homebrew/bin" ] && [ -f "/opt/homebrew/bin/npm" ]; then export PATH="/opt/homebrew/bin:$PATH"',
23
- 'elif [ -d "/usr/local/bin" ] && [ -f "/usr/local/bin/npm" ]; then export PATH="/usr/local/bin:$PATH"',
24
- 'elif [ -d "$HOME/.local/bin" ] && [ -f "$HOME/.local/bin/npm" ]; then export PATH="$HOME/.local/bin:$PATH"',
25
- 'fi'
26
- ].join('; ')
27
- }
28
-
29
- export function createRemoteExecutor({ ssh, rootDir, remoteCwd, writeToLogFile, logProcessing, logSuccess, logError }) {
30
- const profileBootstrap = createProfileBootstrap()
31
-
32
- return async function executeRemote(label, command, options = {}) {
33
- const {
34
- cwd = remoteCwd,
35
- allowFailure = false,
36
- bootstrapEnv = true,
37
- env = {}
38
- // printStdout: legacy option, intentionally ignored (we log to file)
39
- } = options
40
-
41
- logProcessing?.(`\n→ ${label}`)
42
-
43
- let wrappedCommand = command
44
- let execOptions = { cwd }
45
-
46
- let envExports = ''
47
- if (env && Object.keys(env).length > 0) {
48
- envExports = Object.entries(env)
49
- .map(([key, value]) => `${key}='${escapeForSingleQuotes(String(value))}'`)
50
- .join(' ') + ' '
51
- }
52
-
53
- if (bootstrapEnv && cwd) {
54
- const cwdForShell = escapeForDoubleQuotes(cwd)
55
- wrappedCommand = `${profileBootstrap}; cd "${cwdForShell}" && ${envExports}${command}`
56
- execOptions = {}
57
- } else if (envExports) {
58
- wrappedCommand = `${envExports}${command}`
59
- }
60
-
61
- const result = await ssh.execCommand(wrappedCommand, execOptions)
62
-
63
- if (result.stdout && result.stdout.trim()) {
64
- await writeToLogFile(rootDir, `[${label}] STDOUT:\n${result.stdout.trim()}`)
65
- }
66
-
67
- if (result.stderr && result.stderr.trim()) {
68
- await writeToLogFile(rootDir, `[${label}] STDERR:\n${result.stderr.trim()}`)
69
- }
70
-
71
- if (result.code !== 0) {
72
- if (result.stdout && result.stdout.trim()) {
73
- logError?.(`\n[${label}] Output:\n${result.stdout.trim()}`)
74
- }
75
-
76
- if (result.stderr && result.stderr.trim()) {
77
- logError?.(`\n[${label}] Error:\n${result.stderr.trim()}`)
78
- }
79
- }
80
-
81
- if (result.code !== 0 && !allowFailure) {
82
- const stderr = result.stderr?.trim() ?? ''
83
- if (/command not found/.test(stderr) || /is not recognized/.test(stderr)) {
84
- throw new Error(
85
- `Command failed: ${command}. Ensure the remote environment loads required tools for non-interactive shells (e.g. export PATH in profile scripts).`
86
- )
87
- }
88
-
89
- throw new Error(`Command failed: ${command}`)
90
- }
91
-
92
- if (result.code === 0) {
93
- logSuccess?.(`✓ ${command}`)
94
- }
95
-
96
- return result
97
- }
98
- }
99
-
1
+ function escapeForDoubleQuotes(value) {
2
+ return value.replace(/(["\\$`])/g, '\\$1')
3
+ }
4
+
5
+ function escapeForSingleQuotes(value) {
6
+ return value.replace(/'/g, "'\\''")
7
+ }
8
+
9
+ function createProfileBootstrap() {
10
+ return [
11
+ 'if [ -f "$HOME/.profile" ]; then . "$HOME/.profile"; fi',
12
+ 'if [ -f "$HOME/.bash_profile" ]; then . "$HOME/.bash_profile"; fi',
13
+ 'if [ -f "$HOME/.bashrc" ]; then . "$HOME/.bashrc"; fi',
14
+ 'if [ -f "$HOME/.zprofile" ]; then . "$HOME/.zprofile"; fi',
15
+ 'if [ -f "$HOME/.zshrc" ]; then . "$HOME/.zshrc"; fi',
16
+ 'if [ -s "$HOME/.nvm/nvm.sh" ]; then . "$HOME/.nvm/nvm.sh"; fi',
17
+ 'if [ -s "$HOME/.config/nvm/nvm.sh" ]; then . "$HOME/.config/nvm/nvm.sh"; fi',
18
+ 'if [ -s "/usr/local/opt/nvm/nvm.sh" ]; then . "/usr/local/opt/nvm/nvm.sh"; fi',
19
+ 'if command -v npm >/dev/null 2>&1; then :',
20
+ 'elif [ -d "$HOME/.nvm/versions/node" ]; then NODE_VERSION=$(ls -1 "$HOME/.nvm/versions/node" | tail -1) && export PATH="$HOME/.nvm/versions/node/$NODE_VERSION/bin:$PATH"',
21
+ 'elif [ -d "/usr/local/lib/node_modules/npm/bin" ]; then export PATH="/usr/local/lib/node_modules/npm/bin:$PATH"',
22
+ 'elif [ -d "/opt/homebrew/bin" ] && [ -f "/opt/homebrew/bin/npm" ]; then export PATH="/opt/homebrew/bin:$PATH"',
23
+ 'elif [ -d "/usr/local/bin" ] && [ -f "/usr/local/bin/npm" ]; then export PATH="/usr/local/bin:$PATH"',
24
+ 'elif [ -d "$HOME/.local/bin" ] && [ -f "$HOME/.local/bin/npm" ]; then export PATH="$HOME/.local/bin:$PATH"',
25
+ 'fi'
26
+ ].join('; ')
27
+ }
28
+
29
+ export function createRemoteExecutor({ ssh, rootDir, remoteCwd, writeToLogFile, logProcessing, logSuccess, logError }) {
30
+ const profileBootstrap = createProfileBootstrap()
31
+
32
+ return async function executeRemote(label, command, options = {}) {
33
+ const {
34
+ cwd = remoteCwd,
35
+ allowFailure = false,
36
+ bootstrapEnv = true,
37
+ env = {}
38
+ // printStdout: legacy option, intentionally ignored (we log to file)
39
+ } = options
40
+
41
+ logProcessing?.(`\n→ ${label}`)
42
+
43
+ let wrappedCommand = command
44
+ let execOptions = { cwd }
45
+
46
+ let envExports = ''
47
+ if (env && Object.keys(env).length > 0) {
48
+ envExports = Object.entries(env)
49
+ .map(([key, value]) => `${key}='${escapeForSingleQuotes(String(value))}'`)
50
+ .join(' ') + ' '
51
+ }
52
+
53
+ if (bootstrapEnv && cwd) {
54
+ const cwdForShell = escapeForDoubleQuotes(cwd)
55
+ wrappedCommand = `${profileBootstrap}; cd "${cwdForShell}" && ${envExports}${command}`
56
+ execOptions = {}
57
+ } else if (envExports) {
58
+ wrappedCommand = `${envExports}${command}`
59
+ }
60
+
61
+ const result = await ssh.execCommand(wrappedCommand, execOptions)
62
+
63
+ if (result.stdout && result.stdout.trim()) {
64
+ await writeToLogFile(rootDir, `[${label}] STDOUT:\n${result.stdout.trim()}`)
65
+ }
66
+
67
+ if (result.stderr && result.stderr.trim()) {
68
+ await writeToLogFile(rootDir, `[${label}] STDERR:\n${result.stderr.trim()}`)
69
+ }
70
+
71
+ if (result.code !== 0) {
72
+ if (result.stdout && result.stdout.trim()) {
73
+ logError?.(`\n[${label}] Output:\n${result.stdout.trim()}`)
74
+ }
75
+
76
+ if (result.stderr && result.stderr.trim()) {
77
+ logError?.(`\n[${label}] Error:\n${result.stderr.trim()}`)
78
+ }
79
+ }
80
+
81
+ if (result.code !== 0 && !allowFailure) {
82
+ const stderr = result.stderr?.trim() ?? ''
83
+ if (/command not found/.test(stderr) || /is not recognized/.test(stderr)) {
84
+ throw new Error(
85
+ `Command failed: ${command}. Ensure the remote environment loads required tools for non-interactive shells (e.g. export PATH in profile scripts).`
86
+ )
87
+ }
88
+
89
+ throw new Error(`Command failed: ${command}`)
90
+ }
91
+
92
+ if (result.code === 0) {
93
+ logSuccess?.(`✓ ${command}`)
94
+ }
95
+
96
+ return result
97
+ }
98
+ }
99
+
@@ -1,35 +1,35 @@
1
- import fs from 'node:fs/promises'
2
- import { ensureDirectory, getPendingTasksPath, getProjectConfigDir } from '../utils/paths.mjs'
3
-
4
- export async function loadPendingTasksSnapshot(rootDir) {
5
- const snapshotPath = getPendingTasksPath(rootDir)
6
-
7
- try {
8
- const raw = await fs.readFile(snapshotPath, 'utf8')
9
- return JSON.parse(raw)
10
- } catch (error) {
11
- if (error.code === 'ENOENT') {
12
- return null
13
- }
14
-
15
- throw error
16
- }
17
- }
18
-
19
- export async function savePendingTasksSnapshot(rootDir, snapshot) {
20
- const configDir = getProjectConfigDir(rootDir)
21
- await ensureDirectory(configDir)
22
- const payload = `${JSON.stringify(snapshot, null, 2)}\n`
23
- await fs.writeFile(getPendingTasksPath(rootDir), payload)
24
- }
25
-
26
- export async function clearPendingTasksSnapshot(rootDir) {
27
- try {
28
- await fs.unlink(getPendingTasksPath(rootDir))
29
- } catch (error) {
30
- if (error.code !== 'ENOENT') {
31
- throw error
32
- }
33
- }
34
- }
35
-
1
+ import fs from 'node:fs/promises'
2
+ import { ensureDirectory, getPendingTasksPath, getProjectConfigDir } from '../utils/paths.mjs'
3
+
4
+ export async function loadPendingTasksSnapshot(rootDir) {
5
+ const snapshotPath = getPendingTasksPath(rootDir)
6
+
7
+ try {
8
+ const raw = await fs.readFile(snapshotPath, 'utf8')
9
+ return JSON.parse(raw)
10
+ } catch (error) {
11
+ if (error.code === 'ENOENT') {
12
+ return null
13
+ }
14
+
15
+ throw error
16
+ }
17
+ }
18
+
19
+ export async function savePendingTasksSnapshot(rootDir, snapshot) {
20
+ const configDir = getProjectConfigDir(rootDir)
21
+ await ensureDirectory(configDir)
22
+ const payload = `${JSON.stringify(snapshot, null, 2)}\n`
23
+ await fs.writeFile(getPendingTasksPath(rootDir), payload)
24
+ }
25
+
26
+ export async function clearPendingTasksSnapshot(rootDir) {
27
+ try {
28
+ await fs.unlink(getPendingTasksPath(rootDir))
29
+ } catch (error) {
30
+ if (error.code !== 'ENOENT') {
31
+ throw error
32
+ }
33
+ }
34
+ }
35
+
package/src/index.mjs CHANGED
@@ -1,91 +1,91 @@
1
- import chalk from 'chalk'
2
- import inquirer from 'inquirer'
3
- import { NodeSSH } from 'node-ssh'
4
-
5
- import { createChalkLogger } from './utils/output.mjs'
6
- import { runCommand as runCommandBase, runCommandCapture as runCommandCaptureBase } from './utils/command.mjs'
7
- import { createLocalCommandRunners } from './runtime/local-command.mjs'
8
- import { createRunPrompt } from './runtime/prompt.mjs'
9
- import { createSshClientFactory } from './runtime/ssh-client.mjs'
10
- import { generateId } from './utils/id.mjs'
11
-
12
- import { loadServers as loadServersImpl, saveServers } from './config/servers.mjs'
13
- import { loadProjectConfig as loadProjectConfigImpl, saveProjectConfig } from './config/project.mjs'
14
- import * as configFlow from './utils/config-flow.mjs'
15
- import * as sshKeys from './ssh/keys.mjs'
16
- import { writeToLogFile } from './utils/log-file.mjs'
17
-
18
- export { main, runRemoteTasks } from './main.mjs'
19
- export { connectToServer, executeRemoteCommand, readRemoteFile, downloadRemoteFile, deleteRemoteFile } from './ssh/index.mjs'
20
-
21
- const { logProcessing, logSuccess, logWarning, logError } = createChalkLogger(chalk)
22
- const runPrompt = createRunPrompt({ inquirer })
23
- const { runCommand, runCommandCapture } = createLocalCommandRunners({
24
- runCommandBase,
25
- runCommandCaptureBase
26
- })
27
-
28
- // Keep this aligned with main's test injection behavior
29
- const createSshClient = createSshClientFactory({ NodeSSH })
30
-
31
- export { logProcessing, logSuccess, logWarning, logError, runCommand, runCommandCapture, writeToLogFile, createSshClient }
32
-
33
- export async function loadServers() {
34
- return await loadServersImpl({ logSuccess, logWarning })
35
- }
36
-
37
- export async function loadProjectConfig(rootDir, servers) {
38
- return await loadProjectConfigImpl(rootDir, servers, { logSuccess, logWarning })
39
- }
40
-
41
- export function defaultProjectPath(currentDir) {
42
- return configFlow.defaultProjectPath(currentDir)
43
- }
44
-
45
- export async function listGitBranches(currentDir) {
46
- return await configFlow.listGitBranches(currentDir, { runCommandCapture, logWarning })
47
- }
48
-
49
- export async function promptSshDetails(currentDir, existing = {}) {
50
- return await sshKeys.promptSshDetails(currentDir, existing, { runPrompt })
51
- }
52
-
53
- export async function promptServerDetails(existingServers = []) {
54
- return await configFlow.promptServerDetails(existingServers, { runPrompt, generateId })
55
- }
56
-
57
- export async function selectServer(servers) {
58
- return await configFlow.selectServer(servers, {
59
- runPrompt,
60
- logProcessing,
61
- logSuccess,
62
- saveServers,
63
- promptServerDetails
64
- })
65
- }
66
-
67
- export async function promptAppDetails(currentDir, existing = {}) {
68
- return await configFlow.promptAppDetails(currentDir, existing, {
69
- runPrompt,
70
- listGitBranches,
71
- defaultProjectPath,
72
- promptSshDetails
73
- })
74
- }
75
-
76
- export async function selectApp(projectConfig, server, currentDir) {
77
- return await configFlow.selectApp(projectConfig, server, currentDir, {
78
- runPrompt,
79
- logWarning,
80
- logProcessing,
81
- logSuccess,
82
- saveProjectConfig,
83
- generateId,
84
- promptAppDetails
85
- })
86
- }
87
-
88
- export async function selectPreset(projectConfig, servers) {
89
- return await configFlow.selectPreset(projectConfig, servers, { runPrompt })
90
- }
91
-
1
+ import chalk from 'chalk'
2
+ import inquirer from 'inquirer'
3
+ import { NodeSSH } from 'node-ssh'
4
+
5
+ import { createChalkLogger } from './utils/output.mjs'
6
+ import { runCommand as runCommandBase, runCommandCapture as runCommandCaptureBase } from './utils/command.mjs'
7
+ import { createLocalCommandRunners } from './runtime/local-command.mjs'
8
+ import { createRunPrompt } from './runtime/prompt.mjs'
9
+ import { createSshClientFactory } from './runtime/ssh-client.mjs'
10
+ import { generateId } from './utils/id.mjs'
11
+
12
+ import { loadServers as loadServersImpl, saveServers } from './config/servers.mjs'
13
+ import { loadProjectConfig as loadProjectConfigImpl, saveProjectConfig } from './config/project.mjs'
14
+ import * as configFlow from './utils/config-flow.mjs'
15
+ import * as sshKeys from './ssh/keys.mjs'
16
+ import { writeToLogFile } from './utils/log-file.mjs'
17
+
18
+ export { main, runRemoteTasks } from './main.mjs'
19
+ export { connectToServer, executeRemoteCommand, readRemoteFile, downloadRemoteFile, deleteRemoteFile } from './ssh/index.mjs'
20
+
21
+ const { logProcessing, logSuccess, logWarning, logError } = createChalkLogger(chalk)
22
+ const runPrompt = createRunPrompt({ inquirer })
23
+ const { runCommand, runCommandCapture } = createLocalCommandRunners({
24
+ runCommandBase,
25
+ runCommandCaptureBase
26
+ })
27
+
28
+ // Keep this aligned with main's test injection behavior
29
+ const createSshClient = createSshClientFactory({ NodeSSH })
30
+
31
+ export { logProcessing, logSuccess, logWarning, logError, runCommand, runCommandCapture, writeToLogFile, createSshClient }
32
+
33
+ export async function loadServers() {
34
+ return await loadServersImpl({ logSuccess, logWarning })
35
+ }
36
+
37
+ export async function loadProjectConfig(rootDir, servers) {
38
+ return await loadProjectConfigImpl(rootDir, servers, { logSuccess, logWarning })
39
+ }
40
+
41
+ export function defaultProjectPath(currentDir) {
42
+ return configFlow.defaultProjectPath(currentDir)
43
+ }
44
+
45
+ export async function listGitBranches(currentDir) {
46
+ return await configFlow.listGitBranches(currentDir, { runCommandCapture, logWarning })
47
+ }
48
+
49
+ export async function promptSshDetails(currentDir, existing = {}) {
50
+ return await sshKeys.promptSshDetails(currentDir, existing, { runPrompt })
51
+ }
52
+
53
+ export async function promptServerDetails(existingServers = []) {
54
+ return await configFlow.promptServerDetails(existingServers, { runPrompt, generateId })
55
+ }
56
+
57
+ export async function selectServer(servers) {
58
+ return await configFlow.selectServer(servers, {
59
+ runPrompt,
60
+ logProcessing,
61
+ logSuccess,
62
+ saveServers,
63
+ promptServerDetails
64
+ })
65
+ }
66
+
67
+ export async function promptAppDetails(currentDir, existing = {}) {
68
+ return await configFlow.promptAppDetails(currentDir, existing, {
69
+ runPrompt,
70
+ listGitBranches,
71
+ defaultProjectPath,
72
+ promptSshDetails
73
+ })
74
+ }
75
+
76
+ export async function selectApp(projectConfig, server, currentDir) {
77
+ return await configFlow.selectApp(projectConfig, server, currentDir, {
78
+ runPrompt,
79
+ logWarning,
80
+ logProcessing,
81
+ logSuccess,
82
+ saveProjectConfig,
83
+ generateId,
84
+ promptAppDetails
85
+ })
86
+ }
87
+
88
+ export async function selectPreset(projectConfig, servers) {
89
+ return await configFlow.selectPreset(projectConfig, servers, { runPrompt })
90
+ }
91
+