@wyxos/zephyr 0.2.27 → 0.3.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.
Files changed (35) hide show
  1. package/README.md +55 -2
  2. package/bin/zephyr.mjs +3 -1
  3. package/package.json +7 -2
  4. package/src/application/configuration/app-details.mjs +89 -0
  5. package/src/application/configuration/app-selection.mjs +87 -0
  6. package/src/application/configuration/preset-selection.mjs +59 -0
  7. package/src/application/configuration/select-deployment-target.mjs +165 -0
  8. package/src/application/configuration/server-selection.mjs +87 -0
  9. package/src/application/configuration/service.mjs +109 -0
  10. package/src/application/deploy/build-remote-deployment-plan.mjs +174 -0
  11. package/src/application/deploy/bump-local-package-version.mjs +81 -0
  12. package/src/application/deploy/execute-remote-deployment-plan.mjs +61 -0
  13. package/src/{utils/task-planner.mjs → application/deploy/plan-laravel-deployment-tasks.mjs} +5 -4
  14. package/src/application/deploy/prepare-local-deployment.mjs +52 -0
  15. package/src/application/deploy/resolve-local-deployment-context.mjs +17 -0
  16. package/src/application/deploy/resolve-pending-snapshot.mjs +45 -0
  17. package/src/application/deploy/run-deployment.mjs +147 -0
  18. package/src/application/deploy/run-local-deployment-checks.mjs +80 -0
  19. package/src/application/release/release-node-package.mjs +340 -0
  20. package/src/application/release/release-packagist-package.mjs +223 -0
  21. package/src/config/project.mjs +13 -0
  22. package/src/deploy/local-repo.mjs +187 -67
  23. package/src/deploy/preflight.mjs +10 -1
  24. package/src/deploy/remote-exec.mjs +2 -3
  25. package/src/index.mjs +27 -85
  26. package/src/main.mjs +80 -627
  27. package/src/release/shared.mjs +104 -0
  28. package/src/release-node.mjs +20 -481
  29. package/src/release-packagist.mjs +20 -291
  30. package/src/runtime/app-context.mjs +36 -0
  31. package/src/targets/index.mjs +24 -0
  32. package/src/utils/command.mjs +67 -5
  33. package/src/utils/output.mjs +41 -16
  34. package/src/utils/config-flow.mjs +0 -284
  35. /package/src/{utils/php-version.mjs → infrastructure/php/version.mjs} +0 -0
@@ -0,0 +1,104 @@
1
+ import inquirer from 'inquirer'
2
+ import process from 'node:process'
3
+
4
+ import { validateLocalDependencies } from '../dependency-scanner.mjs'
5
+ import { runCommand as runCommandBase, runCommandCapture as runCommandCaptureBase } from '../utils/command.mjs'
6
+ import {
7
+ ensureUpToDateWithUpstream,
8
+ getCurrentBranch,
9
+ getUpstreamRef
10
+ } from '../utils/git.mjs'
11
+
12
+ const RELEASE_TYPES = new Set([
13
+ 'major',
14
+ 'minor',
15
+ 'patch',
16
+ 'premajor',
17
+ 'preminor',
18
+ 'prepatch',
19
+ 'prerelease'
20
+ ])
21
+
22
+ function flagToKey(flag) {
23
+ return flag
24
+ .replace(/^--/, '')
25
+ .replace(/-([a-z])/g, (_match, character) => character.toUpperCase())
26
+ }
27
+
28
+ export function parseReleaseArgs({
29
+ args = process.argv.slice(2),
30
+ booleanFlags = []
31
+ } = {}) {
32
+ const filteredArgs = args.filter((arg) => !arg.startsWith('--type='))
33
+ const positionals = filteredArgs.filter((arg) => !arg.startsWith('--'))
34
+ const presentFlags = new Set(filteredArgs.filter((arg) => arg.startsWith('--')))
35
+ const releaseType = positionals[0] ?? 'patch'
36
+
37
+ if (!RELEASE_TYPES.has(releaseType)) {
38
+ throw new Error(
39
+ `Invalid release type "${releaseType}". Use one of: ${Array.from(RELEASE_TYPES).join(', ')}.`
40
+ )
41
+ }
42
+
43
+ const parsedFlags = Object.fromEntries(
44
+ booleanFlags.map((flag) => [flagToKey(flag), presentFlags.has(flag)])
45
+ )
46
+
47
+ return { releaseType, ...parsedFlags }
48
+ }
49
+
50
+ export async function runReleaseCommand(command, args, {
51
+ cwd = process.cwd(),
52
+ capture = false
53
+ } = {}) {
54
+ if (capture) {
55
+ const { stdout, stderr } = await runCommandCaptureBase(command, args, { cwd })
56
+ return { stdout: stdout.trim(), stderr: stderr.trim() }
57
+ }
58
+
59
+ await runCommandBase(command, args, { cwd })
60
+ return undefined
61
+ }
62
+
63
+ export async function ensureCleanWorkingTree(rootDir = process.cwd(), {
64
+ runCommand = runReleaseCommand
65
+ } = {}) {
66
+ const { stdout } = await runCommand('git', ['status', '--porcelain'], {
67
+ capture: true,
68
+ cwd: rootDir
69
+ })
70
+
71
+ if (stdout.length > 0) {
72
+ throw new Error('Working tree has uncommitted changes. Commit or stash them before releasing.')
73
+ }
74
+ }
75
+
76
+ export async function validateReleaseDependencies(rootDir = process.cwd(), {
77
+ prompt = (questions) => inquirer.prompt(questions),
78
+ logSuccess
79
+ } = {}) {
80
+ await validateLocalDependencies(rootDir, prompt, logSuccess)
81
+ }
82
+
83
+ export async function ensureReleaseBranchReady({
84
+ rootDir = process.cwd(),
85
+ branchMethod = 'show-current',
86
+ getCurrentBranchImpl = getCurrentBranch,
87
+ getUpstreamRefImpl = getUpstreamRef,
88
+ ensureUpToDateWithUpstreamImpl = ensureUpToDateWithUpstream,
89
+ logStep,
90
+ logWarning
91
+ } = {}) {
92
+ const branch = await getCurrentBranchImpl(rootDir, { method: branchMethod })
93
+
94
+ if (!branch) {
95
+ throw new Error('Unable to determine current branch.')
96
+ }
97
+
98
+ logStep?.(`Current branch: ${branch}`)
99
+
100
+ const upstreamRef = await getUpstreamRefImpl(rootDir)
101
+ await ensureUpToDateWithUpstreamImpl({ branch, upstreamRef, rootDir, logStep, logWarning })
102
+
103
+ return { branch, upstreamRef }
104
+ }
@@ -1,488 +1,27 @@
1
- import { join } from 'node:path'
2
- import { readFile } from 'node:fs/promises'
3
- import fs from 'node:fs'
4
- import path from 'node:path'
5
1
  import process from 'node:process'
6
2
  import chalk from 'chalk'
7
- import inquirer from 'inquirer'
8
- import { validateLocalDependencies } from './dependency-scanner.mjs'
9
- import { writeStderr, writeStderrLine, writeStdoutLine } from './utils/output.mjs'
10
- import { runCommand as runCommandBase, runCommandCapture as runCommandCaptureBase } from './utils/command.mjs'
11
- import { ensureUpToDateWithUpstream, getCurrentBranch, getUpstreamRef } from './utils/git.mjs'
3
+ import {createChalkLogger} from './utils/output.mjs'
4
+ import {
5
+ parseReleaseArgs,
6
+ } from './release/shared.mjs'
7
+ import {releaseNodePackage} from './application/release/release-node-package.mjs'
12
8
 
13
- function logStep(message) {
14
- writeStdoutLine(chalk.yellow(`→ ${message}`))
15
- }
16
-
17
- function logSuccess(message) {
18
- writeStdoutLine(chalk.green(`✔ ${message}`))
19
- }
20
-
21
- function logWarning(message) {
22
- writeStderrLine(chalk.yellow(`⚠ ${message}`))
23
- }
24
-
25
- async function runCommand(command, args, { cwd = process.cwd(), capture = false } = {}) {
26
- if (capture) {
27
- const { stdout, stderr } = await runCommandCaptureBase(command, args, { cwd })
28
- return { stdout: stdout.trim(), stderr: stderr.trim() }
29
- }
30
-
31
- await runCommandBase(command, args, { cwd })
32
- return undefined
33
- }
34
-
35
- async function readPackage(rootDir = process.cwd()) {
36
- const packagePath = join(rootDir, 'package.json')
37
- const raw = await readFile(packagePath, 'utf8')
38
- return JSON.parse(raw)
39
- }
40
-
41
- function hasScript(pkg, scriptName) {
42
- return pkg?.scripts?.[scriptName] !== undefined
43
- }
44
-
45
- async function ensureCleanWorkingTree(rootDir = process.cwd()) {
46
- const { stdout } = await runCommand('git', ['status', '--porcelain'], { capture: true, cwd: rootDir })
47
-
48
- if (stdout.length > 0) {
49
- throw new Error('Working tree has uncommitted changes. Commit or stash them before releasing.')
50
- }
51
- }
52
-
53
- // Git helpers imported from src/utils/git.mjs
54
-
55
- function parseArgs() {
56
- const args = process.argv.slice(2)
57
- // Filter out --type flag as it's handled by zephyr CLI
58
- const filteredArgs = args.filter((arg) => !arg.startsWith('--type='))
59
- const positionals = filteredArgs.filter((arg) => !arg.startsWith('--'))
60
- const flags = new Set(filteredArgs.filter((arg) => arg.startsWith('--')))
61
-
62
- const releaseType = positionals[0] ?? 'patch'
63
- const skipTests = flags.has('--skip-tests')
64
- const skipLint = flags.has('--skip-lint')
65
- const skipBuild = flags.has('--skip-build')
66
- const skipDeploy = flags.has('--skip-deploy')
67
-
68
- const allowedTypes = new Set([
69
- 'major',
70
- 'minor',
71
- 'patch',
72
- 'premajor',
73
- 'preminor',
74
- 'prepatch',
75
- 'prerelease'
76
- ])
77
-
78
- if (!allowedTypes.has(releaseType)) {
79
- throw new Error(
80
- `Invalid release type "${releaseType}". Use one of: ${Array.from(allowedTypes).join(', ')}.`
81
- )
82
- }
83
-
84
- return { releaseType, skipTests, skipLint, skipBuild, skipDeploy }
85
- }
86
-
87
- async function runLint(skipLint, pkg, rootDir = process.cwd()) {
88
- if (skipLint) {
89
- logWarning('Skipping lint because --skip-lint flag was provided.')
90
- return
91
- }
92
-
93
- if (!hasScript(pkg, 'lint')) {
94
- logStep('Skipping lint (no lint script found in package.json).')
95
- return
96
- }
97
-
98
- logStep('Running lint...')
99
-
100
- try {
101
- await runCommand('npm', ['run', 'lint'], { cwd: rootDir })
102
- logSuccess('Lint passed.')
103
- } catch (error) {
104
- if (error.stdout) {
105
- writeStderr(error.stdout)
106
- }
107
- if (error.stderr) {
108
- writeStderr(error.stderr)
109
- }
110
- throw error
111
- }
112
- }
113
-
114
- async function runTests(skipTests, pkg, rootDir = process.cwd()) {
115
- if (skipTests) {
116
- logWarning('Skipping tests because --skip-tests flag was provided.')
117
- return
118
- }
119
-
120
- // Check for test:run or test script
121
- if (!hasScript(pkg, 'test:run') && !hasScript(pkg, 'test')) {
122
- logStep('Skipping tests (no test or test:run script found in package.json).')
123
- return
124
- }
125
-
126
- logStep('Running test suite...')
127
-
128
- try {
129
- const testRunScript = pkg?.scripts?.['test:run'] ?? ''
130
- const testScript = pkg?.scripts?.test ?? ''
131
- const usesNodeTest = (script) => /\bnode\b.*\s--test\b/.test(script)
132
-
133
- // Prefer test:run if available, otherwise use test with --run and --reporter flags
134
- if (hasScript(pkg, 'test:run')) {
135
- if (usesNodeTest(testRunScript)) {
136
- await runCommand('npm', ['run', 'test:run'], { cwd: rootDir })
137
- } else {
138
- // Pass reporter flag to test:run script
139
- await runCommand('npm', ['run', 'test:run', '--', '--reporter=dot'], { cwd: rootDir })
140
- }
141
- } else {
142
- if (usesNodeTest(testScript)) {
143
- await runCommand('npm', ['test'], { cwd: rootDir })
144
- } else {
145
- // For test script, pass --run and --reporter flags (works with vitest)
146
- await runCommand('npm', ['test', '--', '--run', '--reporter=dot'], { cwd: rootDir })
147
- }
148
- }
149
-
150
- logSuccess('Tests passed.')
151
- } catch (error) {
152
- if (error.stdout) {
153
- writeStderr(error.stdout)
154
- }
155
- if (error.stderr) {
156
- writeStderr(error.stderr)
157
- }
158
- throw error
159
- }
160
- }
161
-
162
- async function runBuild(skipBuild, pkg, rootDir = process.cwd()) {
163
- if (skipBuild) {
164
- logWarning('Skipping build because --skip-build flag was provided.')
165
- return
166
- }
167
-
168
- if (!hasScript(pkg, 'build')) {
169
- logStep('Skipping build (no build script found in package.json).')
170
- return
171
- }
172
-
173
- logStep('Building project...')
174
-
175
- try {
176
- await runCommand('npm', ['run', 'build'], { cwd: rootDir })
177
- logSuccess('Build completed.')
178
- } catch (error) {
179
- if (error.stdout) {
180
- writeStderr(error.stdout)
181
- }
182
- if (error.stderr) {
183
- writeStderr(error.stderr)
184
- }
185
- throw error
186
- }
187
- }
188
-
189
- async function runLibBuild(skipBuild, pkg, rootDir = process.cwd()) {
190
- if (skipBuild) {
191
- logWarning('Skipping library build because --skip-build flag was provided.')
192
- return
193
- }
194
-
195
- if (!hasScript(pkg, 'build:lib')) {
196
- logStep('Skipping library build (no build:lib script found in package.json).')
197
- return false
198
- }
199
-
200
- logStep('Building library...')
201
-
202
- try {
203
- await runCommand('npm', ['run', 'build:lib'], { cwd: rootDir })
204
- logSuccess('Library built.')
205
- } catch (error) {
206
- if (error.stdout) {
207
- writeStderr(error.stdout)
208
- }
209
- if (error.stderr) {
210
- writeStderr(error.stderr)
211
- }
212
- throw error
213
- }
214
-
215
- // Check for lib changes and commit them if any
216
- const { stdout: statusAfterBuild } = await runCommand('git', ['status', '--porcelain'], { capture: true, cwd: rootDir })
217
- const hasLibChanges = statusAfterBuild.split('\n').some(line => {
218
- const trimmed = line.trim()
219
- return trimmed.includes('lib/') && (trimmed.startsWith('M') || trimmed.startsWith('??') || trimmed.startsWith('A') || trimmed.startsWith('D'))
220
- })
221
-
222
- if (hasLibChanges) {
223
- logStep('Committing lib build artifacts...')
224
- await runCommand('git', ['add', 'lib/'], { capture: true, cwd: rootDir })
225
- await runCommand('git', ['commit', '-m', 'chore: build lib artifacts'], { capture: true, cwd: rootDir })
226
- logSuccess('Lib build artifacts committed.')
227
- }
228
-
229
- return hasLibChanges
230
- }
231
-
232
- async function ensureNpmAuth(pkg, rootDir = process.cwd()) {
233
- const isPrivate = pkg?.publishConfig?.access === 'restricted'
234
- if (isPrivate) {
235
- logStep('Skipping npm authentication check (package is private/restricted).')
236
- return
237
- }
238
-
239
- logStep('Confirming npm authentication...')
240
- try {
241
- const result = await runCommand('npm', ['whoami'], { capture: true, cwd: rootDir })
242
- // Only show username if we captured it, otherwise just show success
243
- if (result?.stdout) {
244
- // Silently authenticated - we don't need to show the username
245
- }
246
- logSuccess('npm authenticated.')
247
- } catch (error) {
248
- if (error.stderr) {
249
- writeStderr(error.stderr)
250
- }
251
- throw error
252
- }
253
- }
254
-
255
- async function bumpVersion(releaseType, rootDir = process.cwd()) {
256
- logStep(`Bumping package version...`)
257
-
258
- // Lib changes should already be committed by runLibBuild, but check anyway
259
- const { stdout: statusBefore } = await runCommand('git', ['status', '--porcelain'], { capture: true, cwd: rootDir })
260
- const hasLibChanges = statusBefore.split('\n').some(line => {
261
- const trimmed = line.trim()
262
- return trimmed.includes('lib/') && (trimmed.startsWith('M') || trimmed.startsWith('??') || trimmed.startsWith('A') || trimmed.startsWith('D'))
263
- })
264
-
265
- if (hasLibChanges) {
266
- logStep('Stashing lib build artifacts...')
267
- await runCommand('git', ['stash', 'push', '-u', '-m', 'temp: lib build artifacts', 'lib/'], { capture: true, cwd: rootDir })
268
- }
269
-
270
- try {
271
- // npm version will update package.json and create a commit with default message
272
- const result = await runCommand('npm', ['version', releaseType], { capture: true, cwd: rootDir })
273
- // Extract version from output (e.g., "v0.2.8" or "0.2.8")
274
- if (result?.stdout) {
275
- const versionMatch = result.stdout.match(/v?(\d+\.\d+\.\d+)/)
276
- if (versionMatch) {
277
- // Version is shown in the logSuccess message below, no need to show it here
278
- }
279
- }
280
- } finally {
281
- // Restore lib changes and ensure they're in the commit
282
- if (hasLibChanges) {
283
- logStep('Restoring lib build artifacts...')
284
- await runCommand('git', ['stash', 'pop'], { capture: true, cwd: rootDir })
285
- await runCommand('git', ['add', 'lib/'], { capture: true, cwd: rootDir })
286
- const { stdout: statusAfter } = await runCommand('git', ['status', '--porcelain'], { capture: true, cwd: rootDir })
287
- if (statusAfter.includes('lib/')) {
288
- await runCommand('git', ['commit', '--amend', '--no-edit'], { capture: true, cwd: rootDir })
289
- }
290
- }
291
- }
292
-
293
- const pkg = await readPackage(rootDir)
294
- const commitMessage = `chore: release ${pkg.version}`
295
-
296
- // Amend the commit message to use our custom format
297
- await runCommand('git', ['commit', '--amend', '-m', commitMessage], { capture: true, cwd: rootDir })
298
-
299
- logSuccess(`Version updated to ${pkg.version}.`)
300
- return pkg
301
- }
302
-
303
- async function pushChanges(rootDir = process.cwd()) {
304
- logStep('Pushing commits and tags to origin...')
305
- try {
306
- await runCommand('git', ['push', '--follow-tags'], { capture: true, cwd: rootDir })
307
- logSuccess('Git push completed.')
308
- } catch (error) {
309
- if (error.stdout) {
310
- writeStderr(error.stdout)
311
- }
312
- if (error.stderr) {
313
- writeStderr(error.stderr)
314
- }
315
- throw error
316
- }
317
- }
318
-
319
- async function publishPackage(pkg, rootDir = process.cwd()) {
320
- // Check if package is configured as private/restricted
321
- const isPrivate = pkg.publishConfig?.access === 'restricted'
322
-
323
- if (isPrivate) {
324
- logWarning('Skipping npm publish (package is configured as private/restricted).')
325
- logWarning('Private packages require npm paid plan. Publish manually or use GitHub Packages.')
326
- return
327
- }
328
-
329
- const publishArgs = ['publish', '--ignore-scripts'] // Skip prepublishOnly since we already built lib
330
-
331
- if (pkg.name.startsWith('@')) {
332
- // For scoped packages, determine access level from publishConfig
333
- // Default to 'public' for scoped packages if not specified (free npm accounts require public for scoped packages)
334
- const access = pkg.publishConfig?.access || 'public'
335
- publishArgs.push('--access', access)
336
- }
337
-
338
- logStep(`Publishing ${pkg.name}@${pkg.version} to npm...`)
339
- try {
340
- await runCommand('npm', publishArgs, { capture: true, cwd: rootDir })
341
- logSuccess('npm publish completed.')
342
- } catch (error) {
343
- if (error.stdout) {
344
- writeStderr(error.stdout)
345
- }
346
- if (error.stderr) {
347
- writeStderr(error.stderr)
348
- }
349
- throw error
350
- }
351
- }
352
-
353
- function extractDomainFromHomepage(homepage) {
354
- if (!homepage) return null
355
- try {
356
- const url = new URL(homepage)
357
- return url.hostname
358
- } catch {
359
- // If it's not a valid URL, try to extract domain from string
360
- const match = homepage.match(/(?:https?:\/\/)?([^/]+)/)
361
- return match ? match[1] : null
362
- }
363
- }
364
-
365
- async function deployGHPages(skipDeploy, pkg, rootDir = process.cwd()) {
366
- if (skipDeploy) {
367
- logWarning('Skipping GitHub Pages deployment because --skip-deploy flag was provided.')
368
- return
369
- }
370
-
371
- // Check if dist directory exists (indicates build output for deployment)
372
- const distPath = path.join(rootDir, 'dist')
373
- let distExists = false
374
- try {
375
- const stats = await fs.promises.stat(distPath)
376
- distExists = stats.isDirectory()
377
- } catch {
378
- distExists = false
379
- }
380
-
381
- if (!distExists) {
382
- logStep('Skipping GitHub Pages deployment (no dist directory found).')
383
- return
384
- }
385
-
386
- logStep('Deploying to GitHub Pages...')
387
-
388
- // Write CNAME file to dist if homepage is set
389
- const cnamePath = path.join(distPath, 'CNAME')
390
-
391
- if (pkg.homepage) {
392
- const domain = extractDomainFromHomepage(pkg.homepage)
393
- if (domain) {
394
- try {
395
- await fs.promises.mkdir(distPath, { recursive: true })
396
- await fs.promises.writeFile(cnamePath, domain)
397
- } catch (error) {
398
- logWarning(`Could not write CNAME file: ${error.message}`)
399
- }
400
- }
401
- }
402
-
403
- const worktreeDir = path.resolve(rootDir, '.gh-pages')
404
-
405
- try {
406
- try {
407
- await runCommand('git', ['worktree', 'remove', worktreeDir, '-f'], { capture: true, cwd: rootDir })
408
- } catch (_error) {
409
- // Ignore if worktree doesn't exist
410
- }
411
-
412
- try {
413
- await runCommand('git', ['worktree', 'add', worktreeDir, 'gh-pages'], { capture: true, cwd: rootDir })
414
- } catch {
415
- await runCommand('git', ['worktree', 'add', worktreeDir, '-b', 'gh-pages'], { capture: true, cwd: rootDir })
416
- }
417
-
418
- await runCommand('git', ['-C', worktreeDir, 'config', 'user.name', 'wyxos'], { capture: true })
419
- await runCommand('git', ['-C', worktreeDir, 'config', 'user.email', 'github@wyxos.com'], { capture: true })
420
-
421
- // Clear worktree directory
422
- for (const entry of fs.readdirSync(worktreeDir)) {
423
- if (entry === '.git') continue
424
- const target = path.join(worktreeDir, entry)
425
- fs.rmSync(target, { recursive: true, force: true })
426
- }
427
-
428
- // Copy dist to worktree
429
- fs.cpSync(distPath, worktreeDir, { recursive: true })
430
-
431
- await runCommand('git', ['-C', worktreeDir, 'add', '-A'], { capture: true })
432
- await runCommand('git', ['-C', worktreeDir, 'commit', '-m', `deploy: demo ${new Date().toISOString()}`, '--allow-empty'], { capture: true })
433
- await runCommand('git', ['-C', worktreeDir, 'push', '-f', 'origin', 'gh-pages'], { capture: true })
434
-
435
- logSuccess('GitHub Pages deployment completed.')
436
- } catch (error) {
437
- if (error.stdout) {
438
- writeStderr(error.stdout)
439
- }
440
- if (error.stderr) {
441
- writeStderr(error.stderr)
442
- }
443
- throw error
444
- }
445
- }
9
+ const {logProcessing: logStep, logSuccess, logWarning} = createChalkLogger(chalk)
446
10
 
447
11
  export async function releaseNode() {
448
- try {
449
- const { releaseType, skipTests, skipLint, skipBuild, skipDeploy } = parseArgs()
12
+ const {releaseType, skipTests, skipLint, skipBuild, skipDeploy} = parseReleaseArgs({
13
+ booleanFlags: ['--skip-tests', '--skip-lint', '--skip-build', '--skip-deploy']
14
+ })
450
15
  const rootDir = process.cwd()
451
-
452
- logStep('Reading package metadata...')
453
- const pkg = await readPackage(rootDir)
454
-
455
- logStep('Validating dependencies...')
456
- await validateLocalDependencies(rootDir, (questions) => inquirer.prompt(questions), logSuccess)
457
-
458
- logStep('Checking working tree status...')
459
- await ensureCleanWorkingTree(rootDir)
460
-
461
- const branch = await getCurrentBranch(rootDir, { method: 'show-current' })
462
- if (!branch) {
463
- throw new Error('Unable to determine current branch.')
464
- }
465
-
466
- logStep(`Current branch: ${branch}`)
467
- const upstreamRef = await getUpstreamRef(rootDir)
468
- await ensureUpToDateWithUpstream({ branch, upstreamRef, rootDir, logStep, logWarning })
469
-
470
- await runLint(skipLint, pkg, rootDir)
471
- await runTests(skipTests, pkg, rootDir)
472
- await runLibBuild(skipBuild, pkg, rootDir)
473
- await ensureNpmAuth(pkg, rootDir)
474
-
475
- const updatedPkg = await bumpVersion(releaseType, rootDir)
476
- await runBuild(skipBuild, updatedPkg, rootDir)
477
- await pushChanges(rootDir)
478
- await publishPackage(updatedPkg, rootDir)
479
- await deployGHPages(skipDeploy, updatedPkg, rootDir)
480
-
481
- logSuccess(`Release workflow completed for ${updatedPkg.name}@${updatedPkg.version}.`)
482
- } catch (error) {
483
- writeStderrLine('\nRelease failed:')
484
- writeStderrLine(error.message)
485
- throw error
486
- }
16
+ await releaseNodePackage({
17
+ releaseType,
18
+ skipTests,
19
+ skipLint,
20
+ skipBuild,
21
+ skipDeploy,
22
+ rootDir,
23
+ logStep,
24
+ logSuccess,
25
+ logWarning
26
+ })
487
27
  }
488
-