@wyxos/zephyr 0.9.3 → 0.9.5

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.3",
3
+ "version": "0.9.5",
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
@@ -97,12 +112,48 @@ function formatVersionReferenceLabel(reference, currentVersion) {
97
112
  return `${prefix} ${reference.shortHash} (${reference.version})`
98
113
  }
99
114
 
115
+
116
+ async function readRecentAppCommitSubjects(rootDir, versionReference, {runCommand, runCommandCapture} = {}) {
117
+ if (!versionReference?.hash) {
118
+ return ''
119
+ }
120
+
121
+ try {
122
+ const stdout = await captureGit(rootDir, [
123
+ 'log',
124
+ '--format=%s',
125
+ '--max-count=80',
126
+ `${versionReference.hash}..HEAD`
127
+ ], {
128
+ runCommand,
129
+ runCommandCapture
130
+ })
131
+
132
+ return stdout.trim()
133
+ } catch {
134
+ return ''
135
+ }
136
+ }
137
+
138
+ function inferMinimumReleaseTypeFromCommitSubjects(commitSubjects) {
139
+ if (/breaking change|breaking changes|^[a-z]+(?:\(.+\))?!:/im.test(commitSubjects)) {
140
+ return 'major'
141
+ }
142
+
143
+ if (/\bfeat(?:\(.+\))?:/im.test(commitSubjects)) {
144
+ return 'minor'
145
+ }
146
+
147
+ return null
148
+ }
149
+
100
150
  async function resolveDeploymentVersionValue(rootDir, {
101
151
  versionArg = null,
102
152
  pkg,
103
153
  interactive = false,
104
154
  runPrompt,
105
155
  runCommand,
156
+ runCommandCapture,
106
157
  logProcessing,
107
158
  logWarning
108
159
  } = {}) {
@@ -110,8 +161,9 @@ async function resolveDeploymentVersionValue(rootDir, {
110
161
  return String(versionArg).trim()
111
162
  }
112
163
 
113
- const versionBumps = await readVersionBumpCommits(rootDir, {runCommand})
164
+ const versionBumps = await readVersionBumpCommits(rootDir, {runCommand, runCommandCapture})
114
165
  const versionReference = selectVersionSuggestionReference(pkg.version, versionBumps)
166
+ const commitSubjects = await readRecentAppCommitSubjects(rootDir, versionReference, {runCommand, runCommandCapture})
115
167
 
116
168
  return await resolveReleaseType({
117
169
  currentVersion: pkg.version,
@@ -120,10 +172,12 @@ async function resolveDeploymentVersionValue(rootDir, {
120
172
  interactive,
121
173
  runPrompt,
122
174
  runCommand,
175
+ runCommandCapture,
123
176
  logStep: logProcessing,
124
177
  logWarning,
125
178
  latestTag: versionReference?.hash ?? null,
126
- referenceLabel: formatVersionReferenceLabel(versionReference, pkg.version)
179
+ referenceLabel: formatVersionReferenceLabel(versionReference, pkg.version),
180
+ minimumReleaseType: inferMinimumReleaseTypeFromCommitSubjects(commitSubjects)
127
181
  })
128
182
  }
129
183
 
@@ -133,6 +187,7 @@ export async function bumpLocalPackageVersion(rootDir, {
133
187
  runPrompt,
134
188
  skipGitHooks = false,
135
189
  runCommand,
190
+ runCommandCapture,
136
191
  logProcessing,
137
192
  logSuccess,
138
193
  logWarning
@@ -159,6 +214,7 @@ export async function bumpLocalPackageVersion(rootDir, {
159
214
  interactive,
160
215
  runPrompt,
161
216
  runCommand,
217
+ runCommandCapture,
162
218
  logProcessing,
163
219
  logWarning
164
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
@@ -79,12 +79,12 @@ function inferReleaseTypeHeuristically({
79
79
  return 'patch'
80
80
  }
81
81
 
82
- function applyHeuristicFloor(releaseType, heuristicReleaseType) {
82
+ function applyReleaseTypeFloor(releaseType, floorReleaseType) {
83
83
  const releaseWeight = RELEASE_TYPE_WEIGHTS[releaseType] ?? -1
84
- const heuristicWeight = RELEASE_TYPE_WEIGHTS[heuristicReleaseType] ?? -1
84
+ const floorWeight = RELEASE_TYPE_WEIGHTS[floorReleaseType] ?? -1
85
85
 
86
- if (heuristicWeight > releaseWeight) {
87
- return heuristicReleaseType
86
+ if (floorWeight > releaseWeight) {
87
+ return floorReleaseType
88
88
  }
89
89
 
90
90
  return releaseType
@@ -107,14 +107,30 @@ function buildChoiceOrder(suggestedReleaseType) {
107
107
  ]
108
108
  }
109
109
 
110
- async function readLatestReleaseTag(rootDir, {runCommand, latestTag = null} = {}) {
110
+
111
+ async function captureCommand(command, args, {runCommand, runCommandCapture, cwd} = {}) {
112
+ if (typeof runCommandCapture === 'function') {
113
+ return {stdout: await runCommandCapture(command, args, {cwd}), stderr: ''}
114
+ }
115
+
116
+ const output = await runCommand(command, args, {capture: true, cwd})
117
+
118
+ if (typeof output === 'string') {
119
+ return {stdout: output, stderr: ''}
120
+ }
121
+
122
+ return output ?? {stdout: '', stderr: ''}
123
+ }
124
+
125
+ async function readLatestReleaseTag(rootDir, {runCommand, runCommandCapture, latestTag = null} = {}) {
111
126
  if (typeof latestTag === 'string' && latestTag.trim() !== '') {
112
127
  return latestTag.trim()
113
128
  }
114
129
 
115
130
  try {
116
- const {stdout} = await runCommand('git', ['describe', '--tags', '--abbrev=0'], {
117
- capture: true,
131
+ const {stdout} = await captureCommand('git', ['describe', '--tags', '--abbrev=0'], {
132
+ runCommand,
133
+ runCommandCapture,
118
134
  cwd: rootDir
119
135
  })
120
136
 
@@ -124,14 +140,15 @@ async function readLatestReleaseTag(rootDir, {runCommand, latestTag = null} = {}
124
140
  }
125
141
  }
126
142
 
127
- async function readCommitLog(rootDir, {runCommand, latestTag} = {}) {
143
+ async function readCommitLog(rootDir, {runCommand, runCommandCapture, latestTag} = {}) {
128
144
  const args = latestTag
129
145
  ? ['log', '--format=%h %s', `${latestTag}..HEAD`]
130
146
  : ['log', '--format=%h %s', '-20']
131
147
 
132
148
  try {
133
- const {stdout} = await runCommand('git', args, {
134
- capture: true,
149
+ const {stdout} = await captureCommand('git', args, {
150
+ runCommand,
151
+ runCommandCapture,
135
152
  cwd: rootDir
136
153
  })
137
154
 
@@ -141,14 +158,15 @@ async function readCommitLog(rootDir, {runCommand, latestTag} = {}) {
141
158
  }
142
159
  }
143
160
 
144
- async function readDiffStat(rootDir, {runCommand, latestTag} = {}) {
161
+ async function readDiffStat(rootDir, {runCommand, runCommandCapture, latestTag} = {}) {
145
162
  const args = latestTag
146
163
  ? ['diff', '--stat', `${latestTag}..HEAD`, '--']
147
164
  : ['diff', '--stat', 'HEAD~20..HEAD', '--']
148
165
 
149
166
  try {
150
- const {stdout} = await runCommand('git', args, {
151
- capture: true,
167
+ const {stdout} = await captureCommand('git', args, {
168
+ runCommand,
169
+ runCommandCapture,
152
170
  cwd: rootDir
153
171
  })
154
172
 
@@ -160,14 +178,15 @@ async function readDiffStat(rootDir, {runCommand, latestTag} = {}) {
160
178
 
161
179
  async function buildReleaseSuggestionContext(rootDir, {
162
180
  runCommand,
181
+ runCommandCapture,
163
182
  currentVersion,
164
183
  packageName,
165
184
  latestTag = null,
166
185
  referenceLabel = null
167
186
  } = {}) {
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})
187
+ const resolvedLatestTag = await readLatestReleaseTag(rootDir, {runCommand, runCommandCapture, latestTag})
188
+ const commitLog = await readCommitLog(rootDir, {runCommand, runCommandCapture, latestTag: resolvedLatestTag})
189
+ const diffStat = await readDiffStat(rootDir, {runCommand, runCommandCapture, latestTag: resolvedLatestTag})
171
190
 
172
191
  return {
173
192
  currentVersion,
@@ -181,23 +200,29 @@ async function buildReleaseSuggestionContext(rootDir, {
181
200
 
182
201
  async function suggestReleaseType(rootDir = process.cwd(), {
183
202
  runCommand,
203
+ runCommandCapture,
184
204
  currentVersion,
185
205
  packageName,
186
206
  commandExistsImpl = commandExists,
187
207
  logStep,
188
208
  logWarning,
189
209
  latestTag = null,
190
- referenceLabel = null
210
+ referenceLabel = null,
211
+ minimumReleaseType = null
191
212
  } = {}) {
192
213
  const context = await buildReleaseSuggestionContext(rootDir, {
193
214
  runCommand,
215
+ runCommandCapture,
194
216
  currentVersion,
195
217
  packageName,
196
218
  latestTag,
197
219
  referenceLabel
198
220
  })
199
221
  const allowedSuggestedReleaseTypes = resolveSuggestedReleaseTypeOptions(currentVersion)
200
- const heuristicReleaseType = inferReleaseTypeHeuristically(context)
222
+ const heuristicReleaseType = applyReleaseTypeFloor(
223
+ inferReleaseTypeHeuristically(context),
224
+ sanitizeSuggestedReleaseType(minimumReleaseType, allowedSuggestedReleaseTypes)
225
+ )
201
226
 
202
227
  if (!commandExistsImpl('codex')) {
203
228
  return {
@@ -254,7 +279,7 @@ async function suggestReleaseType(rootDir = process.cwd(), {
254
279
  }
255
280
  }
256
281
 
257
- const flooredReleaseType = applyHeuristicFloor(releaseType, heuristicReleaseType)
282
+ const flooredReleaseType = applyReleaseTypeFloor(releaseType, heuristicReleaseType)
258
283
 
259
284
  return {
260
285
  ...context,
@@ -284,10 +309,12 @@ export async function resolveReleaseType({
284
309
  interactive = true,
285
310
  runPrompt,
286
311
  runCommand,
312
+ runCommandCapture,
287
313
  logStep,
288
314
  logWarning,
289
315
  latestTag = null,
290
- referenceLabel = null
316
+ referenceLabel = null,
317
+ minimumReleaseType = null
291
318
  } = {}) {
292
319
  if (releaseType) {
293
320
  return releaseType
@@ -295,12 +322,14 @@ export async function resolveReleaseType({
295
322
 
296
323
  const suggested = await suggestReleaseType(rootDir, {
297
324
  runCommand,
325
+ runCommandCapture,
298
326
  currentVersion,
299
327
  packageName,
300
328
  logStep,
301
329
  logWarning,
302
330
  latestTag,
303
- referenceLabel
331
+ referenceLabel,
332
+ minimumReleaseType
304
333
  })
305
334
  const rangeLabel = suggested.referenceLabel || suggested.latestTag
306
335
  ? `based on changes since ${suggested.referenceLabel || suggested.latestTag}`