@bobfrankston/npmglobalize 1.0.174 → 1.0.176

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 +164 -67
  4. package/package.json +3 -3
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() {
@@ -421,6 +444,11 @@ export function installCleanupHandlers() {
421
444
  }
422
445
  aborting = true;
423
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();
424
452
  emergencyRestoreDeps();
425
453
  process.exit(128 + (signal === 'SIGINT' ? 2 : signal === 'SIGTERM' ? 15 : 1));
426
454
  };
@@ -661,7 +689,7 @@ export async function cascadePublicVisibility(cwd, opts = {}) {
661
689
  const access = checkNpmAccess(targetName);
662
690
  if (access === 'restricted') {
663
691
  if (!dryRun) {
664
- 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 });
665
693
  if (r.success) {
666
694
  promoted.push(targetName);
667
695
  console.log(colors.green(` ✓ Flipped ${targetName} to PUBLIC on npm`));
@@ -1624,7 +1652,7 @@ function formatMissingReason(missing) {
1624
1652
  * "fresh clone, no `node_modules/`" and "dep added but `npm install` not
1625
1653
  * re-run" (partial-sync) cases. Also covers `cwd` itself on the first call.
1626
1654
  * Cycle-safe via the shared `visited` set. */
1627
- export function ensureFileDepModules(cwd, verbose = false, visited = new Set()) {
1655
+ export async function ensureFileDepModules(cwd, verbose = false, visited = new Set()) {
1628
1656
  const abs = path.resolve(cwd);
1629
1657
  if (visited.has(abs))
1630
1658
  return;
@@ -1651,7 +1679,7 @@ export function ensureFileDepModules(cwd, verbose = false, visited = new Set())
1651
1679
  catch { /* best-effort */ }
1652
1680
  }
1653
1681
  console.log(colors.yellow(`↻ installing node_modules in ${pkg?.name || abs} (${formatMissingReason(cwdMissing)})`));
1654
- const r = runCommand('npm', ['install'], { cwd: abs, silent: !verbose });
1682
+ const r = await runCommandAsync('npm', ['install'], { cwd: abs, silent: !verbose });
1655
1683
  if (!r.success) {
1656
1684
  console.error(colors.red(` ✗ npm install failed in ${abs}`));
1657
1685
  if (r.stderr)
@@ -1688,14 +1716,14 @@ export function ensureFileDepModules(cwd, verbose = false, visited = new Set())
1688
1716
  catch { /* best-effort */ }
1689
1717
  }
1690
1718
  console.log(colors.yellow(`↻ restoring node_modules in ${name} (${target}) (${formatMissingReason(targetMissing)})`));
1691
- const r = runCommand('npm', ['install'], { cwd: target, silent: !verbose });
1719
+ const r = await runCommandAsync('npm', ['install'], { cwd: target, silent: !verbose });
1692
1720
  if (!r.success) {
1693
1721
  console.error(colors.red(` ✗ npm install failed in ${target}`));
1694
1722
  if (r.stderr)
1695
1723
  console.error(colors.dim(r.stderr.split('\n').slice(0, 5).join('\n')));
1696
1724
  }
1697
1725
  }
1698
- ensureFileDepModules(target, verbose, visited);
1726
+ await ensureFileDepModules(target, verbose, visited);
1699
1727
  }
1700
1728
  }
1701
1729
  }
@@ -1732,7 +1760,7 @@ export async function buildProject(cwd, opts = {}) {
1732
1760
  }
1733
1761
  }
1734
1762
  console.log(`Building ${pkg.name || cwd}...`);
1735
- const buildResult = runCommand('npm', ['run', 'build'], { cwd, silent: true });
1763
+ const buildResult = await runCommandAsync('npm', ['run', 'build'], { cwd, silent: true });
1736
1764
  if (buildResult.success) {
1737
1765
  console.log(colors.green(`✓ Build succeeded (${pkg.name || path.basename(cwd)})`));
1738
1766
  return true;
@@ -1796,7 +1824,7 @@ export async function buildFileDepsTopologically(cwd, opts = {}, visited = new S
1796
1824
  * member `package.json` without a follow-up `npm install` at the root leaves
1797
1825
  * the root `node_modules/` stale — the case where `member/` dirs exist but
1798
1826
  * the actual package (e.g. `marked`) is not hoisted. */
1799
- export function ensureWorkspaceDepModules(rootDir, members, verbose = false) {
1827
+ export async function ensureWorkspaceDepModules(rootDir, members, verbose = false) {
1800
1828
  const root = path.resolve(rootDir);
1801
1829
  let rootPkg;
1802
1830
  try {
@@ -1816,7 +1844,7 @@ export function ensureWorkspaceDepModules(rootDir, members, verbose = false) {
1816
1844
  return;
1817
1845
  const list = [...allMissing];
1818
1846
  console.log(colors.yellow(`↻ installing workspace node_modules in ${rootPkg?.name || path.basename(root)} (${formatMissingReason(list)})`));
1819
- const r = runCommand('npm', ['install'], { cwd: root, silent: !verbose });
1847
+ const r = await runCommandAsync('npm', ['install'], { cwd: root, silent: !verbose });
1820
1848
  if (!r.success) {
1821
1849
  console.error(colors.red(` ✗ npm install failed in ${root}`));
1822
1850
  if (r.stderr)
@@ -1827,14 +1855,14 @@ export function ensureWorkspaceDepModules(rootDir, members, verbose = false) {
1827
1855
  * Brand-new packages (first-time publish) take much longer to become
1828
1856
  * installable than version bumps, so we use longer waits and more
1829
1857
  * attempts when `isNewPackage` is true. */
1830
- function installGlobalWithRetry(pkgSpec, cwd, isNewPackage = false, maxRetries) {
1858
+ async function installGlobalWithRetry(pkgSpec, cwd, isNewPackage = false, maxRetries) {
1831
1859
  const retries = maxRetries ?? (isNewPackage ? 6 : 3);
1832
1860
  const delaySec = isNewPackage ? 30 : 10;
1833
- 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 });
1834
1862
  for (let attempt = 1; attempt < retries && !result.success; attempt++) {
1835
1863
  console.log(colors.yellow(` Retrying install (attempt ${attempt + 1}/${retries}) in ${delaySec} seconds...`));
1836
1864
  sleepSync(delaySec * 1000);
1837
- result = runCommand('npm', ['install', '-g', pkgSpec], { cwd, silent: false, showCommand: true });
1865
+ result = await runCommandAsync('npm', ['install', '-g', pkgSpec], { cwd, silent: false, showCommand: true });
1838
1866
  }
1839
1867
  return result;
1840
1868
  }
@@ -1876,6 +1904,58 @@ export function runCommand(cmd, args, options = {}) {
1876
1904
  return { success: false, output: '', stderr: error.message };
1877
1905
  }
1878
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
+ }
1879
1959
  /** Dump forensic info to a temp file when `npm pack` is killed by a spurious
1880
1960
  * Ctrl+C. Goal: correlate the failure with whatever else was attached to the
1881
1961
  * console (Claude Code wrapper, VS Code task, AV, etc.). Returns the log path. */
@@ -1918,9 +1998,9 @@ function dumpPackCtrlcDiagnostics(pkg, cwd, packOutput, packStderr) {
1918
1998
  /** Install a package globally in WSL, auto-fixing the root-owned /usr/local/lib/node_modules
1919
1999
  * EACCES case by switching npm to a user prefix (~/.npm-global) and retrying once.
1920
2000
  * Output is captured (so we can scan for the error) and then mirrored to the terminal. */
1921
- export function installInWsl(wslArgs, opts = {}) {
2001
+ export async function installInWsl(wslArgs, opts = {}) {
1922
2002
  console.log(colors.cyan(`> wsl ${wslArgs.join(' ')}`));
1923
- let result = runCommand('wsl', wslArgs, { cwd: opts.cwd, silent: true });
2003
+ let result = await runCommandAsync('wsl', wslArgs, { cwd: opts.cwd, silent: true });
1924
2004
  if (result.output)
1925
2005
  process.stdout.write(result.output);
1926
2006
  if (result.stderr)
@@ -1933,7 +2013,7 @@ export function installInWsl(wslArgs, opts = {}) {
1933
2013
  return { success: false, fixed: false };
1934
2014
  console.log(colors.yellow('Detected root-owned npm prefix in WSL — switching to ~/.npm-global and retrying...'));
1935
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`;
1936
- const fixResult = runCommand('wsl', ['bash', '-lc', fix], { silent: true });
2016
+ const fixResult = await runCommandAsync('wsl', ['bash', '-lc', fix], { silent: true });
1937
2017
  if (!fixResult.success) {
1938
2018
  console.error(colors.red('Failed to apply WSL npm prefix fix:'));
1939
2019
  if (fixResult.stderr)
@@ -1942,7 +2022,7 @@ export function installInWsl(wslArgs, opts = {}) {
1942
2022
  }
1943
2023
  console.log(colors.green('✓ WSL npm prefix set to ~/.npm-global; PATH appended to ~/.bashrc'));
1944
2024
  console.log(colors.cyan(`> wsl ${wslArgs.join(' ')}`));
1945
- result = runCommand('wsl', wslArgs, { cwd: opts.cwd, silent: true });
2025
+ result = await runCommandAsync('wsl', wslArgs, { cwd: opts.cwd, silent: true });
1946
2026
  if (result.output)
1947
2027
  process.stdout.write(result.output);
1948
2028
  if (result.stderr)
@@ -2547,11 +2627,11 @@ function parsePushProtection(errorOutput, cwd) {
2547
2627
  }
2548
2628
  /** Try to auto-bypass push protection for installed OAuth secrets via gh API.
2549
2629
  * Returns true if all bypasses succeeded and push should be retried. */
2550
- function tryAutoBypassPushProtection(ppInfo, cwd) {
2630
+ async function tryAutoBypassPushProtection(ppInfo, cwd) {
2551
2631
  if (!ppInfo.allInstalledOAuth || ppInfo.secrets.length === 0)
2552
2632
  return false;
2553
2633
  // Check if gh CLI is available
2554
- const ghCheck = runCommand('gh', ['auth', 'status'], { cwd, silent: true });
2634
+ const ghCheck = await runCommandAsync('gh', ['auth', 'status'], { cwd, silent: true });
2555
2635
  if (!ghCheck.success)
2556
2636
  return false;
2557
2637
  // Extract repo owner/name from unblock URLs or git remote
@@ -2581,7 +2661,7 @@ function tryAutoBypassPushProtection(ppInfo, cwd) {
2581
2661
  }
2582
2662
  const placeholderId = idMatch[1];
2583
2663
  console.log(colors.cyan(` Bypassing push protection for: ${s.type} (false_positive — installed app ID)...`));
2584
- const bypassResult = runCommand('gh', [
2664
+ const bypassResult = await runCommandAsync('gh', [
2585
2665
  'api', `repos/${repo}/secret-scanning/push-protection-bypasses`,
2586
2666
  '-X', 'POST',
2587
2667
  '-f', 'reason=false_positive',
@@ -2619,8 +2699,8 @@ function showPushProtectionGuidance(ppInfo) {
2619
2699
  }
2620
2700
  /** Push to git with push-protection detection and auto-bypass for installed OAuth.
2621
2701
  * Returns true if push succeeded (possibly after auto-bypass). */
2622
- function pushWithProtection(cwd, verbose) {
2623
- let pushResult = runCommand('git', ['push'], { cwd, silent: true });
2702
+ async function pushWithProtection(cwd, verbose) {
2703
+ let pushResult = await runCommandAsync('git', ['push'], { cwd, silent: true });
2624
2704
  if (pushResult.success) {
2625
2705
  if (verbose)
2626
2706
  console.log(colors.green(' ✓ Pushed to remote'));
@@ -2641,7 +2721,7 @@ function pushWithProtection(cwd, verbose) {
2641
2721
  // Detect current branch name
2642
2722
  const branchResult = runCommand('git', ['rev-parse', '--abbrev-ref', 'HEAD'], { cwd, silent: true });
2643
2723
  const branch = branchResult.output.trim() || 'master';
2644
- pushResult = runCommand('git', ['push', '--set-upstream', 'origin', branch], { cwd, silent: true });
2724
+ pushResult = await runCommandAsync('git', ['push', '--set-upstream', 'origin', branch], { cwd, silent: true });
2645
2725
  if (pushResult.success) {
2646
2726
  if (verbose)
2647
2727
  console.log(colors.green(' ✓ Pushed to remote (set upstream)'));
@@ -2651,7 +2731,7 @@ function pushWithProtection(cwd, verbose) {
2651
2731
  // Transient network errors (HTTP 408, RPC failed, unexpected disconnect) — retry once
2652
2732
  if (/rpc failed|curl \d+|unexpected disconnect|hung up unexpectedly/i.test(pushResult.stderr)) {
2653
2733
  console.log(colors.yellow('Git push failed with transient error — retrying...'));
2654
- pushResult = runCommand('git', ['push'], { cwd, silent: true });
2734
+ pushResult = await runCommandAsync('git', ['push'], { cwd, silent: true });
2655
2735
  if (pushResult.success) {
2656
2736
  if (verbose)
2657
2737
  console.log(colors.green(' ✓ Pushed to remote (retry)'));
@@ -2672,9 +2752,9 @@ function pushWithProtection(cwd, verbose) {
2672
2752
  return false;
2673
2753
  }
2674
2754
  // Try auto-bypass for installed OAuth credentials
2675
- if (ppInfo.allInstalledOAuth && tryAutoBypassPushProtection(ppInfo, cwd)) {
2755
+ if (ppInfo.allInstalledOAuth && await tryAutoBypassPushProtection(ppInfo, cwd)) {
2676
2756
  console.log(colors.green(' ✓ Auto-bypassed push protection (installed OAuth — not a real secret)'));
2677
- const retryPush = runCommand('git', ['push'], { cwd, silent: true });
2757
+ const retryPush = await runCommandAsync('git', ['push'], { cwd, silent: true });
2678
2758
  if (retryPush.success) {
2679
2759
  if (verbose)
2680
2760
  console.log(colors.green(' ✓ Pushed to remote'));
@@ -3065,7 +3145,7 @@ export async function initGit(cwd, visibility, dryRun, allowTs) {
3065
3145
  }
3066
3146
  // Create GitHub repo (or link to existing one)
3067
3147
  const visFlag = visibility === 'private' ? '--private' : '--public';
3068
- 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 });
3069
3149
  if (!createResult.success) {
3070
3150
  const errText = createResult.stderr + createResult.output;
3071
3151
  // Check if repo was created but push failed (GitHub propagation delay)
@@ -3098,7 +3178,7 @@ export async function initGit(cwd, visibility, dryRun, allowTs) {
3098
3178
  console.log(colors.yellow(' Retrying push...'));
3099
3179
  spawnSafe(process.platform === 'win32' ? 'timeout' : 'sleep', process.platform === 'win32' ? ['\t', '3', '\nobreak'] : ['3'], { stdio: 'pipe' }); // brief delay for GitHub propagation
3100
3180
  }
3101
- const pushRes = runCommand('git', ['push', '-u', 'origin', 'master'], { cwd, silent: true });
3181
+ const pushRes = await runCommandAsync('git', ['push', '-u', 'origin', 'master'], { cwd, silent: true });
3102
3182
  pushed = pushRes.success;
3103
3183
  }
3104
3184
  if (pushed) {
@@ -3130,9 +3210,9 @@ export async function initGit(cwd, visibility, dryRun, allowTs) {
3130
3210
  }
3131
3211
  /** Main globalize function */
3132
3212
  /** Run npm audit and optionally fix vulnerabilities */
3133
- export function runNpmAudit(cwd, fix = false, verbose = false) {
3213
+ export async function runNpmAudit(cwd, fix = false, verbose = false) {
3134
3214
  if (fix) {
3135
- runCommand('npm', ['audit', 'fix'], { cwd, silent: true });
3215
+ await runCommandAsync('npm', ['audit', 'fix'], { cwd, silent: true });
3136
3216
  }
3137
3217
  // Check remaining vulnerabilities
3138
3218
  const auditResult = spawnSafe('npm', ['audit', '--json'], {
@@ -3207,7 +3287,7 @@ async function doLocalInstall(cwd, options) {
3207
3287
  console.log(' [dry-run] Would run: wsl npm install -g .');
3208
3288
  return true;
3209
3289
  }
3210
- const result = runCommand('npm', ['install', '-g', '.'], { cwd, silent: false, showCommand: true });
3290
+ const result = await runCommandAsync('npm', ['install', '-g', '.'], { cwd, silent: false, showCommand: true });
3211
3291
  if (result.success) {
3212
3292
  console.log(colors.green(`✓ Installed locally: ${pkgName}@${pkgVersion}`));
3213
3293
  }
@@ -3218,7 +3298,7 @@ async function doLocalInstall(cwd, options) {
3218
3298
  }
3219
3299
  if (wsl) {
3220
3300
  console.log(`Installing ${pkgName} in WSL (local)...`);
3221
- 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 });
3222
3302
  if (wslResult.success) {
3223
3303
  console.log(colors.green(`✓ Installed in WSL: ${pkgName}@${pkgVersion}`));
3224
3304
  }
@@ -3323,7 +3403,7 @@ export async function globalize(cwd, options = {}, configOptions = {}) {
3323
3403
  console.log(' [dry-run] Would run: wsl npm install -g .');
3324
3404
  return true;
3325
3405
  }
3326
- const result = runCommand('npm', ['install', '-g', '.'], { cwd, silent: false, showCommand: true });
3406
+ const result = await runCommandAsync('npm', ['install', '-g', '.'], { cwd, silent: false, showCommand: true });
3327
3407
  if (result.success) {
3328
3408
  console.log(colors.green(`✓ Installed locally: ${pkgName}@${pkgVersion}`));
3329
3409
  }
@@ -3541,7 +3621,7 @@ export async function globalize(cwd, options = {}, configOptions = {}) {
3541
3621
  }
3542
3622
  else {
3543
3623
  console.log(`Changing GitHub repo visibility to ${targetVis}...`);
3544
- 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 });
3545
3625
  if (visResult.success) {
3546
3626
  console.log(colors.green(`✓ GitHub repo is now ${targetVis.toUpperCase()}`));
3547
3627
  }
@@ -3625,7 +3705,7 @@ export async function globalize(cwd, options = {}, configOptions = {}) {
3625
3705
  console.log(colors.yellow('Local branch is behind remote.'));
3626
3706
  if (rebase) {
3627
3707
  console.log('Rebasing local changes (--rebase)...');
3628
- 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 });
3629
3709
  if (!rebaseResult.success) {
3630
3710
  console.error(colors.red('ERROR: Rebase failed.'));
3631
3711
  console.error('You may need to resolve conflicts manually.');
@@ -3659,7 +3739,7 @@ export async function globalize(cwd, options = {}, configOptions = {}) {
3659
3739
  // with new declared deps still needs its own `npm install` even if it has
3660
3740
  // no build step. CLI entrypoint already ran this, so skip when _fromCli.
3661
3741
  if (!options._fromCli && !dryRun)
3662
- ensureFileDepModules(cwd, verbose);
3742
+ await ensureFileDepModules(cwd, verbose);
3663
3743
  // Run build step if package.json has a build script (skip if CLI already built)
3664
3744
  if (pkg.scripts?.build && !options._fromCli) {
3665
3745
  console.log(`${timestamp()} Running build...`);
@@ -3670,7 +3750,7 @@ export async function globalize(cwd, options = {}, configOptions = {}) {
3670
3750
  const restoreWorkspaces = reorderWorkspacesForBuild(cwd, pkg, verbose);
3671
3751
  try {
3672
3752
  // Always capture output so we can extract tsc errors for the summary
3673
- const buildResult = runCommand('npm', ['run', 'build'], { cwd, silent: true });
3753
+ const buildResult = await runCommandAsync('npm', ['run', 'build'], { cwd, silent: true });
3674
3754
  if (!buildResult.success) {
3675
3755
  const buildOutput = buildResult.stderr || buildResult.output;
3676
3756
  if (buildOutput)
@@ -3837,7 +3917,7 @@ export async function globalize(cwd, options = {}, configOptions = {}) {
3837
3917
  if (currentAccess === 'public') {
3838
3918
  console.log(colors.yellow(`Package '${pkg.name}' is currently PUBLIC on npm. Converting to private...`));
3839
3919
  if (!dryRun) {
3840
- 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 });
3841
3921
  if (accessResult.success) {
3842
3922
  console.log(colors.green(`✓ Changed ${pkg.name} to PRIVATE on npm`));
3843
3923
  currentAccess = 'restricted';
@@ -3860,7 +3940,7 @@ export async function globalize(cwd, options = {}, configOptions = {}) {
3860
3940
  if (currentAccess === 'restricted') {
3861
3941
  console.log(colors.yellow(`Package '${pkg.name}' is currently PRIVATE on npm. Converting to public...`));
3862
3942
  if (!dryRun) {
3863
- 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 });
3864
3944
  if (accessResult.success) {
3865
3945
  console.log(colors.green(`✓ Changed ${pkg.name} to PUBLIC on npm`));
3866
3946
  currentAccess = 'public';
@@ -4278,7 +4358,7 @@ export async function globalize(cwd, options = {}, configOptions = {}) {
4278
4358
  // Run npm audit if requested or if dependencies were transformed
4279
4359
  if ((fix || updateDeps) && (transformResult.transformed || alreadyTransformed || updateDeps)) {
4280
4360
  if (!dryRun) {
4281
- runNpmAudit(cwd, fix, verbose);
4361
+ await runNpmAudit(cwd, fix, verbose);
4282
4362
  }
4283
4363
  else {
4284
4364
  console.log(' [dry-run] Would run npm audit');
@@ -4286,7 +4366,7 @@ export async function globalize(cwd, options = {}, configOptions = {}) {
4286
4366
  }
4287
4367
  else if (fix && !dryRun) {
4288
4368
  // Run fix even if no deps changed
4289
- runNpmAudit(cwd, fix, verbose);
4369
+ await runNpmAudit(cwd, fix, verbose);
4290
4370
  }
4291
4371
  if (noPublish) {
4292
4372
  console.log('Transform complete (--nopublish mode).');
@@ -4309,7 +4389,7 @@ export async function globalize(cwd, options = {}, configOptions = {}) {
4309
4389
  runCommand('git', ['add', 'package.json'], { cwd });
4310
4390
  gitCommit('Restore file: dependencies', cwd);
4311
4391
  if (currentGitStatus.hasRemote) {
4312
- pushWithProtection(cwd, verbose);
4392
+ await pushWithProtection(cwd, verbose);
4313
4393
  }
4314
4394
  }
4315
4395
  }
@@ -4367,7 +4447,7 @@ export async function globalize(cwd, options = {}, configOptions = {}) {
4367
4447
  runCommand('git', ['add', 'package.json'], { cwd });
4368
4448
  gitCommit('Restore file: dependencies', cwd);
4369
4449
  if (currentGitStatus.hasRemote) {
4370
- pushWithProtection(cwd, verbose);
4450
+ await pushWithProtection(cwd, verbose);
4371
4451
  }
4372
4452
  }
4373
4453
  }
@@ -4429,7 +4509,7 @@ export async function globalize(cwd, options = {}, configOptions = {}) {
4429
4509
  // Push any unpushed commits (e.g., from a previous failed push)
4430
4510
  if (currentGitStatus.hasRemote && currentGitStatus.hasUnpushed && !dryRun) {
4431
4511
  console.log(colors.yellow('Pushing unpushed commits...'));
4432
- pushWithProtection(cwd, verbose);
4512
+ await pushWithProtection(cwd, verbose);
4433
4513
  }
4434
4514
  // If install/link flag is set, install globally
4435
4515
  if (install || link || wsl) {
@@ -4445,7 +4525,7 @@ export async function globalize(cwd, options = {}, configOptions = {}) {
4445
4525
  if (pkg.bin && (install || link || wsl)) {
4446
4526
  if (link) {
4447
4527
  console.log(`Installing ${pkgName} globally from local directory (link)...`);
4448
- const localInstallResult = runCommand('npm', ['install', '-g', '.'], { cwd, silent: false, showCommand: true });
4528
+ const localInstallResult = await runCommandAsync('npm', ['install', '-g', '.'], { cwd, silent: false, showCommand: true });
4449
4529
  if (localInstallResult.success) {
4450
4530
  console.log(colors.green(`✓ Linked globally: ${pkgName}@${pkgVersion}`));
4451
4531
  }
@@ -4460,7 +4540,7 @@ export async function globalize(cwd, options = {}, configOptions = {}) {
4460
4540
  // sibling imports. Local install handles workspace linking correctly.
4461
4541
  if (pkg.workspaces) {
4462
4542
  console.log(`Installing ${pkgName}@${pkgVersion} globally from local (workspace)...`);
4463
- const localResult = runCommand('npm', ['install', '-g', '.'], { cwd, silent: false, showCommand: true });
4543
+ const localResult = await runCommandAsync('npm', ['install', '-g', '.'], { cwd, silent: false, showCommand: true });
4464
4544
  if (localResult.success) {
4465
4545
  console.log(colors.green(`✓ Installed globally: ${pkgName}@${pkgVersion}`));
4466
4546
  }
@@ -4479,7 +4559,7 @@ export async function globalize(cwd, options = {}, configOptions = {}) {
4479
4559
  const vOnNpm = vCheck.status === 0 && vCheck.stdout?.trim() === pkgVersion;
4480
4560
  if (vOnNpm) {
4481
4561
  console.log(`Installing ${pkgName}@${pkgVersion} globally from registry...`);
4482
- const registryInstallResult = installGlobalWithRetry(`${pkgName}@${pkgVersion}`, cwd);
4562
+ const registryInstallResult = await installGlobalWithRetry(`${pkgName}@${pkgVersion}`, cwd);
4483
4563
  if (registryInstallResult.success) {
4484
4564
  console.log(colors.green(`✓ Installed globally: ${pkgName}@${pkgVersion}`));
4485
4565
  }
@@ -4499,7 +4579,7 @@ export async function globalize(cwd, options = {}, configOptions = {}) {
4499
4579
  else {
4500
4580
  console.log(colors.yellow(`${pkgName}@${pkgVersion} not found on npm — installing from local directory.`));
4501
4581
  console.log(colors.dim(' Use -m "message" to publish this version to npm.'));
4502
- const localResult = runCommand('npm', ['install', '-g', '.'], { cwd, silent: false, showCommand: true });
4582
+ const localResult = await runCommandAsync('npm', ['install', '-g', '.'], { cwd, silent: false, showCommand: true });
4503
4583
  if (localResult.success) {
4504
4584
  console.log(colors.green(`✓ Installed globally from local: ${pkgName}@${pkgVersion}`));
4505
4585
  }
@@ -4552,7 +4632,10 @@ export async function globalize(cwd, options = {}, configOptions = {}) {
4552
4632
  if (nulCount > 0 && verbose) {
4553
4633
  console.log(` Removed ${nulCount} 'nul' file(s) (Windows reserved name)`);
4554
4634
  }
4635
+ const addStart = Date.now();
4636
+ process.stdout.write(` git add -A ... `);
4555
4637
  let addResult = runCommand('git', ['add', '-A'], { cwd, silent: true });
4638
+ process.stdout.write(`done (${((Date.now() - addStart) / 1000).toFixed(1)}s)\n`);
4556
4639
  if (!addResult.success) {
4557
4640
  const errText = addResult.stderr + addResult.output;
4558
4641
  const deniedFiles = parseDeniedFiles(errText);
@@ -4572,7 +4655,10 @@ export async function globalize(cwd, options = {}, configOptions = {}) {
4572
4655
  }
4573
4656
  fs.writeFileSync(gitignorePath, gitignoreContent);
4574
4657
  console.log(colors.green('✓ Updated .gitignore'));
4658
+ const retryStart = Date.now();
4659
+ console.log(` ${timestamp()} git add -A (retry) ...`);
4575
4660
  addResult = runCommand('git', ['add', '-A'], { cwd, silent: true });
4661
+ console.log(` ${timestamp()} git add -A done (${((Date.now() - retryStart) / 1000).toFixed(1)}s)`);
4576
4662
  if (addResult.success) {
4577
4663
  console.log(colors.green('✓ git add succeeded after updating .gitignore'));
4578
4664
  }
@@ -4589,7 +4675,10 @@ export async function globalize(cwd, options = {}, configOptions = {}) {
4589
4675
  console.log(colors.yellow('Continuing with --force...'));
4590
4676
  }
4591
4677
  }
4678
+ const commitStart = Date.now();
4679
+ console.log(` ${timestamp()} git commit ... (pre-commit hooks may run)`);
4592
4680
  let commitResult = gitCommit(commitMsg, cwd);
4681
+ console.log(` ${timestamp()} git commit done (${((Date.now() - commitStart) / 1000).toFixed(1)}s)`);
4593
4682
  if (!commitResult.success) {
4594
4683
  // Check for corrupted git index ("invalid object" / "Error building trees")
4595
4684
  const errText = commitResult.stderr + commitResult.output;
@@ -4614,7 +4703,15 @@ export async function globalize(cwd, options = {}, configOptions = {}) {
4614
4703
  }
4615
4704
  // Pull latest from remote before version bump to avoid push rejection
4616
4705
  if (currentGitStatus.hasRemote && !dryRun) {
4617
- const pullResult = runCommand('git', ['pull', '--rebase', 'origin', currentGitStatus.currentBranch], { cwd, silent: true });
4706
+ const pullStart = Date.now();
4707
+ console.log(` ${timestamp()} git pull --rebase origin ${currentGitStatus.currentBranch} ...`);
4708
+ const heartbeat = setInterval(() => {
4709
+ const elapsed = ((Date.now() - pullStart) / 1000).toFixed(0);
4710
+ console.log(` ${timestamp()} ... still pulling (${elapsed}s elapsed)`);
4711
+ }, 10000);
4712
+ const pullResult = await runCommandAsync('git', ['pull', '--rebase', 'origin', currentGitStatus.currentBranch], { cwd, silent: true });
4713
+ clearInterval(heartbeat);
4714
+ console.log(` ${timestamp()} git pull done (${((Date.now() - pullStart) / 1000).toFixed(1)}s)`);
4618
4715
  if (!pullResult.success) {
4619
4716
  console.error(colors.yellow('Warning: git pull --rebase failed before version bump'));
4620
4717
  if (verbose) {
@@ -4715,12 +4812,12 @@ export async function globalize(cwd, options = {}, configOptions = {}) {
4715
4812
  // Version bump + tag succeeded locally; only the push failed.
4716
4813
  // Auto-pull --rebase and retry the push.
4717
4814
  console.log(colors.yellow('\nLocal branch is behind remote — pulling with rebase...'));
4718
- const pullResult = runCommand('git', ['pull', '--rebase', 'origin', currentGitStatus.currentBranch], { cwd });
4815
+ const pullResult = await runCommandAsync('git', ['pull', '--rebase', 'origin', currentGitStatus.currentBranch], { cwd });
4719
4816
  if (pullResult.success) {
4720
4817
  console.log(colors.green(' ✓ Rebased onto remote'));
4721
- const pushResult = runCommand('git', ['push'], { cwd });
4818
+ const pushResult = await runCommandAsync('git', ['push'], { cwd });
4722
4819
  if (pushResult.success) {
4723
- const tagPush = runCommand('git', ['push', '--tags'], { cwd, silent: true });
4820
+ const tagPush = await runCommandAsync('git', ['push', '--tags'], { cwd, silent: true });
4724
4821
  if (tagPush.success) {
4725
4822
  console.log(colors.green(' ✓ Pushed with tags'));
4726
4823
  }
@@ -4995,11 +5092,11 @@ export async function globalize(cwd, options = {}, configOptions = {}) {
4995
5092
  // broadcast to the console group (e.g. from another tool sharing the
4996
5093
  // console) makes cmd.exe print "Terminate batch job (Y/N)?" and kill
4997
5094
  // npm.cmd. Our process survives, so a retry usually succeeds.
4998
- let packResult = runCommand('npm', ['pack'], { cwd, silent: true });
5095
+ let packResult = await runCommandAsync('npm', ['pack'], { cwd, silent: true });
4999
5096
  if (!packResult.success && /Terminate batch job|\^C/.test(packResult.output + packResult.stderr)) {
5000
5097
  const logPath = dumpPackCtrlcDiagnostics(pkg, cwd, packResult.output, packResult.stderr);
5001
5098
  console.error(colors.yellow(` npm pack interrupted by spurious Ctrl+C — retrying once... (diag: ${logPath})`));
5002
- packResult = runCommand('npm', ['pack'], { cwd, silent: true });
5099
+ packResult = await runCommandAsync('npm', ['pack'], { cwd, silent: true });
5003
5100
  }
5004
5101
  // Restore stashed node_modules now that pack is done — must happen
5005
5102
  // before any subsequent install -g symlinks to these dep targets.
@@ -5035,7 +5132,7 @@ export async function globalize(cwd, options = {}, configOptions = {}) {
5035
5132
  if (sizeMB > 50) {
5036
5133
  console.error(colors.red(`\n⚠ Tarball is ${sizeMB.toFixed(0)}MB — something large is being included!`));
5037
5134
  // Show biggest files in the tarball
5038
- const listResult = runCommand('npm', ['pack', '--dry-run'], { cwd, silent: true });
5135
+ const listResult = await runCommandAsync('npm', ['pack', '--dry-run'], { cwd, silent: true });
5039
5136
  if (listResult.success) {
5040
5137
  const lines = listResult.output.trim().split('\n')
5041
5138
  .filter(l => l.includes('B '))
@@ -5084,7 +5181,7 @@ export async function globalize(cwd, options = {}, configOptions = {}) {
5084
5181
  console.log(colors.yellow(`⏱ Waiting ${retryDelay} seconds before retry (attempt ${attempt + 1}/${maxRetries})...`));
5085
5182
  await new Promise(resolve => setTimeout(resolve, retryDelay * 1000));
5086
5183
  }
5087
- publishResult = runCommand('npm', npmArgs, { cwd, silent: true });
5184
+ publishResult = await runCommandAsync('npm', npmArgs, { cwd, silent: true });
5088
5185
  if (publishResult.success) {
5089
5186
  break; // Success!
5090
5187
  }
@@ -5202,7 +5299,7 @@ export async function globalize(cwd, options = {}, configOptions = {}) {
5202
5299
  if (logResult.success) {
5203
5300
  console.log(colors.green(` ✓ Appended to npmchanges.md and removed .commitmsg`));
5204
5301
  if (currentGitStatus.hasRemote)
5205
- pushWithProtection(cwd, verbose);
5302
+ await pushWithProtection(cwd, verbose);
5206
5303
  }
5207
5304
  }
5208
5305
  catch (err) {
@@ -5219,8 +5316,8 @@ export async function globalize(cwd, options = {}, configOptions = {}) {
5219
5316
  console.log(`${timestamp()} Pushing to git...`);
5220
5317
  }
5221
5318
  if (!dryRun) {
5222
- if (pushWithProtection(cwd, verbose)) {
5223
- const tagResult = runCommand('git', ['push', '--tags'], { cwd, silent: true });
5319
+ if (await pushWithProtection(cwd, verbose)) {
5320
+ const tagResult = await runCommandAsync('git', ['push', '--tags'], { cwd, silent: true });
5224
5321
  if (verbose && tagResult.success)
5225
5322
  console.log(colors.green(' ✓ Pushed tags'));
5226
5323
  }
@@ -5242,7 +5339,7 @@ export async function globalize(cwd, options = {}, configOptions = {}) {
5242
5339
  if (link) {
5243
5340
  console.log(`Linking globally: ${pkgName}@${pkgVersion}...`);
5244
5341
  if (!dryRun) {
5245
- const installResult = runCommand('npm', ['install', '-g', '.'], { cwd, silent: false, showCommand: true });
5342
+ const installResult = await runCommandAsync('npm', ['install', '-g', '.'], { cwd, silent: false, showCommand: true });
5246
5343
  if (installResult.success) {
5247
5344
  globalInstallOk = true;
5248
5345
  console.log(colors.green(`✓ Linked globally: ${pkgName}@${pkgVersion}`));
@@ -5262,7 +5359,7 @@ export async function globalize(cwd, options = {}, configOptions = {}) {
5262
5359
  // Workspace packages: install from local for proper workspace linking
5263
5360
  console.log(`Installing ${pkgName}@${pkgVersion} globally from local (workspace)...`);
5264
5361
  if (!dryRun) {
5265
- const installResult = runCommand('npm', ['install', '-g', '.'], { cwd, silent: false, showCommand: true });
5362
+ const installResult = await runCommandAsync('npm', ['install', '-g', '.'], { cwd, silent: false, showCommand: true });
5266
5363
  if (installResult.success) {
5267
5364
  globalInstallOk = true;
5268
5365
  console.log(colors.green(`✓ Installed globally: ${pkgName}@${pkgVersion}`));
@@ -5281,7 +5378,7 @@ export async function globalize(cwd, options = {}, configOptions = {}) {
5281
5378
  console.log(`${timestamp()} Installing globally from registry: ${pkgName}@${pkgVersion}...`);
5282
5379
  if (!dryRun) {
5283
5380
  waitForNpmVersion(pkgName, pkgVersion, wasNewPackage);
5284
- const installResult = installGlobalWithRetry(`${pkgName}@${pkgVersion}`, cwd, wasNewPackage);
5381
+ const installResult = await installGlobalWithRetry(`${pkgName}@${pkgVersion}`, cwd, wasNewPackage);
5285
5382
  if (installResult.success) {
5286
5383
  globalInstallOk = true;
5287
5384
  console.log(colors.green(`✓ Installed globally: ${pkgName}@${pkgVersion}`));
@@ -5313,7 +5410,7 @@ export async function globalize(cwd, options = {}, configOptions = {}) {
5313
5410
  waitForNpmVersion(pkgName, pkgVersion, wasNewPackage);
5314
5411
  console.log(`Installing in WSL${useLocalWsl ? ' (local)' : ' from registry'}: ${pkgName}@${pkgVersion}...`);
5315
5412
  if (!dryRun) {
5316
- const wslResult = installInWsl(wslArgs, { cwd });
5413
+ const wslResult = await installInWsl(wslArgs, { cwd });
5317
5414
  if (wslResult.success) {
5318
5415
  wslInstallOk = true;
5319
5416
  console.log(colors.green(`✓ Installed in WSL: ${pkgName}@${pkgVersion}${wslResult.fixed ? ' (after prefix fix)' : ''}`));
@@ -5353,7 +5450,7 @@ export async function globalize(cwd, options = {}, configOptions = {}) {
5353
5450
  runCommand('git', ['add', 'package.json'], { cwd });
5354
5451
  gitCommit('Restore file: dependencies', cwd);
5355
5452
  if (currentGitStatus.hasRemote) {
5356
- pushWithProtection(cwd, verbose);
5453
+ await pushWithProtection(cwd, verbose);
5357
5454
  }
5358
5455
  }
5359
5456
  }
@@ -5364,7 +5461,7 @@ export async function globalize(cwd, options = {}, configOptions = {}) {
5364
5461
  // Run final audit report if not already run
5365
5462
  const auditAlreadyRun = (fix || updateDeps) && (transformResult.transformed || alreadyTransformed || updateDeps);
5366
5463
  if (!auditAlreadyRun && (fix || updateDeps || transformResult.transformed) && !dryRun) {
5367
- runNpmAudit(cwd, false, verbose); // Just report, don't fix again
5464
+ await runNpmAudit(cwd, false, verbose); // Just report, don't fix again
5368
5465
  }
5369
5466
  // Print summary
5370
5467
  console.log('');
@@ -5627,7 +5724,7 @@ export async function globalizeWorkspace(rootDir, options = {}, configOptions =
5627
5724
  // the case where a dep was added to a member package.json but `npm install`
5628
5725
  // wasn't re-run — the builds would otherwise fail resolving the new dep.
5629
5726
  if (!options.dryRun) {
5630
- ensureWorkspaceDepModules(rootDir, packages.map(p => ({ dir: p.dir, pkg: p.pkg })), !!options.verbose);
5727
+ await ensureWorkspaceDepModules(rootDir, packages.map(p => ({ dir: p.dir, pkg: p.pkg })), !!options.verbose);
5631
5728
  }
5632
5729
  // Prescan: decide which packages actually need processing so we don't waste
5633
5730
  // time rebuilding+republishing ones with no relevant changes.
@@ -5821,7 +5918,7 @@ export async function globalizeWorkspace(rootDir, options = {}, configOptions =
5821
5918
  // Workspace root is private — always install from local directory
5822
5919
  console.log(`Installing ${pkgName}@${pkgVersion} globally from local directory...`);
5823
5920
  if (!dryRun) {
5824
- const installResult = runCommand('npm', ['install', '-g', '.'], { cwd: rootDir, silent: false, showCommand: true });
5921
+ const installResult = await runCommandAsync('npm', ['install', '-g', '.'], { cwd: rootDir, silent: false, showCommand: true });
5825
5922
  if (installResult.success) {
5826
5923
  console.log(colors.green(`✓ Installed globally: ${pkgName}@${pkgVersion}`));
5827
5924
  }
@@ -5838,7 +5935,7 @@ export async function globalizeWorkspace(rootDir, options = {}, configOptions =
5838
5935
  if (wsl) {
5839
5936
  console.log(`Installing ${pkgName} in WSL (local)...`);
5840
5937
  if (!dryRun) {
5841
- const wslResult = installInWsl(['npm', 'install', '-g', '.'], { cwd: rootDir });
5938
+ const wslResult = await installInWsl(['npm', 'install', '-g', '.'], { cwd: rootDir });
5842
5939
  if (wslResult.success) {
5843
5940
  console.log(colors.green(`✓ Installed in WSL: ${pkgName}@${pkgVersion}${wslResult.fixed ? ' (after prefix fix)' : ''}`));
5844
5941
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bobfrankston/npmglobalize",
3
- "version": "1.0.174",
3
+ "version": "1.0.176",
4
4
  "description": "Transform file: dependencies to npm versions for publishing",
5
5
  "main": "index.js",
6
6
  "type": "module",
@@ -36,7 +36,7 @@
36
36
  "@bobfrankston/mailx": "^1.0.466",
37
37
  "@bobfrankston/npmglobalize": "^1.0.153",
38
38
  "@bobfrankston/themecolors": "^0.1.6",
39
- "@bobfrankston/userconfig": "^1.0.8",
39
+ "@bobfrankston/userconfig": "^1.0.9",
40
40
  "@npmcli/package-json": "^7.0.4",
41
41
  "json5": "^2.2.3",
42
42
  "libnpmversion": "^8.0.3",
@@ -68,7 +68,7 @@
68
68
  "@bobfrankston/mailx": "^1.0.466",
69
69
  "@bobfrankston/npmglobalize": "^1.0.153",
70
70
  "@bobfrankston/themecolors": "^0.1.6",
71
- "@bobfrankston/userconfig": "^1.0.8",
71
+ "@bobfrankston/userconfig": "^1.0.9",
72
72
  "@npmcli/package-json": "^7.0.4",
73
73
  "json5": "^2.2.3",
74
74
  "libnpmversion": "^8.0.3",