@wyxos/zephyr 0.8.1 → 0.8.2
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
CHANGED
|
@@ -118,7 +118,7 @@ For Laravel app deployments, `--maintenance on|off` overrides both the saved pre
|
|
|
118
118
|
|
|
119
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`.
|
|
120
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.
|
|
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. Pass `--consumer-auto-commit` when the consumer app may already have pending changes; Zephyr will stage, commit, and push all pending consumer changes before applying the dependency bump. Consumer lockfiles are refreshed only when the consumer repository already tracks an npm lockfile; Zephyr does not create or commit server-side lockfiles.
|
|
122
122
|
|
|
123
123
|
## AI Agents and Automation
|
|
124
124
|
|
package/package.json
CHANGED
|
@@ -12,7 +12,7 @@ import {resolvePendingSnapshot} from '../deploy/resolve-pending-snapshot.mjs'
|
|
|
12
12
|
import {runDeployment} from '../deploy/run-deployment.mjs'
|
|
13
13
|
import {waitForNpmPackageVersion} from './npm-publish-wait.mjs'
|
|
14
14
|
import {
|
|
15
|
-
|
|
15
|
+
ensureConsumerRepoReady,
|
|
16
16
|
updateConsumerDependency
|
|
17
17
|
} from './update-consumer-dependency.mjs'
|
|
18
18
|
|
|
@@ -113,7 +113,7 @@ export async function releasePackageThenDeployConsumer({
|
|
|
113
113
|
createAppContextImpl = createAppContext,
|
|
114
114
|
waitForNpmPackageVersionImpl = waitForNpmPackageVersion,
|
|
115
115
|
updateConsumerDependencyImpl = updateConsumerDependency,
|
|
116
|
-
|
|
116
|
+
ensureConsumerRepoReadyImpl = ensureConsumerRepoReady,
|
|
117
117
|
validateLocalDependenciesImpl = validateLocalDependencies,
|
|
118
118
|
selectDeploymentTargetImpl = selectDeploymentTarget,
|
|
119
119
|
resolvePendingSnapshotImpl = resolvePendingSnapshot,
|
|
@@ -158,7 +158,16 @@ export async function releasePackageThenDeployConsumer({
|
|
|
158
158
|
const configurationService = createConfigurationService(context)
|
|
159
159
|
|
|
160
160
|
logProcessing?.(`Preparing consumer app at ${resolvedConsumerRootDir}...`)
|
|
161
|
-
await
|
|
161
|
+
await ensureConsumerRepoReadyImpl(resolvedConsumerRootDir, {
|
|
162
|
+
runCommand,
|
|
163
|
+
runCommandCapture,
|
|
164
|
+
runPrompt,
|
|
165
|
+
logProcessing,
|
|
166
|
+
logSuccess,
|
|
167
|
+
logWarning,
|
|
168
|
+
autoCommit: executionMode.autoCommit,
|
|
169
|
+
skipGitHooks: executionMode.skipGitHooks
|
|
170
|
+
})
|
|
162
171
|
|
|
163
172
|
await bootstrapImpl.ensureGitignoreEntry(resolvedConsumerRootDir, {
|
|
164
173
|
runCommand,
|
|
@@ -226,6 +235,8 @@ export async function releasePackageThenDeployConsumer({
|
|
|
226
235
|
logProcessing,
|
|
227
236
|
logSuccess,
|
|
228
237
|
logWarning,
|
|
238
|
+
runPrompt,
|
|
239
|
+
autoCommit: executionMode.autoCommit,
|
|
229
240
|
skipGitHooks: executionMode.skipGitHooks
|
|
230
241
|
})
|
|
231
242
|
|
|
@@ -1,7 +1,12 @@
|
|
|
1
1
|
import fs from 'node:fs/promises'
|
|
2
2
|
import path from 'node:path'
|
|
3
3
|
|
|
4
|
-
import {
|
|
4
|
+
import {
|
|
5
|
+
formatWorkingTreePreview,
|
|
6
|
+
parseWorkingTreeEntries,
|
|
7
|
+
suggestReleaseCommitMessage as suggestCommitMessageImpl
|
|
8
|
+
} from '../../release/commit-message.mjs'
|
|
9
|
+
import {gitCommitArgs, gitPushArgs} from '../../utils/git-hooks.mjs'
|
|
5
10
|
|
|
6
11
|
const DEPENDENCY_FIELDS = [
|
|
7
12
|
'dependencies',
|
|
@@ -55,6 +60,113 @@ export async function assertCleanConsumerRepo(rootDir, {runCommandCapture} = {})
|
|
|
55
60
|
}
|
|
56
61
|
}
|
|
57
62
|
|
|
63
|
+
function normalizeCommandOutput(output) {
|
|
64
|
+
return typeof output === 'string' ? output : output?.stdout ?? ''
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function createCaptureAwareRunCommand({runCommand, runCommandCapture}) {
|
|
68
|
+
return async (command, args, {capture = false, cwd} = {}) => {
|
|
69
|
+
if (capture) {
|
|
70
|
+
const captured = await runCommandCapture(command, args, {cwd})
|
|
71
|
+
|
|
72
|
+
if (typeof captured === 'string') {
|
|
73
|
+
return {stdout: captured.trim(), stderr: ''}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
const stdout = captured?.stdout ?? ''
|
|
77
|
+
const stderr = captured?.stderr ?? ''
|
|
78
|
+
return {stdout: stdout.trim(), stderr: stderr.trim()}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
await runCommand(command, args, {cwd})
|
|
82
|
+
return undefined
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
export async function ensureConsumerRepoReady(rootDir, {
|
|
87
|
+
runCommand,
|
|
88
|
+
runCommandCapture,
|
|
89
|
+
runPrompt,
|
|
90
|
+
logProcessing,
|
|
91
|
+
logSuccess,
|
|
92
|
+
logWarning,
|
|
93
|
+
autoCommit = false,
|
|
94
|
+
skipGitHooks = false,
|
|
95
|
+
suggestCommitMessage = suggestCommitMessageImpl
|
|
96
|
+
} = {}) {
|
|
97
|
+
if (typeof runCommandCapture !== 'function') {
|
|
98
|
+
throw new Error('Consumer repository checks require a command capture runner.')
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
const status = normalizeCommandOutput(await runCommandCapture('git', ['status', '--porcelain'], {cwd: rootDir}))
|
|
102
|
+
const statusEntries = parseWorkingTreeEntries(status)
|
|
103
|
+
|
|
104
|
+
if (statusEntries.length === 0) {
|
|
105
|
+
return {committed: false, pushed: false}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
if (!autoCommit && typeof runPrompt !== 'function') {
|
|
109
|
+
throw new Error('Consumer repository has uncommitted changes. Commit, stash, or clean them before running a package-to-consumer release.')
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
if (typeof runCommand !== 'function') {
|
|
113
|
+
throw new Error('Consumer repository auto-commit requires a command runner.')
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
const captureAwareRunCommand = createCaptureAwareRunCommand({runCommand, runCommandCapture})
|
|
117
|
+
const suggestedCommitMessage = await suggestCommitMessage(rootDir, {
|
|
118
|
+
runCommand: captureAwareRunCommand,
|
|
119
|
+
logStep: logProcessing,
|
|
120
|
+
logWarning,
|
|
121
|
+
statusEntries
|
|
122
|
+
})
|
|
123
|
+
let message = suggestedCommitMessage?.trim() ?? ''
|
|
124
|
+
|
|
125
|
+
if (autoCommit) {
|
|
126
|
+
if (!message) {
|
|
127
|
+
throw new Error('Consumer auto-commit failed because Codex could not determine a usable commit message.')
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
logProcessing?.(`Consumer auto-commit enabled. Using Codex-generated commit message "${message}".`)
|
|
131
|
+
} else {
|
|
132
|
+
const {commitMessage} = await runPrompt([
|
|
133
|
+
{
|
|
134
|
+
type: 'input',
|
|
135
|
+
name: 'commitMessage',
|
|
136
|
+
message:
|
|
137
|
+
'Pending changes detected in the consumer repository:\n\n' +
|
|
138
|
+
`${formatWorkingTreePreview(statusEntries)}\n\n` +
|
|
139
|
+
'Enter a commit message to stage, commit, and push all current consumer changes before continuing.\n' +
|
|
140
|
+
'Leave blank to cancel.',
|
|
141
|
+
default: message
|
|
142
|
+
}
|
|
143
|
+
])
|
|
144
|
+
|
|
145
|
+
if (!commitMessage || commitMessage.trim().length === 0) {
|
|
146
|
+
throw new Error('Consumer release cancelled: pending changes were not committed.')
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
message = commitMessage.trim()
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
logProcessing?.('Staging all pending consumer changes before dependency update...')
|
|
153
|
+
await runCommand('git', ['add', '-A'], {cwd: rootDir})
|
|
154
|
+
|
|
155
|
+
logProcessing?.('Committing pending consumer changes before dependency update...')
|
|
156
|
+
await runCommand('git', gitCommitArgs(['-m', message], {skipGitHooks}), {cwd: rootDir})
|
|
157
|
+
|
|
158
|
+
logProcessing?.('Pushing pending consumer changes before dependency update...')
|
|
159
|
+
await runCommand('git', gitPushArgs([], {skipGitHooks}), {cwd: rootDir})
|
|
160
|
+
|
|
161
|
+
const finalStatus = normalizeCommandOutput(await runCommandCapture('git', ['status', '--porcelain'], {cwd: rootDir}))
|
|
162
|
+
if (parseWorkingTreeEntries(finalStatus).length > 0) {
|
|
163
|
+
throw new Error('Consumer repository still has uncommitted changes after auto-commit. Aborting package-to-consumer release.')
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
logSuccess?.(`Committed and pushed consumer changes with "${message}".`)
|
|
167
|
+
return {committed: true, pushed: true, message}
|
|
168
|
+
}
|
|
169
|
+
|
|
58
170
|
async function isTracked(rootDir, filePath, {runCommandCapture} = {}) {
|
|
59
171
|
try {
|
|
60
172
|
await runCommandCapture('git', ['ls-files', '--error-unmatch', filePath], {cwd: rootDir})
|
|
@@ -91,10 +203,13 @@ export async function updateConsumerDependency({
|
|
|
91
203
|
version,
|
|
92
204
|
runCommand,
|
|
93
205
|
runCommandCapture,
|
|
206
|
+
runPrompt,
|
|
94
207
|
logProcessing,
|
|
95
208
|
logSuccess,
|
|
96
209
|
logWarning,
|
|
97
|
-
|
|
210
|
+
autoCommit = false,
|
|
211
|
+
skipGitHooks = false,
|
|
212
|
+
suggestCommitMessage = suggestCommitMessageImpl
|
|
98
213
|
} = {}) {
|
|
99
214
|
if (!rootDir) {
|
|
100
215
|
throw new Error('Consumer root directory is required.')
|
|
@@ -112,7 +227,17 @@ export async function updateConsumerDependency({
|
|
|
112
227
|
throw new Error('Consumer dependency updates require command runners.')
|
|
113
228
|
}
|
|
114
229
|
|
|
115
|
-
await
|
|
230
|
+
await ensureConsumerRepoReady(rootDir, {
|
|
231
|
+
runCommand,
|
|
232
|
+
runCommandCapture,
|
|
233
|
+
runPrompt,
|
|
234
|
+
logProcessing,
|
|
235
|
+
logSuccess,
|
|
236
|
+
logWarning,
|
|
237
|
+
autoCommit,
|
|
238
|
+
skipGitHooks,
|
|
239
|
+
suggestCommitMessage
|
|
240
|
+
})
|
|
116
241
|
|
|
117
242
|
const pkg = await readPackageJson(rootDir)
|
|
118
243
|
const dependencyField = findDependencyField(pkg, packageName)
|