@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.
- package/lib.js +164 -50
- 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
|
-
|
|
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
|
-
|
|
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 =
|
|
3338
|
+
let commitResult = gitCommit(commitMsg, cwd);
|
|
3232
3339
|
if (!commitResult.success) {
|
|
3233
3340
|
// Check for corrupted git index ("invalid object" / "Error building trees")
|
|
3234
|
-
|
|
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 =
|
|
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
|
-
|
|
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 };
|