@wyxos/zephyr 0.5.0 → 0.7.4
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 +33 -4
- package/package.json +1 -1
- package/src/application/configuration/preset-selection.mjs +0 -6
- package/src/application/configuration/select-deployment-target.mjs +108 -71
- package/src/application/deploy/build-remote-deployment-plan.mjs +13 -1
- package/src/application/deploy/plan-laravel-deployment-tasks.mjs +23 -2
- package/src/application/deploy/prepare-local-deployment.mjs +38 -4
- package/src/application/deploy/run-deployment.mjs +7 -1
- package/src/application/deploy/run-local-deployment-checks.mjs +70 -11
- package/src/application/release/release-node-package.mjs +68 -12
- package/src/application/release/release-packagist-package.mjs +56 -12
- package/src/cli/options.mjs +36 -9
- package/src/config/preset-options.mjs +89 -0
- package/src/config/project.mjs +45 -10
- package/src/deploy/local-repo.mjs +28 -27
- package/src/deploy/preflight.mjs +57 -3
- package/src/main.mjs +64 -6
- package/src/release/commit-message.mjs +8 -173
- package/src/release/shared.mjs +9 -21
- package/src/release-node.mjs +8 -1
- package/src/release-packagist.mjs +10 -3
- package/src/runtime/app-context.mjs +13 -1
|
@@ -50,28 +50,58 @@ async function resolveSupportedLaravelTestCommand(rootDir, {runCommandCapture} =
|
|
|
50
50
|
export async function resolveLocalDeploymentCheckSupport({
|
|
51
51
|
rootDir,
|
|
52
52
|
isLaravel,
|
|
53
|
+
skipTests = false,
|
|
54
|
+
skipLint = false,
|
|
53
55
|
runCommandCapture
|
|
54
56
|
} = {}) {
|
|
55
57
|
let lintCommand = null
|
|
56
58
|
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
59
|
+
if (!skipLint) {
|
|
60
|
+
try {
|
|
61
|
+
lintCommand = await preflight.resolveSupportedLintCommand(rootDir, {commandExists})
|
|
62
|
+
} catch (error) {
|
|
63
|
+
if (error?.code !== 'ZEPHYR_LINT_COMMAND_NOT_FOUND') {
|
|
64
|
+
throw error
|
|
65
|
+
}
|
|
62
66
|
}
|
|
63
67
|
}
|
|
64
68
|
|
|
65
|
-
const
|
|
69
|
+
const buildCommand = isLaravel && !skipTests
|
|
70
|
+
? await preflight.resolveSupportedBuildCommand(rootDir, {commandExists})
|
|
71
|
+
: null
|
|
72
|
+
|
|
73
|
+
const testCommand = isLaravel && !skipTests
|
|
66
74
|
? await resolveSupportedLaravelTestCommand(rootDir, {runCommandCapture})
|
|
67
75
|
: null
|
|
68
76
|
|
|
69
77
|
return {
|
|
70
78
|
lintCommand,
|
|
79
|
+
buildCommand,
|
|
71
80
|
testCommand
|
|
72
81
|
}
|
|
73
82
|
}
|
|
74
83
|
|
|
84
|
+
async function runLocalLaravelBuild(rootDir, {runCommand, logProcessing, logSuccess, buildCommand} = {}) {
|
|
85
|
+
try {
|
|
86
|
+
await preflight.runBuild(rootDir, {
|
|
87
|
+
runCommand,
|
|
88
|
+
logProcessing,
|
|
89
|
+
logSuccess,
|
|
90
|
+
commandExists,
|
|
91
|
+
buildCommand
|
|
92
|
+
})
|
|
93
|
+
} catch (error) {
|
|
94
|
+
if (error.code === 'ENOENT') {
|
|
95
|
+
throw new Error(
|
|
96
|
+
'Failed to run local frontend build: npm executable not found.\n' +
|
|
97
|
+
'Make sure npm is installed and available in your PATH.'
|
|
98
|
+
)
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
throw new Error(`Local frontend build failed. Fix build failures before deploying.\n${error.message}`)
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
75
105
|
async function runLocalLaravelTests(rootDir, {runCommand, logProcessing, logSuccess, testCommand} = {}) {
|
|
76
106
|
logProcessing?.('Running Laravel tests locally...')
|
|
77
107
|
|
|
@@ -95,19 +125,25 @@ export async function runLocalDeploymentChecks({
|
|
|
95
125
|
isLaravel,
|
|
96
126
|
hasHook,
|
|
97
127
|
skipGitHooks = false,
|
|
128
|
+
skipTests = false,
|
|
129
|
+
skipLint = false,
|
|
130
|
+
forceRunWhenHookPresent = false,
|
|
98
131
|
runCommand,
|
|
99
132
|
runCommandCapture,
|
|
100
133
|
logProcessing,
|
|
101
134
|
logSuccess,
|
|
102
135
|
logWarning,
|
|
103
136
|
lintCommand = undefined,
|
|
137
|
+
buildCommand = undefined,
|
|
104
138
|
testCommand = undefined
|
|
105
139
|
} = {}) {
|
|
106
|
-
const support = lintCommand !== undefined || testCommand !== undefined
|
|
107
|
-
? {lintCommand, testCommand}
|
|
140
|
+
const support = lintCommand !== undefined || buildCommand !== undefined || testCommand !== undefined
|
|
141
|
+
? {lintCommand, buildCommand, testCommand}
|
|
108
142
|
: await resolveLocalDeploymentCheckSupport({
|
|
109
143
|
rootDir,
|
|
110
144
|
isLaravel,
|
|
145
|
+
skipTests,
|
|
146
|
+
skipLint,
|
|
111
147
|
runCommandCapture
|
|
112
148
|
})
|
|
113
149
|
|
|
@@ -116,6 +152,10 @@ export async function runLocalDeploymentChecks({
|
|
|
116
152
|
logWarning?.(
|
|
117
153
|
'Pre-push git hook detected. Zephyr will run its built-in release checks manually because --skip-git-hooks is enabled, and the hook will be bypassed during git push.'
|
|
118
154
|
)
|
|
155
|
+
} else if (forceRunWhenHookPresent) {
|
|
156
|
+
logProcessing?.(
|
|
157
|
+
'Pre-push git hook detected. Zephyr will run its built-in release checks now before bumping the deployment version. The hook will still run again during git push.'
|
|
158
|
+
)
|
|
119
159
|
} else {
|
|
120
160
|
logProcessing?.(
|
|
121
161
|
'Pre-push git hook detected. Built-in release checks are supported, but Zephyr will skip executing them here. If Zephyr pushes local commits during this release, the hook will run during git push.'
|
|
@@ -124,11 +164,19 @@ export async function runLocalDeploymentChecks({
|
|
|
124
164
|
}
|
|
125
165
|
}
|
|
126
166
|
|
|
127
|
-
if (
|
|
167
|
+
if (hasHook && !skipGitHooks && (skipLint || skipTests)) {
|
|
168
|
+
logWarning?.(
|
|
169
|
+
'Pre-push git hook detected. --skip-lint/--skip-tests only skip Zephyr\'s built-in checks; your hook may still run its own checks during git push.'
|
|
170
|
+
)
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
if (skipLint) {
|
|
174
|
+
logWarning?.('Skipping lint because --skip-lint flag was provided.')
|
|
175
|
+
} else if (support.lintCommand === null) {
|
|
128
176
|
logWarning?.('No supported lint command was found. Skipping linting checks.')
|
|
129
177
|
}
|
|
130
178
|
|
|
131
|
-
const lintRan = support.lintCommand === null
|
|
179
|
+
const lintRan = skipLint || support.lintCommand === null
|
|
132
180
|
? false
|
|
133
181
|
: await preflight.runLinting(rootDir, {
|
|
134
182
|
runCommand,
|
|
@@ -152,7 +200,18 @@ export async function runLocalDeploymentChecks({
|
|
|
152
200
|
}
|
|
153
201
|
}
|
|
154
202
|
|
|
155
|
-
if (isLaravel) {
|
|
203
|
+
if (isLaravel && skipTests) {
|
|
204
|
+
logWarning?.('Skipping tests because --skip-tests flag was provided.')
|
|
205
|
+
} else if (isLaravel) {
|
|
206
|
+
if (support.buildCommand) {
|
|
207
|
+
await runLocalLaravelBuild(rootDir, {
|
|
208
|
+
runCommand,
|
|
209
|
+
logProcessing,
|
|
210
|
+
logSuccess,
|
|
211
|
+
buildCommand: support.buildCommand
|
|
212
|
+
})
|
|
213
|
+
}
|
|
214
|
+
|
|
156
215
|
await runLocalLaravelTests(rootDir, {
|
|
157
216
|
runCommand,
|
|
158
217
|
logProcessing,
|
|
@@ -3,6 +3,7 @@ import {readFile} from 'node:fs/promises'
|
|
|
3
3
|
import fs from 'node:fs'
|
|
4
4
|
import path from 'node:path'
|
|
5
5
|
import process from 'node:process'
|
|
6
|
+
import semver from 'semver'
|
|
6
7
|
|
|
7
8
|
import {writeStderr} from '../../utils/output.mjs'
|
|
8
9
|
import {
|
|
@@ -20,6 +21,44 @@ async function readPackage(rootDir = process.cwd()) {
|
|
|
20
21
|
return JSON.parse(raw)
|
|
21
22
|
}
|
|
22
23
|
|
|
24
|
+
function ensureValidPackageVersion(pkg) {
|
|
25
|
+
const version = pkg?.version
|
|
26
|
+
|
|
27
|
+
if (!version) {
|
|
28
|
+
throw new Error('package.json does not have a version field. Add a valid semver version before using --skip-versioning.')
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
if (!semver.valid(version)) {
|
|
32
|
+
throw new Error(`Invalid current version "${version}" in package.json. Must be a valid semver.`)
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
return version
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
async function ensureReleaseTagMissing(version, rootDir = process.cwd(), {
|
|
39
|
+
runCommand = runReleaseCommand
|
|
40
|
+
} = {}) {
|
|
41
|
+
const tagName = `v${version}`
|
|
42
|
+
const {stdout} = await runCommand('git', ['tag', '-l', tagName], {capture: true, cwd: rootDir})
|
|
43
|
+
|
|
44
|
+
if (stdout.trim() === tagName) {
|
|
45
|
+
throw new Error(
|
|
46
|
+
`Release tag ${tagName} already exists. Remove the existing tag or update package.json before using --skip-versioning.`
|
|
47
|
+
)
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
return tagName
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
async function createReleaseTag(version, rootDir = process.cwd(), {
|
|
54
|
+
logStep,
|
|
55
|
+
runCommand = runReleaseCommand
|
|
56
|
+
} = {}) {
|
|
57
|
+
const tagName = `v${version}`
|
|
58
|
+
logStep?.(`Creating release tag ${tagName}...`)
|
|
59
|
+
await runCommand('git', ['tag', '-a', tagName, '-m', tagName], {capture: true, cwd: rootDir})
|
|
60
|
+
}
|
|
61
|
+
|
|
23
62
|
function hasScript(pkg, scriptName) {
|
|
24
63
|
return pkg?.scripts?.[scriptName] !== undefined
|
|
25
64
|
}
|
|
@@ -354,6 +393,7 @@ export async function releaseNodePackage({
|
|
|
354
393
|
skipGitHooks = false,
|
|
355
394
|
skipTests = false,
|
|
356
395
|
skipLint = false,
|
|
396
|
+
skipVersioning = false,
|
|
357
397
|
skipBuild = false,
|
|
358
398
|
skipDeploy = false,
|
|
359
399
|
rootDir = process.cwd(),
|
|
@@ -373,6 +413,7 @@ export async function releaseNodePackage({
|
|
|
373
413
|
|
|
374
414
|
logStep?.('Reading package metadata...')
|
|
375
415
|
const pkg = await readPackage(rootDir)
|
|
416
|
+
const currentVersion = skipVersioning ? ensureValidPackageVersion(pkg) : null
|
|
376
417
|
|
|
377
418
|
logStep?.('Validating dependencies...')
|
|
378
419
|
await validateReleaseDependencies(rootDir, {
|
|
@@ -393,24 +434,39 @@ export async function releaseNodePackage({
|
|
|
393
434
|
skipGitHooks
|
|
394
435
|
})
|
|
395
436
|
await ensureReleaseBranchReady({rootDir, branchMethod: 'show-current', logStep, logWarning})
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
currentVersion
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
437
|
+
|
|
438
|
+
if (skipVersioning) {
|
|
439
|
+
await ensureReleaseTagMissing(currentVersion, rootDir, {runCommand})
|
|
440
|
+
logWarning?.(
|
|
441
|
+
`Skipping package.json version update because --skip-versioning flag was provided. Releasing current version ${currentVersion}.`
|
|
442
|
+
)
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
const resolvedReleaseType = skipVersioning
|
|
446
|
+
? null
|
|
447
|
+
: await resolveReleaseType({
|
|
448
|
+
releaseType,
|
|
449
|
+
currentVersion: pkg.version,
|
|
450
|
+
packageName: pkg.name,
|
|
451
|
+
rootDir,
|
|
452
|
+
interactive,
|
|
453
|
+
runPrompt,
|
|
454
|
+
runCommand,
|
|
455
|
+
logStep,
|
|
456
|
+
logWarning
|
|
457
|
+
})
|
|
407
458
|
|
|
408
459
|
await runLint(skipLint, pkg, rootDir, {logStep, logSuccess, logWarning, runCommand})
|
|
409
460
|
await runTests(skipTests, pkg, rootDir, {logStep, logSuccess, logWarning, runCommand})
|
|
410
461
|
await runLibBuild(skipBuild, pkg, rootDir, {logStep, logSuccess, logWarning, runCommand, skipGitHooks})
|
|
411
462
|
|
|
412
|
-
const updatedPkg =
|
|
463
|
+
const updatedPkg = skipVersioning
|
|
464
|
+
? {...pkg, version: currentVersion}
|
|
465
|
+
: await bumpVersion(resolvedReleaseType, rootDir, {logStep, logSuccess, runCommand, skipGitHooks})
|
|
413
466
|
await runBuild(skipBuild, updatedPkg, rootDir, {logStep, logSuccess, logWarning, runCommand})
|
|
467
|
+
if (skipVersioning) {
|
|
468
|
+
await createReleaseTag(updatedPkg.version, rootDir, {logStep, runCommand})
|
|
469
|
+
}
|
|
414
470
|
await pushChanges(rootDir, {logStep, logSuccess, runCommand, skipGitHooks})
|
|
415
471
|
await deployGHPages(skipDeploy, updatedPkg, rootDir, {logStep, logSuccess, logWarning, runCommand, skipGitHooks})
|
|
416
472
|
|
|
@@ -20,6 +20,30 @@ async function readComposer(rootDir = process.cwd()) {
|
|
|
20
20
|
return JSON.parse(raw)
|
|
21
21
|
}
|
|
22
22
|
|
|
23
|
+
async function ensureReleaseTagMissing(version, rootDir = process.cwd(), {
|
|
24
|
+
runCommand = runReleaseCommand
|
|
25
|
+
} = {}) {
|
|
26
|
+
const tagName = `v${version}`
|
|
27
|
+
const {stdout} = await runCommand('git', ['tag', '-l', tagName], {capture: true, cwd: rootDir})
|
|
28
|
+
|
|
29
|
+
if (stdout.trim() === tagName) {
|
|
30
|
+
throw new Error(
|
|
31
|
+
`Release tag ${tagName} already exists. Remove the existing tag or update composer.json before using --skip-versioning.`
|
|
32
|
+
)
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
return tagName
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
async function createReleaseTag(version, rootDir = process.cwd(), {
|
|
39
|
+
logStep,
|
|
40
|
+
runCommand = runReleaseCommand
|
|
41
|
+
} = {}) {
|
|
42
|
+
const tagName = `v${version}`
|
|
43
|
+
logStep?.(`Creating git tag ${tagName}...`)
|
|
44
|
+
await runCommand('git', ['tag', tagName], {cwd: rootDir})
|
|
45
|
+
}
|
|
46
|
+
|
|
23
47
|
async function writeComposer(rootDir, composer, composerPath = null) {
|
|
24
48
|
const pathToUse = composerPath || join(rootDir, 'composer.json')
|
|
25
49
|
const content = JSON.stringify(composer, null, 2) + '\n'
|
|
@@ -218,6 +242,7 @@ export async function releasePackagistPackage({
|
|
|
218
242
|
skipGitHooks = false,
|
|
219
243
|
skipTests = false,
|
|
220
244
|
skipLint = false,
|
|
245
|
+
skipVersioning = false,
|
|
221
246
|
rootDir = process.cwd(),
|
|
222
247
|
logStep,
|
|
223
248
|
logSuccess,
|
|
@@ -241,6 +266,10 @@ export async function releasePackagistPackage({
|
|
|
241
266
|
throw new Error('composer.json does not have a version field. Add "version": "0.0.0" to composer.json.')
|
|
242
267
|
}
|
|
243
268
|
|
|
269
|
+
if (skipVersioning && !semver.valid(composer.version)) {
|
|
270
|
+
throw new Error(`Invalid current version "${composer.version}" in composer.json. Must be a valid semver.`)
|
|
271
|
+
}
|
|
272
|
+
|
|
244
273
|
logStep?.('Validating dependencies...')
|
|
245
274
|
await validateReleaseDependencies(rootDir, {
|
|
246
275
|
prompt: runPrompt,
|
|
@@ -260,22 +289,37 @@ export async function releasePackagistPackage({
|
|
|
260
289
|
skipGitHooks
|
|
261
290
|
})
|
|
262
291
|
await ensureReleaseBranchReady({rootDir, branchMethod: 'show-current', logStep, logWarning})
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
292
|
+
|
|
293
|
+
if (skipVersioning) {
|
|
294
|
+
await ensureReleaseTagMissing(composer.version, rootDir, {runCommand})
|
|
295
|
+
logWarning?.(
|
|
296
|
+
`Skipping composer.json version update because --skip-versioning flag was provided. Releasing current version ${composer.version}.`
|
|
297
|
+
)
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
const resolvedReleaseType = skipVersioning
|
|
301
|
+
? null
|
|
302
|
+
: await resolveReleaseType({
|
|
303
|
+
releaseType,
|
|
304
|
+
currentVersion: composer.version,
|
|
305
|
+
packageName: composer.name,
|
|
306
|
+
rootDir,
|
|
307
|
+
interactive,
|
|
308
|
+
runPrompt,
|
|
309
|
+
runCommand,
|
|
310
|
+
logStep,
|
|
311
|
+
logWarning
|
|
312
|
+
})
|
|
274
313
|
|
|
275
314
|
await runLint(skipLint, rootDir, {logStep, logSuccess, logWarning, runCommand, progressWriter})
|
|
276
315
|
await runTests(skipTests, composer, rootDir, {logStep, logSuccess, logWarning, runCommand, progressWriter})
|
|
277
316
|
|
|
278
|
-
const updatedComposer =
|
|
317
|
+
const updatedComposer = skipVersioning
|
|
318
|
+
? {...composer}
|
|
319
|
+
: await bumpVersion(resolvedReleaseType, rootDir, {logStep, logSuccess, runCommand, skipGitHooks})
|
|
320
|
+
if (skipVersioning) {
|
|
321
|
+
await createReleaseTag(updatedComposer.version, rootDir, {logStep, runCommand})
|
|
322
|
+
}
|
|
279
323
|
await pushChanges(rootDir, {logStep, logSuccess, runCommand, skipGitHooks})
|
|
280
324
|
|
|
281
325
|
logSuccess?.(`Release workflow completed for ${composer.name}@${updatedComposer.version}.`)
|
package/src/cli/options.mjs
CHANGED
|
@@ -5,6 +5,11 @@ import {Command} from 'commander'
|
|
|
5
5
|
import {InvalidCliOptionsError} from '../runtime/errors.mjs'
|
|
6
6
|
|
|
7
7
|
const WORKFLOW_TYPES = new Set(['node', 'vue', 'packagist'])
|
|
8
|
+
|
|
9
|
+
function hasFlag(args = [], flag) {
|
|
10
|
+
return args.some((arg) => arg === flag || arg.startsWith(`${flag}=`))
|
|
11
|
+
}
|
|
12
|
+
|
|
8
13
|
function normalizeMaintenanceMode(value) {
|
|
9
14
|
if (value == null) {
|
|
10
15
|
return null
|
|
@@ -35,9 +40,12 @@ export function parseCliOptions(args = process.argv.slice(2)) {
|
|
|
35
40
|
.option('--resume-pending', 'Resume a saved pending deployment snapshot without prompting.')
|
|
36
41
|
.option('--discard-pending', 'Discard a saved pending deployment snapshot without prompting.')
|
|
37
42
|
.option('--maintenance <mode>', 'Laravel maintenance mode policy for app deployments (on|off).')
|
|
43
|
+
.option('--auto-commit', 'Automatically commit dirty deploy changes with a Codex-generated message.')
|
|
44
|
+
.option('--skip-versioning', 'Skip updating package/composer version files before continuing.')
|
|
38
45
|
.option('--skip-git-hooks', 'Bypass local git hooks for any commits and pushes Zephyr performs.')
|
|
39
|
-
.option('--skip-
|
|
40
|
-
.option('--skip-
|
|
46
|
+
.option('--skip-checks', 'Skip Zephyr local lint and test execution.')
|
|
47
|
+
.option('--skip-tests', 'Skip Zephyr local test execution in package release and app deployment workflows.')
|
|
48
|
+
.option('--skip-lint', 'Skip Zephyr local lint execution in package release and app deployment workflows.')
|
|
41
49
|
.option('--skip-build', 'Skip build execution in node/vue release workflows.')
|
|
42
50
|
.option('--skip-deploy', 'Skip GitHub Pages deployment in node/vue release workflows.')
|
|
43
51
|
.argument(
|
|
@@ -53,6 +61,7 @@ export function parseCliOptions(args = process.argv.slice(2)) {
|
|
|
53
61
|
|
|
54
62
|
const options = program.opts()
|
|
55
63
|
const workflowType = options.type ?? null
|
|
64
|
+
const explicitSkipChecks = hasFlag(args, '--skip-checks')
|
|
56
65
|
|
|
57
66
|
if (workflowType && !WORKFLOW_TYPES.has(workflowType)) {
|
|
58
67
|
throw new InvalidCliOptionsError('Invalid value for --type. Use one of: node, vue, packagist.')
|
|
@@ -67,11 +76,21 @@ export function parseCliOptions(args = process.argv.slice(2)) {
|
|
|
67
76
|
resumePending: Boolean(options.resumePending),
|
|
68
77
|
discardPending: Boolean(options.discardPending),
|
|
69
78
|
maintenanceMode: normalizeMaintenanceMode(options.maintenance),
|
|
79
|
+
autoCommit: Boolean(options.autoCommit),
|
|
80
|
+
skipVersioning: Boolean(options.skipVersioning),
|
|
70
81
|
skipGitHooks: Boolean(options.skipGitHooks),
|
|
71
|
-
|
|
72
|
-
|
|
82
|
+
skipChecks: Boolean(options.skipChecks),
|
|
83
|
+
skipTests: Boolean(options.skipTests || options.skipChecks),
|
|
84
|
+
skipLint: Boolean(options.skipLint || options.skipChecks),
|
|
73
85
|
skipBuild: Boolean(options.skipBuild),
|
|
74
|
-
skipDeploy: Boolean(options.skipDeploy)
|
|
86
|
+
skipDeploy: Boolean(options.skipDeploy),
|
|
87
|
+
explicitMaintenanceMode: hasFlag(args, '--maintenance'),
|
|
88
|
+
explicitAutoCommit: hasFlag(args, '--auto-commit'),
|
|
89
|
+
explicitSkipVersioning: hasFlag(args, '--skip-versioning'),
|
|
90
|
+
explicitSkipGitHooks: hasFlag(args, '--skip-git-hooks'),
|
|
91
|
+
explicitSkipChecks,
|
|
92
|
+
explicitSkipTests: hasFlag(args, '--skip-tests') || explicitSkipChecks,
|
|
93
|
+
explicitSkipLint: hasFlag(args, '--skip-lint') || explicitSkipChecks
|
|
75
94
|
}
|
|
76
95
|
}
|
|
77
96
|
|
|
@@ -84,8 +103,8 @@ export function validateCliOptions(options = {}) {
|
|
|
84
103
|
resumePending = false,
|
|
85
104
|
discardPending = false,
|
|
86
105
|
maintenanceMode = null,
|
|
87
|
-
|
|
88
|
-
|
|
106
|
+
autoCommit = false,
|
|
107
|
+
skipVersioning = false,
|
|
89
108
|
skipBuild = false,
|
|
90
109
|
skipDeploy = false
|
|
91
110
|
} = options
|
|
@@ -112,13 +131,21 @@ export function validateCliOptions(options = {}) {
|
|
|
112
131
|
if (maintenanceMode !== null) {
|
|
113
132
|
throw new InvalidCliOptionsError('--maintenance is only valid for app deployments.')
|
|
114
133
|
}
|
|
134
|
+
|
|
135
|
+
if (autoCommit) {
|
|
136
|
+
throw new InvalidCliOptionsError('--auto-commit is only valid for app deployments.')
|
|
137
|
+
}
|
|
115
138
|
} else {
|
|
116
|
-
if (
|
|
117
|
-
throw new InvalidCliOptionsError('
|
|
139
|
+
if (skipBuild || skipDeploy) {
|
|
140
|
+
throw new InvalidCliOptionsError('--skip-build and --skip-deploy are only valid for node/vue release workflows.')
|
|
118
141
|
}
|
|
119
142
|
|
|
120
143
|
if (nonInteractive && !presetName) {
|
|
121
144
|
throw new InvalidCliOptionsError('--non-interactive app deployments require --preset <name>.')
|
|
122
145
|
}
|
|
123
146
|
}
|
|
147
|
+
|
|
148
|
+
if (skipVersioning && options.versionArg) {
|
|
149
|
+
throw new InvalidCliOptionsError('--skip-versioning cannot be used together with an explicit version or bump argument.')
|
|
150
|
+
}
|
|
124
151
|
}
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
export const DEFAULT_PRESET_OPTIONS = Object.freeze({
|
|
2
|
+
maintenanceMode: null,
|
|
3
|
+
skipGitHooks: false,
|
|
4
|
+
skipTests: false,
|
|
5
|
+
skipLint: false,
|
|
6
|
+
skipVersioning: false,
|
|
7
|
+
autoCommit: false
|
|
8
|
+
})
|
|
9
|
+
|
|
10
|
+
function normalizeBoolean(value, fallback = false) {
|
|
11
|
+
return typeof value === 'boolean' ? value : fallback
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
function normalizeMaintenanceMode(value) {
|
|
15
|
+
return typeof value === 'boolean' ? value : null
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export function normalizePresetOptions(options = {}) {
|
|
19
|
+
return {
|
|
20
|
+
maintenanceMode: normalizeMaintenanceMode(options?.maintenanceMode),
|
|
21
|
+
skipGitHooks: normalizeBoolean(options?.skipGitHooks),
|
|
22
|
+
skipTests: normalizeBoolean(options?.skipTests),
|
|
23
|
+
skipLint: normalizeBoolean(options?.skipLint),
|
|
24
|
+
skipVersioning: normalizeBoolean(options?.skipVersioning),
|
|
25
|
+
autoCommit: normalizeBoolean(options?.autoCommit)
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export function mergeDeployOptions(executionMode = {}, presetOptions = {}) {
|
|
30
|
+
const normalizedPresetOptions = normalizePresetOptions(presetOptions)
|
|
31
|
+
|
|
32
|
+
return {
|
|
33
|
+
maintenanceMode: executionMode.explicitMaintenanceMode === true
|
|
34
|
+
? executionMode.maintenanceMode
|
|
35
|
+
: normalizedPresetOptions.maintenanceMode,
|
|
36
|
+
skipGitHooks: executionMode.explicitSkipGitHooks === true
|
|
37
|
+
? executionMode.skipGitHooks === true
|
|
38
|
+
: normalizedPresetOptions.skipGitHooks,
|
|
39
|
+
skipTests: executionMode.explicitSkipTests === true
|
|
40
|
+
? executionMode.skipTests === true
|
|
41
|
+
: normalizedPresetOptions.skipTests,
|
|
42
|
+
skipLint: executionMode.explicitSkipLint === true
|
|
43
|
+
? executionMode.skipLint === true
|
|
44
|
+
: normalizedPresetOptions.skipLint,
|
|
45
|
+
skipVersioning: executionMode.explicitSkipVersioning === true
|
|
46
|
+
? executionMode.skipVersioning === true
|
|
47
|
+
: normalizedPresetOptions.skipVersioning,
|
|
48
|
+
autoCommit: executionMode.explicitAutoCommit === true
|
|
49
|
+
? executionMode.autoCommit === true
|
|
50
|
+
: normalizedPresetOptions.autoCommit
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export function buildPresetOptionsFromExecutionMode(executionMode = {}, existingOptions = {}) {
|
|
55
|
+
const normalizedOptions = normalizePresetOptions(existingOptions)
|
|
56
|
+
|
|
57
|
+
if (executionMode.explicitMaintenanceMode === true) {
|
|
58
|
+
normalizedOptions.maintenanceMode = executionMode.maintenanceMode
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
if (executionMode.explicitSkipGitHooks === true) {
|
|
62
|
+
normalizedOptions.skipGitHooks = executionMode.skipGitHooks === true
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
if (executionMode.explicitSkipTests === true) {
|
|
66
|
+
normalizedOptions.skipTests = executionMode.skipTests === true
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
if (executionMode.explicitSkipLint === true) {
|
|
70
|
+
normalizedOptions.skipLint = executionMode.skipLint === true
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
if (executionMode.explicitSkipVersioning === true) {
|
|
74
|
+
normalizedOptions.skipVersioning = executionMode.skipVersioning === true
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
if (executionMode.explicitAutoCommit === true) {
|
|
78
|
+
normalizedOptions.autoCommit = executionMode.autoCommit === true
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
return normalizedOptions
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
export function presetOptionsEqual(left = {}, right = {}) {
|
|
85
|
+
const normalizedLeft = normalizePresetOptions(left)
|
|
86
|
+
const normalizedRight = normalizePresetOptions(right)
|
|
87
|
+
|
|
88
|
+
return JSON.stringify(normalizedLeft) === JSON.stringify(normalizedRight)
|
|
89
|
+
}
|
package/src/config/project.mjs
CHANGED
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
import fs from 'node:fs/promises'
|
|
2
2
|
|
|
3
3
|
import {ZephyrError} from '../runtime/errors.mjs'
|
|
4
|
+
import {
|
|
5
|
+
normalizePresetOptions,
|
|
6
|
+
presetOptionsEqual
|
|
7
|
+
} from './preset-options.mjs'
|
|
4
8
|
import { ensureDirectory, getProjectConfigDir, getProjectConfigPath } from '../utils/paths.mjs'
|
|
5
9
|
import { generateId } from '../utils/id.mjs'
|
|
6
10
|
|
|
@@ -44,27 +48,58 @@ export function migratePresets(presets, apps) {
|
|
|
44
48
|
return { presets: [], needsMigration: false }
|
|
45
49
|
}
|
|
46
50
|
|
|
47
|
-
const
|
|
51
|
+
const appLookup = new Map()
|
|
48
52
|
apps.forEach((app) => {
|
|
49
53
|
if (app.id && app.serverName && app.projectPath) {
|
|
50
54
|
const key = `${app.serverName}:${app.projectPath}`
|
|
51
|
-
|
|
55
|
+
appLookup.set(key, app)
|
|
52
56
|
}
|
|
53
57
|
})
|
|
54
58
|
|
|
55
59
|
let needsMigration = false
|
|
56
|
-
const migrated = presets.
|
|
57
|
-
|
|
60
|
+
const migrated = presets.flatMap((preset) => {
|
|
61
|
+
if (!preset || typeof preset !== 'object') {
|
|
62
|
+
needsMigration = true
|
|
63
|
+
return []
|
|
64
|
+
}
|
|
58
65
|
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
66
|
+
const updated = {
|
|
67
|
+
name: typeof preset.name === 'string' ? preset.name : '',
|
|
68
|
+
appId: typeof preset.appId === 'string' ? preset.appId : null,
|
|
69
|
+
branch: typeof preset.branch === 'string' ? preset.branch : null,
|
|
70
|
+
options: normalizePresetOptions(preset.options)
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
if (!presetOptionsEqual(updated.options, preset.options)) {
|
|
74
|
+
needsMigration = true
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
if (preset.key) {
|
|
78
|
+
needsMigration = true
|
|
79
|
+
const [serverName = null, projectPath = null, legacyBranch = null] = String(preset.key).split(':')
|
|
80
|
+
const app = serverName && projectPath
|
|
81
|
+
? appLookup.get(`${serverName}:${projectPath}`)
|
|
82
|
+
: null
|
|
83
|
+
|
|
84
|
+
if (app?.id) {
|
|
85
|
+
updated.appId = app.id
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
if (!updated.branch && legacyBranch) {
|
|
89
|
+
updated.branch = legacyBranch
|
|
64
90
|
}
|
|
65
91
|
}
|
|
66
92
|
|
|
67
|
-
|
|
93
|
+
if (!updated.name) {
|
|
94
|
+
needsMigration = true
|
|
95
|
+
return []
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
if (!preset.appId || preset.key || preset.branch !== updated.branch) {
|
|
99
|
+
needsMigration = true
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
return [updated]
|
|
68
103
|
})
|
|
69
104
|
|
|
70
105
|
return { presets: migrated, needsMigration }
|