@wyxos/zephyr 0.2.8 → 0.2.10
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 +104 -104
- package/bin/zephyr.mjs +12 -12
- package/package.json +53 -53
- package/src/index.mjs +2121 -2121
- package/src/release-node.mjs +133 -49
- package/src/release-packagist.mjs +66 -8
- package/src/ssh-utils.mjs +277 -277
package/src/release-node.mjs
CHANGED
|
@@ -5,14 +5,24 @@ import { readFile } from 'node:fs/promises'
|
|
|
5
5
|
import fs from 'node:fs'
|
|
6
6
|
import path from 'node:path'
|
|
7
7
|
import process from 'node:process'
|
|
8
|
-
|
|
8
|
+
|
|
9
|
+
const STEP_PREFIX = '→'
|
|
10
|
+
const OK_PREFIX = '✔'
|
|
11
|
+
const WARN_PREFIX = '⚠'
|
|
9
12
|
|
|
10
13
|
const IS_WINDOWS = process.platform === 'win32'
|
|
11
14
|
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
15
|
+
function logStep(message) {
|
|
16
|
+
console.log(`${STEP_PREFIX} ${message}`)
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function logSuccess(message) {
|
|
20
|
+
console.log(`${OK_PREFIX} ${message}`)
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function logWarning(message) {
|
|
24
|
+
console.warn(`${WARN_PREFIX} ${message}`)
|
|
25
|
+
}
|
|
16
26
|
|
|
17
27
|
function runCommand(command, args, { cwd = process.cwd(), capture = false, useShell = false } = {}) {
|
|
18
28
|
return new Promise((resolve, reject) => {
|
|
@@ -104,10 +114,13 @@ async function ensureUpToDateWithUpstream(branch, upstreamRef, rootDir = process
|
|
|
104
114
|
const remoteBranch = branchParts.join('/')
|
|
105
115
|
|
|
106
116
|
if (remoteName && remoteBranch) {
|
|
107
|
-
|
|
117
|
+
logStep(`Fetching latest updates from ${remoteName}/${remoteBranch}...`)
|
|
108
118
|
try {
|
|
109
|
-
await runCommand('git', ['fetch', remoteName, remoteBranch], { cwd: rootDir })
|
|
119
|
+
await runCommand('git', ['fetch', remoteName, remoteBranch], { capture: true, cwd: rootDir })
|
|
110
120
|
} catch (error) {
|
|
121
|
+
if (error.stderr) {
|
|
122
|
+
console.error(error.stderr)
|
|
123
|
+
}
|
|
111
124
|
throw new Error(`Failed to fetch ${upstreamRef}: ${error.message}`)
|
|
112
125
|
}
|
|
113
126
|
}
|
|
@@ -126,11 +139,14 @@ async function ensureUpToDateWithUpstream(branch, upstreamRef, rootDir = process
|
|
|
126
139
|
|
|
127
140
|
if (Number.isFinite(behind) && behind > 0) {
|
|
128
141
|
if (remoteName && remoteBranch) {
|
|
129
|
-
|
|
142
|
+
logStep(`Fast-forwarding ${branch} with ${upstreamRef}...`)
|
|
130
143
|
|
|
131
144
|
try {
|
|
132
|
-
await runCommand('git', ['pull', '--ff-only', remoteName, remoteBranch], { cwd: rootDir })
|
|
145
|
+
await runCommand('git', ['pull', '--ff-only', remoteName, remoteBranch], { capture: true, cwd: rootDir })
|
|
133
146
|
} catch (error) {
|
|
147
|
+
if (error.stderr) {
|
|
148
|
+
console.error(error.stderr)
|
|
149
|
+
}
|
|
134
150
|
throw new Error(
|
|
135
151
|
`Unable to fast-forward ${branch} with ${upstreamRef}. Resolve conflicts manually, then rerun the release.\n${error.message}`
|
|
136
152
|
)
|
|
@@ -188,13 +204,43 @@ async function runLint(skipLint, pkg, rootDir = process.cwd()) {
|
|
|
188
204
|
}
|
|
189
205
|
|
|
190
206
|
if (!hasScript(pkg, 'lint')) {
|
|
191
|
-
|
|
207
|
+
logStep('Skipping lint (no lint script found in package.json).')
|
|
192
208
|
return
|
|
193
209
|
}
|
|
194
210
|
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
211
|
+
logStep('Running lint...')
|
|
212
|
+
|
|
213
|
+
let dotInterval = null
|
|
214
|
+
try {
|
|
215
|
+
// Capture output and show dots as progress
|
|
216
|
+
process.stdout.write(' ')
|
|
217
|
+
dotInterval = setInterval(() => {
|
|
218
|
+
process.stdout.write('.')
|
|
219
|
+
}, 200)
|
|
220
|
+
|
|
221
|
+
await runCommand('npm', ['run', 'lint'], { capture: true, cwd: rootDir })
|
|
222
|
+
|
|
223
|
+
if (dotInterval) {
|
|
224
|
+
clearInterval(dotInterval)
|
|
225
|
+
dotInterval = null
|
|
226
|
+
}
|
|
227
|
+
process.stdout.write('\n')
|
|
228
|
+
logSuccess('Lint passed.')
|
|
229
|
+
} catch (error) {
|
|
230
|
+
// Clear dots and show error output
|
|
231
|
+
if (dotInterval) {
|
|
232
|
+
clearInterval(dotInterval)
|
|
233
|
+
dotInterval = null
|
|
234
|
+
}
|
|
235
|
+
process.stdout.write('\n')
|
|
236
|
+
if (error.stdout) {
|
|
237
|
+
console.error(error.stdout)
|
|
238
|
+
}
|
|
239
|
+
if (error.stderr) {
|
|
240
|
+
console.error(error.stderr)
|
|
241
|
+
}
|
|
242
|
+
throw error
|
|
243
|
+
}
|
|
198
244
|
}
|
|
199
245
|
|
|
200
246
|
async function runTests(skipTests, pkg, rootDir = process.cwd()) {
|
|
@@ -205,11 +251,11 @@ async function runTests(skipTests, pkg, rootDir = process.cwd()) {
|
|
|
205
251
|
|
|
206
252
|
// Check for test:run or test script
|
|
207
253
|
if (!hasScript(pkg, 'test:run') && !hasScript(pkg, 'test')) {
|
|
208
|
-
|
|
254
|
+
logStep('Skipping tests (no test or test:run script found in package.json).')
|
|
209
255
|
return
|
|
210
256
|
}
|
|
211
257
|
|
|
212
|
-
|
|
258
|
+
logStep('Running test suite...')
|
|
213
259
|
|
|
214
260
|
let dotInterval = null
|
|
215
261
|
try {
|
|
@@ -241,10 +287,10 @@ async function runTests(skipTests, pkg, rootDir = process.cwd()) {
|
|
|
241
287
|
}
|
|
242
288
|
process.stdout.write('\n')
|
|
243
289
|
if (error.stdout) {
|
|
244
|
-
|
|
290
|
+
console.error(error.stdout)
|
|
245
291
|
}
|
|
246
292
|
if (error.stderr) {
|
|
247
|
-
|
|
293
|
+
console.error(error.stderr)
|
|
248
294
|
}
|
|
249
295
|
throw error
|
|
250
296
|
}
|
|
@@ -257,11 +303,11 @@ async function runBuild(skipBuild, pkg, rootDir = process.cwd()) {
|
|
|
257
303
|
}
|
|
258
304
|
|
|
259
305
|
if (!hasScript(pkg, 'build')) {
|
|
260
|
-
|
|
306
|
+
logStep('Skipping build (no build script found in package.json).')
|
|
261
307
|
return
|
|
262
308
|
}
|
|
263
309
|
|
|
264
|
-
|
|
310
|
+
logStep('Building project...')
|
|
265
311
|
await runCommand('npm', ['run', 'build'], { cwd: rootDir })
|
|
266
312
|
logSuccess('Build completed.')
|
|
267
313
|
}
|
|
@@ -273,11 +319,11 @@ async function runLibBuild(skipBuild, pkg, rootDir = process.cwd()) {
|
|
|
273
319
|
}
|
|
274
320
|
|
|
275
321
|
if (!hasScript(pkg, 'build:lib')) {
|
|
276
|
-
|
|
322
|
+
logStep('Skipping library build (no build:lib script found in package.json).')
|
|
277
323
|
return false
|
|
278
324
|
}
|
|
279
325
|
|
|
280
|
-
|
|
326
|
+
logStep('Building library...')
|
|
281
327
|
await runCommand('npm', ['run', 'build:lib'], { cwd: rootDir })
|
|
282
328
|
logSuccess('Library built.')
|
|
283
329
|
|
|
@@ -289,9 +335,9 @@ async function runLibBuild(skipBuild, pkg, rootDir = process.cwd()) {
|
|
|
289
335
|
})
|
|
290
336
|
|
|
291
337
|
if (hasLibChanges) {
|
|
292
|
-
|
|
293
|
-
await runCommand('git', ['add', 'lib/'], { cwd: rootDir })
|
|
294
|
-
await runCommand('git', ['commit', '-m', 'chore: build lib artifacts'], { cwd: rootDir })
|
|
338
|
+
logStep('Committing lib build artifacts...')
|
|
339
|
+
await runCommand('git', ['add', 'lib/'], { capture: true, cwd: rootDir })
|
|
340
|
+
await runCommand('git', ['commit', '-m', 'chore: build lib artifacts'], { capture: true, cwd: rootDir })
|
|
295
341
|
logSuccess('Lib build artifacts committed.')
|
|
296
342
|
}
|
|
297
343
|
|
|
@@ -299,13 +345,24 @@ async function runLibBuild(skipBuild, pkg, rootDir = process.cwd()) {
|
|
|
299
345
|
}
|
|
300
346
|
|
|
301
347
|
async function ensureNpmAuth(rootDir = process.cwd()) {
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
348
|
+
logStep('Confirming npm authentication...')
|
|
349
|
+
try {
|
|
350
|
+
const result = await runCommand('npm', ['whoami'], { capture: true, cwd: rootDir })
|
|
351
|
+
// Only show username if we captured it, otherwise just show success
|
|
352
|
+
if (result?.stdout) {
|
|
353
|
+
// Silently authenticated - we don't need to show the username
|
|
354
|
+
}
|
|
355
|
+
logSuccess('npm authenticated.')
|
|
356
|
+
} catch (error) {
|
|
357
|
+
if (error.stderr) {
|
|
358
|
+
console.error(error.stderr)
|
|
359
|
+
}
|
|
360
|
+
throw error
|
|
361
|
+
}
|
|
305
362
|
}
|
|
306
363
|
|
|
307
364
|
async function bumpVersion(releaseType, rootDir = process.cwd()) {
|
|
308
|
-
|
|
365
|
+
logStep(`Bumping package version...`)
|
|
309
366
|
|
|
310
367
|
// Lib changes should already be committed by runLibBuild, but check anyway
|
|
311
368
|
const { stdout: statusBefore } = await runCommand('git', ['status', '--porcelain'], { capture: true, cwd: rootDir })
|
|
@@ -315,22 +372,29 @@ async function bumpVersion(releaseType, rootDir = process.cwd()) {
|
|
|
315
372
|
})
|
|
316
373
|
|
|
317
374
|
if (hasLibChanges) {
|
|
318
|
-
|
|
319
|
-
await runCommand('git', ['stash', 'push', '-u', '-m', 'temp: lib build artifacts', 'lib/'], { cwd: rootDir })
|
|
375
|
+
logStep('Stashing lib build artifacts...')
|
|
376
|
+
await runCommand('git', ['stash', 'push', '-u', '-m', 'temp: lib build artifacts', 'lib/'], { capture: true, cwd: rootDir })
|
|
320
377
|
}
|
|
321
378
|
|
|
322
379
|
try {
|
|
323
380
|
// npm version will update package.json and create a commit with default message
|
|
324
|
-
await runCommand('npm', ['version', releaseType], { cwd: rootDir })
|
|
381
|
+
const result = await runCommand('npm', ['version', releaseType], { capture: true, cwd: rootDir })
|
|
382
|
+
// Extract version from output (e.g., "v0.2.8" or "0.2.8")
|
|
383
|
+
if (result?.stdout) {
|
|
384
|
+
const versionMatch = result.stdout.match(/v?(\d+\.\d+\.\d+)/)
|
|
385
|
+
if (versionMatch) {
|
|
386
|
+
// Version is shown in the logSuccess message below, no need to show it here
|
|
387
|
+
}
|
|
388
|
+
}
|
|
325
389
|
} finally {
|
|
326
390
|
// Restore lib changes and ensure they're in the commit
|
|
327
391
|
if (hasLibChanges) {
|
|
328
|
-
|
|
329
|
-
await runCommand('git', ['stash', 'pop'], { cwd: rootDir })
|
|
330
|
-
await runCommand('git', ['add', 'lib/'], { cwd: rootDir })
|
|
392
|
+
logStep('Restoring lib build artifacts...')
|
|
393
|
+
await runCommand('git', ['stash', 'pop'], { capture: true, cwd: rootDir })
|
|
394
|
+
await runCommand('git', ['add', 'lib/'], { capture: true, cwd: rootDir })
|
|
331
395
|
const { stdout: statusAfter } = await runCommand('git', ['status', '--porcelain'], { capture: true, cwd: rootDir })
|
|
332
396
|
if (statusAfter.includes('lib/')) {
|
|
333
|
-
await runCommand('git', ['commit', '--amend', '--no-edit'], { cwd: rootDir })
|
|
397
|
+
await runCommand('git', ['commit', '--amend', '--no-edit'], { capture: true, cwd: rootDir })
|
|
334
398
|
}
|
|
335
399
|
}
|
|
336
400
|
}
|
|
@@ -339,16 +403,26 @@ async function bumpVersion(releaseType, rootDir = process.cwd()) {
|
|
|
339
403
|
const commitMessage = `chore: release ${pkg.version}`
|
|
340
404
|
|
|
341
405
|
// Amend the commit message to use our custom format
|
|
342
|
-
await runCommand('git', ['commit', '--amend', '-m', commitMessage], { cwd: rootDir })
|
|
406
|
+
await runCommand('git', ['commit', '--amend', '-m', commitMessage], { capture: true, cwd: rootDir })
|
|
343
407
|
|
|
344
408
|
logSuccess(`Version updated to ${pkg.version}.`)
|
|
345
409
|
return pkg
|
|
346
410
|
}
|
|
347
411
|
|
|
348
412
|
async function pushChanges(rootDir = process.cwd()) {
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
413
|
+
logStep('Pushing commits and tags to origin...')
|
|
414
|
+
try {
|
|
415
|
+
await runCommand('git', ['push', '--follow-tags'], { capture: true, cwd: rootDir })
|
|
416
|
+
logSuccess('Git push completed.')
|
|
417
|
+
} catch (error) {
|
|
418
|
+
if (error.stdout) {
|
|
419
|
+
console.error(error.stdout)
|
|
420
|
+
}
|
|
421
|
+
if (error.stderr) {
|
|
422
|
+
console.error(error.stderr)
|
|
423
|
+
}
|
|
424
|
+
throw error
|
|
425
|
+
}
|
|
352
426
|
}
|
|
353
427
|
|
|
354
428
|
async function publishPackage(pkg, rootDir = process.cwd()) {
|
|
@@ -361,9 +435,19 @@ async function publishPackage(pkg, rootDir = process.cwd()) {
|
|
|
361
435
|
publishArgs.push('--access', access)
|
|
362
436
|
}
|
|
363
437
|
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
438
|
+
logStep(`Publishing ${pkg.name}@${pkg.version} to npm...`)
|
|
439
|
+
try {
|
|
440
|
+
await runCommand('npm', publishArgs, { capture: true, cwd: rootDir })
|
|
441
|
+
logSuccess('npm publish completed.')
|
|
442
|
+
} catch (error) {
|
|
443
|
+
if (error.stdout) {
|
|
444
|
+
console.error(error.stdout)
|
|
445
|
+
}
|
|
446
|
+
if (error.stderr) {
|
|
447
|
+
console.error(error.stderr)
|
|
448
|
+
}
|
|
449
|
+
throw error
|
|
450
|
+
}
|
|
367
451
|
}
|
|
368
452
|
|
|
369
453
|
function extractDomainFromHomepage(homepage) {
|
|
@@ -395,11 +479,11 @@ async function deployGHPages(skipDeploy, pkg, rootDir = process.cwd()) {
|
|
|
395
479
|
}
|
|
396
480
|
|
|
397
481
|
if (!distExists) {
|
|
398
|
-
|
|
482
|
+
logStep('Skipping GitHub Pages deployment (no dist directory found).')
|
|
399
483
|
return
|
|
400
484
|
}
|
|
401
485
|
|
|
402
|
-
|
|
486
|
+
logStep('Deploying to GitHub Pages...')
|
|
403
487
|
|
|
404
488
|
// Write CNAME file to dist if homepage is set
|
|
405
489
|
const cnamePath = path.join(distPath, 'CNAME')
|
|
@@ -453,10 +537,10 @@ export async function releaseNode() {
|
|
|
453
537
|
const { releaseType, skipTests, skipLint, skipBuild, skipDeploy } = parseArgs()
|
|
454
538
|
const rootDir = process.cwd()
|
|
455
539
|
|
|
456
|
-
|
|
540
|
+
logStep('Reading package metadata...')
|
|
457
541
|
const pkg = await readPackage(rootDir)
|
|
458
542
|
|
|
459
|
-
|
|
543
|
+
logStep('Checking working tree status...')
|
|
460
544
|
await ensureCleanWorkingTree(rootDir)
|
|
461
545
|
|
|
462
546
|
const branch = await getCurrentBranch(rootDir)
|
|
@@ -464,7 +548,7 @@ export async function releaseNode() {
|
|
|
464
548
|
throw new Error('Unable to determine current branch.')
|
|
465
549
|
}
|
|
466
550
|
|
|
467
|
-
|
|
551
|
+
logStep(`Current branch: ${branch}`)
|
|
468
552
|
const upstreamRef = await getUpstreamRef(rootDir)
|
|
469
553
|
await ensureUpToDateWithUpstream(branch, upstreamRef, rootDir)
|
|
470
554
|
|
|
@@ -481,8 +565,8 @@ export async function releaseNode() {
|
|
|
481
565
|
|
|
482
566
|
logSuccess(`Release workflow completed for ${updatedPkg.name}@${updatedPkg.version}.`)
|
|
483
567
|
} catch (error) {
|
|
484
|
-
|
|
485
|
-
|
|
568
|
+
console.error('\nRelease failed:')
|
|
569
|
+
console.error(error.message)
|
|
486
570
|
throw error
|
|
487
571
|
}
|
|
488
572
|
}
|
|
@@ -230,8 +230,38 @@ async function runLint(skipLint, rootDir = process.cwd()) {
|
|
|
230
230
|
|
|
231
231
|
logStep('Running Laravel Pint...')
|
|
232
232
|
const pintPath = IS_WINDOWS ? 'vendor\\bin\\pint' : 'vendor/bin/pint'
|
|
233
|
-
|
|
234
|
-
|
|
233
|
+
|
|
234
|
+
let dotInterval = null
|
|
235
|
+
try {
|
|
236
|
+
// Capture output and show dots as progress
|
|
237
|
+
process.stdout.write(' ')
|
|
238
|
+
dotInterval = setInterval(() => {
|
|
239
|
+
process.stdout.write('.')
|
|
240
|
+
}, 200)
|
|
241
|
+
|
|
242
|
+
await runCommand('php', [pintPath], { capture: true, cwd: rootDir })
|
|
243
|
+
|
|
244
|
+
if (dotInterval) {
|
|
245
|
+
clearInterval(dotInterval)
|
|
246
|
+
dotInterval = null
|
|
247
|
+
}
|
|
248
|
+
process.stdout.write('\n')
|
|
249
|
+
logSuccess('Lint passed.')
|
|
250
|
+
} catch (error) {
|
|
251
|
+
// Clear dots and show error output
|
|
252
|
+
if (dotInterval) {
|
|
253
|
+
clearInterval(dotInterval)
|
|
254
|
+
dotInterval = null
|
|
255
|
+
}
|
|
256
|
+
process.stdout.write('\n')
|
|
257
|
+
if (error.stdout) {
|
|
258
|
+
console.error(error.stdout)
|
|
259
|
+
}
|
|
260
|
+
if (error.stderr) {
|
|
261
|
+
console.error(error.stderr)
|
|
262
|
+
}
|
|
263
|
+
throw error
|
|
264
|
+
}
|
|
235
265
|
}
|
|
236
266
|
|
|
237
267
|
async function runTests(skipTests, composer, rootDir = process.cwd()) {
|
|
@@ -250,13 +280,41 @@ async function runTests(skipTests, composer, rootDir = process.cwd()) {
|
|
|
250
280
|
|
|
251
281
|
logStep('Running test suite...')
|
|
252
282
|
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
283
|
+
let dotInterval = null
|
|
284
|
+
try {
|
|
285
|
+
// Capture output and show dots as progress
|
|
286
|
+
process.stdout.write(' ')
|
|
287
|
+
dotInterval = setInterval(() => {
|
|
288
|
+
process.stdout.write('.')
|
|
289
|
+
}, 200)
|
|
290
|
+
|
|
291
|
+
if (hasArtisanFile) {
|
|
292
|
+
await runCommand('php', ['artisan', 'test'], { capture: true, cwd: rootDir })
|
|
293
|
+
} else if (hasTestScript) {
|
|
294
|
+
await runCommand('composer', ['test'], { capture: true, cwd: rootDir })
|
|
295
|
+
}
|
|
258
296
|
|
|
259
|
-
|
|
297
|
+
if (dotInterval) {
|
|
298
|
+
clearInterval(dotInterval)
|
|
299
|
+
dotInterval = null
|
|
300
|
+
}
|
|
301
|
+
process.stdout.write('\n')
|
|
302
|
+
logSuccess('Tests passed.')
|
|
303
|
+
} catch (error) {
|
|
304
|
+
// Clear dots and show error output
|
|
305
|
+
if (dotInterval) {
|
|
306
|
+
clearInterval(dotInterval)
|
|
307
|
+
dotInterval = null
|
|
308
|
+
}
|
|
309
|
+
process.stdout.write('\n')
|
|
310
|
+
if (error.stdout) {
|
|
311
|
+
console.error(error.stdout)
|
|
312
|
+
}
|
|
313
|
+
if (error.stderr) {
|
|
314
|
+
console.error(error.stderr)
|
|
315
|
+
}
|
|
316
|
+
throw error
|
|
317
|
+
}
|
|
260
318
|
}
|
|
261
319
|
|
|
262
320
|
async function bumpVersion(releaseType, rootDir = process.cwd()) {
|