@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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wyxos/zephyr",
3
- "version": "0.9.4",
3
+ "version": "0.9.6",
4
4
  "description": "A streamlined deployment tool for web applications with intelligent Laravel project detection",
5
5
  "type": "module",
6
6
  "main": "./src/index.mjs",
@@ -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 {stdout = ''} = await runCommand('git', ['log', '--format=%H%x00%h%x00%s', '-1000'], {
43
- capture: true,
44
- cwd: rootDir
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 || typeof runCommand !== 'function') {
116
+ async function readRecentAppCommitSubjects(rootDir, versionReference, {runCommand, runCommandCapture} = {}) {
117
+ if (!versionReference?.hash) {
103
118
  return ''
104
119
  }
105
120
 
106
121
  try {
107
- const {stdout = ''} = await runCommand('git', [
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
- capture: true,
114
- cwd: rootDir
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
  })
@@ -70,6 +70,7 @@ export async function prepareLocalDeployment(config, {
70
70
  interactive,
71
71
  runPrompt,
72
72
  runCommand,
73
+ runCommandCapture,
73
74
  logProcessing,
74
75
  logSuccess,
75
76
  logWarning
@@ -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?.(`Codex could not suggest a commit message: ${error.message}`)
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
- async function readLatestReleaseTag(rootDir, {runCommand, latestTag = null} = {}) {
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 runCommand('git', ['describe', '--tags', '--abbrev=0'], {
117
- capture: true,
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 runCommand('git', args, {
134
- capture: true,
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 runCommand('git', args, {
151
- capture: true,
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?.(`Codex could not suggest a release type: ${error.message}`)
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