@wyxos/zephyr 0.4.7 → 0.4.8
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/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import process from 'node:process'
|
|
2
2
|
|
|
3
|
-
import {ensureLocalRepositoryState} from '../../deploy/local-repo.mjs'
|
|
3
|
+
import {ensureCommittedChangesPushed, ensureLocalRepositoryState} from '../../deploy/local-repo.mjs'
|
|
4
4
|
import {bumpLocalPackageVersion} from './bump-local-package-version.mjs'
|
|
5
5
|
import {resolveLocalDeploymentContext} from './resolve-local-deployment-context.mjs'
|
|
6
6
|
import {resolveLocalDeploymentCheckSupport, runLocalDeploymentChecks} from './run-local-deployment-checks.mjs'
|
|
@@ -17,6 +17,16 @@ export async function prepareLocalDeployment(config, {
|
|
|
17
17
|
logSuccess,
|
|
18
18
|
logWarning
|
|
19
19
|
} = {}) {
|
|
20
|
+
await ensureLocalRepositoryState(config.branch, rootDir, {
|
|
21
|
+
runPrompt,
|
|
22
|
+
runCommand,
|
|
23
|
+
runCommandCapture,
|
|
24
|
+
logProcessing,
|
|
25
|
+
logSuccess,
|
|
26
|
+
logWarning,
|
|
27
|
+
skipGitHooks
|
|
28
|
+
})
|
|
29
|
+
|
|
20
30
|
const context = await resolveLocalDeploymentContext(rootDir)
|
|
21
31
|
const checkSupport = await resolveLocalDeploymentCheckSupport({
|
|
22
32
|
rootDir,
|
|
@@ -33,17 +43,16 @@ export async function prepareLocalDeployment(config, {
|
|
|
33
43
|
logSuccess,
|
|
34
44
|
logWarning
|
|
35
45
|
})
|
|
36
|
-
}
|
|
37
46
|
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
}
|
|
47
|
+
await ensureCommittedChangesPushed(config.branch, rootDir, {
|
|
48
|
+
runCommand,
|
|
49
|
+
runCommandCapture,
|
|
50
|
+
logProcessing,
|
|
51
|
+
logSuccess,
|
|
52
|
+
logWarning,
|
|
53
|
+
skipGitHooks
|
|
54
|
+
})
|
|
55
|
+
}
|
|
47
56
|
|
|
48
57
|
await runLocalDeploymentChecks({
|
|
49
58
|
rootDir,
|
|
@@ -1,6 +1,15 @@
|
|
|
1
1
|
import { getCurrentBranch as getCurrentBranchImpl, getUpstreamRef as getUpstreamRefImpl } from '../utils/git.mjs'
|
|
2
2
|
import {hasPrePushHook} from './preflight.mjs'
|
|
3
3
|
import {gitCommitArgs, gitPushArgs} from '../utils/git-hooks.mjs'
|
|
4
|
+
import {
|
|
5
|
+
buildFallbackCommitMessage,
|
|
6
|
+
formatWorkingTreePreview,
|
|
7
|
+
parseWorkingTreeEntries,
|
|
8
|
+
suggestCommitMessage as suggestCommitMessageImpl
|
|
9
|
+
} from '../release/commit-message.mjs'
|
|
10
|
+
|
|
11
|
+
const DIRTY_DEPLOYMENT_MESSAGE = 'Local repository has uncommitted changes. Commit or stash them before deployment.'
|
|
12
|
+
const DIRTY_DEPLOYMENT_CANCELLED_MESSAGE = 'Deployment cancelled: pending changes were not committed.'
|
|
4
13
|
|
|
5
14
|
export async function getCurrentBranch(rootDir) {
|
|
6
15
|
const branch = await getCurrentBranchImpl(rootDir)
|
|
@@ -161,27 +170,84 @@ async function checkoutTargetBranch(targetBranch, currentBranch, rootDir, {
|
|
|
161
170
|
logSuccess?.(`Checked out ${targetBranch} locally.`)
|
|
162
171
|
}
|
|
163
172
|
|
|
164
|
-
async function
|
|
173
|
+
async function commitAndPushPendingChanges(targetBranch, rootDir, {
|
|
165
174
|
runPrompt,
|
|
166
175
|
runCommand,
|
|
176
|
+
runCommandCapture,
|
|
167
177
|
getGitStatus,
|
|
168
178
|
logProcessing,
|
|
169
179
|
logSuccess,
|
|
170
180
|
logWarning,
|
|
171
|
-
skipGitHooks = false
|
|
181
|
+
skipGitHooks = false,
|
|
182
|
+
suggestCommitMessage = suggestCommitMessageImpl
|
|
172
183
|
} = {}) {
|
|
184
|
+
const statusEntries = parseWorkingTreeEntries(await getGitStatus(rootDir))
|
|
185
|
+
|
|
186
|
+
if (statusEntries.length === 0) {
|
|
187
|
+
return
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
if (typeof runPrompt !== 'function') {
|
|
191
|
+
throw new Error(DIRTY_DEPLOYMENT_MESSAGE)
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
const captureAwareRunCommand = async (command, args, { capture = false, cwd } = {}) => {
|
|
195
|
+
if (capture) {
|
|
196
|
+
const captured = await runCommandCapture(command, args, { cwd })
|
|
197
|
+
|
|
198
|
+
if (typeof captured === 'string') {
|
|
199
|
+
return {stdout: captured.trim(), stderr: ''}
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
const stdout = captured?.stdout ?? ''
|
|
203
|
+
const stderr = captured?.stderr ?? ''
|
|
204
|
+
return {stdout: stdout.trim(), stderr: stderr.trim()}
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
await runCommand(command, args, { cwd })
|
|
208
|
+
return undefined
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
const suggestedCommitMessage = await suggestCommitMessage(rootDir, {
|
|
212
|
+
runCommand: captureAwareRunCommand,
|
|
213
|
+
logStep: logProcessing,
|
|
214
|
+
logWarning,
|
|
215
|
+
statusEntries
|
|
216
|
+
}) ?? buildFallbackCommitMessage(statusEntries)
|
|
217
|
+
|
|
218
|
+
const changeLabel = statusEntries.length === 1 ? 'change' : 'changes'
|
|
219
|
+
const {shouldCommitPendingChanges} = await runPrompt([
|
|
220
|
+
{
|
|
221
|
+
type: 'confirm',
|
|
222
|
+
name: 'shouldCommitPendingChanges',
|
|
223
|
+
message:
|
|
224
|
+
`Pending ${changeLabel} detected before deployment:\n\n` +
|
|
225
|
+
`${formatWorkingTreePreview(statusEntries)}\n\n` +
|
|
226
|
+
'Stage and commit all current changes before continuing?',
|
|
227
|
+
default: true
|
|
228
|
+
}
|
|
229
|
+
])
|
|
230
|
+
|
|
231
|
+
if (!shouldCommitPendingChanges) {
|
|
232
|
+
throw new Error(DIRTY_DEPLOYMENT_CANCELLED_MESSAGE)
|
|
233
|
+
}
|
|
234
|
+
|
|
173
235
|
const { commitMessage } = await runPrompt([
|
|
174
236
|
{
|
|
175
237
|
type: 'input',
|
|
176
238
|
name: 'commitMessage',
|
|
177
|
-
message: '
|
|
239
|
+
message: 'Commit message for pending deployment changes',
|
|
240
|
+
default: suggestedCommitMessage,
|
|
178
241
|
validate: (value) => (value && value.trim().length > 0 ? true : 'Commit message cannot be empty.')
|
|
179
242
|
}
|
|
180
243
|
])
|
|
181
244
|
|
|
182
245
|
const message = commitMessage.trim()
|
|
183
246
|
|
|
184
|
-
logProcessing?.('
|
|
247
|
+
logProcessing?.('Staging all pending changes before deployment...')
|
|
248
|
+
await runCommand('git', ['add', '-A'], { cwd: rootDir })
|
|
249
|
+
|
|
250
|
+
logProcessing?.('Committing pending changes before deployment...')
|
|
185
251
|
await runCommand('git', gitCommitArgs(['-m', message], {skipGitHooks}), { cwd: rootDir })
|
|
186
252
|
|
|
187
253
|
const prePushHookPresent = await hasPrePushHook(rootDir)
|
|
@@ -203,7 +269,8 @@ async function commitAndPushStagedChanges(targetBranch, rootDir, {
|
|
|
203
269
|
throw error
|
|
204
270
|
}
|
|
205
271
|
|
|
206
|
-
logSuccess?.(`Committed
|
|
272
|
+
logSuccess?.(`Committed pending changes with "${message}".`)
|
|
273
|
+
logSuccess?.(`Pushed committed changes to origin/${targetBranch}.`)
|
|
207
274
|
|
|
208
275
|
const finalStatus = await getGitStatus(rootDir)
|
|
209
276
|
|
|
@@ -303,6 +370,7 @@ export async function ensureLocalRepositoryState(targetBranch, rootDir = process
|
|
|
303
370
|
logSuccess,
|
|
304
371
|
logWarning,
|
|
305
372
|
skipGitHooks = false,
|
|
373
|
+
suggestCommitMessage: suggestCommitMessageFn = suggestCommitMessageImpl,
|
|
306
374
|
getCurrentBranch: getCurrentBranchFn = getCurrentBranch,
|
|
307
375
|
getGitStatus: getGitStatusFn = (dir) => getGitStatus(dir, { runCommandCapture }),
|
|
308
376
|
readUpstreamSyncState: readUpstreamSyncStateFn = (branch, dir) =>
|
|
@@ -371,21 +439,17 @@ export async function ensureLocalRepositoryState(targetBranch, rootDir = process
|
|
|
371
439
|
return
|
|
372
440
|
}
|
|
373
441
|
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
logProcessing?.('No staged changes detected. Unstaged or untracked files will not affect deployment. Proceeding with deployment.')
|
|
377
|
-
return
|
|
378
|
-
}
|
|
379
|
-
|
|
380
|
-
logWarning?.(`Staged changes detected on ${targetBranch}. A commit is required before deployment.`)
|
|
381
|
-
await commitAndPushStagedChanges(targetBranch, rootDir, {
|
|
442
|
+
logWarning?.(`Pending changes detected on ${targetBranch}. A commit is required before deployment.`)
|
|
443
|
+
await commitAndPushPendingChanges(targetBranch, rootDir, {
|
|
382
444
|
runPrompt,
|
|
383
445
|
runCommand,
|
|
446
|
+
runCommandCapture,
|
|
384
447
|
getGitStatus: getGitStatusFn,
|
|
385
448
|
logProcessing,
|
|
386
449
|
logSuccess,
|
|
387
450
|
logWarning,
|
|
388
|
-
skipGitHooks
|
|
451
|
+
skipGitHooks,
|
|
452
|
+
suggestCommitMessage: suggestCommitMessageFn
|
|
389
453
|
})
|
|
390
454
|
|
|
391
455
|
await ensureCommittedChangesPushedFn(targetBranch, rootDir)
|
|
@@ -9,6 +9,11 @@ const CONVENTIONAL_COMMIT_PATTERN = /^(build|chore|ci|docs|feat|fix|perf|refacto
|
|
|
9
9
|
const GENERIC_SUBJECT_PATTERNS = [
|
|
10
10
|
/^commit pending (release )?changes$/i,
|
|
11
11
|
/^pending (release )?changes$/i,
|
|
12
|
+
/^commit pending changes before .+$/i,
|
|
13
|
+
/^commit pending (release |deployment )?changes before .+$/i,
|
|
14
|
+
/^commit (all )?(current |pending )?changes( before .+)?$/i,
|
|
15
|
+
/^stage and commit (all )?(current |pending )?changes( before .+)?$/i,
|
|
16
|
+
/^(allow|enable|support) committing pending changes( before .+)?$/i,
|
|
12
17
|
/^commit changes$/i,
|
|
13
18
|
/^update changes$/i,
|
|
14
19
|
/^update files$/i,
|
|
@@ -43,6 +48,11 @@ const TOPIC_STOP_WORDS = new Set([
|
|
|
43
48
|
'shared',
|
|
44
49
|
'index',
|
|
45
50
|
'main',
|
|
51
|
+
'local',
|
|
52
|
+
'repo',
|
|
53
|
+
'prepare',
|
|
54
|
+
'commit',
|
|
55
|
+
'message',
|
|
46
56
|
'js',
|
|
47
57
|
'jsx',
|
|
48
58
|
'ts',
|
|
@@ -58,6 +68,24 @@ const TOPIC_STOP_WORDS = new Set([
|
|
|
58
68
|
'lock'
|
|
59
69
|
])
|
|
60
70
|
|
|
71
|
+
function buildTargetedFallbackCommitMessage(statusEntries = []) {
|
|
72
|
+
const paths = statusEntries.map((entry) => entry.path.toLowerCase())
|
|
73
|
+
const touchesDeployPrep = paths.some((pathValue) => pathValue.includes('prepare-local-deployment'))
|
|
74
|
+
const touchesLocalRepo = paths.some((pathValue) => pathValue.includes('local-repo'))
|
|
75
|
+
const touchesCommitMessage = paths.some((pathValue) => pathValue.includes('commit-message'))
|
|
76
|
+
const touchesReleaseFlow = paths.some((pathValue) => pathValue.includes('/release/') || pathValue.includes('release-'))
|
|
77
|
+
|
|
78
|
+
if (touchesDeployPrep && touchesLocalRepo) {
|
|
79
|
+
return 'fix: prompt for dirty deploy changes before version bump'
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
if (touchesCommitMessage && touchesReleaseFlow) {
|
|
83
|
+
return 'fix: tighten release commit suggestions'
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
return null
|
|
87
|
+
}
|
|
88
|
+
|
|
61
89
|
function resolveWorkingTreeEntryLabel(entry) {
|
|
62
90
|
if (entry.indexStatus === '?' && entry.worktreeStatus === '?') {
|
|
63
91
|
return 'untracked'
|
|
@@ -198,6 +226,11 @@ export function sanitizeSuggestedCommitMessage(message) {
|
|
|
198
226
|
}
|
|
199
227
|
|
|
200
228
|
export function buildFallbackCommitMessage(statusEntries = []) {
|
|
229
|
+
const targetedFallback = buildTargetedFallbackCommitMessage(statusEntries)
|
|
230
|
+
if (targetedFallback) {
|
|
231
|
+
return targetedFallback
|
|
232
|
+
}
|
|
233
|
+
|
|
201
234
|
const tokenCounts = new Map()
|
|
202
235
|
|
|
203
236
|
for (const entry of statusEntries) {
|
|
@@ -272,7 +305,7 @@ async function buildCommitMessageContext(rootDir, {
|
|
|
272
305
|
return statusEntries.map((entry) => `- ${summarizeWorkingTreeEntry(entry, {changeCountsByPath})}`).join('\n')
|
|
273
306
|
}
|
|
274
307
|
|
|
275
|
-
export async function
|
|
308
|
+
export async function suggestCommitMessage(rootDir = process.cwd(), {
|
|
276
309
|
runCommand,
|
|
277
310
|
commandExistsImpl = commandExists,
|
|
278
311
|
logStep,
|
|
@@ -310,6 +343,7 @@ export async function suggestReleaseCommitMessage(rootDir = process.cwd(), {
|
|
|
310
343
|
'Use the exact format "<type>: <subject>" with no scope, no exclamation mark, and no extra text.',
|
|
311
344
|
'Choose the most appropriate type from: fix, feat, chore, docs, refactor, test, style, perf, build, ci, revert.',
|
|
312
345
|
'Make the subject specific enough to describe the actual behavior or workflow change, not just that files changed.',
|
|
346
|
+
'Do not describe the commit itself, staging, or "pending changes"; describe the underlying behavior or workflow fix.',
|
|
313
347
|
'Pending change summary:',
|
|
314
348
|
commitContext || '- changed files present'
|
|
315
349
|
].join('\n\n')
|
|
@@ -329,3 +363,5 @@ export async function suggestReleaseCommitMessage(rootDir = process.cwd(), {
|
|
|
329
363
|
}
|
|
330
364
|
}
|
|
331
365
|
}
|
|
366
|
+
|
|
367
|
+
export {suggestCommitMessage as suggestReleaseCommitMessage}
|