@wyxos/zephyr 0.9.4 → 0.9.6
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 +1 -1
- package/src/application/deploy/bump-local-package-version.mjs +34 -15
- package/src/application/deploy/prepare-local-deployment.mjs +1 -0
- package/src/release/commit-message.mjs +4 -2
- package/src/release/release-type.mjs +39 -14
- package/src/runtime/codex-diagnostics.mjs +62 -0
- package/src/runtime/local-command.mjs +14 -0
package/package.json
CHANGED
|
@@ -22,6 +22,25 @@ async function isGitIgnored(rootDir, filePath, {runCommand} = {}) {
|
|
|
22
22
|
}
|
|
23
23
|
}
|
|
24
24
|
|
|
25
|
+
|
|
26
|
+
async function captureGit(rootDir, args, {runCommand, runCommandCapture} = {}) {
|
|
27
|
+
if (typeof runCommandCapture === 'function') {
|
|
28
|
+
return await runCommandCapture('git', args, {cwd: rootDir})
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
if (typeof runCommand !== 'function') {
|
|
32
|
+
return ''
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const output = await runCommand('git', args, {capture: true, cwd: rootDir})
|
|
36
|
+
|
|
37
|
+
if (typeof output === 'string') {
|
|
38
|
+
return output
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
return output?.stdout ?? ''
|
|
42
|
+
}
|
|
43
|
+
|
|
25
44
|
function parseVersionBumpCommit(line) {
|
|
26
45
|
const [hash, shortHash, subject] = line.split('\0')
|
|
27
46
|
const match = /^chore: bump version to (\d+\.\d+\.\d+(?:[-+][^\s]+)?)$/i.exec(subject ?? '')
|
|
@@ -33,15 +52,11 @@ function parseVersionBumpCommit(line) {
|
|
|
33
52
|
return {hash, shortHash, version: match[1]}
|
|
34
53
|
}
|
|
35
54
|
|
|
36
|
-
async function readVersionBumpCommits(rootDir, {runCommand} = {}) {
|
|
37
|
-
if (typeof runCommand !== 'function') {
|
|
38
|
-
return []
|
|
39
|
-
}
|
|
40
|
-
|
|
55
|
+
async function readVersionBumpCommits(rootDir, {runCommand, runCommandCapture} = {}) {
|
|
41
56
|
try {
|
|
42
|
-
const
|
|
43
|
-
|
|
44
|
-
|
|
57
|
+
const stdout = await captureGit(rootDir, ['log', '--format=%H%x00%h%x00%s', '-1000'], {
|
|
58
|
+
runCommand,
|
|
59
|
+
runCommandCapture
|
|
45
60
|
})
|
|
46
61
|
|
|
47
62
|
return stdout
|
|
@@ -98,20 +113,20 @@ function formatVersionReferenceLabel(reference, currentVersion) {
|
|
|
98
113
|
}
|
|
99
114
|
|
|
100
115
|
|
|
101
|
-
async function readRecentAppCommitSubjects(rootDir, versionReference, {runCommand} = {}) {
|
|
102
|
-
if (!versionReference?.hash
|
|
116
|
+
async function readRecentAppCommitSubjects(rootDir, versionReference, {runCommand, runCommandCapture} = {}) {
|
|
117
|
+
if (!versionReference?.hash) {
|
|
103
118
|
return ''
|
|
104
119
|
}
|
|
105
120
|
|
|
106
121
|
try {
|
|
107
|
-
const
|
|
122
|
+
const stdout = await captureGit(rootDir, [
|
|
108
123
|
'log',
|
|
109
124
|
'--format=%s',
|
|
110
125
|
'--max-count=80',
|
|
111
126
|
`${versionReference.hash}..HEAD`
|
|
112
127
|
], {
|
|
113
|
-
|
|
114
|
-
|
|
128
|
+
runCommand,
|
|
129
|
+
runCommandCapture
|
|
115
130
|
})
|
|
116
131
|
|
|
117
132
|
return stdout.trim()
|
|
@@ -138,6 +153,7 @@ async function resolveDeploymentVersionValue(rootDir, {
|
|
|
138
153
|
interactive = false,
|
|
139
154
|
runPrompt,
|
|
140
155
|
runCommand,
|
|
156
|
+
runCommandCapture,
|
|
141
157
|
logProcessing,
|
|
142
158
|
logWarning
|
|
143
159
|
} = {}) {
|
|
@@ -145,9 +161,9 @@ async function resolveDeploymentVersionValue(rootDir, {
|
|
|
145
161
|
return String(versionArg).trim()
|
|
146
162
|
}
|
|
147
163
|
|
|
148
|
-
const versionBumps = await readVersionBumpCommits(rootDir, {runCommand})
|
|
164
|
+
const versionBumps = await readVersionBumpCommits(rootDir, {runCommand, runCommandCapture})
|
|
149
165
|
const versionReference = selectVersionSuggestionReference(pkg.version, versionBumps)
|
|
150
|
-
const commitSubjects = await readRecentAppCommitSubjects(rootDir, versionReference, {runCommand})
|
|
166
|
+
const commitSubjects = await readRecentAppCommitSubjects(rootDir, versionReference, {runCommand, runCommandCapture})
|
|
151
167
|
|
|
152
168
|
return await resolveReleaseType({
|
|
153
169
|
currentVersion: pkg.version,
|
|
@@ -156,6 +172,7 @@ async function resolveDeploymentVersionValue(rootDir, {
|
|
|
156
172
|
interactive,
|
|
157
173
|
runPrompt,
|
|
158
174
|
runCommand,
|
|
175
|
+
runCommandCapture,
|
|
159
176
|
logStep: logProcessing,
|
|
160
177
|
logWarning,
|
|
161
178
|
latestTag: versionReference?.hash ?? null,
|
|
@@ -170,6 +187,7 @@ export async function bumpLocalPackageVersion(rootDir, {
|
|
|
170
187
|
runPrompt,
|
|
171
188
|
skipGitHooks = false,
|
|
172
189
|
runCommand,
|
|
190
|
+
runCommandCapture,
|
|
173
191
|
logProcessing,
|
|
174
192
|
logSuccess,
|
|
175
193
|
logWarning
|
|
@@ -196,6 +214,7 @@ export async function bumpLocalPackageVersion(rootDir, {
|
|
|
196
214
|
interactive,
|
|
197
215
|
runPrompt,
|
|
198
216
|
runCommand,
|
|
217
|
+
runCommandCapture,
|
|
199
218
|
logProcessing,
|
|
200
219
|
logWarning
|
|
201
220
|
})
|
|
@@ -3,6 +3,7 @@ import {tmpdir} from 'node:os'
|
|
|
3
3
|
import path from 'node:path'
|
|
4
4
|
import process from 'node:process'
|
|
5
5
|
|
|
6
|
+
import {describeCodexAdvisorFailure, logCapturedCodexDiagnostics} from '../runtime/codex-diagnostics.mjs'
|
|
6
7
|
import {commandExists} from '../utils/command.mjs'
|
|
7
8
|
|
|
8
9
|
const CONVENTIONAL_COMMIT_PATTERN = /^(build|chore|ci|docs|feat|fix|perf|refactor|revert|style|test): .+/i
|
|
@@ -237,7 +238,7 @@ export async function suggestCommitMessage(rootDir = process.cwd(), {
|
|
|
237
238
|
|
|
238
239
|
logStep?.('Generating a suggested commit message with Codex...')
|
|
239
240
|
|
|
240
|
-
await runCommand('codex', [
|
|
241
|
+
const codexResult = await runCommand('codex', [
|
|
241
242
|
'exec',
|
|
242
243
|
'--ephemeral',
|
|
243
244
|
'--model',
|
|
@@ -258,6 +259,7 @@ export async function suggestCommitMessage(rootDir = process.cwd(), {
|
|
|
258
259
|
capture: true,
|
|
259
260
|
cwd: rootDir
|
|
260
261
|
})
|
|
262
|
+
logCapturedCodexDiagnostics(codexResult, {label: 'commit-message advisor', logWarning})
|
|
261
263
|
|
|
262
264
|
const rawMessage = await readFile(outputPath, 'utf8')
|
|
263
265
|
const message = sanitizeSuggestedCommitMessage(rawMessage)
|
|
@@ -269,7 +271,7 @@ export async function suggestCommitMessage(rootDir = process.cwd(), {
|
|
|
269
271
|
logWarning?.('Codex suggested an unusable commit message.')
|
|
270
272
|
return fallbackMessage()
|
|
271
273
|
} catch (error) {
|
|
272
|
-
logWarning?.(
|
|
274
|
+
logWarning?.(`${describeCodexAdvisorFailure(error, {label: 'commit-message advisor'})} Using path-based fallback.`)
|
|
273
275
|
return fallbackMessage()
|
|
274
276
|
} finally {
|
|
275
277
|
if (tempDir) {
|
|
@@ -4,6 +4,7 @@ import path from 'node:path'
|
|
|
4
4
|
import process from 'node:process'
|
|
5
5
|
import semver from 'semver'
|
|
6
6
|
|
|
7
|
+
import {describeCodexAdvisorFailure, logCapturedCodexDiagnostics} from '../runtime/codex-diagnostics.mjs'
|
|
7
8
|
import {commandExists} from '../utils/command.mjs'
|
|
8
9
|
|
|
9
10
|
export const RELEASE_TYPES = [
|
|
@@ -107,14 +108,30 @@ function buildChoiceOrder(suggestedReleaseType) {
|
|
|
107
108
|
]
|
|
108
109
|
}
|
|
109
110
|
|
|
110
|
-
|
|
111
|
+
|
|
112
|
+
async function captureCommand(command, args, {runCommand, runCommandCapture, cwd} = {}) {
|
|
113
|
+
if (typeof runCommandCapture === 'function') {
|
|
114
|
+
return {stdout: await runCommandCapture(command, args, {cwd}), stderr: ''}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
const output = await runCommand(command, args, {capture: true, cwd})
|
|
118
|
+
|
|
119
|
+
if (typeof output === 'string') {
|
|
120
|
+
return {stdout: output, stderr: ''}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
return output ?? {stdout: '', stderr: ''}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
async function readLatestReleaseTag(rootDir, {runCommand, runCommandCapture, latestTag = null} = {}) {
|
|
111
127
|
if (typeof latestTag === 'string' && latestTag.trim() !== '') {
|
|
112
128
|
return latestTag.trim()
|
|
113
129
|
}
|
|
114
130
|
|
|
115
131
|
try {
|
|
116
|
-
const {stdout} = await
|
|
117
|
-
|
|
132
|
+
const {stdout} = await captureCommand('git', ['describe', '--tags', '--abbrev=0'], {
|
|
133
|
+
runCommand,
|
|
134
|
+
runCommandCapture,
|
|
118
135
|
cwd: rootDir
|
|
119
136
|
})
|
|
120
137
|
|
|
@@ -124,14 +141,15 @@ async function readLatestReleaseTag(rootDir, {runCommand, latestTag = null} = {}
|
|
|
124
141
|
}
|
|
125
142
|
}
|
|
126
143
|
|
|
127
|
-
async function readCommitLog(rootDir, {runCommand, latestTag} = {}) {
|
|
144
|
+
async function readCommitLog(rootDir, {runCommand, runCommandCapture, latestTag} = {}) {
|
|
128
145
|
const args = latestTag
|
|
129
146
|
? ['log', '--format=%h %s', `${latestTag}..HEAD`]
|
|
130
147
|
: ['log', '--format=%h %s', '-20']
|
|
131
148
|
|
|
132
149
|
try {
|
|
133
|
-
const {stdout} = await
|
|
134
|
-
|
|
150
|
+
const {stdout} = await captureCommand('git', args, {
|
|
151
|
+
runCommand,
|
|
152
|
+
runCommandCapture,
|
|
135
153
|
cwd: rootDir
|
|
136
154
|
})
|
|
137
155
|
|
|
@@ -141,14 +159,15 @@ async function readCommitLog(rootDir, {runCommand, latestTag} = {}) {
|
|
|
141
159
|
}
|
|
142
160
|
}
|
|
143
161
|
|
|
144
|
-
async function readDiffStat(rootDir, {runCommand, latestTag} = {}) {
|
|
162
|
+
async function readDiffStat(rootDir, {runCommand, runCommandCapture, latestTag} = {}) {
|
|
145
163
|
const args = latestTag
|
|
146
164
|
? ['diff', '--stat', `${latestTag}..HEAD`, '--']
|
|
147
165
|
: ['diff', '--stat', 'HEAD~20..HEAD', '--']
|
|
148
166
|
|
|
149
167
|
try {
|
|
150
|
-
const {stdout} = await
|
|
151
|
-
|
|
168
|
+
const {stdout} = await captureCommand('git', args, {
|
|
169
|
+
runCommand,
|
|
170
|
+
runCommandCapture,
|
|
152
171
|
cwd: rootDir
|
|
153
172
|
})
|
|
154
173
|
|
|
@@ -160,14 +179,15 @@ async function readDiffStat(rootDir, {runCommand, latestTag} = {}) {
|
|
|
160
179
|
|
|
161
180
|
async function buildReleaseSuggestionContext(rootDir, {
|
|
162
181
|
runCommand,
|
|
182
|
+
runCommandCapture,
|
|
163
183
|
currentVersion,
|
|
164
184
|
packageName,
|
|
165
185
|
latestTag = null,
|
|
166
186
|
referenceLabel = null
|
|
167
187
|
} = {}) {
|
|
168
|
-
const resolvedLatestTag = await readLatestReleaseTag(rootDir, {runCommand, latestTag})
|
|
169
|
-
const commitLog = await readCommitLog(rootDir, {runCommand, latestTag: resolvedLatestTag})
|
|
170
|
-
const diffStat = await readDiffStat(rootDir, {runCommand, latestTag: resolvedLatestTag})
|
|
188
|
+
const resolvedLatestTag = await readLatestReleaseTag(rootDir, {runCommand, runCommandCapture, latestTag})
|
|
189
|
+
const commitLog = await readCommitLog(rootDir, {runCommand, runCommandCapture, latestTag: resolvedLatestTag})
|
|
190
|
+
const diffStat = await readDiffStat(rootDir, {runCommand, runCommandCapture, latestTag: resolvedLatestTag})
|
|
171
191
|
|
|
172
192
|
return {
|
|
173
193
|
currentVersion,
|
|
@@ -181,6 +201,7 @@ async function buildReleaseSuggestionContext(rootDir, {
|
|
|
181
201
|
|
|
182
202
|
async function suggestReleaseType(rootDir = process.cwd(), {
|
|
183
203
|
runCommand,
|
|
204
|
+
runCommandCapture,
|
|
184
205
|
currentVersion,
|
|
185
206
|
packageName,
|
|
186
207
|
commandExistsImpl = commandExists,
|
|
@@ -192,6 +213,7 @@ async function suggestReleaseType(rootDir = process.cwd(), {
|
|
|
192
213
|
} = {}) {
|
|
193
214
|
const context = await buildReleaseSuggestionContext(rootDir, {
|
|
194
215
|
runCommand,
|
|
216
|
+
runCommandCapture,
|
|
195
217
|
currentVersion,
|
|
196
218
|
packageName,
|
|
197
219
|
latestTag,
|
|
@@ -219,7 +241,7 @@ async function suggestReleaseType(rootDir = process.cwd(), {
|
|
|
219
241
|
|
|
220
242
|
logStep?.('Evaluating the recommended version bump with Codex...')
|
|
221
243
|
|
|
222
|
-
await runCommand('codex', [
|
|
244
|
+
const codexResult = await runCommand('codex', [
|
|
223
245
|
'exec',
|
|
224
246
|
'--ephemeral',
|
|
225
247
|
'--model',
|
|
@@ -246,6 +268,7 @@ async function suggestReleaseType(rootDir = process.cwd(), {
|
|
|
246
268
|
capture: true,
|
|
247
269
|
cwd: rootDir
|
|
248
270
|
})
|
|
271
|
+
logCapturedCodexDiagnostics(codexResult, {label: 'release advisor', logWarning})
|
|
249
272
|
|
|
250
273
|
const rawSuggestion = await readFile(outputPath, 'utf8')
|
|
251
274
|
const releaseType = sanitizeSuggestedReleaseType(rawSuggestion, allowedSuggestedReleaseTypes)
|
|
@@ -266,7 +289,7 @@ async function suggestReleaseType(rootDir = process.cwd(), {
|
|
|
266
289
|
source: flooredReleaseType === releaseType ? 'codex' : 'codex+heuristic-floor'
|
|
267
290
|
}
|
|
268
291
|
} catch (error) {
|
|
269
|
-
logWarning?.(
|
|
292
|
+
logWarning?.(`${describeCodexAdvisorFailure(error, {label: 'release advisor'})} Using heuristic release type.`)
|
|
270
293
|
|
|
271
294
|
return {
|
|
272
295
|
...context,
|
|
@@ -288,6 +311,7 @@ export async function resolveReleaseType({
|
|
|
288
311
|
interactive = true,
|
|
289
312
|
runPrompt,
|
|
290
313
|
runCommand,
|
|
314
|
+
runCommandCapture,
|
|
291
315
|
logStep,
|
|
292
316
|
logWarning,
|
|
293
317
|
latestTag = null,
|
|
@@ -300,6 +324,7 @@ export async function resolveReleaseType({
|
|
|
300
324
|
|
|
301
325
|
const suggested = await suggestReleaseType(rootDir, {
|
|
302
326
|
runCommand,
|
|
327
|
+
runCommandCapture,
|
|
303
328
|
currentVersion,
|
|
304
329
|
packageName,
|
|
305
330
|
logStep,
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
function splitLines(text = '') {
|
|
2
|
+
return String(text)
|
|
3
|
+
.split(/\r?\n/)
|
|
4
|
+
.map((line) => line.trim())
|
|
5
|
+
.filter(Boolean)
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
function normalizeCapturedOutput(commandOutput = {}) {
|
|
9
|
+
if (typeof commandOutput === 'string') {
|
|
10
|
+
return {stdout: commandOutput, stderr: ''}
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
return {
|
|
14
|
+
stdout: commandOutput?.stdout ?? '',
|
|
15
|
+
stderr: commandOutput?.stderr ?? ''
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export function collectCodexDiagnosticLines(commandOutput = {}) {
|
|
20
|
+
const {stdout, stderr} = normalizeCapturedOutput(commandOutput)
|
|
21
|
+
const stderrLines = splitLines(stderr)
|
|
22
|
+
const stdoutDiagnosticLines = splitLines(stdout)
|
|
23
|
+
.filter((line) => /\b(error|warn|warning|failed|failure|rejected|declined)\b/i.test(line))
|
|
24
|
+
|
|
25
|
+
return [...stderrLines, ...stdoutDiagnosticLines]
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export function logCapturedCodexDiagnostics(commandOutput = {}, {
|
|
29
|
+
label = 'advisor',
|
|
30
|
+
logWarning
|
|
31
|
+
} = {}) {
|
|
32
|
+
const diagnosticCount = collectCodexDiagnosticLines(commandOutput).length
|
|
33
|
+
|
|
34
|
+
if (diagnosticCount === 0) {
|
|
35
|
+
return
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const noun = diagnosticCount === 1 ? 'line' : 'lines'
|
|
39
|
+
logWarning?.(
|
|
40
|
+
`Codex ${label} emitted ${diagnosticCount} diagnostic ${noun}; ` +
|
|
41
|
+
'captured and hidden because the advisor returned a usable result.'
|
|
42
|
+
)
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export function describeCodexAdvisorFailure(error, {
|
|
46
|
+
label = 'advisor'
|
|
47
|
+
} = {}) {
|
|
48
|
+
const diagnosticCount = collectCodexDiagnosticLines(error).length
|
|
49
|
+
const exitCode = error?.exitCode
|
|
50
|
+
const exitText = exitCode == null ? 'failed' : `exited with code ${exitCode}`
|
|
51
|
+
|
|
52
|
+
if (diagnosticCount > 0) {
|
|
53
|
+
const noun = diagnosticCount === 1 ? 'line' : 'lines'
|
|
54
|
+
return `Codex ${label} ${exitText}; captured ${diagnosticCount} diagnostic ${noun}.`
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
if (error?.code === 'ENOENT') {
|
|
58
|
+
return `Codex ${label} failed because the codex command was not found.`
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
return `Codex ${label} ${exitText}.`
|
|
62
|
+
}
|
|
@@ -6,10 +6,24 @@ export function createLocalCommandRunners({ runCommandBase, runCommandCaptureBas
|
|
|
6
6
|
}
|
|
7
7
|
|
|
8
8
|
const runCommand = async (command, args, {
|
|
9
|
+
capture = false,
|
|
9
10
|
silent = false,
|
|
10
11
|
cwd,
|
|
11
12
|
forwardStdoutToStderr = false
|
|
12
13
|
} = {}) => {
|
|
14
|
+
if (capture) {
|
|
15
|
+
const captured = await runCommandCaptureBase(command, args, {cwd})
|
|
16
|
+
|
|
17
|
+
if (typeof captured === 'string') {
|
|
18
|
+
return {stdout: captured.trim(), stderr: ''}
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
return {
|
|
22
|
+
stdout: (captured?.stdout ?? '').trim(),
|
|
23
|
+
stderr: (captured?.stderr ?? '').trim()
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
13
27
|
const stdio = silent
|
|
14
28
|
? 'ignore'
|
|
15
29
|
: forwardStdoutToStderr
|