@bobfrankston/npmglobalize 1.0.118 → 1.0.120

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 (2) hide show
  1. package/lib.js +164 -50
  2. package/package.json +1 -1
package/lib.js CHANGED
@@ -1015,6 +1015,21 @@ export function runCommand(cmd, args, options = {}) {
1015
1015
  return { success: false, output: '', stderr: error.message };
1016
1016
  }
1017
1017
  }
1018
+ /** Run git commit silently and print a compact summary (no per-file create/delete mode lines). */
1019
+ function gitCommit(msg, cwd) {
1020
+ const result = runCommand('git', ['commit', '-m', msg], { cwd, silent: true });
1021
+ if (result.success) {
1022
+ // Print the summary lines, skip "create mode" / "delete mode" / "rename" noise
1023
+ const lines = result.output.split('\n');
1024
+ for (const line of lines) {
1025
+ const trimmed = line.trim();
1026
+ if (trimmed && !/^ *(create|delete|rename) mode /.test(trimmed)) {
1027
+ console.log(trimmed);
1028
+ }
1029
+ }
1030
+ }
1031
+ return result;
1032
+ }
1018
1033
  /** Extract GitHub repo identifier from repository field */
1019
1034
  function getGitHubRepo(pkg) {
1020
1035
  if (!pkg.repository)
@@ -2982,6 +2997,98 @@ export async function globalize(cwd, options = {}, configOptions = {}) {
2982
2997
  else if (!alreadyTransformed && verbose) {
2983
2998
  console.log(' No file: dependencies found.');
2984
2999
  }
3000
+ // Hoist workspace sub-package deps into root for publishing/global install.
3001
+ // npm install -g only installs root-level dependencies, so sub-package deps
3002
+ // like express must be present in the root package.json.
3003
+ // These are added temporarily — restoreDeps removes them via .dependencies backup.
3004
+ if (pkg.workspaces) {
3005
+ const wsPatterns = Array.isArray(pkg.workspaces) ? pkg.workspaces : pkg.workspaces.packages || [];
3006
+ // Collect all workspace sub-package dirs
3007
+ const wsDirs = [];
3008
+ for (const pattern of wsPatterns) {
3009
+ if (pattern.includes('*')) {
3010
+ // Glob pattern like "packages/*" — enumerate subdirectories
3011
+ const baseDir = path.resolve(cwd, pattern.replace(/\/?\*$/, ''));
3012
+ if (!fs.existsSync(baseDir))
3013
+ continue;
3014
+ for (const entry of fs.readdirSync(baseDir, { withFileTypes: true })) {
3015
+ if (entry.isDirectory())
3016
+ wsDirs.push(path.join(baseDir, entry.name));
3017
+ }
3018
+ }
3019
+ else {
3020
+ // Direct directory like "client"
3021
+ const dir = path.resolve(cwd, pattern);
3022
+ if (fs.existsSync(dir))
3023
+ wsDirs.push(dir);
3024
+ }
3025
+ }
3026
+ // Collect all workspace package names (siblings to skip)
3027
+ const wsNames = new Set();
3028
+ for (const dir of wsDirs) {
3029
+ const pkgJsonPath = path.join(dir, 'package.json');
3030
+ if (!fs.existsSync(pkgJsonPath))
3031
+ continue;
3032
+ try {
3033
+ const subPkg = JSON.parse(fs.readFileSync(pkgJsonPath, 'utf-8'));
3034
+ if (subPkg.name)
3035
+ wsNames.add(subPkg.name);
3036
+ }
3037
+ catch { /* skip */ }
3038
+ }
3039
+ // Find deps in sub-packages not present in root
3040
+ const rootDeps = { ...pkg.dependencies, ...pkg.devDependencies };
3041
+ const hoisted = [];
3042
+ for (const dir of wsDirs) {
3043
+ const pkgJsonPath = path.join(dir, 'package.json');
3044
+ if (!fs.existsSync(pkgJsonPath))
3045
+ continue;
3046
+ let subPkg;
3047
+ try {
3048
+ subPkg = JSON.parse(fs.readFileSync(pkgJsonPath, 'utf-8'));
3049
+ }
3050
+ catch {
3051
+ continue;
3052
+ }
3053
+ const deps = subPkg.dependencies || {};
3054
+ for (const [dep, ver] of Object.entries(deps)) {
3055
+ if (wsNames.has(dep))
3056
+ continue; // sibling workspace — bundled
3057
+ if (dep in rootDeps)
3058
+ continue; // already in root
3059
+ if (hoisted.some(m => m.dep === dep))
3060
+ continue;
3061
+ hoisted.push({ dep, version: ver, from: subPkg.name || path.basename(dir) });
3062
+ }
3063
+ }
3064
+ if (hoisted.length > 0) {
3065
+ // Ensure .dependencies backup exists so restoreDeps will remove hoisted deps
3066
+ if (!pkg['.dependencies']) {
3067
+ pkg['.dependencies'] = { ...(pkg.dependencies || {}) };
3068
+ transformResult.transformed = true;
3069
+ }
3070
+ if (!pkg.dependencies)
3071
+ pkg.dependencies = {};
3072
+ for (const m of hoisted) {
3073
+ let version = m.version;
3074
+ if (typeof version === 'string' && version.startsWith('file:')) {
3075
+ const depDir = path.resolve(cwd, version.replace('file:', ''));
3076
+ try {
3077
+ const depPkg = readPackageJson(depDir);
3078
+ version = '^' + depPkg.version;
3079
+ }
3080
+ catch {
3081
+ // keep original if unresolvable
3082
+ }
3083
+ }
3084
+ pkg.dependencies[m.dep] = version;
3085
+ console.log(colors.green(` + hoisted ${m.dep}: ${version} (from ${m.from})`));
3086
+ }
3087
+ if (!dryRun) {
3088
+ writePackageJson(cwd, pkg);
3089
+ }
3090
+ }
3091
+ }
2985
3092
  // Run npm audit if requested or if dependencies were transformed
2986
3093
  if ((fix || updateDeps) && (transformResult.transformed || alreadyTransformed || updateDeps)) {
2987
3094
  if (!dryRun) {
@@ -3014,7 +3121,7 @@ export async function globalize(cwd, options = {}, configOptions = {}) {
3014
3121
  if (!dryRun) {
3015
3122
  writePackageJson(cwd, finalPkg);
3016
3123
  runCommand('git', ['add', 'package.json'], { cwd });
3017
- runCommand('git', ['commit', '-m', 'Restore file: dependencies'], { cwd });
3124
+ gitCommit('Restore file: dependencies', cwd);
3018
3125
  if (currentGitStatus.hasRemote) {
3019
3126
  pushWithProtection(cwd, verbose);
3020
3127
  }
@@ -3042,7 +3149,7 @@ export async function globalize(cwd, options = {}, configOptions = {}) {
3042
3149
  if (!dryRun) {
3043
3150
  writePackageJson(cwd, finalPkg);
3044
3151
  runCommand('git', ['add', 'package.json'], { cwd });
3045
- runCommand('git', ['commit', '-m', 'Restore file: dependencies'], { cwd });
3152
+ gitCommit('Restore file: dependencies', cwd);
3046
3153
  if (currentGitStatus.hasRemote) {
3047
3154
  pushWithProtection(cwd, verbose);
3048
3155
  }
@@ -3228,16 +3335,14 @@ export async function globalize(cwd, options = {}, configOptions = {}) {
3228
3335
  console.log(colors.yellow('Continuing with --force...'));
3229
3336
  }
3230
3337
  }
3231
- let commitResult = runCommand('git', ['commit', '-m', commitMsg], { cwd });
3338
+ let commitResult = gitCommit(commitMsg, cwd);
3232
3339
  if (!commitResult.success) {
3233
3340
  // Check for corrupted git index ("invalid object" / "Error building trees")
3234
- // Run silently to capture stderr for detection
3235
- const probe = runCommand('git', ['commit', '-m', commitMsg], { cwd, silent: true });
3236
- const errText = probe.stderr + probe.output;
3341
+ const errText = commitResult.stderr + commitResult.output;
3237
3342
  if (errText.includes('invalid object') || errText.includes('Error building trees')) {
3238
3343
  if (repairGitIndex(cwd)) {
3239
3344
  // Retry commit after repair
3240
- commitResult = runCommand('git', ['commit', '-m', commitMsg], { cwd });
3345
+ commitResult = gitCommit(commitMsg, cwd);
3241
3346
  }
3242
3347
  }
3243
3348
  if (!commitResult.success) {
@@ -3792,7 +3897,7 @@ export async function globalize(cwd, options = {}, configOptions = {}) {
3792
3897
  writePackageJson(cwd, finalPkg);
3793
3898
  // Commit the restore
3794
3899
  runCommand('git', ['add', 'package.json'], { cwd });
3795
- runCommand('git', ['commit', '-m', 'Restore file: dependencies'], { cwd });
3900
+ gitCommit('Restore file: dependencies', cwd);
3796
3901
  if (currentGitStatus.hasRemote) {
3797
3902
  pushWithProtection(cwd, verbose);
3798
3903
  }
@@ -3866,49 +3971,7 @@ export async function globalizeWorkspace(rootDir, options = {}, configOptions =
3866
3971
  console.log(`Packages (${packages.length}): ${packages.map(p => p.name).join(', ')}`);
3867
3972
  console.log(`Publish order: ${publishOrder.join(' → ')}`);
3868
3973
  console.log('');
3869
- // Check that workspace packages' deps are hoisted to root (needed for global install)
3870
3974
  const rootPkg = readPackageJson(rootDir);
3871
- if (rootPkg.bin) {
3872
- const wsNames = new Set(packages.map(p => p.name));
3873
- const rootDeps = { ...rootPkg.dependencies, ...rootPkg.devDependencies };
3874
- const missing = [];
3875
- for (const pkgInfo of packages) {
3876
- const deps = pkgInfo.pkg.dependencies || {};
3877
- for (const [dep, ver] of Object.entries(deps)) {
3878
- if (wsNames.has(dep))
3879
- continue; // sibling workspace package — bundled
3880
- if (dep in rootDeps)
3881
- continue; // already in root
3882
- if (missing.some(m => m.dep === dep))
3883
- continue; // already flagged
3884
- missing.push({ dep, version: ver, from: pkgInfo.name });
3885
- }
3886
- }
3887
- if (missing.length > 0) {
3888
- console.log(colors.yellow('⚠ Workspace packages have dependencies not in root package.json:'));
3889
- for (const m of missing) {
3890
- console.log(colors.yellow(` ${m.dep} (${m.version}) — from ${m.from}`));
3891
- }
3892
- console.log(colors.dim(' Global install (npm install -g) only installs root deps.'));
3893
- if (!options.dryRun) {
3894
- const hoist = await confirm('Add missing deps to root package.json?', true);
3895
- if (hoist) {
3896
- if (!rootPkg.dependencies)
3897
- rootPkg.dependencies = {};
3898
- for (const m of missing) {
3899
- rootPkg.dependencies[m.dep] = m.version;
3900
- console.log(colors.green(` + ${m.dep}: ${m.version}`));
3901
- }
3902
- writePackageJson(rootDir, rootPkg);
3903
- console.log(colors.green('✓ Hoisted workspace deps to root package.json'));
3904
- }
3905
- }
3906
- else {
3907
- console.log(colors.dim(' [dry-run] Would offer to hoist missing deps'));
3908
- }
3909
- console.log('');
3910
- }
3911
- }
3912
3975
  // Handle --cleanup: restore deps for all packages and return
3913
3976
  if (options.cleanup) {
3914
3977
  console.log('Restoring workspace dependencies...');
@@ -4062,6 +4125,46 @@ export async function globalizeWorkspace(rootDir, options = {}, configOptions =
4062
4125
  console.log(colors.green('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━'));
4063
4126
  console.log(` Installing workspace root: ${colors.green(pkgName)}`);
4064
4127
  console.log(colors.green('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━'));
4128
+ // Auto-hoist workspace sub-package deps to root for global install,
4129
+ // then restore original root deps afterward.
4130
+ const wsNames = new Set(packages.map(p => p.name));
4131
+ const rootDeps = { ...rootPkgFinal.dependencies, ...rootPkgFinal.devDependencies };
4132
+ const hoisted = [];
4133
+ for (const pkgInfo of packages) {
4134
+ const deps = pkgInfo.pkg.dependencies || {};
4135
+ for (const [dep, ver] of Object.entries(deps)) {
4136
+ if (wsNames.has(dep))
4137
+ continue; // sibling workspace — bundled
4138
+ if (dep in rootDeps)
4139
+ continue; // already in root
4140
+ if (hoisted.some(m => m.dep === dep))
4141
+ continue;
4142
+ hoisted.push({ dep, version: ver, from: pkgInfo.name });
4143
+ }
4144
+ }
4145
+ // Save original deps and add hoisted ones
4146
+ const savedDeps = rootPkgFinal.dependencies ? { ...rootPkgFinal.dependencies } : undefined;
4147
+ if (hoisted.length > 0 && !dryRun) {
4148
+ if (!rootPkgFinal.dependencies)
4149
+ rootPkgFinal.dependencies = {};
4150
+ for (const m of hoisted) {
4151
+ // Convert file: refs to npm versions (the sub-package was just published)
4152
+ let version = m.version;
4153
+ if (typeof version === 'string' && version.startsWith('file:')) {
4154
+ const depDir = path.resolve(rootDir, version.replace('file:', ''));
4155
+ try {
4156
+ const depPkg = readPackageJson(depDir);
4157
+ version = `^${depPkg.version}`;
4158
+ }
4159
+ catch {
4160
+ // keep original if we can't resolve
4161
+ }
4162
+ }
4163
+ rootPkgFinal.dependencies[m.dep] = version;
4164
+ console.log(colors.green(` + hoisted ${m.dep}: ${version} (from ${m.from})`));
4165
+ }
4166
+ writePackageJson(rootDir, rootPkgFinal);
4167
+ }
4065
4168
  if (link || install) {
4066
4169
  // Workspace root is private — always install from local directory
4067
4170
  console.log(`Installing ${pkgName}@${pkgVersion} globally from local directory...`);
@@ -4094,6 +4197,17 @@ export async function globalizeWorkspace(rootDir, options = {}, configOptions =
4094
4197
  console.log(` [dry-run] Would run: wsl npm install -g .`);
4095
4198
  }
4096
4199
  }
4200
+ // Restore original root deps after install
4201
+ if (hoisted.length > 0 && !dryRun) {
4202
+ if (savedDeps === undefined) {
4203
+ delete rootPkgFinal.dependencies;
4204
+ }
4205
+ else {
4206
+ rootPkgFinal.dependencies = savedDeps;
4207
+ }
4208
+ writePackageJson(rootDir, rootPkgFinal);
4209
+ console.log(colors.green('✓ Restored root package.json dependencies'));
4210
+ }
4097
4211
  console.log('');
4098
4212
  }
4099
4213
  return { success: allSuccess, packages: results, publishOrder };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bobfrankston/npmglobalize",
3
- "version": "1.0.118",
3
+ "version": "1.0.120",
4
4
  "description": "Transform file: dependencies to npm versions for publishing",
5
5
  "main": "index.js",
6
6
  "type": "module",