@wyxos/zephyr 0.2.12 → 0.2.14

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.
@@ -1,70 +1,105 @@
1
- import { spawn } from 'node:child_process'
1
+ import { spawn, exec } from 'node:child_process'
2
2
  import { fileURLToPath } from 'node:url'
3
3
  import { dirname, join } from 'node:path'
4
4
  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
-
9
- const STEP_PREFIX = '→'
10
- const OK_PREFIX = '✔'
11
- const WARN_PREFIX = '⚠'
8
+ import chalk from 'chalk'
12
9
 
13
10
  const IS_WINDOWS = process.platform === 'win32'
14
11
 
15
12
  function logStep(message) {
16
- console.log(`${STEP_PREFIX} ${message}`)
13
+ console.log(chalk.yellow(`→ ${message}`))
17
14
  }
18
15
 
19
16
  function logSuccess(message) {
20
- console.log(`${OK_PREFIX} ${message}`)
17
+ console.log(chalk.green(`✔ ${message}`))
21
18
  }
22
19
 
23
20
  function logWarning(message) {
24
- console.warn(`${WARN_PREFIX} ${message}`)
21
+ console.warn(chalk.yellow(`⚠ ${message}`))
25
22
  }
26
23
 
27
24
  function runCommand(command, args, { cwd = process.cwd(), capture = false, useShell = false } = {}) {
28
25
  return new Promise((resolve, reject) => {
29
- // On Windows, npm-related commands need shell: true to resolve npx.cmd
26
+ // On Windows, npm-related commands need shell to resolve npx.cmd
30
27
  // Git commands work fine without shell, so we only use it when explicitly requested
31
- const spawnOptions = {
32
- cwd,
33
- stdio: capture ? ['ignore', 'pipe', 'pipe'] : 'inherit'
34
- }
28
+ const needsShell = useShell || (IS_WINDOWS && (command === 'npm' || command === 'npx'))
29
+
30
+ if (needsShell) {
31
+ // When using shell, use exec to avoid deprecation warning with spawn
32
+ // Properly escape arguments for Windows cmd.exe
33
+ const escapedArgs = args.map(arg => {
34
+ // If arg contains spaces or special chars, wrap in quotes and escape internal quotes
35
+ if (arg.includes(' ') || arg.includes('"') || arg.includes('&') || arg.includes('|')) {
36
+ return `"${arg.replace(/"/g, '\\"')}"`
37
+ }
38
+ return arg
39
+ })
40
+ const commandString = `${command} ${escapedArgs.join(' ')}`
41
+
42
+ exec(commandString, { cwd, encoding: 'utf8' }, (error, stdout, stderr) => {
43
+ if (error) {
44
+ const err = new Error(`Command failed (${error.code}): ${command} ${args.join(' ')}`)
45
+ if (capture) {
46
+ err.stdout = stdout || ''
47
+ err.stderr = stderr || ''
48
+ } else {
49
+ // When not capturing, exec still provides output, so show it
50
+ if (stdout) process.stdout.write(stdout)
51
+ if (stderr) process.stderr.write(stderr)
52
+ }
53
+ err.exitCode = error.code
54
+ reject(err)
55
+ return
56
+ }
35
57
 
36
- if (useShell || (IS_WINDOWS && (command === 'npm' || command === 'npx'))) {
37
- spawnOptions.shell = true
38
- }
58
+ if (capture) {
59
+ resolve({ stdout: stdout.trim(), stderr: stderr.trim() })
60
+ } else {
61
+ // When not capturing, exec still provides output, so show it
62
+ if (stdout) process.stdout.write(stdout)
63
+ if (stderr) process.stderr.write(stderr)
64
+ resolve(undefined)
65
+ }
66
+ })
67
+ } else {
68
+ // Use spawn for commands that don't need shell
69
+ const spawnOptions = {
70
+ cwd,
71
+ stdio: capture ? ['ignore', 'pipe', 'pipe'] : 'inherit'
72
+ }
39
73
 
40
- const child = spawn(command, args, spawnOptions)
41
- let stdout = ''
42
- let stderr = ''
74
+ const child = spawn(command, args, spawnOptions)
75
+ let stdout = ''
76
+ let stderr = ''
43
77
 
44
- if (capture) {
45
- child.stdout.on('data', (chunk) => {
46
- stdout += chunk
47
- })
78
+ if (capture) {
79
+ child.stdout.on('data', (chunk) => {
80
+ stdout += chunk
81
+ })
48
82
 
49
- child.stderr.on('data', (chunk) => {
50
- stderr += chunk
51
- })
52
- }
83
+ child.stderr.on('data', (chunk) => {
84
+ stderr += chunk
85
+ })
86
+ }
53
87
 
54
- child.on('error', reject)
55
- child.on('close', (code) => {
56
- if (code === 0) {
57
- resolve(capture ? { stdout: stdout.trim(), stderr: stderr.trim() } : undefined)
58
- } else {
59
- const error = new Error(`Command failed (${code}): ${command} ${args.join(' ')}`)
60
- if (capture) {
61
- error.stdout = stdout
62
- error.stderr = stderr
88
+ child.on('error', reject)
89
+ child.on('close', (code) => {
90
+ if (code === 0) {
91
+ resolve(capture ? { stdout: stdout.trim(), stderr: stderr.trim() } : undefined)
92
+ } else {
93
+ const error = new Error(`Command failed (${code}): ${command} ${args.join(' ')}`)
94
+ if (capture) {
95
+ error.stdout = stdout
96
+ error.stderr = stderr
97
+ }
98
+ error.exitCode = code
99
+ reject(error)
63
100
  }
64
- error.exitCode = code
65
- reject(error)
66
- }
67
- })
101
+ })
102
+ }
68
103
  })
69
104
  }
70
105
 
@@ -210,29 +245,10 @@ async function runLint(skipLint, pkg, rootDir = process.cwd()) {
210
245
 
211
246
  logStep('Running lint...')
212
247
 
213
- let dotInterval = null
214
248
  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')
249
+ await runCommand('npm', ['run', 'lint'], { cwd: rootDir })
228
250
  logSuccess('Lint passed.')
229
251
  } 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
252
  if (error.stdout) {
237
253
  console.error(error.stdout)
238
254
  }
@@ -257,35 +273,18 @@ async function runTests(skipTests, pkg, rootDir = process.cwd()) {
257
273
 
258
274
  logStep('Running test suite...')
259
275
 
260
- let dotInterval = null
261
276
  try {
262
- // Capture output and show dots as progress
263
- process.stdout.write(' ')
264
- dotInterval = setInterval(() => {
265
- process.stdout.write('.')
266
- }, 200)
267
-
268
- // Prefer test:run if available, otherwise use test with --run flag
277
+ // Prefer test:run if available, otherwise use test with --run and --reporter flags
269
278
  if (hasScript(pkg, 'test:run')) {
270
- await runCommand('npm', ['run', 'test:run'], { capture: true, cwd: rootDir })
279
+ // Pass reporter flag to test:run script
280
+ await runCommand('npm', ['run', 'test:run', '--', '--reporter=dot'], { cwd: rootDir })
271
281
  } else {
272
- // For test script, try to pass --run flag (works with vitest)
273
- await runCommand('npm', ['test', '--', '--run'], { capture: true, cwd: rootDir })
282
+ // For test script, pass --run and --reporter flags (works with vitest)
283
+ await runCommand('npm', ['test', '--', '--run', '--reporter=dot'], { cwd: rootDir })
274
284
  }
275
285
 
276
- if (dotInterval) {
277
- clearInterval(dotInterval)
278
- dotInterval = null
279
- }
280
- process.stdout.write('\n')
281
286
  logSuccess('Tests passed.')
282
287
  } catch (error) {
283
- // Clear dots and show error output
284
- if (dotInterval) {
285
- clearInterval(dotInterval)
286
- dotInterval = null
287
- }
288
- process.stdout.write('\n')
289
288
  if (error.stdout) {
290
289
  console.error(error.stdout)
291
290
  }
@@ -309,29 +308,10 @@ async function runBuild(skipBuild, pkg, rootDir = process.cwd()) {
309
308
 
310
309
  logStep('Building project...')
311
310
 
312
- let dotInterval = null
313
311
  try {
314
- // Capture output and show dots as progress
315
- process.stdout.write(' ')
316
- dotInterval = setInterval(() => {
317
- process.stdout.write('.')
318
- }, 200)
319
-
320
- await runCommand('npm', ['run', 'build'], { capture: true, cwd: rootDir })
321
-
322
- if (dotInterval) {
323
- clearInterval(dotInterval)
324
- dotInterval = null
325
- }
326
- process.stdout.write('\n')
312
+ await runCommand('npm', ['run', 'build'], { cwd: rootDir })
327
313
  logSuccess('Build completed.')
328
314
  } catch (error) {
329
- // Clear dots and show error output
330
- if (dotInterval) {
331
- clearInterval(dotInterval)
332
- dotInterval = null
333
- }
334
- process.stdout.write('\n')
335
315
  if (error.stdout) {
336
316
  console.error(error.stdout)
337
317
  }
@@ -355,29 +335,10 @@ async function runLibBuild(skipBuild, pkg, rootDir = process.cwd()) {
355
335
 
356
336
  logStep('Building library...')
357
337
 
358
- let dotInterval = null
359
338
  try {
360
- // Capture output and show dots as progress
361
- process.stdout.write(' ')
362
- dotInterval = setInterval(() => {
363
- process.stdout.write('.')
364
- }, 200)
365
-
366
- await runCommand('npm', ['run', 'build:lib'], { capture: true, cwd: rootDir })
367
-
368
- if (dotInterval) {
369
- clearInterval(dotInterval)
370
- dotInterval = null
371
- }
372
- process.stdout.write('\n')
339
+ await runCommand('npm', ['run', 'build:lib'], { cwd: rootDir })
373
340
  logSuccess('Library built.')
374
341
  } catch (error) {
375
- // Clear dots and show error output
376
- if (dotInterval) {
377
- clearInterval(dotInterval)
378
- dotInterval = null
379
- }
380
- process.stdout.write('\n')
381
342
  if (error.stdout) {
382
343
  console.error(error.stdout)
383
344
  }
@@ -488,7 +449,7 @@ async function pushChanges(rootDir = process.cwd()) {
488
449
  async function publishPackage(pkg, rootDir = process.cwd()) {
489
450
  // Check if package is configured as private/restricted
490
451
  const isPrivate = pkg.publishConfig?.access === 'restricted'
491
-
452
+
492
453
  if (isPrivate) {
493
454
  logWarning('Skipping npm publish (package is configured as private/restricted).')
494
455
  logWarning('Private packages require npm paid plan. Publish manually or use GitHub Packages.')
@@ -571,14 +532,7 @@ async function deployGHPages(skipDeploy, pkg, rootDir = process.cwd()) {
571
532
 
572
533
  const worktreeDir = path.resolve(rootDir, '.gh-pages')
573
534
 
574
- let dotInterval = null
575
535
  try {
576
- // Capture output and show dots as progress
577
- process.stdout.write(' ')
578
- dotInterval = setInterval(() => {
579
- process.stdout.write('.')
580
- }, 200)
581
-
582
536
  try {
583
537
  await runCommand('git', ['worktree', 'remove', worktreeDir, '-f'], { capture: true, cwd: rootDir })
584
538
  } catch { }
@@ -606,19 +560,8 @@ async function deployGHPages(skipDeploy, pkg, rootDir = process.cwd()) {
606
560
  await runCommand('git', ['-C', worktreeDir, 'commit', '-m', `deploy: demo ${new Date().toISOString()}`, '--allow-empty'], { capture: true })
607
561
  await runCommand('git', ['-C', worktreeDir, 'push', '-f', 'origin', 'gh-pages'], { capture: true })
608
562
 
609
- if (dotInterval) {
610
- clearInterval(dotInterval)
611
- dotInterval = null
612
- }
613
- process.stdout.write('\n')
614
563
  logSuccess('GitHub Pages deployment completed.')
615
564
  } catch (error) {
616
- // Clear dots and show error output
617
- if (dotInterval) {
618
- clearInterval(dotInterval)
619
- dotInterval = null
620
- }
621
- process.stdout.write('\n')
622
565
  if (error.stdout) {
623
566
  console.error(error.stdout)
624
567
  }
@@ -651,11 +594,11 @@ export async function releaseNode() {
651
594
 
652
595
  await runLint(skipLint, pkg, rootDir)
653
596
  await runTests(skipTests, pkg, rootDir)
654
- await runBuild(skipBuild, pkg, rootDir)
655
597
  await runLibBuild(skipBuild, pkg, rootDir)
656
598
  await ensureNpmAuth(rootDir)
657
599
 
658
600
  const updatedPkg = await bumpVersion(releaseType, rootDir)
601
+ await runBuild(skipBuild, updatedPkg, rootDir)
659
602
  await pushChanges(rootDir)
660
603
  await publishPackage(updatedPkg, rootDir)
661
604
  await deployGHPages(skipDeploy, updatedPkg, rootDir)
@@ -27,16 +27,30 @@ function logWarning(message) {
27
27
 
28
28
  function runCommand(command, args, { cwd = process.cwd(), capture = false, useShell = false } = {}) {
29
29
  return new Promise((resolve, reject) => {
30
+ const needsShell = useShell || (IS_WINDOWS && (command === 'php' || command === 'composer'))
31
+
30
32
  const spawnOptions = {
31
33
  cwd,
32
34
  stdio: capture ? ['ignore', 'pipe', 'pipe'] : 'inherit'
33
35
  }
34
36
 
35
- if (useShell || (IS_WINDOWS && (command === 'php' || command === 'composer'))) {
37
+ let child
38
+ if (needsShell) {
39
+ // When using shell, construct the command string to avoid deprecation warning
40
+ // Properly escape arguments for Windows cmd.exe
41
+ const escapedArgs = args.map(arg => {
42
+ // If arg contains spaces or special chars, wrap in quotes and escape internal quotes
43
+ if (arg.includes(' ') || arg.includes('"') || arg.includes('&') || arg.includes('|')) {
44
+ return `"${arg.replace(/"/g, '\\"')}"`
45
+ }
46
+ return arg
47
+ })
48
+ const commandString = `${command} ${escapedArgs.join(' ')}`
36
49
  spawnOptions.shell = true
50
+ child = spawn(commandString, [], spawnOptions)
51
+ } else {
52
+ child = spawn(command, args, spawnOptions)
37
53
  }
38
-
39
- const child = spawn(command, args, spawnOptions)
40
54
  let stdout = ''
41
55
  let stderr = ''
42
56