@wyxos/zephyr 0.3.4 → 0.4.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/README.md +71 -0
- package/bin/zephyr.mjs +15 -23
- package/package.json +1 -1
- package/src/application/configuration/select-deployment-target.mjs +94 -9
- package/src/application/deploy/build-remote-deployment-plan.mjs +17 -2
- package/src/application/deploy/execute-remote-deployment-plan.mjs +2 -2
- package/src/application/deploy/resolve-pending-snapshot.mjs +21 -1
- package/src/application/deploy/run-deployment.mjs +36 -7
- package/src/application/release/release-node-package.mjs +64 -17
- package/src/application/release/release-packagist-package.mjs +54 -19
- package/src/cli/options.mjs +122 -0
- package/src/config/project.mjs +32 -1
- package/src/config/servers.mjs +32 -2
- package/src/dependency-scanner.mjs +11 -1
- package/src/deploy/locks.mjs +40 -24
- package/src/main.mjs +199 -71
- package/src/project/bootstrap.mjs +10 -1
- package/src/release/shared.mjs +15 -5
- package/src/release-node.mjs +27 -17
- package/src/release-packagist.mjs +26 -15
- package/src/runtime/app-context.mjs +33 -6
- package/src/runtime/errors.mjs +46 -0
- package/src/runtime/local-command.mjs +12 -3
- package/src/runtime/prompt.mjs +40 -2
- package/src/utils/output.mjs +45 -0
package/src/main.mjs
CHANGED
|
@@ -3,10 +3,12 @@ import {createRequire} from 'node:module'
|
|
|
3
3
|
import path from 'node:path'
|
|
4
4
|
import process from 'node:process'
|
|
5
5
|
|
|
6
|
+
import {validateCliOptions} from './cli/options.mjs'
|
|
6
7
|
import {releaseNode} from './release-node.mjs'
|
|
7
8
|
import {releasePackagist} from './release-packagist.mjs'
|
|
8
9
|
import {validateLocalDependencies} from './dependency-scanner.mjs'
|
|
9
10
|
import * as bootstrap from './project/bootstrap.mjs'
|
|
11
|
+
import {getErrorCode} from './runtime/errors.mjs'
|
|
10
12
|
import {PROJECT_CONFIG_DIR} from './utils/paths.mjs'
|
|
11
13
|
import {writeStderrLine} from './utils/output.mjs'
|
|
12
14
|
import {createAppContext} from './runtime/app-context.mjs'
|
|
@@ -20,97 +22,223 @@ const RELEASE_SCRIPT_COMMAND = 'npx @wyxos/zephyr@latest'
|
|
|
20
22
|
const require = createRequire(import.meta.url)
|
|
21
23
|
const {version: ZEPHYR_VERSION} = require('../package.json')
|
|
22
24
|
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
25
|
+
function normalizeMainOptions(firstArg = null, secondArg = null) {
|
|
26
|
+
if (firstArg && typeof firstArg === 'object' && !Array.isArray(firstArg)) {
|
|
27
|
+
return {
|
|
28
|
+
workflowType: firstArg.workflowType ?? firstArg.type ?? null,
|
|
29
|
+
versionArg: firstArg.versionArg ?? null,
|
|
30
|
+
nonInteractive: firstArg.nonInteractive === true,
|
|
31
|
+
json: firstArg.json === true,
|
|
32
|
+
presetName: firstArg.presetName ?? null,
|
|
33
|
+
resumePending: firstArg.resumePending === true,
|
|
34
|
+
discardPending: firstArg.discardPending === true,
|
|
35
|
+
maintenanceMode: firstArg.maintenanceMode ?? null,
|
|
36
|
+
skipTests: firstArg.skipTests === true,
|
|
37
|
+
skipLint: firstArg.skipLint === true,
|
|
38
|
+
skipBuild: firstArg.skipBuild === true,
|
|
39
|
+
skipDeploy: firstArg.skipDeploy === true,
|
|
40
|
+
context: firstArg.context ?? null
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
return {
|
|
45
|
+
workflowType: firstArg ?? null,
|
|
46
|
+
versionArg: secondArg ?? null,
|
|
47
|
+
nonInteractive: false,
|
|
48
|
+
json: false,
|
|
49
|
+
presetName: null,
|
|
50
|
+
resumePending: false,
|
|
51
|
+
discardPending: false,
|
|
52
|
+
maintenanceMode: null,
|
|
53
|
+
skipTests: false,
|
|
54
|
+
skipLint: false,
|
|
55
|
+
skipBuild: false,
|
|
56
|
+
skipDeploy: false,
|
|
57
|
+
context: null
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function resolveWorkflowName(workflowType = null) {
|
|
62
|
+
if (workflowType === 'node' || workflowType === 'vue') {
|
|
63
|
+
return `release-${workflowType}`
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
if (workflowType === 'packagist') {
|
|
67
|
+
return 'release-packagist'
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
return 'deploy'
|
|
71
|
+
}
|
|
33
72
|
|
|
34
73
|
async function runRemoteTasks(config, options = {}) {
|
|
35
74
|
return await runDeployment(config, {
|
|
36
75
|
...options,
|
|
37
|
-
context: options.context
|
|
76
|
+
context: options.context
|
|
38
77
|
})
|
|
39
78
|
}
|
|
40
79
|
|
|
41
|
-
async function main(
|
|
42
|
-
|
|
80
|
+
async function main(optionsOrWorkflowType = null, versionArg = null) {
|
|
81
|
+
const options = normalizeMainOptions(optionsOrWorkflowType, versionArg)
|
|
82
|
+
|
|
83
|
+
const executionMode = {
|
|
84
|
+
interactive: !options.nonInteractive,
|
|
85
|
+
json: options.json === true && options.nonInteractive === true,
|
|
86
|
+
workflow: resolveWorkflowName(options.workflowType),
|
|
87
|
+
presetName: options.presetName,
|
|
88
|
+
maintenanceMode: options.maintenanceMode,
|
|
89
|
+
resumePending: options.resumePending,
|
|
90
|
+
discardPending: options.discardPending
|
|
91
|
+
}
|
|
92
|
+
const appContext = options.context ?? createAppContext({executionMode})
|
|
93
|
+
const {
|
|
94
|
+
logProcessing,
|
|
95
|
+
logSuccess,
|
|
96
|
+
logWarning,
|
|
97
|
+
logError,
|
|
98
|
+
runPrompt,
|
|
99
|
+
runCommand,
|
|
100
|
+
emitEvent
|
|
101
|
+
} = appContext
|
|
102
|
+
const currentExecutionMode = appContext.executionMode ?? executionMode
|
|
103
|
+
const configurationService = createConfigurationService(appContext)
|
|
104
|
+
|
|
105
|
+
try {
|
|
106
|
+
validateCliOptions(options)
|
|
107
|
+
|
|
108
|
+
if (currentExecutionMode.json) {
|
|
109
|
+
emitEvent?.('run_started', {
|
|
110
|
+
message: `Zephyr v${ZEPHYR_VERSION} starting`,
|
|
111
|
+
data: {
|
|
112
|
+
version: ZEPHYR_VERSION,
|
|
113
|
+
workflow: currentExecutionMode.workflow,
|
|
114
|
+
nonInteractive: currentExecutionMode.interactive === false,
|
|
115
|
+
presetName: currentExecutionMode.presetName,
|
|
116
|
+
maintenanceMode: currentExecutionMode.maintenanceMode,
|
|
117
|
+
resumePending: currentExecutionMode.resumePending,
|
|
118
|
+
discardPending: currentExecutionMode.discardPending
|
|
119
|
+
}
|
|
120
|
+
})
|
|
121
|
+
} else {
|
|
122
|
+
logProcessing(`Zephyr v${ZEPHYR_VERSION}`)
|
|
123
|
+
}
|
|
43
124
|
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
125
|
+
if (options.workflowType === 'node' || options.workflowType === 'vue') {
|
|
126
|
+
await releaseNode({
|
|
127
|
+
releaseType: options.versionArg,
|
|
128
|
+
skipTests: options.skipTests,
|
|
129
|
+
skipLint: options.skipLint,
|
|
130
|
+
skipBuild: options.skipBuild,
|
|
131
|
+
skipDeploy: options.skipDeploy,
|
|
132
|
+
context: appContext
|
|
133
|
+
})
|
|
134
|
+
emitEvent?.('run_completed', {
|
|
135
|
+
message: 'Zephyr workflow completed successfully.',
|
|
136
|
+
data: {
|
|
137
|
+
version: ZEPHYR_VERSION,
|
|
138
|
+
workflow: currentExecutionMode.workflow
|
|
139
|
+
}
|
|
140
|
+
})
|
|
47
141
|
return
|
|
48
|
-
} catch (error) {
|
|
49
|
-
logError('\nRelease failed:')
|
|
50
|
-
logError(error.message)
|
|
51
|
-
if (error.stack) {
|
|
52
|
-
writeStderrLine(error.stack)
|
|
53
|
-
}
|
|
54
|
-
process.exit(1)
|
|
55
142
|
}
|
|
56
|
-
}
|
|
57
143
|
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
144
|
+
if (options.workflowType === 'packagist') {
|
|
145
|
+
await releasePackagist({
|
|
146
|
+
releaseType: options.versionArg,
|
|
147
|
+
skipTests: options.skipTests,
|
|
148
|
+
skipLint: options.skipLint,
|
|
149
|
+
context: appContext
|
|
150
|
+
})
|
|
151
|
+
emitEvent?.('run_completed', {
|
|
152
|
+
message: 'Zephyr workflow completed successfully.',
|
|
153
|
+
data: {
|
|
154
|
+
version: ZEPHYR_VERSION,
|
|
155
|
+
workflow: currentExecutionMode.workflow
|
|
156
|
+
}
|
|
157
|
+
})
|
|
61
158
|
return
|
|
62
|
-
} catch (error) {
|
|
63
|
-
logError('\nRelease failed:')
|
|
64
|
-
logError(error.message)
|
|
65
|
-
if (error.stack) {
|
|
66
|
-
writeStderrLine(error.stack)
|
|
67
|
-
}
|
|
68
|
-
process.exit(1)
|
|
69
159
|
}
|
|
70
|
-
}
|
|
71
160
|
|
|
72
|
-
|
|
161
|
+
const rootDir = process.cwd()
|
|
73
162
|
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
163
|
+
await bootstrap.ensureGitignoreEntry(rootDir, {
|
|
164
|
+
projectConfigDir: PROJECT_CONFIG_DIR,
|
|
165
|
+
runCommand,
|
|
166
|
+
logSuccess,
|
|
167
|
+
logWarning
|
|
168
|
+
})
|
|
169
|
+
await bootstrap.ensureProjectReleaseScript(rootDir, {
|
|
170
|
+
runPrompt,
|
|
171
|
+
runCommand,
|
|
172
|
+
logSuccess,
|
|
173
|
+
logWarning,
|
|
174
|
+
interactive: currentExecutionMode.interactive,
|
|
175
|
+
releaseScriptName: RELEASE_SCRIPT_NAME,
|
|
176
|
+
releaseScriptCommand: RELEASE_SCRIPT_COMMAND
|
|
177
|
+
})
|
|
88
178
|
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
179
|
+
const packageJsonPath = path.join(rootDir, 'package.json')
|
|
180
|
+
const composerJsonPath = path.join(rootDir, 'composer.json')
|
|
181
|
+
const hasPackageJson = await fs.access(packageJsonPath).then(() => true).catch(() => false)
|
|
182
|
+
const hasComposerJson = await fs.access(composerJsonPath).then(() => true).catch(() => false)
|
|
93
183
|
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
184
|
+
if (hasPackageJson || hasComposerJson) {
|
|
185
|
+
logProcessing('Validating dependencies...')
|
|
186
|
+
await validateLocalDependencies(rootDir, runPrompt, logSuccess, {
|
|
187
|
+
interactive: currentExecutionMode.interactive
|
|
188
|
+
})
|
|
189
|
+
}
|
|
98
190
|
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
191
|
+
const {deploymentConfig} = await selectDeploymentTarget(rootDir, {
|
|
192
|
+
configurationService,
|
|
193
|
+
runPrompt,
|
|
194
|
+
logProcessing,
|
|
195
|
+
logSuccess,
|
|
196
|
+
logWarning,
|
|
197
|
+
emitEvent,
|
|
198
|
+
executionMode: currentExecutionMode
|
|
199
|
+
})
|
|
106
200
|
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
201
|
+
const snapshotToUse = await resolvePendingSnapshot(rootDir, deploymentConfig, {
|
|
202
|
+
runPrompt,
|
|
203
|
+
logProcessing,
|
|
204
|
+
logWarning,
|
|
205
|
+
executionMode: currentExecutionMode
|
|
206
|
+
})
|
|
207
|
+
|
|
208
|
+
await runRemoteTasks(deploymentConfig, {
|
|
209
|
+
rootDir,
|
|
210
|
+
snapshot: snapshotToUse,
|
|
211
|
+
versionArg: options.versionArg,
|
|
212
|
+
context: appContext
|
|
213
|
+
})
|
|
214
|
+
|
|
215
|
+
emitEvent?.('run_completed', {
|
|
216
|
+
message: 'Zephyr workflow completed successfully.',
|
|
217
|
+
data: {
|
|
218
|
+
version: ZEPHYR_VERSION,
|
|
219
|
+
workflow: currentExecutionMode.workflow
|
|
220
|
+
}
|
|
221
|
+
})
|
|
222
|
+
} catch (error) {
|
|
223
|
+
const errorCode = getErrorCode(error)
|
|
224
|
+
emitEvent?.('run_failed', {
|
|
225
|
+
message: error.message,
|
|
226
|
+
code: errorCode,
|
|
227
|
+
data: {
|
|
228
|
+
version: ZEPHYR_VERSION,
|
|
229
|
+
workflow: currentExecutionMode.workflow
|
|
230
|
+
}
|
|
231
|
+
})
|
|
112
232
|
|
|
113
|
-
|
|
233
|
+
if (!currentExecutionMode.json) {
|
|
234
|
+
logError(error.message)
|
|
235
|
+
if (errorCode === 'ZEPHYR_FAILURE' && error.stack) {
|
|
236
|
+
writeStderrLine(error.stack)
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
throw error
|
|
241
|
+
}
|
|
114
242
|
}
|
|
115
243
|
|
|
116
244
|
export {main, runRemoteTasks}
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import fs from 'node:fs/promises'
|
|
2
2
|
import path from 'node:path'
|
|
3
3
|
|
|
4
|
+
import {ZephyrError} from '../runtime/errors.mjs'
|
|
5
|
+
|
|
4
6
|
export async function ensureGitignoreEntry(rootDir, {
|
|
5
7
|
projectConfigDir = '.zephyr',
|
|
6
8
|
runCommand,
|
|
@@ -66,6 +68,7 @@ export async function ensureProjectReleaseScript(rootDir, {
|
|
|
66
68
|
runCommand,
|
|
67
69
|
logSuccess,
|
|
68
70
|
logWarning,
|
|
71
|
+
interactive = true,
|
|
69
72
|
releaseScriptName = 'release',
|
|
70
73
|
releaseScriptCommand = 'npx @wyxos/zephyr@latest'
|
|
71
74
|
} = {}) {
|
|
@@ -96,6 +99,13 @@ export async function ensureProjectReleaseScript(rootDir, {
|
|
|
96
99
|
return false
|
|
97
100
|
}
|
|
98
101
|
|
|
102
|
+
if (!interactive) {
|
|
103
|
+
throw new ZephyrError(
|
|
104
|
+
'Zephyr cannot run non-interactively because package.json is missing the Zephyr release script. Add `"release": "npx @wyxos/zephyr@latest"` and rerun.',
|
|
105
|
+
{code: 'ZEPHYR_RELEASE_SCRIPT_REQUIRED'}
|
|
106
|
+
)
|
|
107
|
+
}
|
|
108
|
+
|
|
99
109
|
const { installReleaseScript } = await runPrompt([
|
|
100
110
|
{
|
|
101
111
|
type: 'confirm',
|
|
@@ -144,4 +154,3 @@ export async function ensureProjectReleaseScript(rootDir, {
|
|
|
144
154
|
|
|
145
155
|
return true
|
|
146
156
|
}
|
|
147
|
-
|
package/src/release/shared.mjs
CHANGED
|
@@ -65,14 +65,23 @@ export function parseReleaseArgs({
|
|
|
65
65
|
|
|
66
66
|
export async function runReleaseCommand(command, args, {
|
|
67
67
|
cwd = process.cwd(),
|
|
68
|
-
capture = false
|
|
68
|
+
capture = false,
|
|
69
|
+
runCommandImpl = runCommandBase,
|
|
70
|
+
runCommandCaptureImpl = runCommandCaptureBase
|
|
69
71
|
} = {}) {
|
|
70
72
|
if (capture) {
|
|
71
|
-
const
|
|
73
|
+
const captured = await runCommandCaptureImpl(command, args, { cwd })
|
|
74
|
+
|
|
75
|
+
if (typeof captured === 'string') {
|
|
76
|
+
return { stdout: captured.trim(), stderr: '' }
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
const stdout = captured?.stdout ?? ''
|
|
80
|
+
const stderr = captured?.stderr ?? ''
|
|
72
81
|
return { stdout: stdout.trim(), stderr: stderr.trim() }
|
|
73
82
|
}
|
|
74
83
|
|
|
75
|
-
await
|
|
84
|
+
await runCommandImpl(command, args, { cwd })
|
|
76
85
|
return undefined
|
|
77
86
|
}
|
|
78
87
|
|
|
@@ -91,9 +100,10 @@ export async function ensureCleanWorkingTree(rootDir = process.cwd(), {
|
|
|
91
100
|
|
|
92
101
|
export async function validateReleaseDependencies(rootDir = process.cwd(), {
|
|
93
102
|
prompt = (questions) => inquirer.prompt(questions),
|
|
94
|
-
logSuccess
|
|
103
|
+
logSuccess,
|
|
104
|
+
interactive = true
|
|
95
105
|
} = {}) {
|
|
96
|
-
await validateLocalDependencies(rootDir, prompt, logSuccess)
|
|
106
|
+
await validateLocalDependencies(rootDir, prompt, logSuccess, { interactive })
|
|
97
107
|
}
|
|
98
108
|
|
|
99
109
|
export async function ensureReleaseBranchReady({
|
package/src/release-node.mjs
CHANGED
|
@@ -1,27 +1,37 @@
|
|
|
1
1
|
import process from 'node:process'
|
|
2
|
-
import
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
parseReleaseArgs,
|
|
6
|
-
} from './release/shared.mjs'
|
|
2
|
+
import {createAppContext} from './runtime/app-context.mjs'
|
|
3
|
+
import {parseReleaseArgs} from './release/shared.mjs'
|
|
7
4
|
import {releaseNodePackage} from './application/release/release-node-package.mjs'
|
|
8
5
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
6
|
+
export async function releaseNode(options = {}) {
|
|
7
|
+
const parsed = options.releaseType
|
|
8
|
+
? options
|
|
9
|
+
: parseReleaseArgs({
|
|
10
|
+
booleanFlags: ['--skip-tests', '--skip-lint', '--skip-build', '--skip-deploy']
|
|
11
|
+
})
|
|
12
|
+
const rootDir = options.rootDir ?? process.cwd()
|
|
13
|
+
const context = options.context ?? createAppContext({
|
|
14
|
+
executionMode: {
|
|
15
|
+
interactive: true,
|
|
16
|
+
json: false,
|
|
17
|
+
workflow: 'release-node'
|
|
18
|
+
}
|
|
14
19
|
})
|
|
15
|
-
const
|
|
20
|
+
const {logProcessing: logStep, logSuccess, logWarning, runPrompt, runCommand, runCommandCapture, executionMode} = context
|
|
21
|
+
|
|
16
22
|
await releaseNodePackage({
|
|
17
|
-
releaseType,
|
|
18
|
-
skipTests,
|
|
19
|
-
skipLint,
|
|
20
|
-
skipBuild,
|
|
21
|
-
skipDeploy,
|
|
23
|
+
releaseType: parsed.releaseType,
|
|
24
|
+
skipTests: parsed.skipTests === true,
|
|
25
|
+
skipLint: parsed.skipLint === true,
|
|
26
|
+
skipBuild: parsed.skipBuild === true,
|
|
27
|
+
skipDeploy: parsed.skipDeploy === true,
|
|
22
28
|
rootDir,
|
|
23
29
|
logStep,
|
|
24
30
|
logSuccess,
|
|
25
|
-
logWarning
|
|
31
|
+
logWarning,
|
|
32
|
+
runPrompt,
|
|
33
|
+
runCommandImpl: runCommand,
|
|
34
|
+
runCommandCaptureImpl: runCommandCapture,
|
|
35
|
+
interactive: executionMode?.interactive !== false
|
|
26
36
|
})
|
|
27
37
|
}
|
|
@@ -1,25 +1,36 @@
|
|
|
1
1
|
import process from 'node:process'
|
|
2
|
-
import
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
parseReleaseArgs,
|
|
6
|
-
} from './release/shared.mjs'
|
|
2
|
+
import {createAppContext} from './runtime/app-context.mjs'
|
|
3
|
+
import {parseReleaseArgs} from './release/shared.mjs'
|
|
7
4
|
import {releasePackagistPackage} from './application/release/release-packagist-package.mjs'
|
|
8
5
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
6
|
+
export async function releasePackagist(options = {}) {
|
|
7
|
+
const parsed = options.releaseType
|
|
8
|
+
? options
|
|
9
|
+
: parseReleaseArgs({
|
|
10
|
+
booleanFlags: ['--skip-tests', '--skip-lint']
|
|
11
|
+
})
|
|
12
|
+
const rootDir = options.rootDir ?? process.cwd()
|
|
13
|
+
const context = options.context ?? createAppContext({
|
|
14
|
+
executionMode: {
|
|
15
|
+
interactive: true,
|
|
16
|
+
json: false,
|
|
17
|
+
workflow: 'release-packagist'
|
|
18
|
+
}
|
|
14
19
|
})
|
|
15
|
-
const
|
|
20
|
+
const {logProcessing: logStep, logSuccess, logWarning, runPrompt, runCommand, runCommandCapture, executionMode} = context
|
|
21
|
+
|
|
16
22
|
await releasePackagistPackage({
|
|
17
|
-
releaseType,
|
|
18
|
-
skipTests,
|
|
19
|
-
skipLint,
|
|
23
|
+
releaseType: parsed.releaseType,
|
|
24
|
+
skipTests: parsed.skipTests === true,
|
|
25
|
+
skipLint: parsed.skipLint === true,
|
|
20
26
|
rootDir,
|
|
21
27
|
logStep,
|
|
22
28
|
logSuccess,
|
|
23
|
-
logWarning
|
|
29
|
+
logWarning,
|
|
30
|
+
runPrompt,
|
|
31
|
+
runCommandImpl: runCommand,
|
|
32
|
+
runCommandCaptureImpl: runCommandCapture,
|
|
33
|
+
interactive: executionMode?.interactive !== false,
|
|
34
|
+
progressWriter: executionMode?.json ? process.stderr : process.stdout
|
|
24
35
|
})
|
|
25
36
|
}
|
|
@@ -2,7 +2,7 @@ import chalk from 'chalk'
|
|
|
2
2
|
import inquirer from 'inquirer'
|
|
3
3
|
import {NodeSSH} from 'node-ssh'
|
|
4
4
|
|
|
5
|
-
import {createChalkLogger} from '../utils/output.mjs'
|
|
5
|
+
import {createChalkLogger, createJsonEventEmitter, createJsonLogger} from '../utils/output.mjs'
|
|
6
6
|
import {runCommand as runCommandBase, runCommandCapture as runCommandCaptureBase} from '../utils/command.mjs'
|
|
7
7
|
import {createLocalCommandRunners} from './local-command.mjs'
|
|
8
8
|
import {createRunPrompt} from './prompt.mjs'
|
|
@@ -13,16 +13,41 @@ export function createAppContext({
|
|
|
13
13
|
inquirerInstance = inquirer,
|
|
14
14
|
NodeSSHClass = NodeSSH,
|
|
15
15
|
runCommandImpl = runCommandBase,
|
|
16
|
-
runCommandCaptureImpl = runCommandCaptureBase
|
|
16
|
+
runCommandCaptureImpl = runCommandCaptureBase,
|
|
17
|
+
executionMode = {}
|
|
17
18
|
} = {}) {
|
|
18
|
-
const
|
|
19
|
-
|
|
19
|
+
const normalizedExecutionMode = {
|
|
20
|
+
interactive: executionMode.interactive !== false,
|
|
21
|
+
json: executionMode.json === true,
|
|
22
|
+
workflow: executionMode.workflow ?? 'deploy',
|
|
23
|
+
presetName: executionMode.presetName ?? null,
|
|
24
|
+
maintenanceMode: executionMode.maintenanceMode ?? null,
|
|
25
|
+
resumePending: executionMode.resumePending === true,
|
|
26
|
+
discardPending: executionMode.discardPending === true
|
|
27
|
+
}
|
|
28
|
+
const emitEvent = normalizedExecutionMode.json
|
|
29
|
+
? createJsonEventEmitter({workflow: normalizedExecutionMode.workflow})
|
|
30
|
+
: null
|
|
31
|
+
const {logProcessing, logSuccess, logWarning, logError} = normalizedExecutionMode.json
|
|
32
|
+
? createJsonLogger({emitEvent})
|
|
33
|
+
: createChalkLogger(chalkInstance)
|
|
34
|
+
const runPrompt = createRunPrompt({
|
|
35
|
+
inquirer: inquirerInstance,
|
|
36
|
+
interactive: normalizedExecutionMode.interactive,
|
|
37
|
+
emitEvent,
|
|
38
|
+
workflow: normalizedExecutionMode.workflow
|
|
39
|
+
})
|
|
20
40
|
const createSshClient = createSshClientFactory({NodeSSH: NodeSSHClass})
|
|
21
41
|
const {runCommand, runCommandCapture} = createLocalCommandRunners({
|
|
22
42
|
runCommandBase: runCommandImpl,
|
|
23
43
|
runCommandCaptureBase: runCommandCaptureImpl
|
|
24
44
|
})
|
|
25
45
|
|
|
46
|
+
const runCommandWithMode = (command, args, options = {}) => runCommand(command, args, {
|
|
47
|
+
...options,
|
|
48
|
+
forwardStdoutToStderr: normalizedExecutionMode.json
|
|
49
|
+
})
|
|
50
|
+
|
|
26
51
|
return {
|
|
27
52
|
logProcessing,
|
|
28
53
|
logSuccess,
|
|
@@ -30,7 +55,9 @@ export function createAppContext({
|
|
|
30
55
|
logError,
|
|
31
56
|
runPrompt,
|
|
32
57
|
createSshClient,
|
|
33
|
-
runCommand,
|
|
34
|
-
runCommandCapture
|
|
58
|
+
runCommand: runCommandWithMode,
|
|
59
|
+
runCommandCapture,
|
|
60
|
+
emitEvent,
|
|
61
|
+
executionMode: normalizedExecutionMode
|
|
35
62
|
}
|
|
36
63
|
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
export class ZephyrError extends Error {
|
|
2
|
+
constructor(message, {
|
|
3
|
+
code = 'ZEPHYR_FAILURE',
|
|
4
|
+
data = {},
|
|
5
|
+
cause
|
|
6
|
+
} = {}) {
|
|
7
|
+
super(message, cause ? {cause} : undefined)
|
|
8
|
+
this.name = this.constructor.name
|
|
9
|
+
this.code = code
|
|
10
|
+
this.data = data
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export class PromptRequiredError extends ZephyrError {
|
|
15
|
+
constructor(message, {
|
|
16
|
+
data = {},
|
|
17
|
+
cause
|
|
18
|
+
} = {}) {
|
|
19
|
+
super(message, {
|
|
20
|
+
code: 'ZEPHYR_PROMPT_REQUIRED',
|
|
21
|
+
data,
|
|
22
|
+
cause
|
|
23
|
+
})
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export class InvalidCliOptionsError extends ZephyrError {
|
|
28
|
+
constructor(message, {
|
|
29
|
+
data = {},
|
|
30
|
+
cause
|
|
31
|
+
} = {}) {
|
|
32
|
+
super(message, {
|
|
33
|
+
code: 'ZEPHYR_INVALID_OPTIONS',
|
|
34
|
+
data,
|
|
35
|
+
cause
|
|
36
|
+
})
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export function getErrorCode(error) {
|
|
41
|
+
if (error && typeof error === 'object' && typeof error.code === 'string' && error.code.length > 0) {
|
|
42
|
+
return error.code
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
return 'ZEPHYR_FAILURE'
|
|
46
|
+
}
|
|
@@ -1,10 +1,20 @@
|
|
|
1
|
+
import process from 'node:process'
|
|
2
|
+
|
|
1
3
|
export function createLocalCommandRunners({ runCommandBase, runCommandCaptureBase }) {
|
|
2
4
|
if (!runCommandBase || !runCommandCaptureBase) {
|
|
3
5
|
throw new Error('createLocalCommandRunners requires runCommandBase and runCommandCaptureBase')
|
|
4
6
|
}
|
|
5
7
|
|
|
6
|
-
const runCommand = async (command, args, {
|
|
7
|
-
|
|
8
|
+
const runCommand = async (command, args, {
|
|
9
|
+
silent = false,
|
|
10
|
+
cwd,
|
|
11
|
+
forwardStdoutToStderr = false
|
|
12
|
+
} = {}) => {
|
|
13
|
+
const stdio = silent
|
|
14
|
+
? 'ignore'
|
|
15
|
+
: forwardStdoutToStderr
|
|
16
|
+
? ['ignore', process.stderr, process.stderr]
|
|
17
|
+
: 'inherit'
|
|
8
18
|
return runCommandBase(command, args, { cwd, stdio })
|
|
9
19
|
}
|
|
10
20
|
|
|
@@ -15,4 +25,3 @@ export function createLocalCommandRunners({ runCommandBase, runCommandCaptureBas
|
|
|
15
25
|
|
|
16
26
|
return { runCommand, runCommandCapture }
|
|
17
27
|
}
|
|
18
|
-
|