|
1 | | -#!/usr/bin/env zx |
2 | | -import 'zx/globals'; |
| 1 | +#!/usr/bin/env node |
| 2 | +import { readFileSync } from 'node:fs'; |
| 3 | +import { spawnSync } from 'node:child_process'; |
3 | 4 |
|
4 | | -// Type declaration for process (zx doesn't include Node.js types) |
5 | | -declare const process: { |
6 | | - env: Record<string, string | undefined>; |
7 | | - cwd: () => string; |
8 | | - argv: string[]; |
9 | | - exit: (code: number) => never; |
10 | | -}; |
| 5 | +interface PackageJson { |
| 6 | + name: string; |
| 7 | + version: string; |
| 8 | + private?: boolean; |
| 9 | +} |
11 | 10 |
|
12 | 11 | /** |
13 | 12 | * Publish a single package to npm if needed |
14 | 13 | * |
15 | 14 | * This script: |
16 | 15 | * - Operates on the current workspace directory |
17 | | - * - Checks npm registry before publishing |
| 16 | + * - Checks npm registry before publishing (idempotent - safe to retry) |
| 17 | + * - Requires yarn npm authentication to be configured (npmAuthToken) |
18 | 18 | * - Skips private packages automatically |
19 | 19 | * |
20 | 20 | * Usage: |
21 | 21 | * # In a workspace directory: |
22 | | - * npx zx publish-package-if-needed.mts # Publish for real |
23 | | - * npx zx publish-package-if-needed.mts --dry-run # Simulate publishing |
| 22 | + * node publish-package-if-needed.mts # Publish for real |
| 23 | + * node publish-package-if-needed.mts --dry-run # Simulate publishing |
24 | 24 | * |
25 | 25 | * # For all workspaces in topological order: |
26 | 26 | * yarn workspaces foreach --all --topological --no-private \ |
27 | | - * exec npx zx .github/scripts/publish-package-if-needed.mts |
| 27 | + * exec node .github/scripts/publish-package-if-needed.mts |
28 | 28 | */ |
29 | 29 |
|
30 | | -// Parse command line arguments |
31 | 30 | const isDryRun = process.argv.includes('--dry-run'); |
32 | 31 |
|
33 | | -// Read package.json from current directory |
34 | | -const packageJson = JSON.parse(fs.readFileSync('package.json', 'utf-8')); |
| 32 | +const packageJson: PackageJson = JSON.parse(readFileSync('package.json', 'utf-8')); |
35 | 33 | const { name, version, private: isPrivate } = packageJson; |
36 | 34 |
|
37 | | -// Skip private packages |
38 | 35 | if (isPrivate) { |
39 | | - echo(`⊘ Skipping private package: ${name}`); |
| 36 | + console.log(`⊘ Skipping private package: ${name}`); |
40 | 37 | process.exit(0); |
41 | 38 | } |
42 | 39 |
|
43 | 40 | // Check if package@version already exists on npm |
44 | | -const result = await $`npm view ${name}@${version} version 2>&1`.nothrow().quiet(); |
45 | | -const shouldPublish = result.exitCode !== 0 || result.stdout.trim() !== version; |
| 41 | +const checkResult = spawnSync('npm', ['view', `${name}@${version}`, 'version'], { |
| 42 | + encoding: 'utf-8', |
| 43 | + stdio: 'pipe', |
| 44 | +}); |
| 45 | +const alreadyPublished = checkResult.status === 0 && checkResult.stdout.trim() === version; |
46 | 46 |
|
47 | | -if (!shouldPublish) { |
48 | | - echo(`✓ Already published: ${name}@${version}`); |
| 47 | +if (alreadyPublished) { |
| 48 | + console.log(`✓ Already published: ${name}@${version}`); |
49 | 49 | process.exit(0); |
50 | 50 | } |
51 | 51 |
|
52 | | -// Publish the package |
53 | 52 | const startMsg = isDryRun ? 'Simulating publish' : 'Publishing'; |
54 | 53 | const endMsg = isDryRun ? 'Dry-run successful for' : 'Successfully published'; |
55 | 54 |
|
56 | | -echo(`→ ${startMsg}: ${name}@${version}`); |
| 55 | +console.log(`→ ${startMsg}: ${name}@${version}`); |
57 | 56 |
|
58 | | -const publishCmd = isDryRun |
59 | | - ? $`yarn npm publish --access public --dry-run` |
60 | | - : $`yarn npm publish --access public`; |
| 57 | +const publishArgs = ['npm', 'publish', '--access', 'public']; |
| 58 | +if (isDryRun) publishArgs.push('--dry-run'); |
61 | 59 |
|
62 | | -await publishCmd; |
63 | | -echo(`✓ ${endMsg}: ${name}@${version}`); |
| 60 | +const publishResult = spawnSync('yarn', publishArgs, { stdio: 'inherit' }); |
| 61 | +if (publishResult.status !== 0) { |
| 62 | + process.exit(publishResult.status ?? 1); |
| 63 | +} |
| 64 | +console.log(`✓ ${endMsg}: ${name}@${version}`); |
0 commit comments