@wyxos/zephyr 0.1.6 → 0.1.8
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/package.json +1 -1
- package/src/index.mjs +63 -9
package/package.json
CHANGED
package/src/index.mjs
CHANGED
|
@@ -20,6 +20,33 @@ const logSuccess = (message = '') => console.log(chalk.green(message))
|
|
|
20
20
|
const logWarning = (message = '') => console.warn(chalk.yellow(message))
|
|
21
21
|
const logError = (message = '') => console.error(chalk.red(message))
|
|
22
22
|
|
|
23
|
+
let logFilePath = null
|
|
24
|
+
|
|
25
|
+
async function getLogFilePath(rootDir) {
|
|
26
|
+
if (logFilePath) {
|
|
27
|
+
return logFilePath
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const configDir = getProjectConfigDir(rootDir)
|
|
31
|
+
await ensureDirectory(configDir)
|
|
32
|
+
|
|
33
|
+
const now = new Date()
|
|
34
|
+
const dateStr = now.toISOString().replace(/:/g, '-').replace(/\..+/, '')
|
|
35
|
+
logFilePath = path.join(configDir, `${dateStr}.log`)
|
|
36
|
+
|
|
37
|
+
return logFilePath
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
async function writeToLogFile(rootDir, message) {
|
|
41
|
+
const logPath = await getLogFilePath(rootDir)
|
|
42
|
+
const timestamp = new Date().toISOString()
|
|
43
|
+
await fs.appendFile(logPath, `${timestamp} - ${message}\n`)
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
async function closeLogFile() {
|
|
47
|
+
logFilePath = null
|
|
48
|
+
}
|
|
49
|
+
|
|
23
50
|
const createSshClient = () => {
|
|
24
51
|
if (typeof globalThis !== 'undefined' && globalThis.__zephyrSSHFactory) {
|
|
25
52
|
return globalThis.__zephyrSSHFactory()
|
|
@@ -778,27 +805,46 @@ async function runRemoteTasks(config, options = {}) {
|
|
|
778
805
|
'if [ -f "$HOME/.zshrc" ]; then . "$HOME/.zshrc"; fi'
|
|
779
806
|
].join('; ')
|
|
780
807
|
|
|
808
|
+
const escapeForDoubleQuotes = (value) => value.replace(/(["\\$`])/g, '\\$1')
|
|
809
|
+
|
|
781
810
|
const executeRemote = async (label, command, options = {}) => {
|
|
782
811
|
const { cwd = remoteCwd, allowFailure = false, printStdout = true, bootstrapEnv = true } = options
|
|
783
812
|
logProcessing(`\n→ ${label}`)
|
|
784
|
-
const wrappedCommand = bootstrapEnv ? `${profileBootstrap}; ${command}` : command
|
|
785
|
-
const result = await ssh.execCommand(wrappedCommand, { cwd })
|
|
786
813
|
|
|
787
|
-
|
|
788
|
-
|
|
814
|
+
let wrappedCommand = command
|
|
815
|
+
let execOptions = { cwd }
|
|
816
|
+
|
|
817
|
+
if (bootstrapEnv) {
|
|
818
|
+
const cwdForShell = escapeForDoubleQuotes(cwd)
|
|
819
|
+
wrappedCommand = `${profileBootstrap}; cd "${cwdForShell}" && ${command}`
|
|
820
|
+
execOptions = {}
|
|
821
|
+
}
|
|
822
|
+
|
|
823
|
+
const result = await ssh.execCommand(wrappedCommand, execOptions)
|
|
824
|
+
|
|
825
|
+
// Log all output to file
|
|
826
|
+
if (result.stdout && result.stdout.trim()) {
|
|
827
|
+
await writeToLogFile(rootDir, `[${label}] STDOUT:\n${result.stdout.trim()}`)
|
|
789
828
|
}
|
|
790
829
|
|
|
791
830
|
if (result.stderr && result.stderr.trim()) {
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
831
|
+
await writeToLogFile(rootDir, `[${label}] STDERR:\n${result.stderr.trim()}`)
|
|
832
|
+
}
|
|
833
|
+
|
|
834
|
+
// Only show errors in terminal
|
|
835
|
+
if (result.code !== 0) {
|
|
836
|
+
if (result.stdout && result.stdout.trim()) {
|
|
837
|
+
logError(`\n[${label}] Output:\n${result.stdout.trim()}`)
|
|
838
|
+
}
|
|
839
|
+
|
|
840
|
+
if (result.stderr && result.stderr.trim()) {
|
|
841
|
+
logError(`\n[${label}] Error:\n${result.stderr.trim()}`)
|
|
796
842
|
}
|
|
797
843
|
}
|
|
798
844
|
|
|
799
845
|
if (result.code !== 0 && !allowFailure) {
|
|
800
846
|
const stderr = result.stderr?.trim() ?? ''
|
|
801
|
-
|
|
847
|
+
if (/command not found/.test(stderr) || /is not recognized/.test(stderr)) {
|
|
802
848
|
throw new Error(
|
|
803
849
|
`Command failed: ${command}. Ensure the remote environment loads required tools for non-interactive shells (e.g. export PATH in profile scripts).`
|
|
804
850
|
)
|
|
@@ -1011,9 +1057,17 @@ async function runRemoteTasks(config, options = {}) {
|
|
|
1011
1057
|
}
|
|
1012
1058
|
|
|
1013
1059
|
logSuccess('\nDeployment commands completed successfully.')
|
|
1060
|
+
|
|
1061
|
+
const logPath = await getLogFilePath(rootDir)
|
|
1062
|
+
logSuccess(`\nAll task output has been logged to: ${logPath}`)
|
|
1014
1063
|
} catch (error) {
|
|
1064
|
+
const logPath = logFilePath || await getLogFilePath(rootDir).catch(() => null)
|
|
1065
|
+
if (logPath) {
|
|
1066
|
+
logError(`\nTask output has been logged to: ${logPath}`)
|
|
1067
|
+
}
|
|
1015
1068
|
throw new Error(`Deployment failed: ${error.message}`)
|
|
1016
1069
|
} finally {
|
|
1070
|
+
await closeLogFile()
|
|
1017
1071
|
ssh.dispose()
|
|
1018
1072
|
}
|
|
1019
1073
|
}
|