@wyxos/zephyr 0.5.0 → 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +6 -1
- package/package.json +1 -1
- package/src/application/deploy/prepare-local-deployment.mjs +25 -0
- package/src/application/deploy/run-deployment.mjs +2 -0
- package/src/application/deploy/run-local-deployment-checks.mjs +32 -9
- package/src/cli/options.mjs +8 -8
- package/src/deploy/local-repo.mjs +9 -21
- package/src/main.mjs +10 -2
- package/src/release/commit-message.mjs +79 -13
- package/src/release/shared.mjs +9 -21
- package/src/runtime/app-context.mjs +3 -0
package/README.md
CHANGED
|
@@ -40,9 +40,12 @@ Common workflows:
|
|
|
40
40
|
# Deploy an app using the saved preset or the interactive prompts
|
|
41
41
|
zephyr
|
|
42
42
|
|
|
43
|
-
# Deploy an app
|
|
43
|
+
# Deploy an app with a local npm package version bump
|
|
44
44
|
zephyr minor
|
|
45
45
|
|
|
46
|
+
# Deploy an app while bypassing Zephyr's built-in local checks
|
|
47
|
+
zephyr minor --skip-checks
|
|
48
|
+
|
|
46
49
|
# Deploy a configured app non-interactively
|
|
47
50
|
zephyr --non-interactive --preset wyxos-release --maintenance off
|
|
48
51
|
|
|
@@ -155,6 +158,8 @@ npm run release
|
|
|
155
158
|
- `npm run release` is the recommended app/package entrypoint once the release script has been installed.
|
|
156
159
|
- For `--type node` workflows, Zephyr runs your project's `lint` script when present.
|
|
157
160
|
- For `--type node` workflows, Zephyr runs `test:run` or `test` when present.
|
|
161
|
+
- For fresh Laravel app deploys, Zephyr runs local lint/test checks before creating the version-bump commit so failed checks do not force repeated bump retries.
|
|
162
|
+
- For critical app deploys, `--skip-checks` is shorthand for `--skip-lint --skip-tests`. It skips Zephyr's built-in local checks, but any local `pre-push` hook can still run its own checks during git push unless you also opt into `--skip-git-hooks`.
|
|
158
163
|
- For non-interactive app deploys, use a saved preset name instead of relying on prompt fallback.
|
|
159
164
|
|
|
160
165
|
## Features
|
package/package.json
CHANGED
|
@@ -10,6 +10,8 @@ export async function prepareLocalDeployment(config, {
|
|
|
10
10
|
rootDir = process.cwd(),
|
|
11
11
|
versionArg = null,
|
|
12
12
|
skipGitHooks = false,
|
|
13
|
+
skipTests = false,
|
|
14
|
+
skipLint = false,
|
|
13
15
|
runPrompt,
|
|
14
16
|
runCommand,
|
|
15
17
|
runCommandCapture,
|
|
@@ -31,10 +33,29 @@ export async function prepareLocalDeployment(config, {
|
|
|
31
33
|
const checkSupport = await resolveLocalDeploymentCheckSupport({
|
|
32
34
|
rootDir,
|
|
33
35
|
isLaravel: context.isLaravel,
|
|
36
|
+
skipTests,
|
|
37
|
+
skipLint,
|
|
34
38
|
runCommandCapture
|
|
35
39
|
})
|
|
36
40
|
|
|
37
41
|
if (!snapshot && context.isLaravel) {
|
|
42
|
+
await runLocalDeploymentChecks({
|
|
43
|
+
rootDir,
|
|
44
|
+
isLaravel: context.isLaravel,
|
|
45
|
+
hasHook: context.hasHook,
|
|
46
|
+
skipGitHooks,
|
|
47
|
+
skipTests,
|
|
48
|
+
skipLint,
|
|
49
|
+
forceRunWhenHookPresent: true,
|
|
50
|
+
runCommand,
|
|
51
|
+
runCommandCapture,
|
|
52
|
+
logProcessing,
|
|
53
|
+
logSuccess,
|
|
54
|
+
logWarning,
|
|
55
|
+
lintCommand: checkSupport.lintCommand,
|
|
56
|
+
testCommand: checkSupport.testCommand
|
|
57
|
+
})
|
|
58
|
+
|
|
38
59
|
await bumpLocalPackageVersion(rootDir, {
|
|
39
60
|
versionArg,
|
|
40
61
|
skipGitHooks,
|
|
@@ -52,6 +73,8 @@ export async function prepareLocalDeployment(config, {
|
|
|
52
73
|
logWarning,
|
|
53
74
|
skipGitHooks
|
|
54
75
|
})
|
|
76
|
+
|
|
77
|
+
return context
|
|
55
78
|
}
|
|
56
79
|
|
|
57
80
|
await runLocalDeploymentChecks({
|
|
@@ -59,6 +82,8 @@ export async function prepareLocalDeployment(config, {
|
|
|
59
82
|
isLaravel: context.isLaravel,
|
|
60
83
|
hasHook: context.hasHook,
|
|
61
84
|
skipGitHooks,
|
|
85
|
+
skipTests,
|
|
86
|
+
skipLint,
|
|
62
87
|
runCommand,
|
|
63
88
|
runCommandCapture,
|
|
64
89
|
logProcessing,
|
|
@@ -366,6 +366,8 @@ export async function runDeployment(config, options = {}) {
|
|
|
366
366
|
rootDir,
|
|
367
367
|
versionArg,
|
|
368
368
|
skipGitHooks: executionMode?.skipGitHooks === true,
|
|
369
|
+
skipTests: executionMode?.skipTests === true,
|
|
370
|
+
skipLint: executionMode?.skipLint === true,
|
|
369
371
|
runPrompt,
|
|
370
372
|
runCommand,
|
|
371
373
|
runCommandCapture: context.runCommandCapture,
|
|
@@ -50,19 +50,23 @@ 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 testCommand = isLaravel
|
|
69
|
+
const testCommand = isLaravel && !skipTests
|
|
66
70
|
? await resolveSupportedLaravelTestCommand(rootDir, {runCommandCapture})
|
|
67
71
|
: null
|
|
68
72
|
|
|
@@ -95,6 +99,9 @@ export async function runLocalDeploymentChecks({
|
|
|
95
99
|
isLaravel,
|
|
96
100
|
hasHook,
|
|
97
101
|
skipGitHooks = false,
|
|
102
|
+
skipTests = false,
|
|
103
|
+
skipLint = false,
|
|
104
|
+
forceRunWhenHookPresent = false,
|
|
98
105
|
runCommand,
|
|
99
106
|
runCommandCapture,
|
|
100
107
|
logProcessing,
|
|
@@ -108,6 +115,8 @@ export async function runLocalDeploymentChecks({
|
|
|
108
115
|
: await resolveLocalDeploymentCheckSupport({
|
|
109
116
|
rootDir,
|
|
110
117
|
isLaravel,
|
|
118
|
+
skipTests,
|
|
119
|
+
skipLint,
|
|
111
120
|
runCommandCapture
|
|
112
121
|
})
|
|
113
122
|
|
|
@@ -116,6 +125,10 @@ export async function runLocalDeploymentChecks({
|
|
|
116
125
|
logWarning?.(
|
|
117
126
|
'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
127
|
)
|
|
128
|
+
} else if (forceRunWhenHookPresent) {
|
|
129
|
+
logProcessing?.(
|
|
130
|
+
'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.'
|
|
131
|
+
)
|
|
119
132
|
} else {
|
|
120
133
|
logProcessing?.(
|
|
121
134
|
'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 +137,19 @@ export async function runLocalDeploymentChecks({
|
|
|
124
137
|
}
|
|
125
138
|
}
|
|
126
139
|
|
|
127
|
-
if (
|
|
140
|
+
if (hasHook && !skipGitHooks && (skipLint || skipTests)) {
|
|
141
|
+
logWarning?.(
|
|
142
|
+
'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.'
|
|
143
|
+
)
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
if (skipLint) {
|
|
147
|
+
logWarning?.('Skipping lint because --skip-lint flag was provided.')
|
|
148
|
+
} else if (support.lintCommand === null) {
|
|
128
149
|
logWarning?.('No supported lint command was found. Skipping linting checks.')
|
|
129
150
|
}
|
|
130
151
|
|
|
131
|
-
const lintRan = support.lintCommand === null
|
|
152
|
+
const lintRan = skipLint || support.lintCommand === null
|
|
132
153
|
? false
|
|
133
154
|
: await preflight.runLinting(rootDir, {
|
|
134
155
|
runCommand,
|
|
@@ -152,7 +173,9 @@ export async function runLocalDeploymentChecks({
|
|
|
152
173
|
}
|
|
153
174
|
}
|
|
154
175
|
|
|
155
|
-
if (isLaravel) {
|
|
176
|
+
if (isLaravel && skipTests) {
|
|
177
|
+
logWarning?.('Skipping tests because --skip-tests flag was provided.')
|
|
178
|
+
} else if (isLaravel) {
|
|
156
179
|
await runLocalLaravelTests(rootDir, {
|
|
157
180
|
runCommand,
|
|
158
181
|
logProcessing,
|
package/src/cli/options.mjs
CHANGED
|
@@ -36,8 +36,9 @@ export function parseCliOptions(args = process.argv.slice(2)) {
|
|
|
36
36
|
.option('--discard-pending', 'Discard a saved pending deployment snapshot without prompting.')
|
|
37
37
|
.option('--maintenance <mode>', 'Laravel maintenance mode policy for app deployments (on|off).')
|
|
38
38
|
.option('--skip-git-hooks', 'Bypass local git hooks for any commits and pushes Zephyr performs.')
|
|
39
|
-
.option('--skip-
|
|
40
|
-
.option('--skip-
|
|
39
|
+
.option('--skip-checks', 'Skip Zephyr local lint and test execution.')
|
|
40
|
+
.option('--skip-tests', 'Skip Zephyr local test execution in package release and app deployment workflows.')
|
|
41
|
+
.option('--skip-lint', 'Skip Zephyr local lint execution in package release and app deployment workflows.')
|
|
41
42
|
.option('--skip-build', 'Skip build execution in node/vue release workflows.')
|
|
42
43
|
.option('--skip-deploy', 'Skip GitHub Pages deployment in node/vue release workflows.')
|
|
43
44
|
.argument(
|
|
@@ -68,8 +69,9 @@ export function parseCliOptions(args = process.argv.slice(2)) {
|
|
|
68
69
|
discardPending: Boolean(options.discardPending),
|
|
69
70
|
maintenanceMode: normalizeMaintenanceMode(options.maintenance),
|
|
70
71
|
skipGitHooks: Boolean(options.skipGitHooks),
|
|
71
|
-
|
|
72
|
-
|
|
72
|
+
skipChecks: Boolean(options.skipChecks),
|
|
73
|
+
skipTests: Boolean(options.skipTests || options.skipChecks),
|
|
74
|
+
skipLint: Boolean(options.skipLint || options.skipChecks),
|
|
73
75
|
skipBuild: Boolean(options.skipBuild),
|
|
74
76
|
skipDeploy: Boolean(options.skipDeploy)
|
|
75
77
|
}
|
|
@@ -84,8 +86,6 @@ export function validateCliOptions(options = {}) {
|
|
|
84
86
|
resumePending = false,
|
|
85
87
|
discardPending = false,
|
|
86
88
|
maintenanceMode = null,
|
|
87
|
-
skipTests = false,
|
|
88
|
-
skipLint = false,
|
|
89
89
|
skipBuild = false,
|
|
90
90
|
skipDeploy = false
|
|
91
91
|
} = options
|
|
@@ -113,8 +113,8 @@ export function validateCliOptions(options = {}) {
|
|
|
113
113
|
throw new InvalidCliOptionsError('--maintenance is only valid for app deployments.')
|
|
114
114
|
}
|
|
115
115
|
} else {
|
|
116
|
-
if (
|
|
117
|
-
throw new InvalidCliOptionsError('
|
|
116
|
+
if (skipBuild || skipDeploy) {
|
|
117
|
+
throw new InvalidCliOptionsError('--skip-build and --skip-deploy are only valid for node/vue release workflows.')
|
|
118
118
|
}
|
|
119
119
|
|
|
120
120
|
if (nonInteractive && !presetName) {
|
|
@@ -2,7 +2,6 @@ import { getCurrentBranch as getCurrentBranchImpl, getUpstreamRef as getUpstream
|
|
|
2
2
|
import {hasPrePushHook} from './preflight.mjs'
|
|
3
3
|
import {gitCommitArgs, gitPushArgs} from '../utils/git-hooks.mjs'
|
|
4
4
|
import {
|
|
5
|
-
buildFallbackCommitMessage,
|
|
6
5
|
formatWorkingTreePreview,
|
|
7
6
|
parseWorkingTreeEntries,
|
|
8
7
|
suggestCommitMessage as suggestCommitMessageImpl
|
|
@@ -213,35 +212,24 @@ async function commitAndPushPendingChanges(targetBranch, rootDir, {
|
|
|
213
212
|
logStep: logProcessing,
|
|
214
213
|
logWarning,
|
|
215
214
|
statusEntries
|
|
216
|
-
})
|
|
217
|
-
|
|
218
|
-
const changeLabel = statusEntries.length === 1 ? 'change' : 'changes'
|
|
219
|
-
const {shouldCommitPendingChanges} = await runPrompt([
|
|
215
|
+
})
|
|
216
|
+
const { commitMessage } = await runPrompt([
|
|
220
217
|
{
|
|
221
|
-
type: '
|
|
222
|
-
name: '
|
|
218
|
+
type: 'input',
|
|
219
|
+
name: 'commitMessage',
|
|
223
220
|
message:
|
|
224
|
-
|
|
221
|
+
'Pending changes detected before deployment:\n\n' +
|
|
225
222
|
`${formatWorkingTreePreview(statusEntries)}\n\n` +
|
|
226
|
-
'
|
|
227
|
-
|
|
223
|
+
'Enter a commit message to stage and commit all current changes before continuing.\n' +
|
|
224
|
+
'Leave blank to cancel.',
|
|
225
|
+
default: suggestedCommitMessage ?? ''
|
|
228
226
|
}
|
|
229
227
|
])
|
|
230
228
|
|
|
231
|
-
if (!
|
|
229
|
+
if (!commitMessage || commitMessage.trim().length === 0) {
|
|
232
230
|
throw new Error(DIRTY_DEPLOYMENT_CANCELLED_MESSAGE)
|
|
233
231
|
}
|
|
234
232
|
|
|
235
|
-
const { commitMessage } = await runPrompt([
|
|
236
|
-
{
|
|
237
|
-
type: 'input',
|
|
238
|
-
name: 'commitMessage',
|
|
239
|
-
message: 'Commit message for pending deployment changes',
|
|
240
|
-
default: suggestedCommitMessage,
|
|
241
|
-
validate: (value) => (value && value.trim().length > 0 ? true : 'Commit message cannot be empty.')
|
|
242
|
-
}
|
|
243
|
-
])
|
|
244
|
-
|
|
245
233
|
const message = commitMessage.trim()
|
|
246
234
|
|
|
247
235
|
logProcessing?.('Staging all pending changes before deployment...')
|
package/src/main.mjs
CHANGED
|
@@ -36,8 +36,9 @@ function normalizeMainOptions(firstArg = null, secondArg = null) {
|
|
|
36
36
|
discardPending: firstArg.discardPending === true,
|
|
37
37
|
maintenanceMode: firstArg.maintenanceMode ?? null,
|
|
38
38
|
skipGitHooks: firstArg.skipGitHooks === true,
|
|
39
|
-
|
|
40
|
-
|
|
39
|
+
skipChecks: firstArg.skipChecks === true,
|
|
40
|
+
skipTests: firstArg.skipTests === true || firstArg.skipChecks === true,
|
|
41
|
+
skipLint: firstArg.skipLint === true || firstArg.skipChecks === true,
|
|
41
42
|
skipBuild: firstArg.skipBuild === true,
|
|
42
43
|
skipDeploy: firstArg.skipDeploy === true,
|
|
43
44
|
context: firstArg.context ?? null
|
|
@@ -54,6 +55,7 @@ function normalizeMainOptions(firstArg = null, secondArg = null) {
|
|
|
54
55
|
discardPending: false,
|
|
55
56
|
maintenanceMode: null,
|
|
56
57
|
skipGitHooks: false,
|
|
58
|
+
skipChecks: false,
|
|
57
59
|
skipTests: false,
|
|
58
60
|
skipLint: false,
|
|
59
61
|
skipBuild: false,
|
|
@@ -109,6 +111,9 @@ async function main(optionsOrWorkflowType = null, versionArg = null) {
|
|
|
109
111
|
presetName: options.presetName,
|
|
110
112
|
maintenanceMode: options.maintenanceMode,
|
|
111
113
|
skipGitHooks: options.skipGitHooks === true,
|
|
114
|
+
skipChecks: options.skipChecks === true,
|
|
115
|
+
skipTests: options.skipTests === true,
|
|
116
|
+
skipLint: options.skipLint === true,
|
|
112
117
|
resumePending: options.resumePending,
|
|
113
118
|
discardPending: options.discardPending
|
|
114
119
|
}
|
|
@@ -138,6 +143,9 @@ async function main(optionsOrWorkflowType = null, versionArg = null) {
|
|
|
138
143
|
presetName: currentExecutionMode.presetName,
|
|
139
144
|
maintenanceMode: currentExecutionMode.maintenanceMode,
|
|
140
145
|
skipGitHooks: currentExecutionMode.skipGitHooks === true,
|
|
146
|
+
skipChecks: currentExecutionMode.skipChecks === true,
|
|
147
|
+
skipTests: currentExecutionMode.skipTests === true,
|
|
148
|
+
skipLint: currentExecutionMode.skipLint === true,
|
|
141
149
|
resumePending: currentExecutionMode.resumePending,
|
|
142
150
|
discardPending: currentExecutionMode.discardPending
|
|
143
151
|
}
|
|
@@ -20,9 +20,12 @@ const GENERIC_SUBJECT_PATTERNS = [
|
|
|
20
20
|
/^update work$/i,
|
|
21
21
|
/^misc(ellaneous)?( updates?)?$/i,
|
|
22
22
|
/^changes$/i,
|
|
23
|
-
/^updates?$/i
|
|
23
|
+
/^updates?$/i,
|
|
24
|
+
/^(improve|update|adjust|refine|align|support|enable)\s+.+\s+(workflow|process|flow)$/i
|
|
24
25
|
]
|
|
25
26
|
const MAX_WORKING_TREE_PREVIEW = 20
|
|
27
|
+
const MAX_DIFF_EXCERPT_LINES = 60
|
|
28
|
+
const MAX_DIFF_EXCERPT_CHARS = 4000
|
|
26
29
|
const STATUS_LABELS = {
|
|
27
30
|
A: 'added',
|
|
28
31
|
C: 'copied',
|
|
@@ -261,10 +264,10 @@ export function buildFallbackCommitMessage(statusEntries = []) {
|
|
|
261
264
|
}
|
|
262
265
|
|
|
263
266
|
if (commitType === 'ci') {
|
|
264
|
-
return `ci: update ${primaryTopic}
|
|
267
|
+
return `ci: update ${primaryTopic} pipeline`
|
|
265
268
|
}
|
|
266
269
|
|
|
267
|
-
return `chore:
|
|
270
|
+
return `chore: update ${primaryTopic} handling`
|
|
268
271
|
}
|
|
269
272
|
|
|
270
273
|
async function collectDiffNumstat(rootDir, {runCommand} = {}) {
|
|
@@ -297,12 +300,68 @@ async function collectDiffNumstat(rootDir, {runCommand} = {}) {
|
|
|
297
300
|
}
|
|
298
301
|
}
|
|
299
302
|
|
|
303
|
+
function shouldIncludeDiffExcerptLine(line = '') {
|
|
304
|
+
if (line.startsWith('diff --git ')) {
|
|
305
|
+
return true
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
if (line.startsWith('@@')) {
|
|
309
|
+
return true
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
return (line.startsWith('+') || line.startsWith('-')) && !line.startsWith('+++') && !line.startsWith('---')
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
function trimDiffExcerpt(diffText = '') {
|
|
316
|
+
const selectedLines = []
|
|
317
|
+
let totalChars = 0
|
|
318
|
+
|
|
319
|
+
for (const rawLine of diffText.split(/\r?\n/)) {
|
|
320
|
+
const line = rawLine.trimEnd()
|
|
321
|
+
|
|
322
|
+
if (!shouldIncludeDiffExcerptLine(line)) {
|
|
323
|
+
continue
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
const truncatedLine = line.length > 180 ? `${line.slice(0, 177)}...` : line
|
|
327
|
+
const nextTotal = totalChars + truncatedLine.length + 1
|
|
328
|
+
|
|
329
|
+
if (selectedLines.length >= MAX_DIFF_EXCERPT_LINES || nextTotal > MAX_DIFF_EXCERPT_CHARS) {
|
|
330
|
+
break
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
selectedLines.push(truncatedLine)
|
|
334
|
+
totalChars = nextTotal
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
return selectedLines.join('\n')
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
async function collectDiffExcerpt(rootDir, {runCommand} = {}) {
|
|
341
|
+
try {
|
|
342
|
+
const {stdout} = await runCommand('git', ['diff', '--unified=0', '--no-color', 'HEAD', '--'], {
|
|
343
|
+
capture: true,
|
|
344
|
+
cwd: rootDir
|
|
345
|
+
})
|
|
346
|
+
|
|
347
|
+
return trimDiffExcerpt(stdout)
|
|
348
|
+
} catch {
|
|
349
|
+
return ''
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
|
|
300
353
|
async function buildCommitMessageContext(rootDir, {
|
|
301
354
|
runCommand,
|
|
302
355
|
statusEntries = []
|
|
303
356
|
} = {}) {
|
|
304
357
|
const changeCountsByPath = await collectDiffNumstat(rootDir, {runCommand})
|
|
305
|
-
|
|
358
|
+
const summary = statusEntries.map((entry) => `- ${summarizeWorkingTreeEntry(entry, {changeCountsByPath})}`).join('\n')
|
|
359
|
+
const diffExcerpt = await collectDiffExcerpt(rootDir, {runCommand})
|
|
360
|
+
|
|
361
|
+
return {
|
|
362
|
+
summary,
|
|
363
|
+
diffExcerpt
|
|
364
|
+
}
|
|
306
365
|
}
|
|
307
366
|
|
|
308
367
|
export async function suggestCommitMessage(rootDir = process.cwd(), {
|
|
@@ -328,6 +387,21 @@ export async function suggestCommitMessage(rootDir = process.cwd(), {
|
|
|
328
387
|
|
|
329
388
|
logStep?.('Generating a suggested commit message with Codex...')
|
|
330
389
|
|
|
390
|
+
const promptSections = [
|
|
391
|
+
'Write exactly one short conventional commit message for these pending changes.',
|
|
392
|
+
'Use the exact format "<type>: <subject>" with no scope, no exclamation mark, and no extra text.',
|
|
393
|
+
'Choose the most appropriate type from: fix, feat, chore, docs, refactor, test, style, perf, build, ci, revert.',
|
|
394
|
+
'Base the subject on the actual behavior, safeguard, bug fix, feature, or API change shown below.',
|
|
395
|
+
'Avoid generic nouns like "workflow", "process", "flow", "changes", or "updates" unless the diff truly changes CI or docs.',
|
|
396
|
+
'Do not describe the commit itself, staging, or "pending changes"; describe the underlying code or product change.',
|
|
397
|
+
'Pending change summary:',
|
|
398
|
+
commitContext.summary || '- changed files present'
|
|
399
|
+
]
|
|
400
|
+
|
|
401
|
+
if (commitContext.diffExcerpt) {
|
|
402
|
+
promptSections.push('Diff excerpt:', commitContext.diffExcerpt)
|
|
403
|
+
}
|
|
404
|
+
|
|
331
405
|
await runCommand('codex', [
|
|
332
406
|
'exec',
|
|
333
407
|
'--ephemeral',
|
|
@@ -338,15 +412,7 @@ export async function suggestCommitMessage(rootDir = process.cwd(), {
|
|
|
338
412
|
'--skip-git-repo-check',
|
|
339
413
|
'--output-last-message',
|
|
340
414
|
outputPath,
|
|
341
|
-
|
|
342
|
-
'Write exactly one short conventional commit message for these pending changes.',
|
|
343
|
-
'Use the exact format "<type>: <subject>" with no scope, no exclamation mark, and no extra text.',
|
|
344
|
-
'Choose the most appropriate type from: fix, feat, chore, docs, refactor, test, style, perf, build, ci, revert.',
|
|
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.',
|
|
347
|
-
'Pending change summary:',
|
|
348
|
-
commitContext || '- changed files present'
|
|
349
|
-
].join('\n\n')
|
|
415
|
+
promptSections.join('\n\n')
|
|
350
416
|
], {
|
|
351
417
|
capture: true,
|
|
352
418
|
cwd: rootDir
|
package/src/release/shared.mjs
CHANGED
|
@@ -10,7 +10,6 @@ import {
|
|
|
10
10
|
getUpstreamRef
|
|
11
11
|
} from '../utils/git.mjs'
|
|
12
12
|
import {
|
|
13
|
-
buildFallbackCommitMessage,
|
|
14
13
|
formatWorkingTreePreview,
|
|
15
14
|
parseWorkingTreeEntries,
|
|
16
15
|
parseWorkingTreeStatus,
|
|
@@ -117,35 +116,24 @@ export async function ensureCleanWorkingTree(rootDir = process.cwd(), {
|
|
|
117
116
|
logStep,
|
|
118
117
|
logWarning,
|
|
119
118
|
statusEntries
|
|
120
|
-
})
|
|
121
|
-
|
|
122
|
-
const changeLabel = statusEntries.length === 1 ? 'change' : 'changes'
|
|
123
|
-
const {shouldCommitPendingChanges} = await runPrompt([
|
|
119
|
+
})
|
|
120
|
+
const {commitMessage} = await runPrompt([
|
|
124
121
|
{
|
|
125
|
-
type: '
|
|
126
|
-
name: '
|
|
122
|
+
type: 'input',
|
|
123
|
+
name: 'commitMessage',
|
|
127
124
|
message:
|
|
128
|
-
|
|
125
|
+
'Pending changes detected before release:\n\n' +
|
|
129
126
|
`${formatWorkingTreePreview(statusEntries)}\n\n` +
|
|
130
|
-
'
|
|
131
|
-
|
|
127
|
+
'Enter a commit message to stage and commit all current changes before continuing.\n' +
|
|
128
|
+
'Leave blank to cancel.',
|
|
129
|
+
default: suggestedCommitMessage ?? ''
|
|
132
130
|
}
|
|
133
131
|
])
|
|
134
132
|
|
|
135
|
-
if (!
|
|
133
|
+
if (!commitMessage || commitMessage.trim().length === 0) {
|
|
136
134
|
throw new Error(DIRTY_WORKING_TREE_CANCELLED_MESSAGE)
|
|
137
135
|
}
|
|
138
136
|
|
|
139
|
-
const {commitMessage} = await runPrompt([
|
|
140
|
-
{
|
|
141
|
-
type: 'input',
|
|
142
|
-
name: 'commitMessage',
|
|
143
|
-
message: 'Commit message for pending release changes',
|
|
144
|
-
default: suggestedCommitMessage,
|
|
145
|
-
validate: (value) => (value && value.trim().length > 0 ? true : 'Commit message cannot be empty.')
|
|
146
|
-
}
|
|
147
|
-
])
|
|
148
|
-
|
|
149
137
|
const message = commitMessage.trim()
|
|
150
138
|
|
|
151
139
|
logStep?.('Staging all pending changes before release...')
|
|
@@ -25,6 +25,9 @@ export function createAppContext({
|
|
|
25
25
|
presetName: executionMode.presetName ?? null,
|
|
26
26
|
maintenanceMode: executionMode.maintenanceMode ?? null,
|
|
27
27
|
skipGitHooks: executionMode.skipGitHooks === true,
|
|
28
|
+
skipChecks: executionMode.skipChecks === true,
|
|
29
|
+
skipTests: executionMode.skipTests === true || executionMode.skipChecks === true,
|
|
30
|
+
skipLint: executionMode.skipLint === true || executionMode.skipChecks === true,
|
|
28
31
|
resumePending: executionMode.resumePending === true,
|
|
29
32
|
discardPending: executionMode.discardPending === true
|
|
30
33
|
}
|