@wyxos/zephyr 0.7.5 → 0.8.1
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 +19 -1
- package/package.json +1 -1
- package/src/application/consumer/npm-publish-wait.mjs +72 -0
- package/src/application/consumer/release-package-then-deploy-consumer.mjs +245 -0
- package/src/application/consumer/update-consumer-dependency.mjs +159 -0
- package/src/application/deploy/run-deployment.mjs +15 -1
- package/src/application/deploy/verify-laravel-setup.mjs +43 -0
- package/src/application/release/release-node-package.mjs +3 -0
- package/src/application/release/release-packagist-package.mjs +3 -0
- package/src/cli/options.mjs +100 -8
- package/src/main.mjs +87 -10
- package/src/release/shared.mjs +27 -16
- package/src/release-node.mjs +5 -2
- package/src/release-packagist.mjs +5 -2
- package/src/runtime/app-context.mjs +1 -0
package/README.md
CHANGED
|
@@ -49,6 +49,9 @@ zephyr minor --skip-checks
|
|
|
49
49
|
# Deploy a configured app non-interactively
|
|
50
50
|
zephyr --non-interactive --preset wyxos-release --maintenance off
|
|
51
51
|
|
|
52
|
+
# Configure a Laravel app target and verify SSH without deploying
|
|
53
|
+
zephyr --setup
|
|
54
|
+
|
|
52
55
|
# Deploy a configured app non-interactively and auto-commit dirty changes
|
|
53
56
|
zephyr --non-interactive --preset wyxos-release --auto-commit
|
|
54
57
|
|
|
@@ -67,6 +70,9 @@ zephyr --type node
|
|
|
67
70
|
# Release a Node/Vue package with an explicit bump
|
|
68
71
|
zephyr --type node minor
|
|
69
72
|
|
|
73
|
+
# Release a Node/Vue package, update a local consumer app, then deploy that app
|
|
74
|
+
zephyr --type node --then-deploy ../php/atlas --consumer-preset wyxos-release --consumer-package @wyxos/vibe
|
|
75
|
+
|
|
70
76
|
# Release a Packagist package
|
|
71
77
|
zephyr --type packagist patch
|
|
72
78
|
|
|
@@ -106,10 +112,14 @@ then non-interactive mode stops immediately with a clear error instead.
|
|
|
106
112
|
|
|
107
113
|
For Laravel app deployments, `--maintenance on|off` overrides both the saved preset preference and the maintenance prompt when you want an explicit choice for the current run.
|
|
108
114
|
|
|
109
|
-
`--
|
|
115
|
+
`--setup` is Laravel-only. It first verifies that the current project is a local Laravel app, then runs the normal local configuration prompts, tests SSH authentication to the selected server, and exits before local deploy preparation, pending snapshot handling, maintenance-mode decisions, locks, or remote deployment commands. On non-Laravel projects it fails before local setup changes are written.
|
|
116
|
+
|
|
117
|
+
`--auto-commit` is available for app deployments and package releases. It tells Zephyr to let local Codex inspect the repo and generate the dirty-tree commit message instead of prompting for one.
|
|
110
118
|
|
|
111
119
|
`--skip-versioning` keeps Zephyr from mutating `package.json` or `composer.json`. On app deploys it skips the local npm version bump step. On package release workflows it releases the version already present in the manifest and creates the release tag from the current `HEAD`.
|
|
112
120
|
|
|
121
|
+
`--then-deploy <path>` is available for `--type node` and `--type vue` package releases. After the package release succeeds and the published version is visible on npm, Zephyr updates the local consumer repository, commits the consumer dependency change, and deploys that consumer through its normal Zephyr app-deploy preset. Use `--consumer-preset <name>` to choose the app preset and `--consumer-package <name>` when the consumer dependency name differs from the released package name. Consumer lockfiles are refreshed only when the consumer repository already tracks an npm lockfile; Zephyr does not create or commit server-side lockfiles.
|
|
122
|
+
|
|
113
123
|
## AI Agents and Automation
|
|
114
124
|
|
|
115
125
|
Zephyr can be used safely by Codex, CI jobs, or other automation once configuration is already in place.
|
|
@@ -127,6 +137,14 @@ zephyr --type node --non-interactive --json minor
|
|
|
127
137
|
zephyr --type packagist --non-interactive --json patch
|
|
128
138
|
```
|
|
129
139
|
|
|
140
|
+
Recommended pattern for releasing a package and immediately deploying a consumer app:
|
|
141
|
+
|
|
142
|
+
```bash
|
|
143
|
+
zephyr --type node --non-interactive --json --then-deploy ../php/atlas --consumer-preset wyxos-release --consumer-package @wyxos/vibe minor
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
The consumer update happens in the local consumer repository before deployment. Production receives the committed consumer state through the normal deployment path.
|
|
147
|
+
|
|
130
148
|
In `--json` mode Zephyr emits NDJSON events on `stdout` with a stable shape:
|
|
131
149
|
|
|
132
150
|
- `run_started`
|
package/package.json
CHANGED
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
const DEFAULT_TIMEOUT_MS = 10 * 60 * 1000
|
|
2
|
+
const DEFAULT_INTERVAL_MS = 10 * 1000
|
|
3
|
+
|
|
4
|
+
function sleep(ms) {
|
|
5
|
+
return new Promise((resolve) => setTimeout(resolve, ms))
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
function npmPackageMetadataUrl(packageName) {
|
|
9
|
+
return `https://registry.npmjs.org/${encodeURIComponent(packageName)}`
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export async function waitForNpmPackageVersion({
|
|
13
|
+
packageName,
|
|
14
|
+
version,
|
|
15
|
+
timeoutMs = DEFAULT_TIMEOUT_MS,
|
|
16
|
+
intervalMs = DEFAULT_INTERVAL_MS,
|
|
17
|
+
fetchImpl = globalThis.fetch,
|
|
18
|
+
delayImpl = sleep,
|
|
19
|
+
nowImpl = Date.now,
|
|
20
|
+
logProcessing,
|
|
21
|
+
logSuccess,
|
|
22
|
+
logWarning
|
|
23
|
+
} = {}) {
|
|
24
|
+
if (!packageName) {
|
|
25
|
+
throw new Error('Package name is required before waiting for npm publication.')
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
if (!version) {
|
|
29
|
+
throw new Error('Package version is required before waiting for npm publication.')
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
if (typeof fetchImpl !== 'function') {
|
|
33
|
+
throw new Error('fetch is not available, so Zephyr cannot verify npm publication.')
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const deadline = nowImpl() + timeoutMs
|
|
37
|
+
const url = npmPackageMetadataUrl(packageName)
|
|
38
|
+
let attempts = 0
|
|
39
|
+
let lastError = null
|
|
40
|
+
|
|
41
|
+
logProcessing?.(`Waiting for ${packageName}@${version} to be visible on npm...`)
|
|
42
|
+
|
|
43
|
+
while (nowImpl() <= deadline) {
|
|
44
|
+
attempts += 1
|
|
45
|
+
|
|
46
|
+
try {
|
|
47
|
+
const response = await fetchImpl(url)
|
|
48
|
+
|
|
49
|
+
if (response.ok) {
|
|
50
|
+
const metadata = await response.json()
|
|
51
|
+
if (metadata?.versions?.[version]) {
|
|
52
|
+
logSuccess?.(`${packageName}@${version} is visible on npm.`)
|
|
53
|
+
return {packageName, version, attempts}
|
|
54
|
+
}
|
|
55
|
+
} else {
|
|
56
|
+
lastError = new Error(`npm registry responded with ${response.status}`)
|
|
57
|
+
}
|
|
58
|
+
} catch (error) {
|
|
59
|
+
lastError = error
|
|
60
|
+
logWarning?.(`npm visibility check failed: ${error.message}`)
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
if (nowImpl() + intervalMs > deadline) {
|
|
64
|
+
break
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
await delayImpl(intervalMs)
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
const suffix = lastError ? ` Last error: ${lastError.message}` : ''
|
|
71
|
+
throw new Error(`Timed out waiting for ${packageName}@${version} to be visible on npm.${suffix}`)
|
|
72
|
+
}
|
|
@@ -0,0 +1,245 @@
|
|
|
1
|
+
import fs from 'node:fs/promises'
|
|
2
|
+
import path from 'node:path'
|
|
3
|
+
import process from 'node:process'
|
|
4
|
+
|
|
5
|
+
import {validateLocalDependencies} from '../../dependency-scanner.mjs'
|
|
6
|
+
import * as bootstrap from '../../project/bootstrap.mjs'
|
|
7
|
+
import {createAppContext} from '../../runtime/app-context.mjs'
|
|
8
|
+
import {mergeDeployOptions} from '../../config/preset-options.mjs'
|
|
9
|
+
import {createConfigurationService} from '../configuration/service.mjs'
|
|
10
|
+
import {selectDeploymentTarget} from '../configuration/select-deployment-target.mjs'
|
|
11
|
+
import {resolvePendingSnapshot} from '../deploy/resolve-pending-snapshot.mjs'
|
|
12
|
+
import {runDeployment} from '../deploy/run-deployment.mjs'
|
|
13
|
+
import {waitForNpmPackageVersion} from './npm-publish-wait.mjs'
|
|
14
|
+
import {
|
|
15
|
+
assertCleanConsumerRepo,
|
|
16
|
+
updateConsumerDependency
|
|
17
|
+
} from './update-consumer-dependency.mjs'
|
|
18
|
+
|
|
19
|
+
function resolveConsumerRootDir(producerRootDir, consumerRootDir) {
|
|
20
|
+
if (!consumerRootDir) {
|
|
21
|
+
throw new Error('--then-deploy requires a consumer repository path.')
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
return path.isAbsolute(consumerRootDir)
|
|
25
|
+
? path.resolve(consumerRootDir)
|
|
26
|
+
: path.resolve(producerRootDir, consumerRootDir)
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
async function fileExists(filePath) {
|
|
30
|
+
return fs.access(filePath).then(() => true).catch(() => false)
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function createConsumerExecutionMode({
|
|
34
|
+
presetName,
|
|
35
|
+
maintenanceMode,
|
|
36
|
+
skipChecks = false,
|
|
37
|
+
skipTests = false,
|
|
38
|
+
skipLint = false,
|
|
39
|
+
skipVersioning = false,
|
|
40
|
+
skipGitHooks = false,
|
|
41
|
+
autoCommit = false,
|
|
42
|
+
json = false,
|
|
43
|
+
explicitMaintenanceMode = false,
|
|
44
|
+
explicitSkipChecks = false,
|
|
45
|
+
explicitSkipTests = false,
|
|
46
|
+
explicitSkipLint = false,
|
|
47
|
+
explicitSkipVersioning = false,
|
|
48
|
+
explicitSkipGitHooks = false,
|
|
49
|
+
explicitAutoCommit = false
|
|
50
|
+
} = {}) {
|
|
51
|
+
return {
|
|
52
|
+
interactive: false,
|
|
53
|
+
json: json === true,
|
|
54
|
+
workflow: 'deploy',
|
|
55
|
+
setup: false,
|
|
56
|
+
presetName,
|
|
57
|
+
maintenanceMode,
|
|
58
|
+
skipChecks: skipChecks === true,
|
|
59
|
+
skipTests: skipTests === true || skipChecks === true,
|
|
60
|
+
skipLint: skipLint === true || skipChecks === true,
|
|
61
|
+
skipVersioning: skipVersioning === true,
|
|
62
|
+
skipGitHooks: skipGitHooks === true,
|
|
63
|
+
autoCommit: autoCommit === true,
|
|
64
|
+
resumePending: false,
|
|
65
|
+
discardPending: false,
|
|
66
|
+
explicitMaintenanceMode: explicitMaintenanceMode === true,
|
|
67
|
+
explicitSkipChecks: explicitSkipChecks === true,
|
|
68
|
+
explicitSkipTests: explicitSkipTests === true || explicitSkipChecks === true,
|
|
69
|
+
explicitSkipLint: explicitSkipLint === true || explicitSkipChecks === true,
|
|
70
|
+
explicitSkipVersioning: explicitSkipVersioning === true,
|
|
71
|
+
explicitSkipGitHooks: explicitSkipGitHooks === true,
|
|
72
|
+
explicitAutoCommit: explicitAutoCommit === true
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function resolvePackageDetails({releasedPackage, packageName, version}) {
|
|
77
|
+
const resolvedPackageName = packageName ?? releasedPackage?.name
|
|
78
|
+
const resolvedVersion = version ?? releasedPackage?.version
|
|
79
|
+
|
|
80
|
+
if (!resolvedPackageName) {
|
|
81
|
+
throw new Error('Unable to determine the package name to update in the consumer app. Pass --consumer-package <name>.')
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
if (!resolvedVersion) {
|
|
85
|
+
throw new Error('Unable to determine the released package version for the consumer app update.')
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
return {packageName: resolvedPackageName, version: resolvedVersion}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
export async function releasePackageThenDeployConsumer({
|
|
92
|
+
producerRootDir,
|
|
93
|
+
consumerRootDir,
|
|
94
|
+
releasedPackage,
|
|
95
|
+
packageName = null,
|
|
96
|
+
version = null,
|
|
97
|
+
presetName,
|
|
98
|
+
maintenanceMode = null,
|
|
99
|
+
skipChecks = false,
|
|
100
|
+
skipTests = false,
|
|
101
|
+
skipLint = false,
|
|
102
|
+
skipVersioning = false,
|
|
103
|
+
skipGitHooks = false,
|
|
104
|
+
autoCommit = false,
|
|
105
|
+
json = false,
|
|
106
|
+
explicitMaintenanceMode = false,
|
|
107
|
+
explicitSkipChecks = false,
|
|
108
|
+
explicitSkipTests = false,
|
|
109
|
+
explicitSkipLint = false,
|
|
110
|
+
explicitSkipVersioning = false,
|
|
111
|
+
explicitSkipGitHooks = false,
|
|
112
|
+
explicitAutoCommit = false,
|
|
113
|
+
createAppContextImpl = createAppContext,
|
|
114
|
+
waitForNpmPackageVersionImpl = waitForNpmPackageVersion,
|
|
115
|
+
updateConsumerDependencyImpl = updateConsumerDependency,
|
|
116
|
+
assertCleanConsumerRepoImpl = assertCleanConsumerRepo,
|
|
117
|
+
validateLocalDependenciesImpl = validateLocalDependencies,
|
|
118
|
+
selectDeploymentTargetImpl = selectDeploymentTarget,
|
|
119
|
+
resolvePendingSnapshotImpl = resolvePendingSnapshot,
|
|
120
|
+
runDeploymentImpl = runDeployment,
|
|
121
|
+
bootstrapImpl = bootstrap
|
|
122
|
+
} = {}) {
|
|
123
|
+
if (!presetName) {
|
|
124
|
+
throw new Error('--then-deploy requires --consumer-preset <name>.')
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
const resolvedProducerRootDir = producerRootDir ?? process.cwd()
|
|
128
|
+
const resolvedConsumerRootDir = resolveConsumerRootDir(resolvedProducerRootDir, consumerRootDir)
|
|
129
|
+
const details = resolvePackageDetails({releasedPackage, packageName, version})
|
|
130
|
+
const executionMode = createConsumerExecutionMode({
|
|
131
|
+
presetName,
|
|
132
|
+
maintenanceMode,
|
|
133
|
+
skipChecks,
|
|
134
|
+
skipTests,
|
|
135
|
+
skipLint,
|
|
136
|
+
skipVersioning,
|
|
137
|
+
skipGitHooks,
|
|
138
|
+
autoCommit,
|
|
139
|
+
json,
|
|
140
|
+
explicitMaintenanceMode,
|
|
141
|
+
explicitSkipChecks,
|
|
142
|
+
explicitSkipTests,
|
|
143
|
+
explicitSkipLint,
|
|
144
|
+
explicitSkipVersioning,
|
|
145
|
+
explicitSkipGitHooks,
|
|
146
|
+
explicitAutoCommit
|
|
147
|
+
})
|
|
148
|
+
const context = createAppContextImpl({executionMode})
|
|
149
|
+
const {
|
|
150
|
+
logProcessing,
|
|
151
|
+
logSuccess,
|
|
152
|
+
logWarning,
|
|
153
|
+
runPrompt,
|
|
154
|
+
runCommand,
|
|
155
|
+
runCommandCapture,
|
|
156
|
+
emitEvent
|
|
157
|
+
} = context
|
|
158
|
+
const configurationService = createConfigurationService(context)
|
|
159
|
+
|
|
160
|
+
logProcessing?.(`Preparing consumer app at ${resolvedConsumerRootDir}...`)
|
|
161
|
+
await assertCleanConsumerRepoImpl(resolvedConsumerRootDir, {runCommandCapture})
|
|
162
|
+
|
|
163
|
+
await bootstrapImpl.ensureGitignoreEntry(resolvedConsumerRootDir, {
|
|
164
|
+
runCommand,
|
|
165
|
+
logSuccess,
|
|
166
|
+
logWarning,
|
|
167
|
+
skipGitHooks: executionMode.skipGitHooks
|
|
168
|
+
})
|
|
169
|
+
await bootstrapImpl.ensureProjectReleaseScript(resolvedConsumerRootDir, {
|
|
170
|
+
runPrompt,
|
|
171
|
+
runCommand,
|
|
172
|
+
logSuccess,
|
|
173
|
+
logWarning,
|
|
174
|
+
skipGitHooks: executionMode.skipGitHooks,
|
|
175
|
+
interactive: executionMode.interactive
|
|
176
|
+
})
|
|
177
|
+
|
|
178
|
+
const hasPackageJson = await fileExists(path.join(resolvedConsumerRootDir, 'package.json'))
|
|
179
|
+
const hasComposerJson = await fileExists(path.join(resolvedConsumerRootDir, 'composer.json'))
|
|
180
|
+
|
|
181
|
+
if (hasPackageJson || hasComposerJson) {
|
|
182
|
+
logProcessing?.('Validating consumer dependencies...')
|
|
183
|
+
await validateLocalDependenciesImpl(resolvedConsumerRootDir, runPrompt, logSuccess, {
|
|
184
|
+
interactive: executionMode.interactive,
|
|
185
|
+
skipGitHooks: executionMode.skipGitHooks
|
|
186
|
+
})
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
const {deploymentConfig, presetState} = await selectDeploymentTargetImpl(resolvedConsumerRootDir, {
|
|
190
|
+
configurationService,
|
|
191
|
+
runPrompt,
|
|
192
|
+
logProcessing,
|
|
193
|
+
logSuccess,
|
|
194
|
+
logWarning,
|
|
195
|
+
emitEvent,
|
|
196
|
+
executionMode,
|
|
197
|
+
promptPresetOptions: true
|
|
198
|
+
})
|
|
199
|
+
|
|
200
|
+
if (presetState) {
|
|
201
|
+
const effectiveDeployOptions = mergeDeployOptions(executionMode, presetState.options)
|
|
202
|
+
Object.assign(executionMode, {
|
|
203
|
+
presetName: presetState.name,
|
|
204
|
+
...effectiveDeployOptions,
|
|
205
|
+
skipChecks: executionMode.skipChecks === true ||
|
|
206
|
+
(effectiveDeployOptions.skipTests === true && effectiveDeployOptions.skipLint === true)
|
|
207
|
+
})
|
|
208
|
+
context.executionMode = executionMode
|
|
209
|
+
await presetState.applyExecutionMode(executionMode)
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
await waitForNpmPackageVersionImpl({
|
|
213
|
+
packageName: details.packageName,
|
|
214
|
+
version: details.version,
|
|
215
|
+
logProcessing,
|
|
216
|
+
logSuccess,
|
|
217
|
+
logWarning
|
|
218
|
+
})
|
|
219
|
+
|
|
220
|
+
await updateConsumerDependencyImpl({
|
|
221
|
+
rootDir: resolvedConsumerRootDir,
|
|
222
|
+
packageName: details.packageName,
|
|
223
|
+
version: details.version,
|
|
224
|
+
runCommand,
|
|
225
|
+
runCommandCapture,
|
|
226
|
+
logProcessing,
|
|
227
|
+
logSuccess,
|
|
228
|
+
logWarning,
|
|
229
|
+
skipGitHooks: executionMode.skipGitHooks
|
|
230
|
+
})
|
|
231
|
+
|
|
232
|
+
const snapshotToUse = await resolvePendingSnapshotImpl(resolvedConsumerRootDir, deploymentConfig, {
|
|
233
|
+
runPrompt,
|
|
234
|
+
logProcessing,
|
|
235
|
+
logWarning,
|
|
236
|
+
executionMode
|
|
237
|
+
})
|
|
238
|
+
|
|
239
|
+
await runDeploymentImpl(deploymentConfig, {
|
|
240
|
+
rootDir: resolvedConsumerRootDir,
|
|
241
|
+
snapshot: snapshotToUse,
|
|
242
|
+
context,
|
|
243
|
+
presetState
|
|
244
|
+
})
|
|
245
|
+
}
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
import fs from 'node:fs/promises'
|
|
2
|
+
import path from 'node:path'
|
|
3
|
+
|
|
4
|
+
import {gitCommitArgs} from '../../utils/git-hooks.mjs'
|
|
5
|
+
|
|
6
|
+
const DEPENDENCY_FIELDS = [
|
|
7
|
+
'dependencies',
|
|
8
|
+
'devDependencies',
|
|
9
|
+
'peerDependencies',
|
|
10
|
+
'optionalDependencies'
|
|
11
|
+
]
|
|
12
|
+
const TRACKED_NPM_LOCK_FILES = ['package-lock.json', 'npm-shrinkwrap.json']
|
|
13
|
+
|
|
14
|
+
async function readPackageJson(rootDir) {
|
|
15
|
+
const packageJsonPath = path.join(rootDir, 'package.json')
|
|
16
|
+
const raw = await fs.readFile(packageJsonPath, 'utf8')
|
|
17
|
+
return JSON.parse(raw)
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
async function writePackageJson(rootDir, pkg) {
|
|
21
|
+
const packageJsonPath = path.join(rootDir, 'package.json')
|
|
22
|
+
await fs.writeFile(packageJsonPath, `${JSON.stringify(pkg, null, 2)}\n`, 'utf8')
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function findDependencyField(pkg, packageName) {
|
|
26
|
+
return DEPENDENCY_FIELDS.find((field) => Object.hasOwn(pkg?.[field] ?? {}, packageName)) ?? null
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function formatDependencyVersion(currentValue, version) {
|
|
30
|
+
if (typeof currentValue !== 'string' || currentValue.length === 0) {
|
|
31
|
+
return `^${version}`
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
if (currentValue.startsWith('~')) {
|
|
35
|
+
return `~${version}`
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
if (currentValue.startsWith('^')) {
|
|
39
|
+
return `^${version}`
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
if (/^\d/.test(currentValue)) {
|
|
43
|
+
return version
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
return `^${version}`
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export async function assertCleanConsumerRepo(rootDir, {runCommandCapture} = {}) {
|
|
50
|
+
const status = await runCommandCapture('git', ['status', '--porcelain'], {cwd: rootDir})
|
|
51
|
+
const normalizedStatus = typeof status === 'string' ? status : status?.stdout ?? ''
|
|
52
|
+
|
|
53
|
+
if (normalizedStatus.trim().length > 0) {
|
|
54
|
+
throw new Error('Consumer repository has uncommitted changes. Commit, stash, or clean them before running a package-to-consumer release.')
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
async function isTracked(rootDir, filePath, {runCommandCapture} = {}) {
|
|
59
|
+
try {
|
|
60
|
+
await runCommandCapture('git', ['ls-files', '--error-unmatch', filePath], {cwd: rootDir})
|
|
61
|
+
return true
|
|
62
|
+
} catch {
|
|
63
|
+
return false
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
async function getChangedFiles(rootDir, files, {runCommandCapture} = {}) {
|
|
68
|
+
const status = await runCommandCapture('git', ['status', '--porcelain', '--', ...files], {cwd: rootDir})
|
|
69
|
+
const normalizedStatus = typeof status === 'string' ? status : status?.stdout ?? ''
|
|
70
|
+
return normalizedStatus
|
|
71
|
+
.split('\n')
|
|
72
|
+
.map((line) => line.trim())
|
|
73
|
+
.filter(Boolean)
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
async function trackedNpmLockFiles(rootDir, {runCommandCapture} = {}) {
|
|
77
|
+
const tracked = []
|
|
78
|
+
|
|
79
|
+
for (const filePath of TRACKED_NPM_LOCK_FILES) {
|
|
80
|
+
if (await isTracked(rootDir, filePath, {runCommandCapture})) {
|
|
81
|
+
tracked.push(filePath)
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
return tracked
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
export async function updateConsumerDependency({
|
|
89
|
+
rootDir,
|
|
90
|
+
packageName,
|
|
91
|
+
version,
|
|
92
|
+
runCommand,
|
|
93
|
+
runCommandCapture,
|
|
94
|
+
logProcessing,
|
|
95
|
+
logSuccess,
|
|
96
|
+
logWarning,
|
|
97
|
+
skipGitHooks = false
|
|
98
|
+
} = {}) {
|
|
99
|
+
if (!rootDir) {
|
|
100
|
+
throw new Error('Consumer root directory is required.')
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
if (!packageName) {
|
|
104
|
+
throw new Error('Consumer package name is required.')
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
if (!version) {
|
|
108
|
+
throw new Error('Consumer package version is required.')
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
if (typeof runCommand !== 'function' || typeof runCommandCapture !== 'function') {
|
|
112
|
+
throw new Error('Consumer dependency updates require command runners.')
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
await assertCleanConsumerRepo(rootDir, {runCommandCapture})
|
|
116
|
+
|
|
117
|
+
const pkg = await readPackageJson(rootDir)
|
|
118
|
+
const dependencyField = findDependencyField(pkg, packageName)
|
|
119
|
+
|
|
120
|
+
if (!dependencyField) {
|
|
121
|
+
throw new Error(`Consumer package.json does not depend on ${packageName}. Add it before running --then-deploy.`)
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
const currentValue = pkg[dependencyField][packageName]
|
|
125
|
+
const nextValue = formatDependencyVersion(currentValue, version)
|
|
126
|
+
const lockFiles = await trackedNpmLockFiles(rootDir, {runCommandCapture})
|
|
127
|
+
|
|
128
|
+
if (currentValue !== nextValue) {
|
|
129
|
+
logProcessing?.(`Updating ${packageName} in consumer package.json from ${currentValue} to ${nextValue}...`)
|
|
130
|
+
pkg[dependencyField][packageName] = nextValue
|
|
131
|
+
await writePackageJson(rootDir, pkg)
|
|
132
|
+
} else {
|
|
133
|
+
logProcessing?.(`Consumer package.json already references ${packageName}@${nextValue}.`)
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
if (lockFiles.length > 0) {
|
|
137
|
+
logProcessing?.(`Refreshing tracked npm lock file${lockFiles.length === 1 ? '' : 's'}: ${lockFiles.join(', ')}...`)
|
|
138
|
+
await runCommand('npm', ['install', '--package-lock-only', '--ignore-scripts'], {cwd: rootDir})
|
|
139
|
+
} else {
|
|
140
|
+
logWarning?.('No tracked npm lock file found in consumer repo; committing manifest update only.')
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
const filesToCommit = ['package.json', ...lockFiles]
|
|
144
|
+
const changedFiles = await getChangedFiles(rootDir, filesToCommit, {runCommandCapture})
|
|
145
|
+
|
|
146
|
+
if (changedFiles.length === 0) {
|
|
147
|
+
logSuccess?.(`Consumer already uses ${packageName}@${version}.`)
|
|
148
|
+
return {committed: false, files: []}
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
const commitMessage = `chore: update ${packageName} to ${version}`
|
|
152
|
+
|
|
153
|
+
await runCommand('git', ['add', '--', ...filesToCommit], {cwd: rootDir})
|
|
154
|
+
await runCommand('git', gitCommitArgs(['-m', commitMessage, '--', ...filesToCommit], {skipGitHooks}), {cwd: rootDir})
|
|
155
|
+
|
|
156
|
+
logSuccess?.(`Committed consumer dependency update with "${commitMessage}".`)
|
|
157
|
+
|
|
158
|
+
return {committed: true, files: filesToCommit, message: commitMessage}
|
|
159
|
+
}
|
|
@@ -15,6 +15,7 @@ import {resolveRemotePath} from '../../utils/remote-path.mjs'
|
|
|
15
15
|
import {buildRemoteDeploymentPlan, resolveRemoteDeploymentState} from './build-remote-deployment-plan.mjs'
|
|
16
16
|
import {executeRemoteDeploymentPlan} from './execute-remote-deployment-plan.mjs'
|
|
17
17
|
import {prepareLocalDeployment} from './prepare-local-deployment.mjs'
|
|
18
|
+
import {verifyLaravelSetup} from './verify-laravel-setup.mjs'
|
|
18
19
|
|
|
19
20
|
async function resolveRemoteHome(ssh, sshUser) {
|
|
20
21
|
const remoteHomeResult = await ssh.execCommand('printf "%s" "$HOME"')
|
|
@@ -263,9 +264,22 @@ export async function runDeployment(config, options = {}) {
|
|
|
263
264
|
executionMode
|
|
264
265
|
} = context
|
|
265
266
|
|
|
267
|
+
const sshUser = config.sshUser || os.userInfo().username
|
|
268
|
+
|
|
269
|
+
if (executionMode?.setup === true) {
|
|
270
|
+
await verifyLaravelSetup({
|
|
271
|
+
config,
|
|
272
|
+
rootDir,
|
|
273
|
+
createSshClient,
|
|
274
|
+
sshUser,
|
|
275
|
+
logProcessing,
|
|
276
|
+
logSuccess
|
|
277
|
+
})
|
|
278
|
+
return
|
|
279
|
+
}
|
|
280
|
+
|
|
266
281
|
await cleanupOldLogs(rootDir)
|
|
267
282
|
|
|
268
|
-
const sshUser = config.sshUser || os.userInfo().username
|
|
269
283
|
const privateKeyPath = await resolveSshKeyPath(config.sshKey)
|
|
270
284
|
const privateKey = await fs.readFile(privateKeyPath, 'utf8')
|
|
271
285
|
let ssh = null
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import fs from 'node:fs/promises'
|
|
2
|
+
|
|
3
|
+
import {isLocalLaravelProject} from '../../deploy/preflight.mjs'
|
|
4
|
+
import {ZephyrError} from '../../runtime/errors.mjs'
|
|
5
|
+
import {resolveSshKeyPath} from '../../ssh/keys.mjs'
|
|
6
|
+
|
|
7
|
+
export async function assertLaravelSetupProject(rootDir) {
|
|
8
|
+
const isLaravel = await isLocalLaravelProject(rootDir)
|
|
9
|
+
|
|
10
|
+
if (!isLaravel) {
|
|
11
|
+
throw new ZephyrError(
|
|
12
|
+
'Zephyr setup is only supported for Laravel app projects.',
|
|
13
|
+
{code: 'ZEPHYR_SETUP_REQUIRES_LARAVEL'}
|
|
14
|
+
)
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export async function verifyLaravelSetup({
|
|
19
|
+
config,
|
|
20
|
+
rootDir,
|
|
21
|
+
createSshClient,
|
|
22
|
+
sshUser,
|
|
23
|
+
logProcessing,
|
|
24
|
+
logSuccess
|
|
25
|
+
} = {}) {
|
|
26
|
+
await assertLaravelSetupProject(rootDir)
|
|
27
|
+
|
|
28
|
+
const privateKeyPath = await resolveSshKeyPath(config.sshKey)
|
|
29
|
+
const privateKey = await fs.readFile(privateKeyPath, 'utf8')
|
|
30
|
+
const ssh = createSshClient()
|
|
31
|
+
|
|
32
|
+
try {
|
|
33
|
+
logProcessing?.(`\nConnecting to ${config.serverIp} as ${sshUser} to verify SSH setup...`)
|
|
34
|
+
await ssh.connect({
|
|
35
|
+
host: config.serverIp,
|
|
36
|
+
username: sshUser,
|
|
37
|
+
privateKey
|
|
38
|
+
})
|
|
39
|
+
logSuccess?.('Setup verified. SSH connection succeeded for this Laravel app.')
|
|
40
|
+
} finally {
|
|
41
|
+
ssh.dispose()
|
|
42
|
+
}
|
|
43
|
+
}
|
|
@@ -391,6 +391,7 @@ async function deployGHPages(skipDeploy, pkg, rootDir = process.cwd(), {
|
|
|
391
391
|
export async function releaseNodePackage({
|
|
392
392
|
releaseType,
|
|
393
393
|
skipGitHooks = false,
|
|
394
|
+
autoCommit = false,
|
|
394
395
|
skipTests = false,
|
|
395
396
|
skipLint = false,
|
|
396
397
|
skipVersioning = false,
|
|
@@ -431,6 +432,7 @@ export async function releaseNodePackage({
|
|
|
431
432
|
logSuccess,
|
|
432
433
|
logWarning,
|
|
433
434
|
interactive,
|
|
435
|
+
autoCommit,
|
|
434
436
|
skipGitHooks
|
|
435
437
|
})
|
|
436
438
|
await ensureReleaseBranchReady({rootDir, branchMethod: 'show-current', logStep, logWarning})
|
|
@@ -472,4 +474,5 @@ export async function releaseNodePackage({
|
|
|
472
474
|
|
|
473
475
|
logStep?.('Publishing will be handled by GitHub Actions via trusted publishing.')
|
|
474
476
|
logSuccess?.(`Release workflow completed for ${updatedPkg.name}@${updatedPkg.version}.`)
|
|
477
|
+
return updatedPkg
|
|
475
478
|
}
|
|
@@ -240,6 +240,7 @@ async function pushChanges(rootDir = process.cwd(), {
|
|
|
240
240
|
export async function releasePackagistPackage({
|
|
241
241
|
releaseType,
|
|
242
242
|
skipGitHooks = false,
|
|
243
|
+
autoCommit = false,
|
|
243
244
|
skipTests = false,
|
|
244
245
|
skipLint = false,
|
|
245
246
|
skipVersioning = false,
|
|
@@ -286,6 +287,7 @@ export async function releasePackagistPackage({
|
|
|
286
287
|
logSuccess,
|
|
287
288
|
logWarning,
|
|
288
289
|
interactive,
|
|
290
|
+
autoCommit,
|
|
289
291
|
skipGitHooks
|
|
290
292
|
})
|
|
291
293
|
await ensureReleaseBranchReady({rootDir, branchMethod: 'show-current', logStep, logWarning})
|
|
@@ -324,4 +326,5 @@ export async function releasePackagistPackage({
|
|
|
324
326
|
|
|
325
327
|
logSuccess?.(`Release workflow completed for ${composer.name}@${updatedComposer.version}.`)
|
|
326
328
|
logStep?.('Note: Packagist will automatically detect the new git tag and update the package.')
|
|
329
|
+
return updatedComposer
|
|
327
330
|
}
|
package/src/cli/options.mjs
CHANGED
|
@@ -35,12 +35,17 @@ export function parseCliOptions(args = process.argv.slice(2)) {
|
|
|
35
35
|
.exitOverride()
|
|
36
36
|
.option('--type <type>', 'Workflow type (node|vue|packagist). Omit for normal app deployments.')
|
|
37
37
|
.option('--non-interactive', 'Fail instead of prompting when Zephyr needs user input.')
|
|
38
|
+
.option('--then-deploy <path>', 'After a node/vue package release, update and deploy a local consumer app repo.')
|
|
39
|
+
.option('--consumer-package <name>', 'Package name to update in the --then-deploy consumer. Defaults to the released package name.')
|
|
40
|
+
.option('--consumer-preset <name>', 'Preset name to use for the --then-deploy consumer app deployment.')
|
|
41
|
+
.option('--consumer-maintenance <mode>', 'Laravel maintenance mode policy for the --then-deploy consumer app deployment (on|off).')
|
|
38
42
|
.option('--json', 'Emit NDJSON events to stdout. Requires --non-interactive.')
|
|
43
|
+
.option('--setup', 'Configure an app deployment target and verify SSH connectivity without deploying.')
|
|
39
44
|
.option('--preset <name>', 'Preset name to use for non-interactive app deployments.')
|
|
40
45
|
.option('--resume-pending', 'Resume a saved pending deployment snapshot without prompting.')
|
|
41
46
|
.option('--discard-pending', 'Discard a saved pending deployment snapshot without prompting.')
|
|
42
47
|
.option('--maintenance <mode>', 'Laravel maintenance mode policy for app deployments (on|off).')
|
|
43
|
-
.option('--auto-commit', 'Automatically commit dirty
|
|
48
|
+
.option('--auto-commit', 'Automatically commit dirty changes with a Codex-generated message.')
|
|
44
49
|
.option('--skip-versioning', 'Skip updating package/composer version files before continuing.')
|
|
45
50
|
.option('--skip-git-hooks', 'Bypass local git hooks for any commits and pushes Zephyr performs.')
|
|
46
51
|
.option('--skip-checks', 'Skip Zephyr local lint and test execution.')
|
|
@@ -48,6 +53,12 @@ export function parseCliOptions(args = process.argv.slice(2)) {
|
|
|
48
53
|
.option('--skip-lint', 'Skip Zephyr local lint execution in package release and app deployment workflows.')
|
|
49
54
|
.option('--skip-build', 'Skip build execution in node/vue release workflows.')
|
|
50
55
|
.option('--skip-deploy', 'Skip GitHub Pages deployment in node/vue release workflows.')
|
|
56
|
+
.option('--consumer-skip-checks', 'Skip Zephyr local lint and test execution in the --then-deploy consumer deployment.')
|
|
57
|
+
.option('--consumer-skip-tests', 'Skip Zephyr local test execution in the --then-deploy consumer deployment.')
|
|
58
|
+
.option('--consumer-skip-lint', 'Skip Zephyr local lint execution in the --then-deploy consumer deployment.')
|
|
59
|
+
.option('--consumer-skip-versioning', 'Skip local version bumping in the --then-deploy consumer deployment.')
|
|
60
|
+
.option('--consumer-skip-git-hooks', 'Bypass local git hooks for commits and pushes in the --then-deploy consumer repo.')
|
|
61
|
+
.option('--consumer-auto-commit', 'Automatically commit dirty deploy changes in the --then-deploy consumer repo.')
|
|
51
62
|
.argument(
|
|
52
63
|
'[version]',
|
|
53
64
|
'Version or npm bump type for deployments (e.g. 1.2.3, patch, minor, major).'
|
|
@@ -62,6 +73,7 @@ export function parseCliOptions(args = process.argv.slice(2)) {
|
|
|
62
73
|
const options = program.opts()
|
|
63
74
|
const workflowType = options.type ?? null
|
|
64
75
|
const explicitSkipChecks = hasFlag(args, '--skip-checks')
|
|
76
|
+
const explicitConsumerSkipChecks = hasFlag(args, '--consumer-skip-checks')
|
|
65
77
|
|
|
66
78
|
if (workflowType && !WORKFLOW_TYPES.has(workflowType)) {
|
|
67
79
|
throw new InvalidCliOptionsError('Invalid value for --type. Use one of: node, vue, packagist.')
|
|
@@ -71,7 +83,9 @@ export function parseCliOptions(args = process.argv.slice(2)) {
|
|
|
71
83
|
workflowType,
|
|
72
84
|
versionArg: program.args[0] ?? null,
|
|
73
85
|
nonInteractive: Boolean(options.nonInteractive),
|
|
86
|
+
thenDeploy: options.thenDeploy ?? null,
|
|
74
87
|
json: Boolean(options.json),
|
|
88
|
+
setup: Boolean(options.setup),
|
|
75
89
|
presetName: options.preset ?? null,
|
|
76
90
|
resumePending: Boolean(options.resumePending),
|
|
77
91
|
discardPending: Boolean(options.discardPending),
|
|
@@ -84,13 +98,29 @@ export function parseCliOptions(args = process.argv.slice(2)) {
|
|
|
84
98
|
skipLint: Boolean(options.skipLint || options.skipChecks),
|
|
85
99
|
skipBuild: Boolean(options.skipBuild),
|
|
86
100
|
skipDeploy: Boolean(options.skipDeploy),
|
|
101
|
+
consumerPackage: options.consumerPackage ?? null,
|
|
102
|
+
consumerPresetName: options.consumerPreset ?? null,
|
|
103
|
+
consumerMaintenanceMode: normalizeMaintenanceMode(options.consumerMaintenance),
|
|
104
|
+
consumerSkipChecks: Boolean(options.consumerSkipChecks),
|
|
105
|
+
consumerSkipTests: Boolean(options.consumerSkipTests || options.consumerSkipChecks),
|
|
106
|
+
consumerSkipLint: Boolean(options.consumerSkipLint || options.consumerSkipChecks),
|
|
107
|
+
consumerSkipVersioning: Boolean(options.consumerSkipVersioning),
|
|
108
|
+
consumerSkipGitHooks: Boolean(options.consumerSkipGitHooks),
|
|
109
|
+
consumerAutoCommit: Boolean(options.consumerAutoCommit),
|
|
87
110
|
explicitMaintenanceMode: hasFlag(args, '--maintenance'),
|
|
88
111
|
explicitAutoCommit: hasFlag(args, '--auto-commit'),
|
|
89
112
|
explicitSkipVersioning: hasFlag(args, '--skip-versioning'),
|
|
90
113
|
explicitSkipGitHooks: hasFlag(args, '--skip-git-hooks'),
|
|
91
114
|
explicitSkipChecks,
|
|
92
115
|
explicitSkipTests: hasFlag(args, '--skip-tests') || explicitSkipChecks,
|
|
93
|
-
explicitSkipLint: hasFlag(args, '--skip-lint') || explicitSkipChecks
|
|
116
|
+
explicitSkipLint: hasFlag(args, '--skip-lint') || explicitSkipChecks,
|
|
117
|
+
explicitConsumerMaintenanceMode: hasFlag(args, '--consumer-maintenance'),
|
|
118
|
+
explicitConsumerSkipChecks,
|
|
119
|
+
explicitConsumerSkipTests: hasFlag(args, '--consumer-skip-tests') || explicitConsumerSkipChecks,
|
|
120
|
+
explicitConsumerSkipLint: hasFlag(args, '--consumer-skip-lint') || explicitConsumerSkipChecks,
|
|
121
|
+
explicitConsumerSkipVersioning: hasFlag(args, '--consumer-skip-versioning'),
|
|
122
|
+
explicitConsumerSkipGitHooks: hasFlag(args, '--consumer-skip-git-hooks'),
|
|
123
|
+
explicitConsumerAutoCommit: hasFlag(args, '--consumer-auto-commit')
|
|
94
124
|
}
|
|
95
125
|
}
|
|
96
126
|
|
|
@@ -99,14 +129,29 @@ export function validateCliOptions(options = {}) {
|
|
|
99
129
|
workflowType = null,
|
|
100
130
|
nonInteractive = false,
|
|
101
131
|
json = false,
|
|
132
|
+
setup = false,
|
|
133
|
+
thenDeploy = null,
|
|
102
134
|
presetName = null,
|
|
103
135
|
resumePending = false,
|
|
104
136
|
discardPending = false,
|
|
105
137
|
maintenanceMode = null,
|
|
106
138
|
autoCommit = false,
|
|
107
139
|
skipVersioning = false,
|
|
140
|
+
skipChecks = false,
|
|
141
|
+
skipTests = false,
|
|
142
|
+
skipLint = false,
|
|
108
143
|
skipBuild = false,
|
|
109
|
-
skipDeploy = false
|
|
144
|
+
skipDeploy = false,
|
|
145
|
+
versionArg = null,
|
|
146
|
+
consumerPackage = null,
|
|
147
|
+
consumerPresetName = null,
|
|
148
|
+
consumerMaintenanceMode = null,
|
|
149
|
+
consumerSkipChecks = false,
|
|
150
|
+
consumerSkipTests = false,
|
|
151
|
+
consumerSkipLint = false,
|
|
152
|
+
consumerSkipVersioning = false,
|
|
153
|
+
consumerSkipGitHooks = false,
|
|
154
|
+
consumerAutoCommit = false
|
|
110
155
|
} = options
|
|
111
156
|
|
|
112
157
|
if (json && !nonInteractive) {
|
|
@@ -118,8 +163,37 @@ export function validateCliOptions(options = {}) {
|
|
|
118
163
|
}
|
|
119
164
|
|
|
120
165
|
const isPackageRelease = workflowType === 'node' || workflowType === 'vue' || workflowType === 'packagist'
|
|
166
|
+
const isNodePackageRelease = workflowType === 'node' || workflowType === 'vue'
|
|
167
|
+
const hasConsumerOptions = Boolean(
|
|
168
|
+
thenDeploy ||
|
|
169
|
+
consumerPackage ||
|
|
170
|
+
consumerPresetName ||
|
|
171
|
+
consumerMaintenanceMode !== null ||
|
|
172
|
+
consumerSkipChecks ||
|
|
173
|
+
consumerSkipTests ||
|
|
174
|
+
consumerSkipLint ||
|
|
175
|
+
consumerSkipVersioning ||
|
|
176
|
+
consumerSkipGitHooks ||
|
|
177
|
+
consumerAutoCommit
|
|
178
|
+
)
|
|
179
|
+
|
|
180
|
+
if (hasConsumerOptions && !thenDeploy) {
|
|
181
|
+
throw new InvalidCliOptionsError('--consumer-* options require --then-deploy <path>.')
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
if (thenDeploy && !isNodePackageRelease) {
|
|
185
|
+
throw new InvalidCliOptionsError('--then-deploy is only valid for node/vue package release workflows.')
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
if (thenDeploy && !consumerPresetName) {
|
|
189
|
+
throw new InvalidCliOptionsError('--then-deploy requires --consumer-preset <name>.')
|
|
190
|
+
}
|
|
121
191
|
|
|
122
192
|
if (isPackageRelease) {
|
|
193
|
+
if (setup) {
|
|
194
|
+
throw new InvalidCliOptionsError('--setup is only valid for app deployments.')
|
|
195
|
+
}
|
|
196
|
+
|
|
123
197
|
if (presetName) {
|
|
124
198
|
throw new InvalidCliOptionsError('--preset is only valid for app deployments.')
|
|
125
199
|
}
|
|
@@ -131,21 +205,39 @@ export function validateCliOptions(options = {}) {
|
|
|
131
205
|
if (maintenanceMode !== null) {
|
|
132
206
|
throw new InvalidCliOptionsError('--maintenance is only valid for app deployments.')
|
|
133
207
|
}
|
|
134
|
-
|
|
135
|
-
if (autoCommit) {
|
|
136
|
-
throw new InvalidCliOptionsError('--auto-commit is only valid for app deployments.')
|
|
137
|
-
}
|
|
138
208
|
} else {
|
|
139
209
|
if (skipBuild || skipDeploy) {
|
|
140
210
|
throw new InvalidCliOptionsError('--skip-build and --skip-deploy are only valid for node/vue release workflows.')
|
|
141
211
|
}
|
|
142
212
|
|
|
213
|
+
if (setup) {
|
|
214
|
+
if (versionArg) {
|
|
215
|
+
throw new InvalidCliOptionsError('--setup cannot be used with a version or bump argument.')
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
if (resumePending || discardPending) {
|
|
219
|
+
throw new InvalidCliOptionsError('--setup cannot be used with pending deployment snapshot flags.')
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
if (maintenanceMode !== null) {
|
|
223
|
+
throw new InvalidCliOptionsError('--setup cannot be used with --maintenance.')
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
if (autoCommit) {
|
|
227
|
+
throw new InvalidCliOptionsError('--setup cannot be used with --auto-commit.')
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
if (skipVersioning || skipChecks || skipTests || skipLint) {
|
|
231
|
+
throw new InvalidCliOptionsError('--setup cannot be used with deployment skip flags.')
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
|
|
143
235
|
if (nonInteractive && !presetName) {
|
|
144
236
|
throw new InvalidCliOptionsError('--non-interactive app deployments require --preset <name>.')
|
|
145
237
|
}
|
|
146
238
|
}
|
|
147
239
|
|
|
148
|
-
if (skipVersioning &&
|
|
240
|
+
if (skipVersioning && versionArg) {
|
|
149
241
|
throw new InvalidCliOptionsError('--skip-versioning cannot be used together with an explicit version or bump argument.')
|
|
150
242
|
}
|
|
151
243
|
}
|
package/src/main.mjs
CHANGED
|
@@ -17,6 +17,8 @@ import {createConfigurationService} from './application/configuration/service.mj
|
|
|
17
17
|
import {selectDeploymentTarget} from './application/configuration/select-deployment-target.mjs'
|
|
18
18
|
import {resolvePendingSnapshot} from './application/deploy/resolve-pending-snapshot.mjs'
|
|
19
19
|
import {runDeployment} from './application/deploy/run-deployment.mjs'
|
|
20
|
+
import {assertLaravelSetupProject} from './application/deploy/verify-laravel-setup.mjs'
|
|
21
|
+
import {releasePackageThenDeployConsumer} from './application/consumer/release-package-then-deploy-consumer.mjs'
|
|
20
22
|
import {SKIP_GIT_HOOKS_WARNING} from './utils/git-hooks.mjs'
|
|
21
23
|
import {notifyWorkflowResult} from './utils/notifications.mjs'
|
|
22
24
|
|
|
@@ -31,7 +33,9 @@ function normalizeMainOptions(firstArg = null, secondArg = null) {
|
|
|
31
33
|
workflowType: firstArg.workflowType ?? firstArg.type ?? null,
|
|
32
34
|
versionArg: firstArg.versionArg ?? null,
|
|
33
35
|
nonInteractive: firstArg.nonInteractive === true,
|
|
36
|
+
thenDeploy: firstArg.thenDeploy ?? null,
|
|
34
37
|
json: firstArg.json === true,
|
|
38
|
+
setup: firstArg.setup === true,
|
|
35
39
|
presetName: firstArg.presetName ?? null,
|
|
36
40
|
resumePending: firstArg.resumePending === true,
|
|
37
41
|
discardPending: firstArg.discardPending === true,
|
|
@@ -44,6 +48,22 @@ function normalizeMainOptions(firstArg = null, secondArg = null) {
|
|
|
44
48
|
skipLint: firstArg.skipLint === true || firstArg.skipChecks === true,
|
|
45
49
|
skipBuild: firstArg.skipBuild === true,
|
|
46
50
|
skipDeploy: firstArg.skipDeploy === true,
|
|
51
|
+
consumerPackage: firstArg.consumerPackage ?? null,
|
|
52
|
+
consumerPresetName: firstArg.consumerPresetName ?? null,
|
|
53
|
+
consumerMaintenanceMode: firstArg.consumerMaintenanceMode ?? null,
|
|
54
|
+
consumerSkipChecks: firstArg.consumerSkipChecks === true,
|
|
55
|
+
consumerSkipTests: firstArg.consumerSkipTests === true || firstArg.consumerSkipChecks === true,
|
|
56
|
+
consumerSkipLint: firstArg.consumerSkipLint === true || firstArg.consumerSkipChecks === true,
|
|
57
|
+
consumerSkipVersioning: firstArg.consumerSkipVersioning === true,
|
|
58
|
+
consumerSkipGitHooks: firstArg.consumerSkipGitHooks === true,
|
|
59
|
+
consumerAutoCommit: firstArg.consumerAutoCommit === true,
|
|
60
|
+
explicitConsumerMaintenanceMode: firstArg.explicitConsumerMaintenanceMode === true || 'consumerMaintenanceMode' in firstArg,
|
|
61
|
+
explicitConsumerSkipChecks: firstArg.explicitConsumerSkipChecks === true || 'consumerSkipChecks' in firstArg,
|
|
62
|
+
explicitConsumerSkipTests: firstArg.explicitConsumerSkipTests === true || 'consumerSkipTests' in firstArg || 'consumerSkipChecks' in firstArg,
|
|
63
|
+
explicitConsumerSkipLint: firstArg.explicitConsumerSkipLint === true || 'consumerSkipLint' in firstArg || 'consumerSkipChecks' in firstArg,
|
|
64
|
+
explicitConsumerSkipVersioning: firstArg.explicitConsumerSkipVersioning === true || 'consumerSkipVersioning' in firstArg,
|
|
65
|
+
explicitConsumerSkipGitHooks: firstArg.explicitConsumerSkipGitHooks === true || 'consumerSkipGitHooks' in firstArg,
|
|
66
|
+
explicitConsumerAutoCommit: firstArg.explicitConsumerAutoCommit === true || 'consumerAutoCommit' in firstArg,
|
|
47
67
|
explicitMaintenanceMode: firstArg.explicitMaintenanceMode === true || 'maintenanceMode' in firstArg,
|
|
48
68
|
explicitAutoCommit: firstArg.explicitAutoCommit === true || 'autoCommit' in firstArg,
|
|
49
69
|
explicitSkipVersioning: firstArg.explicitSkipVersioning === true || 'skipVersioning' in firstArg,
|
|
@@ -59,7 +79,9 @@ function normalizeMainOptions(firstArg = null, secondArg = null) {
|
|
|
59
79
|
workflowType: firstArg ?? null,
|
|
60
80
|
versionArg: secondArg ?? null,
|
|
61
81
|
nonInteractive: false,
|
|
82
|
+
thenDeploy: null,
|
|
62
83
|
json: false,
|
|
84
|
+
setup: false,
|
|
63
85
|
presetName: null,
|
|
64
86
|
resumePending: false,
|
|
65
87
|
discardPending: false,
|
|
@@ -72,6 +94,22 @@ function normalizeMainOptions(firstArg = null, secondArg = null) {
|
|
|
72
94
|
skipLint: false,
|
|
73
95
|
skipBuild: false,
|
|
74
96
|
skipDeploy: false,
|
|
97
|
+
consumerPackage: null,
|
|
98
|
+
consumerPresetName: null,
|
|
99
|
+
consumerMaintenanceMode: null,
|
|
100
|
+
consumerSkipChecks: false,
|
|
101
|
+
consumerSkipTests: false,
|
|
102
|
+
consumerSkipLint: false,
|
|
103
|
+
consumerSkipVersioning: false,
|
|
104
|
+
consumerSkipGitHooks: false,
|
|
105
|
+
consumerAutoCommit: false,
|
|
106
|
+
explicitConsumerMaintenanceMode: false,
|
|
107
|
+
explicitConsumerSkipChecks: false,
|
|
108
|
+
explicitConsumerSkipTests: false,
|
|
109
|
+
explicitConsumerSkipLint: false,
|
|
110
|
+
explicitConsumerSkipVersioning: false,
|
|
111
|
+
explicitConsumerSkipGitHooks: false,
|
|
112
|
+
explicitConsumerAutoCommit: false,
|
|
75
113
|
explicitMaintenanceMode: false,
|
|
76
114
|
explicitAutoCommit: false,
|
|
77
115
|
explicitSkipVersioning: false,
|
|
@@ -127,6 +165,7 @@ async function main(optionsOrWorkflowType = null, versionArg = null) {
|
|
|
127
165
|
interactive: !options.nonInteractive,
|
|
128
166
|
json: options.json === true && options.nonInteractive === true,
|
|
129
167
|
workflow: resolveWorkflowName(options.workflowType),
|
|
168
|
+
setup: options.setup === true,
|
|
130
169
|
presetName: options.presetName,
|
|
131
170
|
maintenanceMode: options.maintenanceMode,
|
|
132
171
|
autoCommit: options.autoCommit === true,
|
|
@@ -157,7 +196,8 @@ async function main(optionsOrWorkflowType = null, versionArg = null) {
|
|
|
157
196
|
} = appContext
|
|
158
197
|
let currentExecutionMode = {
|
|
159
198
|
...executionMode,
|
|
160
|
-
...(appContext.executionMode ?? {})
|
|
199
|
+
...(appContext.executionMode ?? {}),
|
|
200
|
+
setup: executionMode.setup === true || appContext.executionMode?.setup === true
|
|
161
201
|
}
|
|
162
202
|
appContext.executionMode = currentExecutionMode
|
|
163
203
|
const configurationService = createConfigurationService(appContext)
|
|
@@ -171,6 +211,7 @@ async function main(optionsOrWorkflowType = null, versionArg = null) {
|
|
|
171
211
|
data: {
|
|
172
212
|
version: ZEPHYR_VERSION,
|
|
173
213
|
workflow: currentExecutionMode.workflow,
|
|
214
|
+
setup: currentExecutionMode.setup === true,
|
|
174
215
|
nonInteractive: currentExecutionMode.interactive === false,
|
|
175
216
|
presetName: currentExecutionMode.presetName,
|
|
176
217
|
maintenanceMode: currentExecutionMode.maintenanceMode,
|
|
@@ -198,9 +239,14 @@ async function main(optionsOrWorkflowType = null, versionArg = null) {
|
|
|
198
239
|
appContext
|
|
199
240
|
})
|
|
200
241
|
|
|
242
|
+
if (currentExecutionMode.setup) {
|
|
243
|
+
await assertLaravelSetupProject(rootDir)
|
|
244
|
+
}
|
|
245
|
+
|
|
201
246
|
if (options.workflowType === 'node' || options.workflowType === 'vue') {
|
|
202
|
-
await releaseNode({
|
|
247
|
+
const releasedPackage = await releaseNode({
|
|
203
248
|
releaseType: options.versionArg,
|
|
249
|
+
autoCommit: options.autoCommit,
|
|
204
250
|
skipGitHooks: options.skipGitHooks,
|
|
205
251
|
skipTests: options.skipTests,
|
|
206
252
|
skipLint: options.skipLint,
|
|
@@ -209,6 +255,32 @@ async function main(optionsOrWorkflowType = null, versionArg = null) {
|
|
|
209
255
|
skipDeploy: options.skipDeploy,
|
|
210
256
|
context: appContext
|
|
211
257
|
})
|
|
258
|
+
|
|
259
|
+
if (options.thenDeploy) {
|
|
260
|
+
await releasePackageThenDeployConsumer({
|
|
261
|
+
producerRootDir: rootDir,
|
|
262
|
+
consumerRootDir: options.thenDeploy,
|
|
263
|
+
releasedPackage,
|
|
264
|
+
packageName: options.consumerPackage,
|
|
265
|
+
presetName: options.consumerPresetName,
|
|
266
|
+
maintenanceMode: options.consumerMaintenanceMode,
|
|
267
|
+
skipChecks: options.consumerSkipChecks,
|
|
268
|
+
skipTests: options.consumerSkipTests,
|
|
269
|
+
skipLint: options.consumerSkipLint,
|
|
270
|
+
skipVersioning: options.consumerSkipVersioning,
|
|
271
|
+
skipGitHooks: options.consumerSkipGitHooks,
|
|
272
|
+
autoCommit: options.consumerAutoCommit,
|
|
273
|
+
explicitMaintenanceMode: options.explicitConsumerMaintenanceMode,
|
|
274
|
+
explicitSkipChecks: options.explicitConsumerSkipChecks,
|
|
275
|
+
explicitSkipTests: options.explicitConsumerSkipTests,
|
|
276
|
+
explicitSkipLint: options.explicitConsumerSkipLint,
|
|
277
|
+
explicitSkipVersioning: options.explicitConsumerSkipVersioning,
|
|
278
|
+
explicitSkipGitHooks: options.explicitConsumerSkipGitHooks,
|
|
279
|
+
explicitAutoCommit: options.explicitConsumerAutoCommit,
|
|
280
|
+
json: currentExecutionMode.json
|
|
281
|
+
})
|
|
282
|
+
}
|
|
283
|
+
|
|
212
284
|
emitEvent?.('run_completed', {
|
|
213
285
|
message: 'Zephyr workflow completed successfully.',
|
|
214
286
|
data: {
|
|
@@ -230,12 +302,14 @@ async function main(optionsOrWorkflowType = null, versionArg = null) {
|
|
|
230
302
|
if (options.workflowType === 'packagist') {
|
|
231
303
|
await releasePackagist({
|
|
232
304
|
releaseType: options.versionArg,
|
|
305
|
+
autoCommit: options.autoCommit,
|
|
233
306
|
skipGitHooks: options.skipGitHooks,
|
|
234
307
|
skipTests: options.skipTests,
|
|
235
308
|
skipLint: options.skipLint,
|
|
236
309
|
skipVersioning: options.skipVersioning,
|
|
237
310
|
context: appContext
|
|
238
311
|
})
|
|
312
|
+
|
|
239
313
|
emitEvent?.('run_completed', {
|
|
240
314
|
message: 'Zephyr workflow completed successfully.',
|
|
241
315
|
data: {
|
|
@@ -277,7 +351,7 @@ async function main(optionsOrWorkflowType = null, versionArg = null) {
|
|
|
277
351
|
const hasPackageJson = await fs.access(packageJsonPath).then(() => true).catch(() => false)
|
|
278
352
|
const hasComposerJson = await fs.access(composerJsonPath).then(() => true).catch(() => false)
|
|
279
353
|
|
|
280
|
-
if (hasPackageJson || hasComposerJson) {
|
|
354
|
+
if (!currentExecutionMode.setup && (hasPackageJson || hasComposerJson)) {
|
|
281
355
|
logProcessing('Validating dependencies...')
|
|
282
356
|
await validateLocalDependencies(rootDir, runPrompt, logSuccess, {
|
|
283
357
|
interactive: currentExecutionMode.interactive,
|
|
@@ -292,7 +366,8 @@ async function main(optionsOrWorkflowType = null, versionArg = null) {
|
|
|
292
366
|
logSuccess,
|
|
293
367
|
logWarning,
|
|
294
368
|
emitEvent,
|
|
295
|
-
executionMode: currentExecutionMode
|
|
369
|
+
executionMode: currentExecutionMode,
|
|
370
|
+
promptPresetOptions: currentExecutionMode.setup !== true
|
|
296
371
|
})
|
|
297
372
|
|
|
298
373
|
if (presetState) {
|
|
@@ -308,12 +383,14 @@ async function main(optionsOrWorkflowType = null, versionArg = null) {
|
|
|
308
383
|
await presetState.applyExecutionMode(currentExecutionMode)
|
|
309
384
|
}
|
|
310
385
|
|
|
311
|
-
const snapshotToUse =
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
386
|
+
const snapshotToUse = currentExecutionMode.setup
|
|
387
|
+
? null
|
|
388
|
+
: await resolvePendingSnapshot(rootDir, deploymentConfig, {
|
|
389
|
+
runPrompt,
|
|
390
|
+
logProcessing,
|
|
391
|
+
logWarning,
|
|
392
|
+
executionMode: currentExecutionMode
|
|
393
|
+
})
|
|
317
394
|
|
|
318
395
|
await runRemoteTasks(deploymentConfig, {
|
|
319
396
|
rootDir,
|
package/src/release/shared.mjs
CHANGED
|
@@ -94,6 +94,7 @@ export async function ensureCleanWorkingTree(rootDir = process.cwd(), {
|
|
|
94
94
|
logSuccess,
|
|
95
95
|
logWarning,
|
|
96
96
|
interactive = true,
|
|
97
|
+
autoCommit = false,
|
|
97
98
|
skipGitHooks = false,
|
|
98
99
|
suggestCommitMessage = suggestReleaseCommitMessage
|
|
99
100
|
} = {}) {
|
|
@@ -107,7 +108,7 @@ export async function ensureCleanWorkingTree(rootDir = process.cwd(), {
|
|
|
107
108
|
return
|
|
108
109
|
}
|
|
109
110
|
|
|
110
|
-
if (!interactive || typeof runPrompt !== 'function') {
|
|
111
|
+
if (!autoCommit && (!interactive || typeof runPrompt !== 'function')) {
|
|
111
112
|
throw new Error(DIRTY_WORKING_TREE_MESSAGE)
|
|
112
113
|
}
|
|
113
114
|
|
|
@@ -117,24 +118,34 @@ export async function ensureCleanWorkingTree(rootDir = process.cwd(), {
|
|
|
117
118
|
logWarning,
|
|
118
119
|
statusEntries
|
|
119
120
|
})
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
message
|
|
125
|
-
'Pending changes detected before release:\n\n' +
|
|
126
|
-
`${formatWorkingTreePreview(statusEntries)}\n\n` +
|
|
127
|
-
'Enter a commit message to stage and commit all current changes before continuing.\n' +
|
|
128
|
-
'Leave blank to cancel.',
|
|
129
|
-
default: suggestedCommitMessage ?? ''
|
|
121
|
+
let message = suggestedCommitMessage?.trim() ?? ''
|
|
122
|
+
|
|
123
|
+
if (autoCommit) {
|
|
124
|
+
if (!message) {
|
|
125
|
+
throw new Error('Release auto-commit failed because Codex could not determine a usable commit message.')
|
|
130
126
|
}
|
|
131
|
-
])
|
|
132
127
|
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
128
|
+
logStep?.(`Auto-commit enabled. Using Codex-generated commit message "${message}".`)
|
|
129
|
+
} else {
|
|
130
|
+
const {commitMessage} = await runPrompt([
|
|
131
|
+
{
|
|
132
|
+
type: 'input',
|
|
133
|
+
name: 'commitMessage',
|
|
134
|
+
message:
|
|
135
|
+
'Pending changes detected before release:\n\n' +
|
|
136
|
+
`${formatWorkingTreePreview(statusEntries)}\n\n` +
|
|
137
|
+
'Enter a commit message to stage and commit all current changes before continuing.\n' +
|
|
138
|
+
'Leave blank to cancel.',
|
|
139
|
+
default: message
|
|
140
|
+
}
|
|
141
|
+
])
|
|
142
|
+
|
|
143
|
+
if (!commitMessage || commitMessage.trim().length === 0) {
|
|
144
|
+
throw new Error(DIRTY_WORKING_TREE_CANCELLED_MESSAGE)
|
|
145
|
+
}
|
|
136
146
|
|
|
137
|
-
|
|
147
|
+
message = commitMessage.trim()
|
|
148
|
+
}
|
|
138
149
|
|
|
139
150
|
logStep?.('Staging all pending changes before release...')
|
|
140
151
|
await runCommand('git', ['add', '-A'], {
|
package/src/release-node.mjs
CHANGED
|
@@ -10,6 +10,7 @@ function hasExplicitReleaseOptions(options = {}) {
|
|
|
10
10
|
'skipTests',
|
|
11
11
|
'skipLint',
|
|
12
12
|
'skipVersioning',
|
|
13
|
+
'autoCommit',
|
|
13
14
|
'skipBuild',
|
|
14
15
|
'skipDeploy'
|
|
15
16
|
].some((key) => key in options)
|
|
@@ -23,11 +24,12 @@ export async function releaseNode(options = {}) {
|
|
|
23
24
|
skipTests: options.skipTests === true,
|
|
24
25
|
skipLint: options.skipLint === true,
|
|
25
26
|
skipVersioning: options.skipVersioning === true,
|
|
27
|
+
autoCommit: options.autoCommit === true,
|
|
26
28
|
skipBuild: options.skipBuild === true,
|
|
27
29
|
skipDeploy: options.skipDeploy === true
|
|
28
30
|
}
|
|
29
31
|
: parseReleaseArgs({
|
|
30
|
-
booleanFlags: ['--skip-git-hooks', '--skip-tests', '--skip-lint', '--skip-versioning', '--skip-build', '--skip-deploy']
|
|
32
|
+
booleanFlags: ['--skip-git-hooks', '--auto-commit', '--skip-tests', '--skip-lint', '--skip-versioning', '--skip-build', '--skip-deploy']
|
|
31
33
|
})
|
|
32
34
|
|
|
33
35
|
if (parsed.skipVersioning && parsed.releaseType) {
|
|
@@ -43,10 +45,11 @@ export async function releaseNode(options = {}) {
|
|
|
43
45
|
})
|
|
44
46
|
const {logProcessing: logStep, logSuccess, logWarning, runPrompt, runCommand, runCommandCapture, executionMode} = context
|
|
45
47
|
|
|
46
|
-
await releaseNodePackage({
|
|
48
|
+
return await releaseNodePackage({
|
|
47
49
|
releaseType: parsed.releaseType,
|
|
48
50
|
skipGitHooks: parsed.skipGitHooks === true || executionMode?.skipGitHooks === true,
|
|
49
51
|
skipTests: parsed.skipTests === true,
|
|
52
|
+
autoCommit: parsed.autoCommit === true,
|
|
50
53
|
skipLint: parsed.skipLint === true,
|
|
51
54
|
skipVersioning: parsed.skipVersioning === true,
|
|
52
55
|
skipBuild: parsed.skipBuild === true,
|
|
@@ -9,6 +9,7 @@ function hasExplicitReleaseOptions(options = {}) {
|
|
|
9
9
|
'skipGitHooks',
|
|
10
10
|
'skipTests',
|
|
11
11
|
'skipLint',
|
|
12
|
+
'autoCommit',
|
|
12
13
|
'skipVersioning'
|
|
13
14
|
].some((key) => key in options)
|
|
14
15
|
}
|
|
@@ -20,10 +21,11 @@ export async function releasePackagist(options = {}) {
|
|
|
20
21
|
skipGitHooks: options.skipGitHooks === true,
|
|
21
22
|
skipTests: options.skipTests === true,
|
|
22
23
|
skipLint: options.skipLint === true,
|
|
24
|
+
autoCommit: options.autoCommit === true,
|
|
23
25
|
skipVersioning: options.skipVersioning === true
|
|
24
26
|
}
|
|
25
27
|
: parseReleaseArgs({
|
|
26
|
-
booleanFlags: ['--skip-git-hooks', '--skip-tests', '--skip-lint', '--skip-versioning']
|
|
28
|
+
booleanFlags: ['--skip-git-hooks', '--auto-commit', '--skip-tests', '--skip-lint', '--skip-versioning']
|
|
27
29
|
})
|
|
28
30
|
|
|
29
31
|
if (parsed.skipVersioning && parsed.releaseType) {
|
|
@@ -39,9 +41,10 @@ export async function releasePackagist(options = {}) {
|
|
|
39
41
|
})
|
|
40
42
|
const {logProcessing: logStep, logSuccess, logWarning, runPrompt, runCommand, runCommandCapture, executionMode} = context
|
|
41
43
|
|
|
42
|
-
await releasePackagistPackage({
|
|
44
|
+
return await releasePackagistPackage({
|
|
43
45
|
releaseType: parsed.releaseType,
|
|
44
46
|
skipGitHooks: parsed.skipGitHooks === true || executionMode?.skipGitHooks === true,
|
|
47
|
+
autoCommit: parsed.autoCommit === true,
|
|
45
48
|
skipTests: parsed.skipTests === true,
|
|
46
49
|
skipLint: parsed.skipLint === true,
|
|
47
50
|
skipVersioning: parsed.skipVersioning === true,
|
|
@@ -22,6 +22,7 @@ export function createAppContext({
|
|
|
22
22
|
interactive: executionMode.interactive !== false,
|
|
23
23
|
json: executionMode.json === true,
|
|
24
24
|
workflow: executionMode.workflow ?? 'deploy',
|
|
25
|
+
setup: executionMode.setup === true,
|
|
25
26
|
presetName: executionMode.presetName ?? null,
|
|
26
27
|
maintenanceMode: executionMode.maintenanceMode ?? null,
|
|
27
28
|
autoCommit: executionMode.autoCommit === true,
|