@wyxos/zephyr 0.2.16 → 0.2.17
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/bin/zephyr.mjs +8 -17
- package/package.json +5 -1
- package/src/dependency-scanner.mjs +41 -11
- package/src/index.mjs +47 -45
- package/src/release-node.mjs +5 -4
- package/src/release-packagist.mjs +1 -3
- package/src/ssh-utils.mjs +3 -3
- package/src/version-checker.mjs +51 -14
package/bin/zephyr.mjs
CHANGED
|
@@ -1,24 +1,15 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import
|
|
3
|
-
import {
|
|
4
|
-
import inquirer from 'inquirer'
|
|
2
|
+
import process from 'node:process'
|
|
3
|
+
import { logError, main } from '../src/index.mjs'
|
|
5
4
|
|
|
6
5
|
// Parse --type flag from command line arguments
|
|
7
6
|
const args = process.argv.slice(2)
|
|
8
7
|
const typeFlag = args.find(arg => arg.startsWith('--type='))
|
|
9
8
|
const releaseType = typeFlag ? typeFlag.split('=')[1] : null
|
|
10
9
|
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
}
|
|
18
|
-
// No update or user declined, continue with normal execution
|
|
19
|
-
return main(releaseType)
|
|
20
|
-
})
|
|
21
|
-
.catch((error) => {
|
|
22
|
-
console.error(error.message)
|
|
23
|
-
process.exit(1)
|
|
24
|
-
})
|
|
10
|
+
try {
|
|
11
|
+
await main(releaseType)
|
|
12
|
+
} catch (error) {
|
|
13
|
+
logError(error?.message || String(error))
|
|
14
|
+
process.exitCode = 1
|
|
15
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@wyxos/zephyr",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.17",
|
|
4
4
|
"description": "A streamlined deployment tool for web applications with intelligent Laravel project detection",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./src/index.mjs",
|
|
@@ -13,6 +13,7 @@
|
|
|
13
13
|
},
|
|
14
14
|
"scripts": {
|
|
15
15
|
"test": "vitest run",
|
|
16
|
+
"lint": "eslint .",
|
|
16
17
|
"release": "node bin/zephyr.mjs --type=node"
|
|
17
18
|
},
|
|
18
19
|
"keywords": [
|
|
@@ -48,6 +49,9 @@
|
|
|
48
49
|
"semver": "^7.6.3"
|
|
49
50
|
},
|
|
50
51
|
"devDependencies": {
|
|
52
|
+
"@eslint/js": "^9.39.2",
|
|
53
|
+
"eslint": "^9.39.2",
|
|
54
|
+
"globals": "^17.0.0",
|
|
51
55
|
"vitest": "^2.1.8"
|
|
52
56
|
}
|
|
53
57
|
}
|
|
@@ -2,6 +2,7 @@ import { readFile, writeFile } from 'node:fs/promises'
|
|
|
2
2
|
import path from 'node:path'
|
|
3
3
|
import { spawn } from 'node:child_process'
|
|
4
4
|
import process from 'node:process'
|
|
5
|
+
import chalk from 'chalk'
|
|
5
6
|
|
|
6
7
|
const IS_WINDOWS = process.platform === 'win32'
|
|
7
8
|
|
|
@@ -147,7 +148,7 @@ async function fetchLatestNpmVersion(packageName) {
|
|
|
147
148
|
}
|
|
148
149
|
const data = await response.json()
|
|
149
150
|
return data.version || null
|
|
150
|
-
} catch (
|
|
151
|
+
} catch (_error) {
|
|
151
152
|
return null
|
|
152
153
|
}
|
|
153
154
|
}
|
|
@@ -166,7 +167,7 @@ async function fetchLatestPackagistVersion(packageName) {
|
|
|
166
167
|
return latest.version || null
|
|
167
168
|
}
|
|
168
169
|
return null
|
|
169
|
-
} catch (
|
|
170
|
+
} catch (_error) {
|
|
170
171
|
return null
|
|
171
172
|
}
|
|
172
173
|
}
|
|
@@ -262,7 +263,7 @@ async function getGitStatus(rootDir) {
|
|
|
262
263
|
try {
|
|
263
264
|
const result = await runCommand('git', ['status', '--porcelain'], { capture: true, cwd: rootDir })
|
|
264
265
|
return result.stdout || ''
|
|
265
|
-
} catch (
|
|
266
|
+
} catch (_error) {
|
|
266
267
|
return ''
|
|
267
268
|
}
|
|
268
269
|
}
|
|
@@ -280,7 +281,7 @@ function hasStagedChanges(statusOutput) {
|
|
|
280
281
|
})
|
|
281
282
|
}
|
|
282
283
|
|
|
283
|
-
async function commitDependencyUpdates(rootDir, updatedFiles, logFn) {
|
|
284
|
+
async function commitDependencyUpdates(rootDir, updatedFiles, promptFn, logFn) {
|
|
284
285
|
try {
|
|
285
286
|
// Check if we're in a git repository
|
|
286
287
|
await runCommand('git', ['rev-parse', '--is-inside-work-tree'], { capture: true, cwd: rootDir })
|
|
@@ -289,7 +290,30 @@ async function commitDependencyUpdates(rootDir, updatedFiles, logFn) {
|
|
|
289
290
|
return false
|
|
290
291
|
}
|
|
291
292
|
|
|
292
|
-
const
|
|
293
|
+
const statusBefore = await getGitStatus(rootDir)
|
|
294
|
+
|
|
295
|
+
// Avoid accidentally committing unrelated staged changes
|
|
296
|
+
if (hasStagedChanges(statusBefore)) {
|
|
297
|
+
if (logFn) {
|
|
298
|
+
logFn('Staged changes detected. Skipping auto-commit of dependency updates.')
|
|
299
|
+
}
|
|
300
|
+
return false
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
const fileList = updatedFiles.map((f) => path.basename(f)).join(', ')
|
|
304
|
+
|
|
305
|
+
const { shouldCommit } = await promptFn([
|
|
306
|
+
{
|
|
307
|
+
type: 'confirm',
|
|
308
|
+
name: 'shouldCommit',
|
|
309
|
+
message: `Commit dependency updates now? (${fileList})`,
|
|
310
|
+
default: true
|
|
311
|
+
}
|
|
312
|
+
])
|
|
313
|
+
|
|
314
|
+
if (!shouldCommit) {
|
|
315
|
+
return false
|
|
316
|
+
}
|
|
293
317
|
|
|
294
318
|
// Stage the updated files
|
|
295
319
|
for (const file of updatedFiles) {
|
|
@@ -306,7 +330,6 @@ async function commitDependencyUpdates(rootDir, updatedFiles, logFn) {
|
|
|
306
330
|
}
|
|
307
331
|
|
|
308
332
|
// Build commit message
|
|
309
|
-
const fileList = updatedFiles.map(f => path.basename(f)).join(', ')
|
|
310
333
|
const commitMessage = `chore: update local file dependencies to online versions (${fileList})`
|
|
311
334
|
|
|
312
335
|
if (logFn) {
|
|
@@ -353,20 +376,27 @@ async function validateLocalDependencies(rootDir, promptFn, logFn = null) {
|
|
|
353
376
|
})
|
|
354
377
|
)
|
|
355
378
|
|
|
356
|
-
// Build warning messages
|
|
379
|
+
// Build warning messages with colored output (danger color for package name and version)
|
|
357
380
|
const messages = depsWithVersions.map((dep) => {
|
|
381
|
+
const packageNameColored = chalk.red(dep.packageName)
|
|
382
|
+
const pathColored = chalk.dim(dep.path)
|
|
358
383
|
const versionInfo = dep.latestVersion
|
|
359
|
-
? ` Latest version available: ${dep.latestVersion}.`
|
|
384
|
+
? ` Latest version available: ${chalk.red(dep.latestVersion)}.`
|
|
360
385
|
: ' Latest version could not be determined.'
|
|
361
|
-
return `Dependency
|
|
386
|
+
return `Dependency ${packageNameColored} is pointing to a local path outside the repository: ${pathColored}.${versionInfo}`
|
|
362
387
|
})
|
|
363
388
|
|
|
389
|
+
// Build the prompt message with colored count (danger color)
|
|
390
|
+
const countColored = chalk.red(allDeps.length)
|
|
391
|
+
const countText = allDeps.length === 1 ? 'dependency' : 'dependencies'
|
|
392
|
+
const promptMessage = `Found ${countColored} local file ${countText} pointing outside the repository:\n\n${messages.join('\n\n')}\n\nUpdate to latest version?`
|
|
393
|
+
|
|
364
394
|
// Prompt user
|
|
365
395
|
const { shouldUpdate } = await promptFn([
|
|
366
396
|
{
|
|
367
397
|
type: 'confirm',
|
|
368
398
|
name: 'shouldUpdate',
|
|
369
|
-
message:
|
|
399
|
+
message: promptMessage,
|
|
370
400
|
default: true
|
|
371
401
|
}
|
|
372
402
|
])
|
|
@@ -441,7 +471,7 @@ async function validateLocalDependencies(rootDir, promptFn, logFn = null) {
|
|
|
441
471
|
|
|
442
472
|
// Commit the changes if any files were updated
|
|
443
473
|
if (updatedFiles.size > 0) {
|
|
444
|
-
await commitDependencyUpdates(rootDir, Array.from(updatedFiles), logFn)
|
|
474
|
+
await commitDependencyUpdates(rootDir, Array.from(updatedFiles), promptFn, logFn)
|
|
445
475
|
}
|
|
446
476
|
}
|
|
447
477
|
|
package/src/index.mjs
CHANGED
|
@@ -10,6 +10,7 @@ import { NodeSSH } from 'node-ssh'
|
|
|
10
10
|
import { releaseNode } from './release-node.mjs'
|
|
11
11
|
import { releasePackagist } from './release-packagist.mjs'
|
|
12
12
|
import { validateLocalDependencies } from './dependency-scanner.mjs'
|
|
13
|
+
import { checkAndUpdateVersion } from './version-checker.mjs'
|
|
13
14
|
|
|
14
15
|
const IS_WINDOWS = process.platform === 'win32'
|
|
15
16
|
|
|
@@ -89,7 +90,7 @@ async function cleanupOldLogs(rootDir) {
|
|
|
89
90
|
for (const file of filesToDelete) {
|
|
90
91
|
try {
|
|
91
92
|
await fs.unlink(file.path)
|
|
92
|
-
} catch (
|
|
93
|
+
} catch (_error) {
|
|
93
94
|
// Ignore errors when deleting old logs
|
|
94
95
|
}
|
|
95
96
|
}
|
|
@@ -414,7 +415,7 @@ async function ensureProjectReleaseScript(rootDir) {
|
|
|
414
415
|
let packageJson
|
|
415
416
|
try {
|
|
416
417
|
packageJson = JSON.parse(raw)
|
|
417
|
-
} catch (
|
|
418
|
+
} catch (_error) {
|
|
418
419
|
logWarning('Unable to parse package.json; skipping release script injection.')
|
|
419
420
|
return false
|
|
420
421
|
}
|
|
@@ -453,7 +454,7 @@ async function ensureProjectReleaseScript(rootDir) {
|
|
|
453
454
|
try {
|
|
454
455
|
await runCommand('git', ['rev-parse', '--is-inside-work-tree'], { cwd: rootDir, silent: true })
|
|
455
456
|
isGitRepo = true
|
|
456
|
-
} catch (
|
|
457
|
+
} catch (_error) {
|
|
457
458
|
logWarning('Not a git repository; skipping commit for release script addition.')
|
|
458
459
|
}
|
|
459
460
|
|
|
@@ -541,7 +542,7 @@ async function readRemoteLock(ssh, remoteCwd) {
|
|
|
541
542
|
if (checkResult.stdout && checkResult.stdout.trim() !== 'LOCK_NOT_FOUND' && checkResult.stdout.trim() !== '') {
|
|
542
543
|
try {
|
|
543
544
|
return JSON.parse(checkResult.stdout.trim())
|
|
544
|
-
} catch (
|
|
545
|
+
} catch (_error) {
|
|
545
546
|
return { raw: checkResult.stdout.trim() }
|
|
546
547
|
}
|
|
547
548
|
}
|
|
@@ -605,7 +606,7 @@ async function acquireRemoteLock(ssh, remoteCwd, rootDir) {
|
|
|
605
606
|
let details = {}
|
|
606
607
|
try {
|
|
607
608
|
details = JSON.parse(checkResult.stdout.trim())
|
|
608
|
-
} catch (
|
|
609
|
+
} catch (_error) {
|
|
609
610
|
details = { raw: checkResult.stdout.trim() }
|
|
610
611
|
}
|
|
611
612
|
|
|
@@ -620,7 +621,7 @@ async function acquireRemoteLock(ssh, remoteCwd, rootDir) {
|
|
|
620
621
|
let details = {}
|
|
621
622
|
try {
|
|
622
623
|
details = JSON.parse(checkResult.stdout.trim())
|
|
623
|
-
} catch (
|
|
624
|
+
} catch (_error) {
|
|
624
625
|
details = { raw: checkResult.stdout.trim() }
|
|
625
626
|
}
|
|
626
627
|
|
|
@@ -727,7 +728,7 @@ async function ensureGitignoreEntry(rootDir) {
|
|
|
727
728
|
cwd: rootDir
|
|
728
729
|
})
|
|
729
730
|
isGitRepo = true
|
|
730
|
-
} catch (
|
|
731
|
+
} catch (_error) {
|
|
731
732
|
logWarning('Not a git repository; skipping commit for .gitignore update.')
|
|
732
733
|
}
|
|
733
734
|
|
|
@@ -949,7 +950,7 @@ async function listGitBranches(currentDir) {
|
|
|
949
950
|
.filter(Boolean)
|
|
950
951
|
|
|
951
952
|
return branches.length ? branches : ['master']
|
|
952
|
-
} catch (
|
|
953
|
+
} catch (_error) {
|
|
953
954
|
logWarning('Unable to read git branches; defaulting to master.')
|
|
954
955
|
return ['master']
|
|
955
956
|
}
|
|
@@ -1002,7 +1003,7 @@ async function isPrivateKeyFile(filePath) {
|
|
|
1002
1003
|
try {
|
|
1003
1004
|
const content = await fs.readFile(filePath, 'utf8')
|
|
1004
1005
|
return /-----BEGIN [A-Z ]*PRIVATE KEY-----/.test(content)
|
|
1005
|
-
} catch (
|
|
1006
|
+
} catch (_error) {
|
|
1006
1007
|
return false
|
|
1007
1008
|
}
|
|
1008
1009
|
}
|
|
@@ -1091,7 +1092,7 @@ async function resolveSshKeyPath(targetPath) {
|
|
|
1091
1092
|
|
|
1092
1093
|
try {
|
|
1093
1094
|
await fs.access(expanded)
|
|
1094
|
-
} catch (
|
|
1095
|
+
} catch (_error) {
|
|
1095
1096
|
throw new Error(`SSH key not accessible at ${expanded}`)
|
|
1096
1097
|
}
|
|
1097
1098
|
|
|
@@ -1247,14 +1248,14 @@ async function runRemoteTasks(config, options = {}) {
|
|
|
1247
1248
|
}
|
|
1248
1249
|
|
|
1249
1250
|
// Run tests for Laravel projects
|
|
1250
|
-
|
|
1251
|
-
|
|
1252
|
-
|
|
1253
|
-
|
|
1254
|
-
|
|
1255
|
-
|
|
1256
|
-
|
|
1257
|
-
|
|
1251
|
+
if (isLaravel) {
|
|
1252
|
+
logProcessing('Running Laravel tests locally...')
|
|
1253
|
+
try {
|
|
1254
|
+
await runCommand('php', ['artisan', 'test', '--compact'], { cwd: rootDir })
|
|
1255
|
+
logSuccess('Local tests passed.')
|
|
1256
|
+
} catch (error) {
|
|
1257
|
+
throw new Error(`Local tests failed. Fix test failures before deploying. ${error.message}`)
|
|
1258
|
+
}
|
|
1258
1259
|
}
|
|
1259
1260
|
} else {
|
|
1260
1261
|
logProcessing('Pre-push git hook detected. Skipping local linting and test execution.')
|
|
@@ -1314,7 +1315,7 @@ async function runRemoteTasks(config, options = {}) {
|
|
|
1314
1315
|
const escapeForDoubleQuotes = (value) => value.replace(/(["\\$`])/g, '\\$1')
|
|
1315
1316
|
|
|
1316
1317
|
const executeRemote = async (label, command, options = {}) => {
|
|
1317
|
-
const { cwd = remoteCwd, allowFailure = false,
|
|
1318
|
+
const { cwd = remoteCwd, allowFailure = false, bootstrapEnv = true } = options
|
|
1318
1319
|
logProcessing(`\n→ ${label}`)
|
|
1319
1320
|
|
|
1320
1321
|
let wrappedCommand = command
|
|
@@ -1583,7 +1584,7 @@ async function runRemoteTasks(config, options = {}) {
|
|
|
1583
1584
|
const remoteHome = remoteHomeResult.stdout.trim() || `/home/${sshUser}`
|
|
1584
1585
|
const remoteCwd = resolveRemotePath(config.projectPath, remoteHome)
|
|
1585
1586
|
await compareLocksAndPrompt(rootDir, ssh, remoteCwd)
|
|
1586
|
-
} catch (
|
|
1587
|
+
} catch (_lockError) {
|
|
1587
1588
|
// Ignore lock comparison errors during error handling
|
|
1588
1589
|
}
|
|
1589
1590
|
}
|
|
@@ -1739,9 +1740,9 @@ async function selectApp(projectConfig, server, currentDir) {
|
|
|
1739
1740
|
if (apps.length > 0) {
|
|
1740
1741
|
const availableServers = [...new Set(apps.map((app) => app.serverName).filter(Boolean))]
|
|
1741
1742
|
if (availableServers.length > 0) {
|
|
1742
|
-
|
|
1743
|
-
|
|
1744
|
-
|
|
1743
|
+
logWarning(
|
|
1744
|
+
`No applications configured for server "${server.serverName}". Available servers: ${availableServers.join(', ')}`
|
|
1745
|
+
)
|
|
1745
1746
|
}
|
|
1746
1747
|
}
|
|
1747
1748
|
logProcessing(`No applications configured for ${server.serverName}. Let's create one.`)
|
|
@@ -1758,7 +1759,7 @@ async function selectApp(projectConfig, server, currentDir) {
|
|
|
1758
1759
|
return appConfig
|
|
1759
1760
|
}
|
|
1760
1761
|
|
|
1761
|
-
const choices = matches.map(({ app
|
|
1762
|
+
const choices = matches.map(({ app }, matchIndex) => ({
|
|
1762
1763
|
name: `${app.projectPath} (${app.branch})`,
|
|
1763
1764
|
value: matchIndex
|
|
1764
1765
|
}))
|
|
@@ -1796,23 +1797,6 @@ async function selectApp(projectConfig, server, currentDir) {
|
|
|
1796
1797
|
return chosen
|
|
1797
1798
|
}
|
|
1798
1799
|
|
|
1799
|
-
async function promptPresetName() {
|
|
1800
|
-
const { presetName } = await runPrompt([
|
|
1801
|
-
{
|
|
1802
|
-
type: 'input',
|
|
1803
|
-
name: 'presetName',
|
|
1804
|
-
message: 'Enter a name for this preset',
|
|
1805
|
-
validate: (value) => (value && value.trim().length > 0 ? true : 'Preset name cannot be empty.')
|
|
1806
|
-
}
|
|
1807
|
-
])
|
|
1808
|
-
|
|
1809
|
-
return presetName.trim()
|
|
1810
|
-
}
|
|
1811
|
-
|
|
1812
|
-
function generatePresetKey(serverName, projectPath) {
|
|
1813
|
-
return `${serverName}:${projectPath}`
|
|
1814
|
-
}
|
|
1815
|
-
|
|
1816
1800
|
async function selectPreset(projectConfig, servers) {
|
|
1817
1801
|
const presets = projectConfig.presets ?? []
|
|
1818
1802
|
const apps = projectConfig.apps ?? []
|
|
@@ -1844,7 +1828,7 @@ async function selectPreset(projectConfig, servers) {
|
|
|
1844
1828
|
|
|
1845
1829
|
return {
|
|
1846
1830
|
name: displayName,
|
|
1847
|
-
|
|
1831
|
+
value: index
|
|
1848
1832
|
}
|
|
1849
1833
|
})
|
|
1850
1834
|
|
|
@@ -1871,6 +1855,24 @@ async function selectPreset(projectConfig, servers) {
|
|
|
1871
1855
|
}
|
|
1872
1856
|
|
|
1873
1857
|
async function main(releaseType = null) {
|
|
1858
|
+
// Best-effort update check (skip during tests or when explicitly disabled)
|
|
1859
|
+
// If an update is accepted, the process will re-execute via npx @latest and we should exit early.
|
|
1860
|
+
if (
|
|
1861
|
+
process.env.ZEPHYR_SKIP_VERSION_CHECK !== '1' &&
|
|
1862
|
+
process.env.NODE_ENV !== 'test' &&
|
|
1863
|
+
process.env.VITEST !== 'true'
|
|
1864
|
+
) {
|
|
1865
|
+
try {
|
|
1866
|
+
const args = process.argv.slice(2)
|
|
1867
|
+
const reExecuted = await checkAndUpdateVersion(runPrompt, args)
|
|
1868
|
+
if (reExecuted) {
|
|
1869
|
+
return
|
|
1870
|
+
}
|
|
1871
|
+
} catch (_error) {
|
|
1872
|
+
// Never block execution due to update check issues
|
|
1873
|
+
}
|
|
1874
|
+
}
|
|
1875
|
+
|
|
1874
1876
|
// Handle node/vue package release
|
|
1875
1877
|
if (releaseType === 'node' || releaseType === 'vue') {
|
|
1876
1878
|
try {
|
|
@@ -2041,11 +2043,11 @@ async function main(releaseType = null) {
|
|
|
2041
2043
|
} else {
|
|
2042
2044
|
// Check if preset with this appId already exists
|
|
2043
2045
|
const existingIndex = presets.findIndex((p) => p.appId === appId)
|
|
2044
|
-
|
|
2046
|
+
if (existingIndex >= 0) {
|
|
2045
2047
|
presets[existingIndex].name = trimmedName
|
|
2046
2048
|
presets[existingIndex].branch = deploymentConfig.branch
|
|
2047
|
-
|
|
2048
|
-
|
|
2049
|
+
} else {
|
|
2050
|
+
presets.push({
|
|
2049
2051
|
name: trimmedName,
|
|
2050
2052
|
appId: appId,
|
|
2051
2053
|
branch: deploymentConfig.branch
|
package/src/release-node.mjs
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import { spawn, exec } from 'node:child_process'
|
|
2
|
-
import {
|
|
3
|
-
import { dirname, join } from 'node:path'
|
|
2
|
+
import { join } from 'node:path'
|
|
4
3
|
import { readFile } from 'node:fs/promises'
|
|
5
4
|
import fs from 'node:fs'
|
|
6
5
|
import path from 'node:path'
|
|
@@ -489,7 +488,7 @@ function extractDomainFromHomepage(homepage) {
|
|
|
489
488
|
return url.hostname
|
|
490
489
|
} catch {
|
|
491
490
|
// If it's not a valid URL, try to extract domain from string
|
|
492
|
-
const match = homepage.match(/(?:https?:\/\/)?([
|
|
491
|
+
const match = homepage.match(/(?:https?:\/\/)?([^/]+)/)
|
|
493
492
|
return match ? match[1] : null
|
|
494
493
|
}
|
|
495
494
|
}
|
|
@@ -537,7 +536,9 @@ async function deployGHPages(skipDeploy, pkg, rootDir = process.cwd()) {
|
|
|
537
536
|
try {
|
|
538
537
|
try {
|
|
539
538
|
await runCommand('git', ['worktree', 'remove', worktreeDir, '-f'], { capture: true, cwd: rootDir })
|
|
540
|
-
} catch {
|
|
539
|
+
} catch (_error) {
|
|
540
|
+
// Ignore if worktree doesn't exist
|
|
541
|
+
}
|
|
541
542
|
|
|
542
543
|
try {
|
|
543
544
|
await runCommand('git', ['worktree', 'add', worktreeDir, 'gh-pages'], { capture: true, cwd: rootDir })
|
|
@@ -1,9 +1,7 @@
|
|
|
1
1
|
import { spawn } from 'node:child_process'
|
|
2
|
-
import {
|
|
3
|
-
import { dirname, join } from 'node:path'
|
|
2
|
+
import { join } from 'node:path'
|
|
4
3
|
import { readFile, writeFile } from 'node:fs/promises'
|
|
5
4
|
import fs from 'node:fs'
|
|
6
|
-
import path from 'node:path'
|
|
7
5
|
import process from 'node:process'
|
|
8
6
|
import semver from 'semver'
|
|
9
7
|
import inquirer from 'inquirer'
|
package/src/ssh-utils.mjs
CHANGED
|
@@ -25,7 +25,7 @@ async function resolveSshKeyPath(targetPath) {
|
|
|
25
25
|
const expanded = expandHomePath(targetPath)
|
|
26
26
|
try {
|
|
27
27
|
await fs.access(expanded)
|
|
28
|
-
} catch (
|
|
28
|
+
} catch (_error) {
|
|
29
29
|
throw new Error(`SSH key not accessible at ${expanded}`)
|
|
30
30
|
}
|
|
31
31
|
return expanded
|
|
@@ -64,7 +64,7 @@ const createSshClient = () => {
|
|
|
64
64
|
* @param {string} rootDir - Local root directory for logging
|
|
65
65
|
* @returns {Promise<{ssh: NodeSSH, remoteCwd: string, remoteHome: string}>}
|
|
66
66
|
*/
|
|
67
|
-
export async function connectToServer(config,
|
|
67
|
+
export async function connectToServer(config, _rootDir) {
|
|
68
68
|
const ssh = createSshClient()
|
|
69
69
|
const sshUser = config.sshUser || os.userInfo().username
|
|
70
70
|
const privateKeyPath = await resolveSshKeyPath(config.sshKey)
|
|
@@ -96,7 +96,7 @@ export async function connectToServer(config, rootDir) {
|
|
|
96
96
|
* @returns {Promise<Object>} Command result
|
|
97
97
|
*/
|
|
98
98
|
export async function executeRemoteCommand(ssh, label, command, options = {}) {
|
|
99
|
-
const { cwd, allowFailure = false,
|
|
99
|
+
const { cwd, allowFailure = false, bootstrapEnv = true, rootDir = null, writeToLogFile = null, env = {} } = options
|
|
100
100
|
|
|
101
101
|
logProcessing(`\n→ ${label}`)
|
|
102
102
|
|
package/src/version-checker.mjs
CHANGED
|
@@ -3,9 +3,11 @@ import { fileURLToPath } from 'node:url'
|
|
|
3
3
|
import path from 'node:path'
|
|
4
4
|
import { spawn } from 'node:child_process'
|
|
5
5
|
import process from 'node:process'
|
|
6
|
+
import https from 'node:https'
|
|
6
7
|
import semver from 'semver'
|
|
7
8
|
|
|
8
9
|
const IS_WINDOWS = process.platform === 'win32'
|
|
10
|
+
const ZEPHYR_SKIP_VERSION_CHECK_ENV = 'ZEPHYR_SKIP_VERSION_CHECK'
|
|
9
11
|
|
|
10
12
|
async function getCurrentVersion() {
|
|
11
13
|
try {
|
|
@@ -18,21 +20,57 @@ async function getCurrentVersion() {
|
|
|
18
20
|
)
|
|
19
21
|
const packageJson = JSON.parse(await readFile(packageJsonPath, 'utf8'))
|
|
20
22
|
return packageJson.version
|
|
21
|
-
} catch (
|
|
23
|
+
} catch (_error) {
|
|
22
24
|
// If we can't read package.json, return null
|
|
23
25
|
return null
|
|
24
26
|
}
|
|
25
27
|
}
|
|
26
28
|
|
|
29
|
+
function httpsGetJson(url) {
|
|
30
|
+
return new Promise((resolve, reject) => {
|
|
31
|
+
const request = https.get(
|
|
32
|
+
url,
|
|
33
|
+
{
|
|
34
|
+
headers: {
|
|
35
|
+
accept: 'application/json'
|
|
36
|
+
}
|
|
37
|
+
},
|
|
38
|
+
(response) => {
|
|
39
|
+
const { statusCode } = response
|
|
40
|
+
if (!statusCode || statusCode < 200 || statusCode >= 300) {
|
|
41
|
+
response.resume()
|
|
42
|
+
resolve(null)
|
|
43
|
+
return
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
response.setEncoding('utf8')
|
|
47
|
+
let raw = ''
|
|
48
|
+
response.on('data', (chunk) => {
|
|
49
|
+
raw += chunk
|
|
50
|
+
})
|
|
51
|
+
response.on('end', () => {
|
|
52
|
+
try {
|
|
53
|
+
resolve(JSON.parse(raw))
|
|
54
|
+
} catch (error) {
|
|
55
|
+
reject(error)
|
|
56
|
+
}
|
|
57
|
+
})
|
|
58
|
+
}
|
|
59
|
+
)
|
|
60
|
+
|
|
61
|
+
request.on('error', reject)
|
|
62
|
+
request.end()
|
|
63
|
+
})
|
|
64
|
+
}
|
|
65
|
+
|
|
27
66
|
async function getLatestVersion() {
|
|
28
67
|
try {
|
|
29
|
-
const
|
|
30
|
-
if (!
|
|
68
|
+
const data = await httpsGetJson('https://registry.npmjs.org/@wyxos/zephyr/latest')
|
|
69
|
+
if (!data) {
|
|
31
70
|
return null
|
|
32
71
|
}
|
|
33
|
-
const data = await response.json()
|
|
34
72
|
return data.version || null
|
|
35
|
-
} catch (
|
|
73
|
+
} catch (_error) {
|
|
36
74
|
return null
|
|
37
75
|
}
|
|
38
76
|
}
|
|
@@ -45,7 +83,7 @@ function isNewerVersionAvailable(current, latest) {
|
|
|
45
83
|
// Use semver to properly compare versions
|
|
46
84
|
try {
|
|
47
85
|
return semver.gt(latest, current)
|
|
48
|
-
} catch (
|
|
86
|
+
} catch (_error) {
|
|
49
87
|
// If semver comparison fails, fall back to simple string comparison
|
|
50
88
|
return latest !== current
|
|
51
89
|
}
|
|
@@ -59,7 +97,11 @@ async function reExecuteWithLatest(args) {
|
|
|
59
97
|
return new Promise((resolve, reject) => {
|
|
60
98
|
const child = spawn(command, npxArgs, {
|
|
61
99
|
stdio: 'inherit',
|
|
62
|
-
shell: IS_WINDOWS
|
|
100
|
+
shell: IS_WINDOWS,
|
|
101
|
+
env: {
|
|
102
|
+
...process.env,
|
|
103
|
+
[ZEPHYR_SKIP_VERSION_CHECK_ENV]: '1'
|
|
104
|
+
}
|
|
63
105
|
})
|
|
64
106
|
|
|
65
107
|
child.on('error', reject)
|
|
@@ -75,12 +117,7 @@ async function reExecuteWithLatest(args) {
|
|
|
75
117
|
|
|
76
118
|
export async function checkAndUpdateVersion(promptFn, args) {
|
|
77
119
|
try {
|
|
78
|
-
|
|
79
|
-
// When npx runs @latest, the version should already be latest
|
|
80
|
-
const isRunningLatest = process.env.npm_config_user_config?.includes('@latest') ||
|
|
81
|
-
process.argv.some(arg => arg.includes('@latest'))
|
|
82
|
-
|
|
83
|
-
if (isRunningLatest) {
|
|
120
|
+
if (process.env[ZEPHYR_SKIP_VERSION_CHECK_ENV] === '1') {
|
|
84
121
|
return false
|
|
85
122
|
}
|
|
86
123
|
|
|
@@ -118,7 +155,7 @@ export async function checkAndUpdateVersion(promptFn, args) {
|
|
|
118
155
|
// User confirmed, re-execute with latest version
|
|
119
156
|
await reExecuteWithLatest(args)
|
|
120
157
|
return true // Indicates we've re-executed, so the current process should exit
|
|
121
|
-
} catch (
|
|
158
|
+
} catch (_error) {
|
|
122
159
|
// If version check fails, just continue with current version
|
|
123
160
|
// Don't block the user from using the tool
|
|
124
161
|
return false
|