@bobfrankston/npmglobalize 1.0.173 → 1.0.175

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 (4) hide show
  1. package/cli.js +1 -1
  2. package/lib.d.ts +22 -6
  3. package/lib.js +163 -67
  4. package/package.json +4 -1
package/cli.js CHANGED
@@ -446,7 +446,7 @@ export async function main() {
446
446
  // Ensures consumers' tsc sees up-to-date `.d.ts` from sibling checkouts
447
447
  // whose source has changed since their last build.
448
448
  if (!cliOptions.cleanup) {
449
- ensureFileDepModules(cwd, !!cliOptions.verbose);
449
+ await ensureFileDepModules(cwd, !!cliOptions.verbose);
450
450
  const depsOk = await buildFileDepsTopologically(cwd, { verbose: !!cliOptions.verbose, force: !!cliOptions.force });
451
451
  if (!depsOk && !cliOptions.force) {
452
452
  printBuildSummary();
package/lib.d.ts CHANGED
@@ -293,7 +293,7 @@ export declare function missingDeps(pkgDir: string, pkg: any): string[];
293
293
  * "fresh clone, no `node_modules/`" and "dep added but `npm install` not
294
294
  * re-run" (partial-sync) cases. Also covers `cwd` itself on the first call.
295
295
  * Cycle-safe via the shared `visited` set. */
296
- export declare function ensureFileDepModules(cwd: string, verbose?: boolean, visited?: Set<string>): void;
296
+ export declare function ensureFileDepModules(cwd: string, verbose?: boolean, visited?: Set<string>): Promise<void>;
297
297
  /** Build a single project: detect tsconfig, prompt to add `build: tsc` if a
298
298
  * TypeScript project lacks a build script, run `npm run build`, record
299
299
  * failures. Returns true if build succeeded (or was skipped because no
@@ -319,7 +319,7 @@ export declare function buildFileDepsTopologically(cwd: string, opts?: {
319
319
  export declare function ensureWorkspaceDepModules(rootDir: string, members: Array<{
320
320
  dir: string;
321
321
  pkg: any;
322
- }>, verbose?: boolean): void;
322
+ }>, verbose?: boolean): Promise<void>;
323
323
  /** Run a command and return success status */
324
324
  export declare function runCommand(cmd: string, args: string[], options?: {
325
325
  silent?: boolean;
@@ -331,15 +331,31 @@ export declare function runCommand(cmd: string, args: string[], options?: {
331
331
  output: string;
332
332
  stderr: string;
333
333
  };
334
+ /** Async variant of `runCommand`: spawns the child via `child_process.spawn`
335
+ * (not spawnSync) so the JS event loop keeps turning. Used for long-running
336
+ * commands (npm install/pack/publish/build, git push/pull, gh, wsl) so that
337
+ * a SIGINT pressed mid-command can fire its handler immediately, kill the
338
+ * child, restore deps, and exit — instead of being queued behind spawnSync. */
339
+ export declare function runCommandAsync(cmd: string, args: string[], options?: {
340
+ silent?: boolean;
341
+ verbose?: boolean;
342
+ showCommand?: boolean;
343
+ cwd?: string;
344
+ }): Promise<{
345
+ success: boolean;
346
+ output: string;
347
+ stderr: string;
348
+ signal: NodeJS.Signals | null;
349
+ }>;
334
350
  /** Install a package globally in WSL, auto-fixing the root-owned /usr/local/lib/node_modules
335
351
  * EACCES case by switching npm to a user prefix (~/.npm-global) and retrying once.
336
352
  * Output is captured (so we can scan for the error) and then mirrored to the terminal. */
337
353
  export declare function installInWsl(wslArgs: string[], opts?: {
338
354
  cwd?: string;
339
- }): {
355
+ }): Promise<{
340
356
  success: boolean;
341
357
  fixed: boolean;
342
- };
358
+ }>;
343
359
  /** Run a command and throw on failure */
344
360
  export declare function runCommandOrThrow(cmd: string, args: string[], options?: {
345
361
  silent?: boolean;
@@ -370,11 +386,11 @@ export declare function promptChoice(message: string, choices: string[]): Promis
370
386
  export declare function initGit(cwd: string, visibility: 'private' | 'public', dryRun: boolean, allowTs?: boolean): Promise<boolean>;
371
387
  /** Main globalize function */
372
388
  /** Run npm audit and optionally fix vulnerabilities */
373
- export declare function runNpmAudit(cwd: string, fix?: boolean, verbose?: boolean): {
389
+ export declare function runNpmAudit(cwd: string, fix?: boolean, verbose?: boolean): Promise<{
374
390
  success: boolean;
375
391
  report: string;
376
392
  hasVulnerabilities: boolean;
377
- };
393
+ }>;
378
394
  /** Get the version of npmglobalize itself */
379
395
  export declare function getToolVersion(): string;
380
396
  export declare function globalize(cwd: string, options?: GlobalizeOptions, configOptions?: Partial<GlobalizeOptions>): Promise<boolean>;
package/lib.js CHANGED
@@ -12,7 +12,7 @@
12
12
  import fs from 'fs';
13
13
  import os from 'os';
14
14
  import path from 'path';
15
- import { execSync, spawnSync } from 'child_process';
15
+ import { execSync, spawn, spawnSync } from 'child_process';
16
16
  import { readConfig as readUserConfig, writeConfig as writeUserConfig, configDir } from '@bobfrankston/userconfig';
17
17
  import { freezeDependencies } from '@bobfrankston/freezepak';
18
18
  import { importgen as runImportgen } from '@bobfrankston/importgen';
@@ -403,6 +403,29 @@ function emergencyRestoreDeps() {
403
403
  }
404
404
  dirtyPackageJsons.clear();
405
405
  }
406
+ /** Children spawned by `runCommandAsync`. The SIGINT handler iterates and kills
407
+ * these on abort so `npm` / `git` don't keep running and so cmd.exe doesn't
408
+ * prompt "Terminate batch job (Y/N)?" after we exit. */
409
+ const activeChildren = new Set();
410
+ /** Force-terminate a child process (and on Windows, its whole tree, since
411
+ * npm.cmd / gh.cmd are batch files that spawn node and won't be killed by a
412
+ * bare child.kill). */
413
+ function killChildTree(child) {
414
+ if (!child.pid || child.exitCode !== null || child.signalCode !== null)
415
+ return;
416
+ if (process.platform === 'win32') {
417
+ try {
418
+ spawnSync('taskkill', ['/T', '/F', '/PID', String(child.pid)], { stdio: 'ignore' });
419
+ }
420
+ catch { /* best-effort */ }
421
+ }
422
+ else {
423
+ try {
424
+ child.kill('SIGTERM');
425
+ }
426
+ catch { /* best-effort */ }
427
+ }
428
+ }
406
429
  let cleanupHandlersInstalled = false;
407
430
  /** Install signal/exit handlers that restore `.dependencies` backups on abnormal exit. */
408
431
  export function installCleanupHandlers() {
@@ -410,7 +433,22 @@ export function installCleanupHandlers() {
410
433
  return;
411
434
  cleanupHandlersInstalled = true;
412
435
  process.on('exit', emergencyRestoreDeps);
436
+ // Idempotent abort: first ^C prints an immediate ack so the user doesn't
437
+ // press again and accidentally kill the process mid-restore. Subsequent
438
+ // signals while already aborting are absorbed (with a brief reminder).
439
+ let aborting = false;
413
440
  const signalHandler = (signal) => {
441
+ if (aborting) {
442
+ console.error(colors.yellow(' ...still aborting, please wait'));
443
+ return;
444
+ }
445
+ aborting = true;
446
+ console.error(colors.yellow(`\n${signal} received — aborting, restoring file: dependencies...`));
447
+ // Kill any in-flight child process (npm/git/etc.) so it stops promptly
448
+ // and Windows cmd.exe doesn't ask "Terminate batch job (Y/N)?".
449
+ for (const child of activeChildren)
450
+ killChildTree(child);
451
+ activeChildren.clear();
414
452
  emergencyRestoreDeps();
415
453
  process.exit(128 + (signal === 'SIGINT' ? 2 : signal === 'SIGTERM' ? 15 : 1));
416
454
  };
@@ -418,11 +456,17 @@ export function installCleanupHandlers() {
418
456
  process.on('SIGTERM', signalHandler);
419
457
  process.on('SIGHUP', signalHandler);
420
458
  process.on('uncaughtException', (err) => {
459
+ if (aborting)
460
+ return;
461
+ aborting = true;
421
462
  console.error(colors.red('\nUncaught exception:'), err);
422
463
  emergencyRestoreDeps();
423
464
  process.exit(1);
424
465
  });
425
466
  process.on('unhandledRejection', (reason) => {
467
+ if (aborting)
468
+ return;
469
+ aborting = true;
426
470
  console.error(colors.red('\nUnhandled rejection:'), reason);
427
471
  emergencyRestoreDeps();
428
472
  process.exit(1);
@@ -645,7 +689,7 @@ export async function cascadePublicVisibility(cwd, opts = {}) {
645
689
  const access = checkNpmAccess(targetName);
646
690
  if (access === 'restricted') {
647
691
  if (!dryRun) {
648
- const r = runCommand('npm', ['access', 'set', 'status=public', targetName], { cwd: targetPath, silent: true });
692
+ const r = await runCommandAsync('npm', ['access', 'set', 'status=public', targetName], { cwd: targetPath, silent: true });
649
693
  if (r.success) {
650
694
  promoted.push(targetName);
651
695
  console.log(colors.green(` ✓ Flipped ${targetName} to PUBLIC on npm`));
@@ -1608,7 +1652,7 @@ function formatMissingReason(missing) {
1608
1652
  * "fresh clone, no `node_modules/`" and "dep added but `npm install` not
1609
1653
  * re-run" (partial-sync) cases. Also covers `cwd` itself on the first call.
1610
1654
  * Cycle-safe via the shared `visited` set. */
1611
- export function ensureFileDepModules(cwd, verbose = false, visited = new Set()) {
1655
+ export async function ensureFileDepModules(cwd, verbose = false, visited = new Set()) {
1612
1656
  const abs = path.resolve(cwd);
1613
1657
  if (visited.has(abs))
1614
1658
  return;
@@ -1635,7 +1679,7 @@ export function ensureFileDepModules(cwd, verbose = false, visited = new Set())
1635
1679
  catch { /* best-effort */ }
1636
1680
  }
1637
1681
  console.log(colors.yellow(`↻ installing node_modules in ${pkg?.name || abs} (${formatMissingReason(cwdMissing)})`));
1638
- const r = runCommand('npm', ['install'], { cwd: abs, silent: !verbose });
1682
+ const r = await runCommandAsync('npm', ['install'], { cwd: abs, silent: !verbose });
1639
1683
  if (!r.success) {
1640
1684
  console.error(colors.red(` ✗ npm install failed in ${abs}`));
1641
1685
  if (r.stderr)
@@ -1672,14 +1716,14 @@ export function ensureFileDepModules(cwd, verbose = false, visited = new Set())
1672
1716
  catch { /* best-effort */ }
1673
1717
  }
1674
1718
  console.log(colors.yellow(`↻ restoring node_modules in ${name} (${target}) (${formatMissingReason(targetMissing)})`));
1675
- const r = runCommand('npm', ['install'], { cwd: target, silent: !verbose });
1719
+ const r = await runCommandAsync('npm', ['install'], { cwd: target, silent: !verbose });
1676
1720
  if (!r.success) {
1677
1721
  console.error(colors.red(` ✗ npm install failed in ${target}`));
1678
1722
  if (r.stderr)
1679
1723
  console.error(colors.dim(r.stderr.split('\n').slice(0, 5).join('\n')));
1680
1724
  }
1681
1725
  }
1682
- ensureFileDepModules(target, verbose, visited);
1726
+ await ensureFileDepModules(target, verbose, visited);
1683
1727
  }
1684
1728
  }
1685
1729
  }
@@ -1716,7 +1760,7 @@ export async function buildProject(cwd, opts = {}) {
1716
1760
  }
1717
1761
  }
1718
1762
  console.log(`Building ${pkg.name || cwd}...`);
1719
- const buildResult = runCommand('npm', ['run', 'build'], { cwd, silent: true });
1763
+ const buildResult = await runCommandAsync('npm', ['run', 'build'], { cwd, silent: true });
1720
1764
  if (buildResult.success) {
1721
1765
  console.log(colors.green(`✓ Build succeeded (${pkg.name || path.basename(cwd)})`));
1722
1766
  return true;
@@ -1780,7 +1824,7 @@ export async function buildFileDepsTopologically(cwd, opts = {}, visited = new S
1780
1824
  * member `package.json` without a follow-up `npm install` at the root leaves
1781
1825
  * the root `node_modules/` stale — the case where `member/` dirs exist but
1782
1826
  * the actual package (e.g. `marked`) is not hoisted. */
1783
- export function ensureWorkspaceDepModules(rootDir, members, verbose = false) {
1827
+ export async function ensureWorkspaceDepModules(rootDir, members, verbose = false) {
1784
1828
  const root = path.resolve(rootDir);
1785
1829
  let rootPkg;
1786
1830
  try {
@@ -1800,7 +1844,7 @@ export function ensureWorkspaceDepModules(rootDir, members, verbose = false) {
1800
1844
  return;
1801
1845
  const list = [...allMissing];
1802
1846
  console.log(colors.yellow(`↻ installing workspace node_modules in ${rootPkg?.name || path.basename(root)} (${formatMissingReason(list)})`));
1803
- const r = runCommand('npm', ['install'], { cwd: root, silent: !verbose });
1847
+ const r = await runCommandAsync('npm', ['install'], { cwd: root, silent: !verbose });
1804
1848
  if (!r.success) {
1805
1849
  console.error(colors.red(` ✗ npm install failed in ${root}`));
1806
1850
  if (r.stderr)
@@ -1811,14 +1855,14 @@ export function ensureWorkspaceDepModules(rootDir, members, verbose = false) {
1811
1855
  * Brand-new packages (first-time publish) take much longer to become
1812
1856
  * installable than version bumps, so we use longer waits and more
1813
1857
  * attempts when `isNewPackage` is true. */
1814
- function installGlobalWithRetry(pkgSpec, cwd, isNewPackage = false, maxRetries) {
1858
+ async function installGlobalWithRetry(pkgSpec, cwd, isNewPackage = false, maxRetries) {
1815
1859
  const retries = maxRetries ?? (isNewPackage ? 6 : 3);
1816
1860
  const delaySec = isNewPackage ? 30 : 10;
1817
- let result = runCommand('npm', ['install', '-g', pkgSpec], { cwd, silent: false, showCommand: true });
1861
+ let result = await runCommandAsync('npm', ['install', '-g', pkgSpec], { cwd, silent: false, showCommand: true });
1818
1862
  for (let attempt = 1; attempt < retries && !result.success; attempt++) {
1819
1863
  console.log(colors.yellow(` Retrying install (attempt ${attempt + 1}/${retries}) in ${delaySec} seconds...`));
1820
1864
  sleepSync(delaySec * 1000);
1821
- result = runCommand('npm', ['install', '-g', pkgSpec], { cwd, silent: false, showCommand: true });
1865
+ result = await runCommandAsync('npm', ['install', '-g', pkgSpec], { cwd, silent: false, showCommand: true });
1822
1866
  }
1823
1867
  return result;
1824
1868
  }
@@ -1860,6 +1904,58 @@ export function runCommand(cmd, args, options = {}) {
1860
1904
  return { success: false, output: '', stderr: error.message };
1861
1905
  }
1862
1906
  }
1907
+ /** Async variant of `runCommand`: spawns the child via `child_process.spawn`
1908
+ * (not spawnSync) so the JS event loop keeps turning. Used for long-running
1909
+ * commands (npm install/pack/publish/build, git push/pull, gh, wsl) so that
1910
+ * a SIGINT pressed mid-command can fire its handler immediately, kill the
1911
+ * child, restore deps, and exit — instead of being queued behind spawnSync. */
1912
+ export function runCommandAsync(cmd, args, options = {}) {
1913
+ const { silent = false, verbose = false, showCommand = false, cwd } = options;
1914
+ const needsShell = cmd === 'npm' || cmd === 'npm.cmd' || cmd === 'gh';
1915
+ if (!silent && (showCommand || verbose)) {
1916
+ console.log(colors.cyan(`> ${cmd} ${args.join(' ')}`));
1917
+ }
1918
+ return new Promise((resolve) => {
1919
+ let child;
1920
+ try {
1921
+ const spawnOpts = {
1922
+ stdio: silent ? 'pipe' : 'inherit',
1923
+ cwd,
1924
+ env: process.env,
1925
+ shell: needsShell,
1926
+ };
1927
+ // Match spawnSafe's quoting when shell:true to avoid DEP0190
1928
+ if (needsShell && args.length > 0) {
1929
+ const cmdStr = [cmd, ...args].map(a => /[\s"&|<>^]/.test(a) ? `"${a.replace(/"/g, '\\"')}"` : a).join(' ');
1930
+ child = spawn(cmdStr, spawnOpts);
1931
+ }
1932
+ else {
1933
+ child = spawn(cmd, args, spawnOpts);
1934
+ }
1935
+ }
1936
+ catch (err) {
1937
+ resolve({ success: false, output: '', stderr: err.message, signal: null });
1938
+ return;
1939
+ }
1940
+ activeChildren.add(child);
1941
+ let stdout = '';
1942
+ let stderr = '';
1943
+ if (silent) {
1944
+ child.stdout?.setEncoding('utf-8');
1945
+ child.stderr?.setEncoding('utf-8');
1946
+ child.stdout?.on('data', (d) => { stdout += d; });
1947
+ child.stderr?.on('data', (d) => { stderr += d; });
1948
+ }
1949
+ child.on('error', (err) => {
1950
+ activeChildren.delete(child);
1951
+ resolve({ success: false, output: stdout, stderr: stderr || err.message, signal: null });
1952
+ });
1953
+ child.on('close', (code, signal) => {
1954
+ activeChildren.delete(child);
1955
+ resolve({ success: code === 0, output: stdout, stderr, signal });
1956
+ });
1957
+ });
1958
+ }
1863
1959
  /** Dump forensic info to a temp file when `npm pack` is killed by a spurious
1864
1960
  * Ctrl+C. Goal: correlate the failure with whatever else was attached to the
1865
1961
  * console (Claude Code wrapper, VS Code task, AV, etc.). Returns the log path. */
@@ -1902,9 +1998,9 @@ function dumpPackCtrlcDiagnostics(pkg, cwd, packOutput, packStderr) {
1902
1998
  /** Install a package globally in WSL, auto-fixing the root-owned /usr/local/lib/node_modules
1903
1999
  * EACCES case by switching npm to a user prefix (~/.npm-global) and retrying once.
1904
2000
  * Output is captured (so we can scan for the error) and then mirrored to the terminal. */
1905
- export function installInWsl(wslArgs, opts = {}) {
2001
+ export async function installInWsl(wslArgs, opts = {}) {
1906
2002
  console.log(colors.cyan(`> wsl ${wslArgs.join(' ')}`));
1907
- let result = runCommand('wsl', wslArgs, { cwd: opts.cwd, silent: true });
2003
+ let result = await runCommandAsync('wsl', wslArgs, { cwd: opts.cwd, silent: true });
1908
2004
  if (result.output)
1909
2005
  process.stdout.write(result.output);
1910
2006
  if (result.stderr)
@@ -1917,7 +2013,7 @@ export function installInWsl(wslArgs, opts = {}) {
1917
2013
  return { success: false, fixed: false };
1918
2014
  console.log(colors.yellow('Detected root-owned npm prefix in WSL — switching to ~/.npm-global and retrying...'));
1919
2015
  const fix = `set -e; npm config set prefix "$HOME/.npm-global"; mkdir -p "$HOME/.npm-global/bin"; if ! grep -q '\\.npm-global/bin' "$HOME/.bashrc" 2>/dev/null; then printf '\\n# npm user-prefix bin (added by npmglobalize)\\nexport PATH="$HOME/.npm-global/bin:$PATH"\\n' >> "$HOME/.bashrc"; fi`;
1920
- const fixResult = runCommand('wsl', ['bash', '-lc', fix], { silent: true });
2016
+ const fixResult = await runCommandAsync('wsl', ['bash', '-lc', fix], { silent: true });
1921
2017
  if (!fixResult.success) {
1922
2018
  console.error(colors.red('Failed to apply WSL npm prefix fix:'));
1923
2019
  if (fixResult.stderr)
@@ -1926,7 +2022,7 @@ export function installInWsl(wslArgs, opts = {}) {
1926
2022
  }
1927
2023
  console.log(colors.green('✓ WSL npm prefix set to ~/.npm-global; PATH appended to ~/.bashrc'));
1928
2024
  console.log(colors.cyan(`> wsl ${wslArgs.join(' ')}`));
1929
- result = runCommand('wsl', wslArgs, { cwd: opts.cwd, silent: true });
2025
+ result = await runCommandAsync('wsl', wslArgs, { cwd: opts.cwd, silent: true });
1930
2026
  if (result.output)
1931
2027
  process.stdout.write(result.output);
1932
2028
  if (result.stderr)
@@ -2531,11 +2627,11 @@ function parsePushProtection(errorOutput, cwd) {
2531
2627
  }
2532
2628
  /** Try to auto-bypass push protection for installed OAuth secrets via gh API.
2533
2629
  * Returns true if all bypasses succeeded and push should be retried. */
2534
- function tryAutoBypassPushProtection(ppInfo, cwd) {
2630
+ async function tryAutoBypassPushProtection(ppInfo, cwd) {
2535
2631
  if (!ppInfo.allInstalledOAuth || ppInfo.secrets.length === 0)
2536
2632
  return false;
2537
2633
  // Check if gh CLI is available
2538
- const ghCheck = runCommand('gh', ['auth', 'status'], { cwd, silent: true });
2634
+ const ghCheck = await runCommandAsync('gh', ['auth', 'status'], { cwd, silent: true });
2539
2635
  if (!ghCheck.success)
2540
2636
  return false;
2541
2637
  // Extract repo owner/name from unblock URLs or git remote
@@ -2565,7 +2661,7 @@ function tryAutoBypassPushProtection(ppInfo, cwd) {
2565
2661
  }
2566
2662
  const placeholderId = idMatch[1];
2567
2663
  console.log(colors.cyan(` Bypassing push protection for: ${s.type} (false_positive — installed app ID)...`));
2568
- const bypassResult = runCommand('gh', [
2664
+ const bypassResult = await runCommandAsync('gh', [
2569
2665
  'api', `repos/${repo}/secret-scanning/push-protection-bypasses`,
2570
2666
  '-X', 'POST',
2571
2667
  '-f', 'reason=false_positive',
@@ -2603,8 +2699,8 @@ function showPushProtectionGuidance(ppInfo) {
2603
2699
  }
2604
2700
  /** Push to git with push-protection detection and auto-bypass for installed OAuth.
2605
2701
  * Returns true if push succeeded (possibly after auto-bypass). */
2606
- function pushWithProtection(cwd, verbose) {
2607
- let pushResult = runCommand('git', ['push'], { cwd, silent: true });
2702
+ async function pushWithProtection(cwd, verbose) {
2703
+ let pushResult = await runCommandAsync('git', ['push'], { cwd, silent: true });
2608
2704
  if (pushResult.success) {
2609
2705
  if (verbose)
2610
2706
  console.log(colors.green(' ✓ Pushed to remote'));
@@ -2625,7 +2721,7 @@ function pushWithProtection(cwd, verbose) {
2625
2721
  // Detect current branch name
2626
2722
  const branchResult = runCommand('git', ['rev-parse', '--abbrev-ref', 'HEAD'], { cwd, silent: true });
2627
2723
  const branch = branchResult.output.trim() || 'master';
2628
- pushResult = runCommand('git', ['push', '--set-upstream', 'origin', branch], { cwd, silent: true });
2724
+ pushResult = await runCommandAsync('git', ['push', '--set-upstream', 'origin', branch], { cwd, silent: true });
2629
2725
  if (pushResult.success) {
2630
2726
  if (verbose)
2631
2727
  console.log(colors.green(' ✓ Pushed to remote (set upstream)'));
@@ -2635,7 +2731,7 @@ function pushWithProtection(cwd, verbose) {
2635
2731
  // Transient network errors (HTTP 408, RPC failed, unexpected disconnect) — retry once
2636
2732
  if (/rpc failed|curl \d+|unexpected disconnect|hung up unexpectedly/i.test(pushResult.stderr)) {
2637
2733
  console.log(colors.yellow('Git push failed with transient error — retrying...'));
2638
- pushResult = runCommand('git', ['push'], { cwd, silent: true });
2734
+ pushResult = await runCommandAsync('git', ['push'], { cwd, silent: true });
2639
2735
  if (pushResult.success) {
2640
2736
  if (verbose)
2641
2737
  console.log(colors.green(' ✓ Pushed to remote (retry)'));
@@ -2656,9 +2752,9 @@ function pushWithProtection(cwd, verbose) {
2656
2752
  return false;
2657
2753
  }
2658
2754
  // Try auto-bypass for installed OAuth credentials
2659
- if (ppInfo.allInstalledOAuth && tryAutoBypassPushProtection(ppInfo, cwd)) {
2755
+ if (ppInfo.allInstalledOAuth && await tryAutoBypassPushProtection(ppInfo, cwd)) {
2660
2756
  console.log(colors.green(' ✓ Auto-bypassed push protection (installed OAuth — not a real secret)'));
2661
- const retryPush = runCommand('git', ['push'], { cwd, silent: true });
2757
+ const retryPush = await runCommandAsync('git', ['push'], { cwd, silent: true });
2662
2758
  if (retryPush.success) {
2663
2759
  if (verbose)
2664
2760
  console.log(colors.green(' ✓ Pushed to remote'));
@@ -3049,7 +3145,7 @@ export async function initGit(cwd, visibility, dryRun, allowTs) {
3049
3145
  }
3050
3146
  // Create GitHub repo (or link to existing one)
3051
3147
  const visFlag = visibility === 'private' ? '--private' : '--public';
3052
- const createResult = runCommand('gh', ['repo', 'create', repoName, visFlag, '--source=.', '--push'], { cwd, silent: true });
3148
+ const createResult = await runCommandAsync('gh', ['repo', 'create', repoName, visFlag, '--source=.', '--push'], { cwd, silent: true });
3053
3149
  if (!createResult.success) {
3054
3150
  const errText = createResult.stderr + createResult.output;
3055
3151
  // Check if repo was created but push failed (GitHub propagation delay)
@@ -3082,7 +3178,7 @@ export async function initGit(cwd, visibility, dryRun, allowTs) {
3082
3178
  console.log(colors.yellow(' Retrying push...'));
3083
3179
  spawnSafe(process.platform === 'win32' ? 'timeout' : 'sleep', process.platform === 'win32' ? ['\t', '3', '\nobreak'] : ['3'], { stdio: 'pipe' }); // brief delay for GitHub propagation
3084
3180
  }
3085
- const pushRes = runCommand('git', ['push', '-u', 'origin', 'master'], { cwd, silent: true });
3181
+ const pushRes = await runCommandAsync('git', ['push', '-u', 'origin', 'master'], { cwd, silent: true });
3086
3182
  pushed = pushRes.success;
3087
3183
  }
3088
3184
  if (pushed) {
@@ -3114,9 +3210,9 @@ export async function initGit(cwd, visibility, dryRun, allowTs) {
3114
3210
  }
3115
3211
  /** Main globalize function */
3116
3212
  /** Run npm audit and optionally fix vulnerabilities */
3117
- export function runNpmAudit(cwd, fix = false, verbose = false) {
3213
+ export async function runNpmAudit(cwd, fix = false, verbose = false) {
3118
3214
  if (fix) {
3119
- runCommand('npm', ['audit', 'fix'], { cwd, silent: true });
3215
+ await runCommandAsync('npm', ['audit', 'fix'], { cwd, silent: true });
3120
3216
  }
3121
3217
  // Check remaining vulnerabilities
3122
3218
  const auditResult = spawnSafe('npm', ['audit', '--json'], {
@@ -3191,7 +3287,7 @@ async function doLocalInstall(cwd, options) {
3191
3287
  console.log(' [dry-run] Would run: wsl npm install -g .');
3192
3288
  return true;
3193
3289
  }
3194
- const result = runCommand('npm', ['install', '-g', '.'], { cwd, silent: false, showCommand: true });
3290
+ const result = await runCommandAsync('npm', ['install', '-g', '.'], { cwd, silent: false, showCommand: true });
3195
3291
  if (result.success) {
3196
3292
  console.log(colors.green(`✓ Installed locally: ${pkgName}@${pkgVersion}`));
3197
3293
  }
@@ -3202,7 +3298,7 @@ async function doLocalInstall(cwd, options) {
3202
3298
  }
3203
3299
  if (wsl) {
3204
3300
  console.log(`Installing ${pkgName} in WSL (local)...`);
3205
- const wslResult = runCommand('wsl', ['npm', 'install', '-g', '.'], { cwd, silent: false, showCommand: true });
3301
+ const wslResult = await runCommandAsync('wsl', ['npm', 'install', '-g', '.'], { cwd, silent: false, showCommand: true });
3206
3302
  if (wslResult.success) {
3207
3303
  console.log(colors.green(`✓ Installed in WSL: ${pkgName}@${pkgVersion}`));
3208
3304
  }
@@ -3307,7 +3403,7 @@ export async function globalize(cwd, options = {}, configOptions = {}) {
3307
3403
  console.log(' [dry-run] Would run: wsl npm install -g .');
3308
3404
  return true;
3309
3405
  }
3310
- const result = runCommand('npm', ['install', '-g', '.'], { cwd, silent: false, showCommand: true });
3406
+ const result = await runCommandAsync('npm', ['install', '-g', '.'], { cwd, silent: false, showCommand: true });
3311
3407
  if (result.success) {
3312
3408
  console.log(colors.green(`✓ Installed locally: ${pkgName}@${pkgVersion}`));
3313
3409
  }
@@ -3525,7 +3621,7 @@ export async function globalize(cwd, options = {}, configOptions = {}) {
3525
3621
  }
3526
3622
  else {
3527
3623
  console.log(`Changing GitHub repo visibility to ${targetVis}...`);
3528
- const visResult = runCommand('gh', ['repo', 'edit', '--visibility', targetVis, '--accept-visibility-change-consequences'], { cwd, silent: true });
3624
+ const visResult = await runCommandAsync('gh', ['repo', 'edit', '--visibility', targetVis, '--accept-visibility-change-consequences'], { cwd, silent: true });
3529
3625
  if (visResult.success) {
3530
3626
  console.log(colors.green(`✓ GitHub repo is now ${targetVis.toUpperCase()}`));
3531
3627
  }
@@ -3609,7 +3705,7 @@ export async function globalize(cwd, options = {}, configOptions = {}) {
3609
3705
  console.log(colors.yellow('Local branch is behind remote.'));
3610
3706
  if (rebase) {
3611
3707
  console.log('Rebasing local changes (--rebase)...');
3612
- const rebaseResult = runCommand('git', ['pull', '--rebase', 'origin', currentGitStatus.currentBranch], { cwd, silent: false });
3708
+ const rebaseResult = await runCommandAsync('git', ['pull', '--rebase', 'origin', currentGitStatus.currentBranch], { cwd, silent: false });
3613
3709
  if (!rebaseResult.success) {
3614
3710
  console.error(colors.red('ERROR: Rebase failed.'));
3615
3711
  console.error('You may need to resolve conflicts manually.');
@@ -3643,7 +3739,7 @@ export async function globalize(cwd, options = {}, configOptions = {}) {
3643
3739
  // with new declared deps still needs its own `npm install` even if it has
3644
3740
  // no build step. CLI entrypoint already ran this, so skip when _fromCli.
3645
3741
  if (!options._fromCli && !dryRun)
3646
- ensureFileDepModules(cwd, verbose);
3742
+ await ensureFileDepModules(cwd, verbose);
3647
3743
  // Run build step if package.json has a build script (skip if CLI already built)
3648
3744
  if (pkg.scripts?.build && !options._fromCli) {
3649
3745
  console.log(`${timestamp()} Running build...`);
@@ -3654,7 +3750,7 @@ export async function globalize(cwd, options = {}, configOptions = {}) {
3654
3750
  const restoreWorkspaces = reorderWorkspacesForBuild(cwd, pkg, verbose);
3655
3751
  try {
3656
3752
  // Always capture output so we can extract tsc errors for the summary
3657
- const buildResult = runCommand('npm', ['run', 'build'], { cwd, silent: true });
3753
+ const buildResult = await runCommandAsync('npm', ['run', 'build'], { cwd, silent: true });
3658
3754
  if (!buildResult.success) {
3659
3755
  const buildOutput = buildResult.stderr || buildResult.output;
3660
3756
  if (buildOutput)
@@ -3821,7 +3917,7 @@ export async function globalize(cwd, options = {}, configOptions = {}) {
3821
3917
  if (currentAccess === 'public') {
3822
3918
  console.log(colors.yellow(`Package '${pkg.name}' is currently PUBLIC on npm. Converting to private...`));
3823
3919
  if (!dryRun) {
3824
- const accessResult = runCommand('npm', ['access', 'set', 'status=restricted', pkg.name], { cwd, silent: false });
3920
+ const accessResult = await runCommandAsync('npm', ['access', 'set', 'status=restricted', pkg.name], { cwd, silent: false });
3825
3921
  if (accessResult.success) {
3826
3922
  console.log(colors.green(`✓ Changed ${pkg.name} to PRIVATE on npm`));
3827
3923
  currentAccess = 'restricted';
@@ -3844,7 +3940,7 @@ export async function globalize(cwd, options = {}, configOptions = {}) {
3844
3940
  if (currentAccess === 'restricted') {
3845
3941
  console.log(colors.yellow(`Package '${pkg.name}' is currently PRIVATE on npm. Converting to public...`));
3846
3942
  if (!dryRun) {
3847
- const accessResult = runCommand('npm', ['access', 'set', 'status=public', pkg.name], { cwd, silent: false });
3943
+ const accessResult = await runCommandAsync('npm', ['access', 'set', 'status=public', pkg.name], { cwd, silent: false });
3848
3944
  if (accessResult.success) {
3849
3945
  console.log(colors.green(`✓ Changed ${pkg.name} to PUBLIC on npm`));
3850
3946
  currentAccess = 'public';
@@ -4262,7 +4358,7 @@ export async function globalize(cwd, options = {}, configOptions = {}) {
4262
4358
  // Run npm audit if requested or if dependencies were transformed
4263
4359
  if ((fix || updateDeps) && (transformResult.transformed || alreadyTransformed || updateDeps)) {
4264
4360
  if (!dryRun) {
4265
- runNpmAudit(cwd, fix, verbose);
4361
+ await runNpmAudit(cwd, fix, verbose);
4266
4362
  }
4267
4363
  else {
4268
4364
  console.log(' [dry-run] Would run npm audit');
@@ -4270,7 +4366,7 @@ export async function globalize(cwd, options = {}, configOptions = {}) {
4270
4366
  }
4271
4367
  else if (fix && !dryRun) {
4272
4368
  // Run fix even if no deps changed
4273
- runNpmAudit(cwd, fix, verbose);
4369
+ await runNpmAudit(cwd, fix, verbose);
4274
4370
  }
4275
4371
  if (noPublish) {
4276
4372
  console.log('Transform complete (--nopublish mode).');
@@ -4293,7 +4389,7 @@ export async function globalize(cwd, options = {}, configOptions = {}) {
4293
4389
  runCommand('git', ['add', 'package.json'], { cwd });
4294
4390
  gitCommit('Restore file: dependencies', cwd);
4295
4391
  if (currentGitStatus.hasRemote) {
4296
- pushWithProtection(cwd, verbose);
4392
+ await pushWithProtection(cwd, verbose);
4297
4393
  }
4298
4394
  }
4299
4395
  }
@@ -4351,7 +4447,7 @@ export async function globalize(cwd, options = {}, configOptions = {}) {
4351
4447
  runCommand('git', ['add', 'package.json'], { cwd });
4352
4448
  gitCommit('Restore file: dependencies', cwd);
4353
4449
  if (currentGitStatus.hasRemote) {
4354
- pushWithProtection(cwd, verbose);
4450
+ await pushWithProtection(cwd, verbose);
4355
4451
  }
4356
4452
  }
4357
4453
  }
@@ -4413,7 +4509,7 @@ export async function globalize(cwd, options = {}, configOptions = {}) {
4413
4509
  // Push any unpushed commits (e.g., from a previous failed push)
4414
4510
  if (currentGitStatus.hasRemote && currentGitStatus.hasUnpushed && !dryRun) {
4415
4511
  console.log(colors.yellow('Pushing unpushed commits...'));
4416
- pushWithProtection(cwd, verbose);
4512
+ await pushWithProtection(cwd, verbose);
4417
4513
  }
4418
4514
  // If install/link flag is set, install globally
4419
4515
  if (install || link || wsl) {
@@ -4429,7 +4525,7 @@ export async function globalize(cwd, options = {}, configOptions = {}) {
4429
4525
  if (pkg.bin && (install || link || wsl)) {
4430
4526
  if (link) {
4431
4527
  console.log(`Installing ${pkgName} globally from local directory (link)...`);
4432
- const localInstallResult = runCommand('npm', ['install', '-g', '.'], { cwd, silent: false, showCommand: true });
4528
+ const localInstallResult = await runCommandAsync('npm', ['install', '-g', '.'], { cwd, silent: false, showCommand: true });
4433
4529
  if (localInstallResult.success) {
4434
4530
  console.log(colors.green(`✓ Linked globally: ${pkgName}@${pkgVersion}`));
4435
4531
  }
@@ -4444,7 +4540,7 @@ export async function globalize(cwd, options = {}, configOptions = {}) {
4444
4540
  // sibling imports. Local install handles workspace linking correctly.
4445
4541
  if (pkg.workspaces) {
4446
4542
  console.log(`Installing ${pkgName}@${pkgVersion} globally from local (workspace)...`);
4447
- const localResult = runCommand('npm', ['install', '-g', '.'], { cwd, silent: false, showCommand: true });
4543
+ const localResult = await runCommandAsync('npm', ['install', '-g', '.'], { cwd, silent: false, showCommand: true });
4448
4544
  if (localResult.success) {
4449
4545
  console.log(colors.green(`✓ Installed globally: ${pkgName}@${pkgVersion}`));
4450
4546
  }
@@ -4463,7 +4559,7 @@ export async function globalize(cwd, options = {}, configOptions = {}) {
4463
4559
  const vOnNpm = vCheck.status === 0 && vCheck.stdout?.trim() === pkgVersion;
4464
4560
  if (vOnNpm) {
4465
4561
  console.log(`Installing ${pkgName}@${pkgVersion} globally from registry...`);
4466
- const registryInstallResult = installGlobalWithRetry(`${pkgName}@${pkgVersion}`, cwd);
4562
+ const registryInstallResult = await installGlobalWithRetry(`${pkgName}@${pkgVersion}`, cwd);
4467
4563
  if (registryInstallResult.success) {
4468
4564
  console.log(colors.green(`✓ Installed globally: ${pkgName}@${pkgVersion}`));
4469
4565
  }
@@ -4483,7 +4579,7 @@ export async function globalize(cwd, options = {}, configOptions = {}) {
4483
4579
  else {
4484
4580
  console.log(colors.yellow(`${pkgName}@${pkgVersion} not found on npm — installing from local directory.`));
4485
4581
  console.log(colors.dim(' Use -m "message" to publish this version to npm.'));
4486
- const localResult = runCommand('npm', ['install', '-g', '.'], { cwd, silent: false, showCommand: true });
4582
+ const localResult = await runCommandAsync('npm', ['install', '-g', '.'], { cwd, silent: false, showCommand: true });
4487
4583
  if (localResult.success) {
4488
4584
  console.log(colors.green(`✓ Installed globally from local: ${pkgName}@${pkgVersion}`));
4489
4585
  }
@@ -4598,7 +4694,7 @@ export async function globalize(cwd, options = {}, configOptions = {}) {
4598
4694
  }
4599
4695
  // Pull latest from remote before version bump to avoid push rejection
4600
4696
  if (currentGitStatus.hasRemote && !dryRun) {
4601
- const pullResult = runCommand('git', ['pull', '--rebase', 'origin', currentGitStatus.currentBranch], { cwd, silent: true });
4697
+ const pullResult = await runCommandAsync('git', ['pull', '--rebase', 'origin', currentGitStatus.currentBranch], { cwd, silent: true });
4602
4698
  if (!pullResult.success) {
4603
4699
  console.error(colors.yellow('Warning: git pull --rebase failed before version bump'));
4604
4700
  if (verbose) {
@@ -4699,12 +4795,12 @@ export async function globalize(cwd, options = {}, configOptions = {}) {
4699
4795
  // Version bump + tag succeeded locally; only the push failed.
4700
4796
  // Auto-pull --rebase and retry the push.
4701
4797
  console.log(colors.yellow('\nLocal branch is behind remote — pulling with rebase...'));
4702
- const pullResult = runCommand('git', ['pull', '--rebase', 'origin', currentGitStatus.currentBranch], { cwd });
4798
+ const pullResult = await runCommandAsync('git', ['pull', '--rebase', 'origin', currentGitStatus.currentBranch], { cwd });
4703
4799
  if (pullResult.success) {
4704
4800
  console.log(colors.green(' ✓ Rebased onto remote'));
4705
- const pushResult = runCommand('git', ['push'], { cwd });
4801
+ const pushResult = await runCommandAsync('git', ['push'], { cwd });
4706
4802
  if (pushResult.success) {
4707
- const tagPush = runCommand('git', ['push', '--tags'], { cwd, silent: true });
4803
+ const tagPush = await runCommandAsync('git', ['push', '--tags'], { cwd, silent: true });
4708
4804
  if (tagPush.success) {
4709
4805
  console.log(colors.green(' ✓ Pushed with tags'));
4710
4806
  }
@@ -4979,11 +5075,11 @@ export async function globalize(cwd, options = {}, configOptions = {}) {
4979
5075
  // broadcast to the console group (e.g. from another tool sharing the
4980
5076
  // console) makes cmd.exe print "Terminate batch job (Y/N)?" and kill
4981
5077
  // npm.cmd. Our process survives, so a retry usually succeeds.
4982
- let packResult = runCommand('npm', ['pack'], { cwd, silent: true });
5078
+ let packResult = await runCommandAsync('npm', ['pack'], { cwd, silent: true });
4983
5079
  if (!packResult.success && /Terminate batch job|\^C/.test(packResult.output + packResult.stderr)) {
4984
5080
  const logPath = dumpPackCtrlcDiagnostics(pkg, cwd, packResult.output, packResult.stderr);
4985
5081
  console.error(colors.yellow(` npm pack interrupted by spurious Ctrl+C — retrying once... (diag: ${logPath})`));
4986
- packResult = runCommand('npm', ['pack'], { cwd, silent: true });
5082
+ packResult = await runCommandAsync('npm', ['pack'], { cwd, silent: true });
4987
5083
  }
4988
5084
  // Restore stashed node_modules now that pack is done — must happen
4989
5085
  // before any subsequent install -g symlinks to these dep targets.
@@ -5019,7 +5115,7 @@ export async function globalize(cwd, options = {}, configOptions = {}) {
5019
5115
  if (sizeMB > 50) {
5020
5116
  console.error(colors.red(`\n⚠ Tarball is ${sizeMB.toFixed(0)}MB — something large is being included!`));
5021
5117
  // Show biggest files in the tarball
5022
- const listResult = runCommand('npm', ['pack', '--dry-run'], { cwd, silent: true });
5118
+ const listResult = await runCommandAsync('npm', ['pack', '--dry-run'], { cwd, silent: true });
5023
5119
  if (listResult.success) {
5024
5120
  const lines = listResult.output.trim().split('\n')
5025
5121
  .filter(l => l.includes('B '))
@@ -5068,7 +5164,7 @@ export async function globalize(cwd, options = {}, configOptions = {}) {
5068
5164
  console.log(colors.yellow(`⏱ Waiting ${retryDelay} seconds before retry (attempt ${attempt + 1}/${maxRetries})...`));
5069
5165
  await new Promise(resolve => setTimeout(resolve, retryDelay * 1000));
5070
5166
  }
5071
- publishResult = runCommand('npm', npmArgs, { cwd, silent: true });
5167
+ publishResult = await runCommandAsync('npm', npmArgs, { cwd, silent: true });
5072
5168
  if (publishResult.success) {
5073
5169
  break; // Success!
5074
5170
  }
@@ -5186,7 +5282,7 @@ export async function globalize(cwd, options = {}, configOptions = {}) {
5186
5282
  if (logResult.success) {
5187
5283
  console.log(colors.green(` ✓ Appended to npmchanges.md and removed .commitmsg`));
5188
5284
  if (currentGitStatus.hasRemote)
5189
- pushWithProtection(cwd, verbose);
5285
+ await pushWithProtection(cwd, verbose);
5190
5286
  }
5191
5287
  }
5192
5288
  catch (err) {
@@ -5203,8 +5299,8 @@ export async function globalize(cwd, options = {}, configOptions = {}) {
5203
5299
  console.log(`${timestamp()} Pushing to git...`);
5204
5300
  }
5205
5301
  if (!dryRun) {
5206
- if (pushWithProtection(cwd, verbose)) {
5207
- const tagResult = runCommand('git', ['push', '--tags'], { cwd, silent: true });
5302
+ if (await pushWithProtection(cwd, verbose)) {
5303
+ const tagResult = await runCommandAsync('git', ['push', '--tags'], { cwd, silent: true });
5208
5304
  if (verbose && tagResult.success)
5209
5305
  console.log(colors.green(' ✓ Pushed tags'));
5210
5306
  }
@@ -5226,7 +5322,7 @@ export async function globalize(cwd, options = {}, configOptions = {}) {
5226
5322
  if (link) {
5227
5323
  console.log(`Linking globally: ${pkgName}@${pkgVersion}...`);
5228
5324
  if (!dryRun) {
5229
- const installResult = runCommand('npm', ['install', '-g', '.'], { cwd, silent: false, showCommand: true });
5325
+ const installResult = await runCommandAsync('npm', ['install', '-g', '.'], { cwd, silent: false, showCommand: true });
5230
5326
  if (installResult.success) {
5231
5327
  globalInstallOk = true;
5232
5328
  console.log(colors.green(`✓ Linked globally: ${pkgName}@${pkgVersion}`));
@@ -5246,7 +5342,7 @@ export async function globalize(cwd, options = {}, configOptions = {}) {
5246
5342
  // Workspace packages: install from local for proper workspace linking
5247
5343
  console.log(`Installing ${pkgName}@${pkgVersion} globally from local (workspace)...`);
5248
5344
  if (!dryRun) {
5249
- const installResult = runCommand('npm', ['install', '-g', '.'], { cwd, silent: false, showCommand: true });
5345
+ const installResult = await runCommandAsync('npm', ['install', '-g', '.'], { cwd, silent: false, showCommand: true });
5250
5346
  if (installResult.success) {
5251
5347
  globalInstallOk = true;
5252
5348
  console.log(colors.green(`✓ Installed globally: ${pkgName}@${pkgVersion}`));
@@ -5265,7 +5361,7 @@ export async function globalize(cwd, options = {}, configOptions = {}) {
5265
5361
  console.log(`${timestamp()} Installing globally from registry: ${pkgName}@${pkgVersion}...`);
5266
5362
  if (!dryRun) {
5267
5363
  waitForNpmVersion(pkgName, pkgVersion, wasNewPackage);
5268
- const installResult = installGlobalWithRetry(`${pkgName}@${pkgVersion}`, cwd, wasNewPackage);
5364
+ const installResult = await installGlobalWithRetry(`${pkgName}@${pkgVersion}`, cwd, wasNewPackage);
5269
5365
  if (installResult.success) {
5270
5366
  globalInstallOk = true;
5271
5367
  console.log(colors.green(`✓ Installed globally: ${pkgName}@${pkgVersion}`));
@@ -5297,7 +5393,7 @@ export async function globalize(cwd, options = {}, configOptions = {}) {
5297
5393
  waitForNpmVersion(pkgName, pkgVersion, wasNewPackage);
5298
5394
  console.log(`Installing in WSL${useLocalWsl ? ' (local)' : ' from registry'}: ${pkgName}@${pkgVersion}...`);
5299
5395
  if (!dryRun) {
5300
- const wslResult = installInWsl(wslArgs, { cwd });
5396
+ const wslResult = await installInWsl(wslArgs, { cwd });
5301
5397
  if (wslResult.success) {
5302
5398
  wslInstallOk = true;
5303
5399
  console.log(colors.green(`✓ Installed in WSL: ${pkgName}@${pkgVersion}${wslResult.fixed ? ' (after prefix fix)' : ''}`));
@@ -5337,7 +5433,7 @@ export async function globalize(cwd, options = {}, configOptions = {}) {
5337
5433
  runCommand('git', ['add', 'package.json'], { cwd });
5338
5434
  gitCommit('Restore file: dependencies', cwd);
5339
5435
  if (currentGitStatus.hasRemote) {
5340
- pushWithProtection(cwd, verbose);
5436
+ await pushWithProtection(cwd, verbose);
5341
5437
  }
5342
5438
  }
5343
5439
  }
@@ -5348,7 +5444,7 @@ export async function globalize(cwd, options = {}, configOptions = {}) {
5348
5444
  // Run final audit report if not already run
5349
5445
  const auditAlreadyRun = (fix || updateDeps) && (transformResult.transformed || alreadyTransformed || updateDeps);
5350
5446
  if (!auditAlreadyRun && (fix || updateDeps || transformResult.transformed) && !dryRun) {
5351
- runNpmAudit(cwd, false, verbose); // Just report, don't fix again
5447
+ await runNpmAudit(cwd, false, verbose); // Just report, don't fix again
5352
5448
  }
5353
5449
  // Print summary
5354
5450
  console.log('');
@@ -5611,7 +5707,7 @@ export async function globalizeWorkspace(rootDir, options = {}, configOptions =
5611
5707
  // the case where a dep was added to a member package.json but `npm install`
5612
5708
  // wasn't re-run — the builds would otherwise fail resolving the new dep.
5613
5709
  if (!options.dryRun) {
5614
- ensureWorkspaceDepModules(rootDir, packages.map(p => ({ dir: p.dir, pkg: p.pkg })), !!options.verbose);
5710
+ await ensureWorkspaceDepModules(rootDir, packages.map(p => ({ dir: p.dir, pkg: p.pkg })), !!options.verbose);
5615
5711
  }
5616
5712
  // Prescan: decide which packages actually need processing so we don't waste
5617
5713
  // time rebuilding+republishing ones with no relevant changes.
@@ -5805,7 +5901,7 @@ export async function globalizeWorkspace(rootDir, options = {}, configOptions =
5805
5901
  // Workspace root is private — always install from local directory
5806
5902
  console.log(`Installing ${pkgName}@${pkgVersion} globally from local directory...`);
5807
5903
  if (!dryRun) {
5808
- const installResult = runCommand('npm', ['install', '-g', '.'], { cwd: rootDir, silent: false, showCommand: true });
5904
+ const installResult = await runCommandAsync('npm', ['install', '-g', '.'], { cwd: rootDir, silent: false, showCommand: true });
5809
5905
  if (installResult.success) {
5810
5906
  console.log(colors.green(`✓ Installed globally: ${pkgName}@${pkgVersion}`));
5811
5907
  }
@@ -5822,7 +5918,7 @@ export async function globalizeWorkspace(rootDir, options = {}, configOptions =
5822
5918
  if (wsl) {
5823
5919
  console.log(`Installing ${pkgName} in WSL (local)...`);
5824
5920
  if (!dryRun) {
5825
- const wslResult = installInWsl(['npm', 'install', '-g', '.'], { cwd: rootDir });
5921
+ const wslResult = await installInWsl(['npm', 'install', '-g', '.'], { cwd: rootDir });
5826
5922
  if (wslResult.success) {
5827
5923
  console.log(colors.green(`✓ Installed in WSL: ${pkgName}@${pkgVersion}${wslResult.fixed ? ' (after prefix fix)' : ''}`));
5828
5924
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bobfrankston/npmglobalize",
3
- "version": "1.0.173",
3
+ "version": "1.0.175",
4
4
  "description": "Transform file: dependencies to npm versions for publishing",
5
5
  "main": "index.js",
6
6
  "type": "module",
@@ -33,6 +33,7 @@
33
33
  "dependencies": {
34
34
  "@bobfrankston/freezepak": "^0.1.8",
35
35
  "@bobfrankston/importgen": "^0.1.35",
36
+ "@bobfrankston/mailx": "^1.0.466",
36
37
  "@bobfrankston/npmglobalize": "^1.0.153",
37
38
  "@bobfrankston/themecolors": "^0.1.6",
38
39
  "@bobfrankston/userconfig": "^1.0.8",
@@ -49,6 +50,7 @@
49
50
  ".dependencies": {
50
51
  "@bobfrankston/freezepak": "file:../freezepak",
51
52
  "@bobfrankston/importgen": "file:../importgen",
53
+ "@bobfrankston/mailx": "^1.0.466",
52
54
  "@bobfrankston/npmglobalize": "^1.0.153",
53
55
  "@bobfrankston/themecolors": "file:../themecolors",
54
56
  "@bobfrankston/userconfig": "file:../userconfig",
@@ -63,6 +65,7 @@
63
65
  "dependencies": {
64
66
  "@bobfrankston/freezepak": "^0.1.8",
65
67
  "@bobfrankston/importgen": "^0.1.35",
68
+ "@bobfrankston/mailx": "^1.0.466",
66
69
  "@bobfrankston/npmglobalize": "^1.0.153",
67
70
  "@bobfrankston/themecolors": "^0.1.6",
68
71
  "@bobfrankston/userconfig": "^1.0.8",