@wyxos/zephyr 0.8.4 → 0.9.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 CHANGED
@@ -40,7 +40,10 @@ Common workflows:
40
40
  # Deploy an app using the saved preset or the interactive prompts
41
41
  zephyr
42
42
 
43
- # Deploy an app with a local npm package version bump
43
+ # Deploy an app with Zephyr's recommended local npm package version bump
44
+ zephyr
45
+
46
+ # Deploy an app with an explicit local npm package version bump
44
47
  zephyr minor
45
48
 
46
49
  # Deploy an app while bypassing Zephyr's built-in local checks
@@ -64,7 +67,7 @@ zephyr --non-interactive --preset wyxos-release --resume-pending --maintenance o
64
67
  # Emit NDJSON events for automation or agent tooling
65
68
  zephyr --non-interactive --json --preset wyxos-release --maintenance on
66
69
 
67
- # Release a Node/Vue package (defaults to a patch bump)
70
+ # Release a Node/Vue package with Zephyr's recommended bump
68
71
  zephyr --type node
69
72
 
70
73
  # Release a Node/Vue package with an explicit bump
@@ -81,7 +84,7 @@ zephyr --type node --skip-versioning
81
84
  zephyr --type packagist --skip-versioning
82
85
  ```
83
86
 
84
- When `--type node` or `--type vue` is used without a bump argument, Zephyr defaults to `patch`.
87
+ When an app deployment or a `--type node`/`--type vue` package release omits the bump argument, Zephyr recommends a version bump from changes since the last release reference, using Codex when available and deterministic heuristics as a fallback.
85
88
 
86
89
  ## Interactive and Non-Interactive Modes
87
90
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wyxos/zephyr",
3
- "version": "0.8.4",
3
+ "version": "0.9.0",
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",
@@ -3,6 +3,7 @@ import path from 'node:path'
3
3
 
4
4
  import {commandExists} from '../../utils/command.mjs'
5
5
  import {gitCommitArgs} from '../../utils/git-hooks.mjs'
6
+ import {resolveReleaseType} from '../../release/release-type.mjs'
6
7
 
7
8
  async function readPackageJson(rootDir) {
8
9
  const packageJsonPath = path.join(rootDir, 'package.json')
@@ -19,8 +20,63 @@ async function isGitIgnored(rootDir, filePath, {runCommand} = {}) {
19
20
  }
20
21
  }
21
22
 
23
+ async function readLatestVersionBumpCommit(rootDir, {runCommand} = {}) {
24
+ if (typeof runCommand !== 'function') {
25
+ return null
26
+ }
27
+
28
+ try {
29
+ const {stdout = ''} = await runCommand('git', ['log', '--format=%H%x00%h%x00%s', '-50'], {
30
+ capture: true,
31
+ cwd: rootDir
32
+ })
33
+
34
+ for (const line of stdout.split('\n')) {
35
+ const [hash, shortHash, subject] = line.split('\0')
36
+ if (/^chore: bump version to \d+\.\d+\.\d+(?:[-+].*)?$/i.test(subject ?? '')) {
37
+ return {hash, shortHash}
38
+ }
39
+ }
40
+ } catch {
41
+ return null
42
+ }
43
+
44
+ return null
45
+ }
46
+
47
+ async function resolveDeploymentVersionValue(rootDir, {
48
+ versionArg = null,
49
+ pkg,
50
+ interactive = false,
51
+ runPrompt,
52
+ runCommand,
53
+ logProcessing,
54
+ logWarning
55
+ } = {}) {
56
+ if (versionArg && String(versionArg).trim().length > 0) {
57
+ return String(versionArg).trim()
58
+ }
59
+
60
+ const latestVersionBump = await readLatestVersionBumpCommit(rootDir, {runCommand})
61
+
62
+ return await resolveReleaseType({
63
+ currentVersion: pkg.version,
64
+ packageName: pkg.name ?? 'this app',
65
+ rootDir,
66
+ interactive,
67
+ runPrompt,
68
+ runCommand,
69
+ logStep: logProcessing,
70
+ logWarning,
71
+ latestTag: latestVersionBump?.hash ?? null,
72
+ referenceLabel: latestVersionBump ? `last app version bump ${latestVersionBump.shortHash}` : null
73
+ })
74
+ }
75
+
22
76
  export async function bumpLocalPackageVersion(rootDir, {
23
77
  versionArg = null,
78
+ interactive = false,
79
+ runPrompt,
24
80
  skipGitHooks = false,
25
81
  runCommand,
26
82
  logProcessing,
@@ -43,9 +99,15 @@ export async function bumpLocalPackageVersion(rootDir, {
43
99
  return null
44
100
  }
45
101
 
46
- const releaseValue = (versionArg && String(versionArg).trim().length > 0)
47
- ? String(versionArg).trim()
48
- : 'patch'
102
+ const releaseValue = await resolveDeploymentVersionValue(rootDir, {
103
+ versionArg,
104
+ pkg,
105
+ interactive,
106
+ runPrompt,
107
+ runCommand,
108
+ logProcessing,
109
+ logWarning
110
+ })
49
111
 
50
112
  logProcessing?.(`Bumping npm package version (${releaseValue})...`)
51
113
  await runCommand('npm', ['version', releaseValue, '--no-git-tag-version', '--force'], {cwd: rootDir})
@@ -14,6 +14,7 @@ export async function prepareLocalDeployment(config, {
14
14
  skipLint = false,
15
15
  skipVersioning = false,
16
16
  autoCommit = false,
17
+ interactive = true,
17
18
  runPrompt,
18
19
  runCommand,
19
20
  runCommandCapture,
@@ -66,6 +67,8 @@ export async function prepareLocalDeployment(config, {
66
67
  await bumpLocalPackageVersion(rootDir, {
67
68
  versionArg,
68
69
  skipGitHooks,
70
+ interactive,
71
+ runPrompt,
69
72
  runCommand,
70
73
  logProcessing,
71
74
  logSuccess,
@@ -385,6 +385,7 @@ export async function runDeployment(config, options = {}) {
385
385
  skipLint: executionMode?.skipLint === true,
386
386
  skipVersioning: executionMode?.skipVersioning === true,
387
387
  autoCommit: executionMode?.autoCommit === true,
388
+ interactive: executionMode?.interactive !== false,
388
389
  runPrompt,
389
390
  runCommand,
390
391
  runCommandCapture: context.runCommandCapture,
@@ -87,7 +87,11 @@ function buildChoiceOrder(suggestedReleaseType) {
87
87
  ]
88
88
  }
89
89
 
90
- async function readLatestReleaseTag(rootDir, {runCommand} = {}) {
90
+ async function readLatestReleaseTag(rootDir, {runCommand, latestTag = null} = {}) {
91
+ if (typeof latestTag === 'string' && latestTag.trim() !== '') {
92
+ return latestTag.trim()
93
+ }
94
+
91
95
  try {
92
96
  const {stdout} = await runCommand('git', ['describe', '--tags', '--abbrev=0'], {
93
97
  capture: true,
@@ -137,16 +141,19 @@ async function readDiffStat(rootDir, {runCommand, latestTag} = {}) {
137
141
  async function buildReleaseSuggestionContext(rootDir, {
138
142
  runCommand,
139
143
  currentVersion,
140
- packageName
144
+ packageName,
145
+ latestTag = null,
146
+ referenceLabel = null
141
147
  } = {}) {
142
- const latestTag = await readLatestReleaseTag(rootDir, {runCommand})
143
- const commitLog = await readCommitLog(rootDir, {runCommand, latestTag})
144
- const diffStat = await readDiffStat(rootDir, {runCommand, latestTag})
148
+ const resolvedLatestTag = await readLatestReleaseTag(rootDir, {runCommand, latestTag})
149
+ const commitLog = await readCommitLog(rootDir, {runCommand, latestTag: resolvedLatestTag})
150
+ const diffStat = await readDiffStat(rootDir, {runCommand, latestTag: resolvedLatestTag})
145
151
 
146
152
  return {
147
153
  currentVersion,
148
154
  packageName,
149
- latestTag,
155
+ latestTag: resolvedLatestTag,
156
+ referenceLabel: referenceLabel ?? resolvedLatestTag,
150
157
  commitLog,
151
158
  diffStat
152
159
  }
@@ -158,12 +165,16 @@ async function suggestReleaseType(rootDir = process.cwd(), {
158
165
  packageName,
159
166
  commandExistsImpl = commandExists,
160
167
  logStep,
161
- logWarning
168
+ logWarning,
169
+ latestTag = null,
170
+ referenceLabel = null
162
171
  } = {}) {
163
172
  const context = await buildReleaseSuggestionContext(rootDir, {
164
173
  runCommand,
165
174
  currentVersion,
166
- packageName
175
+ packageName,
176
+ latestTag,
177
+ referenceLabel
167
178
  })
168
179
  const allowedSuggestedReleaseTypes = resolveSuggestedReleaseTypeOptions(currentVersion)
169
180
  const heuristicReleaseType = inferReleaseTypeHeuristically(context)
@@ -201,7 +212,7 @@ async function suggestReleaseType(rootDir = process.cwd(), {
201
212
  'Prefer stable release types unless the current version already has a prerelease identifier.',
202
213
  `Package: ${packageName || 'unknown package'}`,
203
214
  `Current version: ${currentVersion || 'unknown'}`,
204
- `Latest release tag: ${context.latestTag || 'none found'}`,
215
+ `Latest release reference: ${context.referenceLabel || context.latestTag || 'none found'}`,
205
216
  'Commits since the last release:',
206
217
  context.commitLog || '- no commits found',
207
218
  'Diff summary since the last release:',
@@ -252,7 +263,9 @@ export async function resolveReleaseType({
252
263
  runPrompt,
253
264
  runCommand,
254
265
  logStep,
255
- logWarning
266
+ logWarning,
267
+ latestTag = null,
268
+ referenceLabel = null
256
269
  } = {}) {
257
270
  if (releaseType) {
258
271
  return releaseType
@@ -263,10 +276,12 @@ export async function resolveReleaseType({
263
276
  currentVersion,
264
277
  packageName,
265
278
  logStep,
266
- logWarning
279
+ logWarning,
280
+ latestTag,
281
+ referenceLabel
267
282
  })
268
- const rangeLabel = suggested.latestTag
269
- ? `based on changes since ${suggested.latestTag}`
283
+ const rangeLabel = suggested.referenceLabel || suggested.latestTag
284
+ ? `based on changes since ${suggested.referenceLabel || suggested.latestTag}`
270
285
  : 'based on recent changes'
271
286
 
272
287
  if (!interactive || typeof runPrompt !== 'function') {