@bobfrankston/npmglobalize 1.0.123 → 1.0.125

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 (3) hide show
  1. package/lib.d.ts +2 -0
  2. package/lib.js +78 -21
  3. package/package.json +1 -1
package/lib.d.ts CHANGED
@@ -78,6 +78,8 @@ export interface GlobalizeOptions {
78
78
  local?: boolean;
79
79
  /** Freeze node_modules: replace symlinks/junctions with real copies for network share use */
80
80
  freeze?: boolean;
81
+ /** Internal: auto-initialize git repos without prompting (user chose "all") */
82
+ autoInit?: boolean;
81
83
  /** Internal: signals this call is from workspace orchestrator */
82
84
  _fromWorkspace?: boolean;
83
85
  /** Internal: signals this call is from CLI (version already printed) */
package/lib.js CHANGED
@@ -681,6 +681,55 @@ function hasUnpublishedTransitiveDeps(packageName, pkg, baseDir, verbose) {
681
681
  }
682
682
  return false;
683
683
  }
684
+ /** Check if local package directory has changes newer than the npm-published version.
685
+ * Detects uncommitted changes or commits made after the version was published. */
686
+ function hasLocalChanges(packageName, version, targetPath, verbose) {
687
+ try {
688
+ // Check 1: Uncommitted changes in the package directory
689
+ const statusResult = spawnSafe('git', ['-C', targetPath, 'status', '--porcelain', '.'], {
690
+ encoding: 'utf-8',
691
+ stdio: 'pipe',
692
+ shell: true
693
+ });
694
+ if (statusResult.status === 0 && statusResult.stdout.trim()) {
695
+ if (verbose) {
696
+ console.log(colors.yellow(` ${packageName} has uncommitted changes`));
697
+ }
698
+ return true;
699
+ }
700
+ // Check 2: Compare latest commit time in directory vs npm publish time
701
+ const timeResult = spawnSafe('npm', ['view', packageName, 'time', '--json'], {
702
+ encoding: 'utf-8',
703
+ stdio: 'pipe',
704
+ shell: true
705
+ });
706
+ if (timeResult.status !== 0 || !timeResult.stdout.trim())
707
+ return false;
708
+ const times = JSON.parse(timeResult.stdout.trim());
709
+ const publishTime = times[version];
710
+ if (!publishTime)
711
+ return false;
712
+ const publishDate = new Date(publishTime);
713
+ const logResult = spawnSafe('git', ['-C', targetPath, 'log', '-1', '--format=%aI', '--', '.'], {
714
+ encoding: 'utf-8',
715
+ stdio: 'pipe',
716
+ shell: true
717
+ });
718
+ if (logResult.status !== 0 || !logResult.stdout.trim())
719
+ return false;
720
+ const latestCommitDate = new Date(logResult.stdout.trim());
721
+ if (latestCommitDate > publishDate) {
722
+ if (verbose) {
723
+ console.log(colors.yellow(` ${packageName} has commits newer than npm publish`));
724
+ }
725
+ return true;
726
+ }
727
+ return false;
728
+ }
729
+ catch {
730
+ return false;
731
+ }
732
+ }
684
733
  /** Transform file: dependencies to npm versions */
685
734
  export function transformDeps(pkg, baseDir, verbose = false, forcePublish = false) {
686
735
  let transformed = false;
@@ -744,6 +793,10 @@ export function transformDeps(pkg, baseDir, verbose = false, forcePublish = fals
744
793
  unpublished.push({ name, version: targetVersion, path: targetPath });
745
794
  console.log(colors.yellow(` ⟳ ${name}@${targetVersion} has unpublished transitive deps — will republish`));
746
795
  }
796
+ else if (hasLocalChanges(name, targetVersion, targetPath, verbose)) {
797
+ unpublished.push({ name, version: targetVersion, path: targetPath });
798
+ console.log(colors.yellow(` ⟳ ${name}@${targetVersion} has local changes not on npm — will republish`));
799
+ }
747
800
  }
748
801
  pkg[key][name] = npmVersion;
749
802
  if (verbose) {
@@ -2379,8 +2432,8 @@ export async function globalize(cwd, options = {}, configOptions = {}) {
2379
2432
  if (dryRun) {
2380
2433
  console.log(' [dry-run] Would initialize git repository');
2381
2434
  }
2382
- else if (!init) {
2383
- const choice = await promptChoice('No git repository found. What would you like to do?\n 1) Initialize git repository (default)\n 2) Use local install only (skip git/publish)\n 3) Abort\nChoice:', ['1', '2', '3', '']);
2435
+ else if (!init && !options.autoInit) {
2436
+ const choice = await promptChoice('No git repository found. What would you like to do?\n 1) Initialize git repository (default)\n a) Initialize ALL (don\'t ask again for remaining deps)\n 2) Use local install only (skip git/publish)\n 3) Abort\nChoice:', ['1', 'a', '2', '3', '']);
2384
2437
  if (choice === '2') {
2385
2438
  console.log(colors.dim('Switching to local-only mode...'));
2386
2439
  writeConfig(cwd, { ...configOptions, local: true }, new Set(['local']));
@@ -2390,7 +2443,10 @@ export async function globalize(cwd, options = {}, configOptions = {}) {
2390
2443
  console.log('Aborted. Run with --init to initialize.');
2391
2444
  return false;
2392
2445
  }
2393
- // choice is '1' or '' (default)
2446
+ if (choice === 'a') {
2447
+ options.autoInit = true;
2448
+ }
2449
+ // choice is '1', 'a', or '' (default)
2394
2450
  const success = await initGit(cwd, gitVisibility, dryRun);
2395
2451
  if (!success)
2396
2452
  return false;
@@ -2405,8 +2461,8 @@ export async function globalize(cwd, options = {}, configOptions = {}) {
2405
2461
  }
2406
2462
  else if (!gitStatus.hasRemote) {
2407
2463
  // Git repo exists but no remote - need to create GitHub repo
2408
- if (!init) {
2409
- const choice = await promptChoice('No git remote configured. What would you like to do?\n 1) Create GitHub repository (default)\n 2) Use local install only (skip git/publish)\n 3) Abort\nChoice:', ['1', '2', '3', '']);
2464
+ if (!init && !options.autoInit) {
2465
+ const choice = await promptChoice('No git remote configured. What would you like to do?\n 1) Create GitHub repository (default)\n a) Initialize ALL (don\'t ask again for remaining deps)\n 2) Use local install only (skip git/publish)\n 3) Abort\nChoice:', ['1', 'a', '2', '3', '']);
2410
2466
  if (choice === '2') {
2411
2467
  console.log(colors.dim('Switching to local-only mode...'));
2412
2468
  writeConfig(cwd, { ...configOptions, local: true }, new Set(['local']));
@@ -2416,6 +2472,9 @@ export async function globalize(cwd, options = {}, configOptions = {}) {
2416
2472
  console.log('Aborted. Run with --init to set up GitHub repository.');
2417
2473
  return false;
2418
2474
  }
2475
+ if (choice === 'a') {
2476
+ options.autoInit = true;
2477
+ }
2419
2478
  }
2420
2479
  const success = await initGit(cwd, gitVisibility, dryRun);
2421
2480
  if (!success)
@@ -2959,7 +3018,8 @@ export async function globalize(cwd, options = {}, configOptions = {}) {
2959
3018
  conform, // Propagate conform to dependencies
2960
3019
  publishDeps, // Propagate so transitive deps also get published
2961
3020
  forcePublish, // Propagate so transitive deps get force-published too
2962
- rebase // Propagate so behind-remote deps get rebased automatically
3021
+ rebase, // Propagate so behind-remote deps get rebased automatically
3022
+ autoInit: options.autoInit // Propagate "init all" choice
2963
3023
  });
2964
3024
  if (!depSuccess) {
2965
3025
  console.error(colors.red(`Failed to publish ${name}`));
@@ -2997,13 +3057,12 @@ export async function globalize(cwd, options = {}, configOptions = {}) {
2997
3057
  else if (!alreadyTransformed && verbose) {
2998
3058
  console.log(' No file: dependencies found.');
2999
3059
  }
3000
- // Hoist workspace sub-package deps into root for publishing/global install.
3001
- // npm install -g only installs root-level dependencies and doesn't process
3002
- // the workspaces field, so:
3003
- // 1. Third-party deps (express, ws, etc.) must be in root dependencies
3004
- // 2. Sibling workspace packages must be in bundledDependencies so npm
3005
- // includes them in the tarball's node_modules/
3006
- // All changes are temporary — restoreDeps removes them via .dependencies backup.
3060
+ // Hoist workspace sub-package third-party deps into root for global install.
3061
+ // npm install -g from local handles workspace linking, but third-party deps
3062
+ // (express, ws, etc.) in sub-packages must also be in root dependencies
3063
+ // so they get installed. Workspace sibling refs are skipped npm install -g .
3064
+ // handles those via workspace linking.
3065
+ // Changes are temporary — restoreDeps removes them via .dependencies backup.
3007
3066
  if (pkg.workspaces) {
3008
3067
  const wsPatterns = Array.isArray(pkg.workspaces) ? pkg.workspaces : pkg.workspaces.packages || [];
3009
3068
  // Collect all workspace sub-package dirs
@@ -3024,8 +3083,8 @@ export async function globalize(cwd, options = {}, configOptions = {}) {
3024
3083
  wsDirs.push(dir);
3025
3084
  }
3026
3085
  }
3027
- // Collect workspace package names and their relative paths
3028
- const wsPackages = new Map(); // name -> relative dir
3086
+ // Collect workspace package names (to skip as siblings)
3087
+ const wsNames = new Set();
3029
3088
  for (const dir of wsDirs) {
3030
3089
  const pkgJsonPath = path.join(dir, 'package.json');
3031
3090
  if (!fs.existsSync(pkgJsonPath))
@@ -3033,7 +3092,7 @@ export async function globalize(cwd, options = {}, configOptions = {}) {
3033
3092
  try {
3034
3093
  const subPkg = JSON.parse(fs.readFileSync(pkgJsonPath, 'utf-8'));
3035
3094
  if (subPkg.name)
3036
- wsPackages.set(subPkg.name, path.relative(cwd, dir).replace(/\\/g, '/'));
3095
+ wsNames.add(subPkg.name);
3037
3096
  }
3038
3097
  catch { /* skip */ }
3039
3098
  }
@@ -3053,8 +3112,8 @@ export async function globalize(cwd, options = {}, configOptions = {}) {
3053
3112
  }
3054
3113
  const deps = subPkg.dependencies || {};
3055
3114
  for (const [dep, ver] of Object.entries(deps)) {
3056
- if (wsPackages.has(dep))
3057
- continue; // sibling workspace — handled via bundledDependencies
3115
+ if (wsNames.has(dep))
3116
+ continue; // sibling workspace — handled by npm install -g .
3058
3117
  if (dep in rootDeps)
3059
3118
  continue; // already in root
3060
3119
  if (hoisted.some(m => m.dep === dep))
@@ -3062,8 +3121,7 @@ export async function globalize(cwd, options = {}, configOptions = {}) {
3062
3121
  hoisted.push({ dep, version: ver, from: subPkg.name || path.basename(dir) });
3063
3122
  }
3064
3123
  }
3065
- const needsUpdate = hoisted.length > 0 || wsPackages.size > 0;
3066
- if (needsUpdate) {
3124
+ if (hoisted.length > 0) {
3067
3125
  // Ensure .dependencies backup exists so restoreDeps will remove hoisted deps
3068
3126
  if (!pkg['.dependencies']) {
3069
3127
  pkg['.dependencies'] = { ...(pkg.dependencies || {}) };
@@ -3071,7 +3129,6 @@ export async function globalize(cwd, options = {}, configOptions = {}) {
3071
3129
  }
3072
3130
  if (!pkg.dependencies)
3073
3131
  pkg.dependencies = {};
3074
- // Hoist third-party deps
3075
3132
  for (const m of hoisted) {
3076
3133
  let version = m.version;
3077
3134
  if (typeof version === 'string' && version.startsWith('file:')) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bobfrankston/npmglobalize",
3
- "version": "1.0.123",
3
+ "version": "1.0.125",
4
4
  "description": "Transform file: dependencies to npm versions for publishing",
5
5
  "main": "index.js",
6
6
  "type": "module",