@bobfrankston/npmglobalize 1.0.78 → 1.0.80
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/cli.js +97 -4
- package/index.d.ts +1 -0
- package/index.js +1 -0
- package/lib.d.ts +44 -1
- package/lib.js +291 -30
- package/package.json +1 -1
package/cli.js
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
/**
|
|
3
3
|
* npmglobalize CLI - Transform file: dependencies to npm versions for publishing
|
|
4
4
|
*/
|
|
5
|
-
import { globalize, readConfig } from './lib.js';
|
|
5
|
+
import { globalize, globalizeWorkspace, readConfig, readPackageJson, writeConfig, writePackageJson } from './lib.js';
|
|
6
6
|
import fs from 'fs';
|
|
7
7
|
import path from 'path';
|
|
8
8
|
import { fileURLToPath } from 'url';
|
|
@@ -65,7 +65,8 @@ Dependency Options:
|
|
|
65
65
|
--fix Run npm audit fix after transformation
|
|
66
66
|
|
|
67
67
|
Install Options:
|
|
68
|
-
--install, -i Global install after publish (
|
|
68
|
+
--install, -i Global install after publish (from registry)
|
|
69
|
+
--link Global install via symlink (npm install -g .)
|
|
69
70
|
--wsl Also install globally in WSL
|
|
70
71
|
|
|
71
72
|
Mode Options:
|
|
@@ -78,6 +79,11 @@ Git/npm Visibility:
|
|
|
78
79
|
--npm private Mark npm package private (skip publish) (default)
|
|
79
80
|
--npm public Publish to npm
|
|
80
81
|
|
|
82
|
+
Workspace Options:
|
|
83
|
+
-w, --workspace <pkg> Filter to specific package (repeatable)
|
|
84
|
+
--no-workspace Disable workspace mode at a workspace root
|
|
85
|
+
--continue-on-error Continue if a package fails in workspace mode
|
|
86
|
+
|
|
81
87
|
Other Options:
|
|
82
88
|
--init Initialize git/npm if needed
|
|
83
89
|
--force Continue despite git errors
|
|
@@ -88,6 +94,7 @@ Other Options:
|
|
|
88
94
|
--asis Skip ignore file checks (or set "asis": true in .globalize.json5)
|
|
89
95
|
--rebase Automatically rebase if local is behind remote
|
|
90
96
|
--show Show package.json dependency changes
|
|
97
|
+
--package, -pkg Update package.json scripts to use npmglobalize
|
|
91
98
|
-h, --help Show this help
|
|
92
99
|
-v, --version Show version number
|
|
93
100
|
|
|
@@ -102,18 +109,27 @@ Examples:
|
|
|
102
109
|
npmglobalize -npd Skip auto-publishing file: deps (use with caution)
|
|
103
110
|
npmglobalize --force-publish Force republish all file: dependencies
|
|
104
111
|
npmglobalize --fix Fix security vulnerabilities
|
|
105
|
-
npmglobalize --install --wsl Release + install on Windows and WSL
|
|
112
|
+
npmglobalize --install --wsl Release + install on Windows and WSL (from registry)
|
|
113
|
+
npmglobalize --link --wsl Release + link on Windows and WSL (symlink)
|
|
106
114
|
npmglobalize -np Just transform, no publish
|
|
107
115
|
npmglobalize --cleanup Restore original dependencies
|
|
108
116
|
npmglobalize --init Initialize new git repo + release
|
|
109
117
|
npmglobalize --dry-run Preview what would happen
|
|
118
|
+
npmglobalize --package Migrate scripts to use npmglobalize
|
|
119
|
+
|
|
120
|
+
Workspace (auto-detected when run from a workspace root):
|
|
121
|
+
npmglobalize Publish all workspace packages in dependency order
|
|
122
|
+
npmglobalize -w msgcommon Publish only msgcommon
|
|
123
|
+
npmglobalize --no-workspace Skip workspace detection, run single-package mode
|
|
124
|
+
npmglobalize --continue-on-error Keep going if a package fails
|
|
110
125
|
`);
|
|
111
126
|
}
|
|
112
127
|
function parseArgs(args) {
|
|
113
128
|
const options = {
|
|
114
129
|
help: false,
|
|
115
130
|
version: false,
|
|
116
|
-
error: ''
|
|
131
|
+
error: '',
|
|
132
|
+
explicitKeys: new Set()
|
|
117
133
|
};
|
|
118
134
|
const unrecognized = [];
|
|
119
135
|
const positional = [];
|
|
@@ -148,25 +164,35 @@ function parseArgs(args) {
|
|
|
148
164
|
case '--install':
|
|
149
165
|
case '-i':
|
|
150
166
|
options.install = true;
|
|
167
|
+
options.explicitKeys.add('install');
|
|
151
168
|
break;
|
|
152
169
|
case '--noinstall':
|
|
153
170
|
case '-ni':
|
|
154
171
|
options.install = false;
|
|
172
|
+
options.explicitKeys.add('install');
|
|
173
|
+
break;
|
|
174
|
+
case '--link':
|
|
175
|
+
options.link = true;
|
|
176
|
+
options.explicitKeys.add('link');
|
|
155
177
|
break;
|
|
156
178
|
case '--wsl':
|
|
157
179
|
options.wsl = true;
|
|
180
|
+
options.explicitKeys.add('wsl');
|
|
158
181
|
break;
|
|
159
182
|
case '--nowsl':
|
|
160
183
|
options.wsl = false;
|
|
184
|
+
options.explicitKeys.add('wsl');
|
|
161
185
|
break;
|
|
162
186
|
case '--force':
|
|
163
187
|
options.force = true;
|
|
164
188
|
break;
|
|
165
189
|
case '--files':
|
|
166
190
|
options.files = true;
|
|
191
|
+
options.explicitKeys.add('files');
|
|
167
192
|
break;
|
|
168
193
|
case '--nofiles':
|
|
169
194
|
options.files = false;
|
|
195
|
+
options.explicitKeys.add('files');
|
|
170
196
|
break;
|
|
171
197
|
case '--dry-run':
|
|
172
198
|
options.dryRun = true;
|
|
@@ -185,6 +211,7 @@ function parseArgs(args) {
|
|
|
185
211
|
i++;
|
|
186
212
|
if (args[i] === 'private' || args[i] === 'public') {
|
|
187
213
|
options.gitVisibility = args[i];
|
|
214
|
+
options.explicitKeys.add('gitVisibility');
|
|
188
215
|
}
|
|
189
216
|
else {
|
|
190
217
|
options.error = `--git requires 'private' or 'public', got: ${args[i]}`;
|
|
@@ -194,6 +221,7 @@ function parseArgs(args) {
|
|
|
194
221
|
i++;
|
|
195
222
|
if (args[i] === 'private' || args[i] === 'public') {
|
|
196
223
|
options.npmVisibility = args[i];
|
|
224
|
+
options.explicitKeys.add('npmVisibility');
|
|
197
225
|
}
|
|
198
226
|
else {
|
|
199
227
|
options.error = `--npm requires 'private' or 'public', got: ${args[i]}`;
|
|
@@ -244,9 +272,33 @@ function parseArgs(args) {
|
|
|
244
272
|
break;
|
|
245
273
|
case '--fix':
|
|
246
274
|
options.fix = true;
|
|
275
|
+
options.explicitKeys.add('fix');
|
|
247
276
|
break;
|
|
248
277
|
case '--no-fix':
|
|
249
278
|
options.fix = false;
|
|
279
|
+
options.explicitKeys.add('fix');
|
|
280
|
+
break;
|
|
281
|
+
case '--workspace':
|
|
282
|
+
case '-w':
|
|
283
|
+
i++;
|
|
284
|
+
if (args[i]) {
|
|
285
|
+
if (!options.workspaceFilter)
|
|
286
|
+
options.workspaceFilter = [];
|
|
287
|
+
options.workspaceFilter.push(args[i]);
|
|
288
|
+
}
|
|
289
|
+
else {
|
|
290
|
+
options.error = '-w/--workspace requires a package name';
|
|
291
|
+
}
|
|
292
|
+
break;
|
|
293
|
+
case '--no-workspace':
|
|
294
|
+
options.noWorkspace = true;
|
|
295
|
+
break;
|
|
296
|
+
case '--continue-on-error':
|
|
297
|
+
options.continueOnError = true;
|
|
298
|
+
break;
|
|
299
|
+
case '--package':
|
|
300
|
+
case '-pkg':
|
|
301
|
+
options.package = true;
|
|
250
302
|
break;
|
|
251
303
|
default:
|
|
252
304
|
if (arg.startsWith('-')) {
|
|
@@ -341,10 +393,51 @@ export async function main() {
|
|
|
341
393
|
process.exit(1);
|
|
342
394
|
}
|
|
343
395
|
}
|
|
396
|
+
// Handle --package: update scripts in target package.json
|
|
397
|
+
if (cliOptions.package) {
|
|
398
|
+
const pkg = readPackageJson(cwd);
|
|
399
|
+
if (!pkg.scripts)
|
|
400
|
+
pkg.scripts = {};
|
|
401
|
+
const scripts = pkg.scripts;
|
|
402
|
+
const changes = [];
|
|
403
|
+
if (scripts.release) {
|
|
404
|
+
scripts['old-release'] = scripts.release;
|
|
405
|
+
changes.push(`renamed release → old-release`);
|
|
406
|
+
}
|
|
407
|
+
if (scripts.installer) {
|
|
408
|
+
scripts['old-installer'] = scripts.installer;
|
|
409
|
+
changes.push(`renamed installer → old-installer`);
|
|
410
|
+
}
|
|
411
|
+
scripts.release = 'npmglobalize';
|
|
412
|
+
changes.push(`added release: "npmglobalize"`);
|
|
413
|
+
writePackageJson(cwd, pkg);
|
|
414
|
+
console.log(`Updated ${path.join(cwd, 'package.json')} scripts:`);
|
|
415
|
+
changes.forEach(c => console.log(` ${c}`));
|
|
416
|
+
process.exit(0);
|
|
417
|
+
}
|
|
344
418
|
// Load config file and merge with CLI options (CLI takes precedence)
|
|
345
419
|
const configOptions = readConfig(cwd);
|
|
346
420
|
const options = { ...configOptions, ...cliOptions };
|
|
421
|
+
// Persist explicitly set CLI flags to .globalize.json5
|
|
422
|
+
if (cliOptions.explicitKeys.size > 0) {
|
|
423
|
+
const persistable = { ...configOptions };
|
|
424
|
+
for (const key of cliOptions.explicitKeys) {
|
|
425
|
+
persistable[key] = options[key];
|
|
426
|
+
}
|
|
427
|
+
writeConfig(cwd, persistable, cliOptions.explicitKeys);
|
|
428
|
+
}
|
|
347
429
|
try {
|
|
430
|
+
// Auto-detect workspace root: private package with workspaces[] field
|
|
431
|
+
if (!options.noWorkspace) {
|
|
432
|
+
const pkgJsonPath = path.join(cwd, 'package.json');
|
|
433
|
+
if (fs.existsSync(pkgJsonPath)) {
|
|
434
|
+
const rootPkg = readPackageJson(cwd);
|
|
435
|
+
if (rootPkg.private && Array.isArray(rootPkg.workspaces)) {
|
|
436
|
+
const result = await globalizeWorkspace(cwd, options, configOptions);
|
|
437
|
+
process.exit(result.success ? 0 : 1);
|
|
438
|
+
}
|
|
439
|
+
}
|
|
440
|
+
}
|
|
348
441
|
const success = await globalize(cwd, options, configOptions);
|
|
349
442
|
process.exit(success ? 0 : 1);
|
|
350
443
|
}
|
package/index.d.ts
CHANGED
package/index.js
CHANGED
package/lib.d.ts
CHANGED
|
@@ -17,8 +17,10 @@ export interface GlobalizeOptions {
|
|
|
17
17
|
noPublish?: boolean;
|
|
18
18
|
/** Restore from .dependencies */
|
|
19
19
|
cleanup?: boolean;
|
|
20
|
-
/** Global install after publish */
|
|
20
|
+
/** Global install after publish (from registry) */
|
|
21
21
|
install?: boolean;
|
|
22
|
+
/** Global install via symlink (npm install -g .) */
|
|
23
|
+
link?: boolean;
|
|
22
24
|
/** Also install in WSL */
|
|
23
25
|
wsl?: boolean;
|
|
24
26
|
/** Continue despite git errors */
|
|
@@ -59,6 +61,30 @@ export interface GlobalizeOptions {
|
|
|
59
61
|
rebase?: boolean;
|
|
60
62
|
/** Show package.json dependency changes */
|
|
61
63
|
show?: boolean;
|
|
64
|
+
/** Filter to specific workspace packages (by name or dir name) */
|
|
65
|
+
workspaceFilter?: string[];
|
|
66
|
+
/** Disable workspace mode even at a workspace root */
|
|
67
|
+
noWorkspace?: boolean;
|
|
68
|
+
/** Continue processing remaining packages if one fails (workspace mode) */
|
|
69
|
+
continueOnError?: boolean;
|
|
70
|
+
/** Update package.json scripts to use npmglobalize */
|
|
71
|
+
package?: boolean;
|
|
72
|
+
/** Internal: signals this call is from workspace orchestrator */
|
|
73
|
+
_fromWorkspace?: boolean;
|
|
74
|
+
}
|
|
75
|
+
/** Result from a single package in workspace mode */
|
|
76
|
+
interface WorkspacePackageResult {
|
|
77
|
+
name: string;
|
|
78
|
+
dir: string;
|
|
79
|
+
success: boolean;
|
|
80
|
+
version?: string;
|
|
81
|
+
error?: string;
|
|
82
|
+
}
|
|
83
|
+
/** Aggregate result from workspace orchestration */
|
|
84
|
+
export interface WorkspaceResult {
|
|
85
|
+
success: boolean;
|
|
86
|
+
packages: WorkspacePackageResult[];
|
|
87
|
+
publishOrder: string[];
|
|
62
88
|
}
|
|
63
89
|
/** Read and parse package.json from a directory */
|
|
64
90
|
export declare function readPackageJson(dir: string): any;
|
|
@@ -101,6 +127,20 @@ export declare function getFileRefs(pkg: any): Map<string, {
|
|
|
101
127
|
name: string;
|
|
102
128
|
value: string;
|
|
103
129
|
}>;
|
|
130
|
+
/** Resolve workspace entries to package info. Skips dirs without package.json or with private:true. */
|
|
131
|
+
export declare function resolveWorkspacePackages(rootDir: string): Array<{
|
|
132
|
+
name: string;
|
|
133
|
+
dir: string;
|
|
134
|
+
pkg: any;
|
|
135
|
+
}>;
|
|
136
|
+
/** Build a dependency graph among workspace packages. Returns Map<name, Set<depName>>. */
|
|
137
|
+
export declare function buildDependencyGraph(packages: Array<{
|
|
138
|
+
name: string;
|
|
139
|
+
dir: string;
|
|
140
|
+
pkg: any;
|
|
141
|
+
}>): Map<string, Set<string>>;
|
|
142
|
+
/** Topological sort with cycle detection. Returns package names in dependency order. */
|
|
143
|
+
export declare function topologicalSort(graph: Map<string, Set<string>>): string[];
|
|
104
144
|
/** Transform file: dependencies to npm versions */
|
|
105
145
|
export declare function transformDeps(pkg: any, baseDir: string, verbose?: boolean, forcePublish?: boolean): {
|
|
106
146
|
transformed: boolean;
|
|
@@ -173,8 +213,11 @@ export declare function runNpmAudit(cwd: string, fix?: boolean, verbose?: boolea
|
|
|
173
213
|
/** Get the version of npmglobalize itself */
|
|
174
214
|
export declare function getToolVersion(): string;
|
|
175
215
|
export declare function globalize(cwd: string, options?: GlobalizeOptions, configOptions?: Partial<GlobalizeOptions>): Promise<boolean>;
|
|
216
|
+
/** Orchestrate globalize across all packages in an npm workspace */
|
|
217
|
+
export declare function globalizeWorkspace(rootDir: string, options?: GlobalizeOptions, configOptions?: Partial<GlobalizeOptions>): Promise<WorkspaceResult>;
|
|
176
218
|
declare const _default: {
|
|
177
219
|
globalize: typeof globalize;
|
|
220
|
+
globalizeWorkspace: typeof globalizeWorkspace;
|
|
178
221
|
transformDeps: typeof transformDeps;
|
|
179
222
|
restoreDeps: typeof restoreDeps;
|
|
180
223
|
readPackageJson: typeof readPackageJson;
|
package/lib.js
CHANGED
|
@@ -138,7 +138,9 @@ export function writeConfig(dir, config, explicitKeys) {
|
|
|
138
138
|
// Add inline comment for clarity
|
|
139
139
|
let comment = '';
|
|
140
140
|
if (key === 'install')
|
|
141
|
-
comment = ' // Auto-install globally after publish';
|
|
141
|
+
comment = ' // Auto-install globally after publish (from registry)';
|
|
142
|
+
else if (key === 'link')
|
|
143
|
+
comment = ' // Install globally via symlink (npm install -g .)';
|
|
142
144
|
else if (key === 'wsl')
|
|
143
145
|
comment = ' // Also install in WSL';
|
|
144
146
|
else if (key === 'files')
|
|
@@ -164,7 +166,8 @@ export function writeConfig(dir, config, explicitKeys) {
|
|
|
164
166
|
// Add commented reference for all options
|
|
165
167
|
lines.push(' // Defaults (omitted above):');
|
|
166
168
|
lines.push(' // "bump": "patch" // Version bump: patch, minor, major');
|
|
167
|
-
lines.push(' // "install": false // Auto-install globally after publish');
|
|
169
|
+
lines.push(' // "install": false // Auto-install globally after publish (from registry)');
|
|
170
|
+
lines.push(' // "link": false // Install globally via symlink (npm install -g .)');
|
|
168
171
|
lines.push(' // "wsl": false // Also install in WSL');
|
|
169
172
|
lines.push(' // "files": true // Keep file: paths after publish');
|
|
170
173
|
lines.push(' // "force": false // Continue despite git errors');
|
|
@@ -415,6 +418,91 @@ export function getFileRefs(pkg) {
|
|
|
415
418
|
}
|
|
416
419
|
return refs;
|
|
417
420
|
}
|
|
421
|
+
// ─── Workspace helpers ───────────────────────────────────────────────
|
|
422
|
+
/** Resolve workspace entries to package info. Skips dirs without package.json or with private:true. */
|
|
423
|
+
export function resolveWorkspacePackages(rootDir) {
|
|
424
|
+
const rootPkg = readPackageJson(rootDir);
|
|
425
|
+
const workspaces = rootPkg.workspaces;
|
|
426
|
+
if (!Array.isArray(workspaces))
|
|
427
|
+
return [];
|
|
428
|
+
const results = [];
|
|
429
|
+
for (const entry of workspaces) {
|
|
430
|
+
const pkgDir = path.resolve(rootDir, entry);
|
|
431
|
+
const pkgJsonPath = path.join(pkgDir, 'package.json');
|
|
432
|
+
if (!fs.existsSync(pkgJsonPath))
|
|
433
|
+
continue;
|
|
434
|
+
const pkg = readPackageJson(pkgDir);
|
|
435
|
+
if (pkg.private)
|
|
436
|
+
continue; // skip private packages (they can't be published)
|
|
437
|
+
results.push({ name: pkg.name || entry, dir: pkgDir, pkg });
|
|
438
|
+
}
|
|
439
|
+
return results;
|
|
440
|
+
}
|
|
441
|
+
/** Build a dependency graph among workspace packages. Returns Map<name, Set<depName>>. */
|
|
442
|
+
export function buildDependencyGraph(packages) {
|
|
443
|
+
const nameSet = new Set(packages.map(p => p.name));
|
|
444
|
+
const dirToName = new Map();
|
|
445
|
+
for (const p of packages) {
|
|
446
|
+
dirToName.set(p.dir, p.name);
|
|
447
|
+
}
|
|
448
|
+
const graph = new Map();
|
|
449
|
+
for (const p of packages) {
|
|
450
|
+
const deps = new Set();
|
|
451
|
+
for (const depKey of DEP_KEYS) {
|
|
452
|
+
if (!p.pkg[depKey])
|
|
453
|
+
continue;
|
|
454
|
+
for (const [depName, depValue] of Object.entries(p.pkg[depKey])) {
|
|
455
|
+
// Check by npm name
|
|
456
|
+
if (nameSet.has(depName)) {
|
|
457
|
+
deps.add(depName);
|
|
458
|
+
continue;
|
|
459
|
+
}
|
|
460
|
+
// Check file: refs that resolve to a sibling workspace dir
|
|
461
|
+
if (isFileRef(depValue)) {
|
|
462
|
+
const resolved = resolveFilePath(depValue, p.dir);
|
|
463
|
+
const resolvedNorm = path.resolve(resolved);
|
|
464
|
+
const match = dirToName.get(resolvedNorm);
|
|
465
|
+
if (match) {
|
|
466
|
+
deps.add(match);
|
|
467
|
+
}
|
|
468
|
+
}
|
|
469
|
+
}
|
|
470
|
+
}
|
|
471
|
+
graph.set(p.name, deps);
|
|
472
|
+
}
|
|
473
|
+
return graph;
|
|
474
|
+
}
|
|
475
|
+
/** Topological sort with cycle detection. Returns package names in dependency order. */
|
|
476
|
+
export function topologicalSort(graph) {
|
|
477
|
+
const visited = new Set();
|
|
478
|
+
const visiting = new Set(); // cycle detection
|
|
479
|
+
const result = [];
|
|
480
|
+
function visit(node) {
|
|
481
|
+
if (visited.has(node))
|
|
482
|
+
return;
|
|
483
|
+
if (visiting.has(node)) {
|
|
484
|
+
throw new Error(`Circular dependency detected involving: ${node}`);
|
|
485
|
+
}
|
|
486
|
+
visiting.add(node);
|
|
487
|
+
const deps = graph.get(node);
|
|
488
|
+
if (deps) {
|
|
489
|
+
for (const dep of deps) {
|
|
490
|
+
if (dep === node)
|
|
491
|
+
continue; // skip self-references
|
|
492
|
+
if (graph.has(dep)) { // only visit workspace-internal deps
|
|
493
|
+
visit(dep);
|
|
494
|
+
}
|
|
495
|
+
}
|
|
496
|
+
}
|
|
497
|
+
visiting.delete(node);
|
|
498
|
+
visited.add(node);
|
|
499
|
+
result.push(node);
|
|
500
|
+
}
|
|
501
|
+
for (const node of graph.keys()) {
|
|
502
|
+
visit(node);
|
|
503
|
+
}
|
|
504
|
+
return result;
|
|
505
|
+
}
|
|
418
506
|
/** Transform file: dependencies to npm versions */
|
|
419
507
|
export function transformDeps(pkg, baseDir, verbose = false, forcePublish = false) {
|
|
420
508
|
let transformed = false;
|
|
@@ -1308,16 +1396,20 @@ export function getToolVersion() {
|
|
|
1308
1396
|
}
|
|
1309
1397
|
}
|
|
1310
1398
|
export async function globalize(cwd, options = {}, configOptions = {}) {
|
|
1311
|
-
const { bump = 'patch', noPublish = false, cleanup = false, install = false, wsl = false, force = false, files = true, dryRun = false, quiet = true, verbose = false, init = false, gitVisibility = 'private', npmVisibility = 'private', message, conform = false, asis = false, updateDeps = false, updateMajor = false, publishDeps = true, // Default to publishing deps for safety
|
|
1399
|
+
const { bump = 'patch', noPublish = false, cleanup = false, install = false, link = false, wsl = false, force = false, files = true, dryRun = false, quiet = true, verbose = false, init = false, gitVisibility = 'private', npmVisibility = 'private', message, conform = false, asis = false, updateDeps = false, updateMajor = false, publishDeps = true, // Default to publishing deps for safety
|
|
1312
1400
|
forcePublish = false, fix = false, fixTags = false, rebase = false, show = false } = options;
|
|
1313
|
-
// Show tool version
|
|
1401
|
+
// Show tool version (skip when called from workspace orchestrator)
|
|
1314
1402
|
const toolVersion = getToolVersion();
|
|
1315
|
-
|
|
1403
|
+
if (!options._fromWorkspace) {
|
|
1404
|
+
console.log(colors.italic(`npmglobalize v${toolVersion}`));
|
|
1405
|
+
}
|
|
1316
1406
|
// Show settings from .globalize.json5 if any
|
|
1317
1407
|
if (Object.keys(configOptions).length > 0) {
|
|
1318
1408
|
const settings = [];
|
|
1319
1409
|
if (configOptions.install)
|
|
1320
1410
|
settings.push('--install');
|
|
1411
|
+
if (configOptions.link)
|
|
1412
|
+
settings.push('--link');
|
|
1321
1413
|
if (configOptions.npmVisibility)
|
|
1322
1414
|
settings.push(`--npm-visibility ${configOptions.npmVisibility}`);
|
|
1323
1415
|
if (configOptions.gitVisibility)
|
|
@@ -1579,6 +1671,26 @@ export async function globalize(cwd, options = {}, configOptions = {}) {
|
|
|
1579
1671
|
}
|
|
1580
1672
|
// Read package.json
|
|
1581
1673
|
const pkg = readPackageJson(cwd);
|
|
1674
|
+
// Run build step if package.json has a build script
|
|
1675
|
+
if (pkg.scripts?.build) {
|
|
1676
|
+
console.log('Running build...');
|
|
1677
|
+
if (!dryRun) {
|
|
1678
|
+
const buildResult = runCommand('npm', ['run', 'build'], { cwd, silent: !verbose });
|
|
1679
|
+
if (!buildResult.success) {
|
|
1680
|
+
console.error(colors.red('ERROR: Build failed:'), buildResult.stderr || buildResult.output);
|
|
1681
|
+
if (!force) {
|
|
1682
|
+
return false;
|
|
1683
|
+
}
|
|
1684
|
+
console.log(colors.yellow('Continuing with --force despite build failure...'));
|
|
1685
|
+
}
|
|
1686
|
+
else {
|
|
1687
|
+
console.log(colors.green('✓ Build succeeded'));
|
|
1688
|
+
}
|
|
1689
|
+
}
|
|
1690
|
+
else {
|
|
1691
|
+
console.log(' [dry-run] Would run: npm run build');
|
|
1692
|
+
}
|
|
1693
|
+
}
|
|
1582
1694
|
// Pre-flight check: fix version/tag mismatches silently
|
|
1583
1695
|
if (!dryRun) {
|
|
1584
1696
|
fixVersionTagMismatch(cwd, pkg, verbose);
|
|
@@ -1916,40 +2028,51 @@ export async function globalize(cwd, options = {}, configOptions = {}) {
|
|
|
1916
2028
|
if (!currentGitStatus.hasUncommitted && !message && !justInitialized && !isFirstPublish) {
|
|
1917
2029
|
console.log('');
|
|
1918
2030
|
console.log('No changes to commit and no custom message specified.');
|
|
1919
|
-
// If install flag is set, install
|
|
1920
|
-
if (install || wsl) {
|
|
2031
|
+
// If install/link flag is set, install globally
|
|
2032
|
+
if (install || link || wsl) {
|
|
1921
2033
|
if (verbose) {
|
|
1922
2034
|
console.log('');
|
|
1923
|
-
console.log('Installing from local directory...');
|
|
2035
|
+
console.log(link ? 'Installing from local directory (link)...' : 'Installing from registry...');
|
|
1924
2036
|
}
|
|
1925
2037
|
const pkgName = pkg.name;
|
|
1926
|
-
|
|
2038
|
+
const pkgVersion = pkg.version;
|
|
2039
|
+
if (!pkg.bin && (install || link || wsl)) {
|
|
1927
2040
|
console.log(colors.yellow('Note: This is a library (no bin field), skipping global installation.'));
|
|
1928
2041
|
console.log(colors.yellow('To use in projects: npm install ' + pkgName));
|
|
1929
2042
|
}
|
|
1930
2043
|
else {
|
|
1931
|
-
if (
|
|
1932
|
-
console.log(`Installing ${pkgName} globally from local directory...`);
|
|
2044
|
+
if (link) {
|
|
2045
|
+
console.log(`Installing ${pkgName} globally from local directory (link)...`);
|
|
1933
2046
|
const localInstallResult = runCommand('npm', ['install', '-g', '.'], { cwd, silent: false });
|
|
1934
2047
|
if (localInstallResult.success) {
|
|
1935
|
-
|
|
1936
|
-
console.log(colors.green(`✓ Installed globally: ${pkgName}@${version}`));
|
|
2048
|
+
console.log(colors.green(`✓ Linked globally: ${pkgName}@${pkgVersion}`));
|
|
1937
2049
|
}
|
|
1938
2050
|
else {
|
|
1939
|
-
console.error(colors.red(`✗ Global install failed`));
|
|
2051
|
+
console.error(colors.red(`✗ Global link install failed`));
|
|
1940
2052
|
console.error(colors.yellow(' Try running manually: npm install -g .'));
|
|
1941
2053
|
}
|
|
1942
2054
|
}
|
|
2055
|
+
else if (install) {
|
|
2056
|
+
console.log(`Installing ${pkgName}@${pkgVersion} globally from registry...`);
|
|
2057
|
+
const registryInstallResult = runCommand('npm', ['install', '-g', `${pkgName}@${pkgVersion}`], { cwd, silent: false });
|
|
2058
|
+
if (registryInstallResult.success) {
|
|
2059
|
+
console.log(colors.green(`✓ Installed globally: ${pkgName}@${pkgVersion}`));
|
|
2060
|
+
}
|
|
2061
|
+
else {
|
|
2062
|
+
console.error(colors.red(`✗ Global install failed`));
|
|
2063
|
+
console.error(colors.yellow(` Try running manually: npm install -g ${pkgName}@${pkgVersion}`));
|
|
2064
|
+
}
|
|
2065
|
+
}
|
|
1943
2066
|
if (wsl) {
|
|
1944
|
-
|
|
1945
|
-
|
|
2067
|
+
const wslArgs = link ? ['npm', 'install', '-g', '.'] : ['npm', 'install', '-g', `${pkgName}@${pkgVersion}`];
|
|
2068
|
+
console.log(`Installing ${pkgName} in WSL${link ? ' (link)' : ' from registry'}...`);
|
|
2069
|
+
const wslInstallResult = runCommand('wsl', wslArgs, { cwd, silent: false });
|
|
1946
2070
|
if (wslInstallResult.success) {
|
|
1947
|
-
|
|
1948
|
-
console.log(colors.green(`✓ Installed in WSL: ${pkgName}@${version}`));
|
|
2071
|
+
console.log(colors.green(`✓ Installed in WSL: ${pkgName}@${pkgVersion}`));
|
|
1949
2072
|
}
|
|
1950
2073
|
else {
|
|
1951
2074
|
console.error(colors.red(`✗ WSL install failed`));
|
|
1952
|
-
console.error(colors.yellow(' Try running manually in WSL: npm install -g .'));
|
|
2075
|
+
console.error(colors.yellow(' Try running manually in WSL: npm install -g ' + (link ? '.' : pkgName)));
|
|
1953
2076
|
}
|
|
1954
2077
|
}
|
|
1955
2078
|
}
|
|
@@ -2363,21 +2486,20 @@ export async function globalize(cwd, options = {}, configOptions = {}) {
|
|
|
2363
2486
|
const updatedPkg = readPackageJson(cwd); // Re-read to get updated version
|
|
2364
2487
|
const pkgName = updatedPkg.name;
|
|
2365
2488
|
const pkgVersion = updatedPkg.version;
|
|
2366
|
-
if (!updatedPkg.bin && (install || wsl)) {
|
|
2489
|
+
if (!updatedPkg.bin && (install || link || wsl)) {
|
|
2367
2490
|
console.log(colors.yellow('Note: This is a library (no bin field), skipping global installation.'));
|
|
2368
2491
|
console.log(colors.yellow('To use in projects: npm install ' + pkgName));
|
|
2369
2492
|
}
|
|
2370
2493
|
else {
|
|
2371
|
-
if (
|
|
2372
|
-
console.log(`
|
|
2494
|
+
if (link) {
|
|
2495
|
+
console.log(`Linking globally: ${pkgName}@${pkgVersion}...`);
|
|
2373
2496
|
if (!dryRun) {
|
|
2374
|
-
// Install from local directory (faster and works immediately after publish)
|
|
2375
2497
|
const installResult = runCommand('npm', ['install', '-g', '.'], { cwd, silent: false });
|
|
2376
2498
|
if (installResult.success) {
|
|
2377
|
-
console.log(colors.green(`✓
|
|
2499
|
+
console.log(colors.green(`✓ Linked globally: ${pkgName}@${pkgVersion}`));
|
|
2378
2500
|
}
|
|
2379
2501
|
else {
|
|
2380
|
-
console.error(colors.red(`✗ Global install failed`));
|
|
2502
|
+
console.error(colors.red(`✗ Global link install failed`));
|
|
2381
2503
|
console.error(colors.yellow(' Try running manually: npm install -g .'));
|
|
2382
2504
|
}
|
|
2383
2505
|
}
|
|
@@ -2385,11 +2507,27 @@ export async function globalize(cwd, options = {}, configOptions = {}) {
|
|
|
2385
2507
|
console.log(` [dry-run] Would run: npm install -g .`);
|
|
2386
2508
|
}
|
|
2387
2509
|
}
|
|
2510
|
+
else if (install) {
|
|
2511
|
+
console.log(`Installing globally from registry: ${pkgName}@${pkgVersion}...`);
|
|
2512
|
+
if (!dryRun) {
|
|
2513
|
+
const installResult = runCommand('npm', ['install', '-g', `${pkgName}@${pkgVersion}`], { cwd, silent: false });
|
|
2514
|
+
if (installResult.success) {
|
|
2515
|
+
console.log(colors.green(`✓ Installed globally: ${pkgName}@${pkgVersion}`));
|
|
2516
|
+
}
|
|
2517
|
+
else {
|
|
2518
|
+
console.error(colors.red(`✗ Global install failed`));
|
|
2519
|
+
console.error(colors.yellow(` Try running manually: npm install -g ${pkgName}@${pkgVersion}`));
|
|
2520
|
+
}
|
|
2521
|
+
}
|
|
2522
|
+
else {
|
|
2523
|
+
console.log(` [dry-run] Would run: npm install -g ${pkgName}@${pkgVersion}`);
|
|
2524
|
+
}
|
|
2525
|
+
}
|
|
2388
2526
|
if (wsl) {
|
|
2389
|
-
|
|
2527
|
+
const wslArgs = link ? ['npm', 'install', '-g', '.'] : ['npm', 'install', '-g', `${pkgName}@${pkgVersion}`];
|
|
2528
|
+
console.log(`Installing in WSL${link ? ' (link)' : ' from registry'}: ${pkgName}@${pkgVersion}...`);
|
|
2390
2529
|
if (!dryRun) {
|
|
2391
|
-
|
|
2392
|
-
const wslResult = runCommand('wsl', ['npm', 'install', '-g', '.'], { cwd, silent: false });
|
|
2530
|
+
const wslResult = runCommand('wsl', wslArgs, { cwd, silent: false });
|
|
2393
2531
|
if (wslResult.success) {
|
|
2394
2532
|
console.log(colors.green(`✓ Installed in WSL: ${pkgName}@${pkgVersion}`));
|
|
2395
2533
|
}
|
|
@@ -2398,7 +2536,7 @@ export async function globalize(cwd, options = {}, configOptions = {}) {
|
|
|
2398
2536
|
}
|
|
2399
2537
|
}
|
|
2400
2538
|
else {
|
|
2401
|
-
console.log(` [dry-run] Would run: wsl npm install -g ${pkgName}`);
|
|
2539
|
+
console.log(` [dry-run] Would run: wsl npm install -g ${link ? '.' : pkgName}`);
|
|
2402
2540
|
}
|
|
2403
2541
|
}
|
|
2404
2542
|
}
|
|
@@ -2438,7 +2576,10 @@ export async function globalize(cwd, options = {}, configOptions = {}) {
|
|
|
2438
2576
|
console.log(` Version: ${colors.green('v' + finalPkg.version)}`);
|
|
2439
2577
|
console.log(` Published: ${colors.green('✓')} (${accessLabel})`);
|
|
2440
2578
|
console.log(` Git pushed: ${colors.green('✓')}`);
|
|
2441
|
-
if (
|
|
2579
|
+
if (link) {
|
|
2580
|
+
console.log(` Linked globally: ${colors.green('✓')}`);
|
|
2581
|
+
}
|
|
2582
|
+
else if (install) {
|
|
2442
2583
|
console.log(` Installed globally: ${colors.green('✓')}`);
|
|
2443
2584
|
}
|
|
2444
2585
|
if (wsl) {
|
|
@@ -2457,8 +2598,128 @@ export async function globalize(cwd, options = {}, configOptions = {}) {
|
|
|
2457
2598
|
}
|
|
2458
2599
|
return true;
|
|
2459
2600
|
}
|
|
2601
|
+
// ─── Workspace orchestration ─────────────────────────────────────────
|
|
2602
|
+
/** Orchestrate globalize across all packages in an npm workspace */
|
|
2603
|
+
export async function globalizeWorkspace(rootDir, options = {}, configOptions = {}) {
|
|
2604
|
+
const toolVersion = getToolVersion();
|
|
2605
|
+
console.log(colors.italic(`npmglobalize v${toolVersion}`) + colors.dim(' (workspace mode)'));
|
|
2606
|
+
console.log('');
|
|
2607
|
+
// Resolve workspace packages
|
|
2608
|
+
const packages = resolveWorkspacePackages(rootDir);
|
|
2609
|
+
if (packages.length === 0) {
|
|
2610
|
+
console.error(colors.red('No publishable workspace packages found.'));
|
|
2611
|
+
return { success: false, packages: [], publishOrder: [] };
|
|
2612
|
+
}
|
|
2613
|
+
// Build dependency graph and determine publish order
|
|
2614
|
+
const graph = buildDependencyGraph(packages);
|
|
2615
|
+
const publishOrder = topologicalSort(graph);
|
|
2616
|
+
console.log(`Workspace: ${colors.green(path.basename(rootDir))}`);
|
|
2617
|
+
console.log(`Packages (${packages.length}): ${packages.map(p => p.name).join(', ')}`);
|
|
2618
|
+
console.log(`Publish order: ${publishOrder.join(' → ')}`);
|
|
2619
|
+
console.log('');
|
|
2620
|
+
// Handle --cleanup: restore deps for all packages and return
|
|
2621
|
+
if (options.cleanup) {
|
|
2622
|
+
console.log('Restoring workspace dependencies...');
|
|
2623
|
+
for (const pkgName of publishOrder) {
|
|
2624
|
+
const pkgInfo = packages.find(p => p.name === pkgName);
|
|
2625
|
+
if (!pkgInfo)
|
|
2626
|
+
continue;
|
|
2627
|
+
const pkg = readPackageJson(pkgInfo.dir);
|
|
2628
|
+
const restored = restoreDeps(pkg);
|
|
2629
|
+
if (restored) {
|
|
2630
|
+
writePackageJson(pkgInfo.dir, pkg);
|
|
2631
|
+
console.log(` ${colors.green('✓')} ${pkgName}: restored`);
|
|
2632
|
+
}
|
|
2633
|
+
else {
|
|
2634
|
+
console.log(` ${colors.dim('–')} ${pkgName}: nothing to restore`);
|
|
2635
|
+
}
|
|
2636
|
+
}
|
|
2637
|
+
return { success: true, packages: [], publishOrder };
|
|
2638
|
+
}
|
|
2639
|
+
// Apply workspace filter if specified
|
|
2640
|
+
let filteredOrder = publishOrder;
|
|
2641
|
+
if (options.workspaceFilter && options.workspaceFilter.length > 0) {
|
|
2642
|
+
const filter = new Set(options.workspaceFilter);
|
|
2643
|
+
filteredOrder = publishOrder.filter(name => {
|
|
2644
|
+
const pkgInfo = packages.find(p => p.name === name);
|
|
2645
|
+
if (!pkgInfo)
|
|
2646
|
+
return false;
|
|
2647
|
+
// Match by package name or directory basename
|
|
2648
|
+
return filter.has(name) || filter.has(path.basename(pkgInfo.dir));
|
|
2649
|
+
});
|
|
2650
|
+
if (filteredOrder.length === 0) {
|
|
2651
|
+
console.error(colors.red(`No packages matched filter: ${options.workspaceFilter.join(', ')}`));
|
|
2652
|
+
return { success: false, packages: [], publishOrder };
|
|
2653
|
+
}
|
|
2654
|
+
console.log(`Filtered to: ${filteredOrder.join(', ')}`);
|
|
2655
|
+
console.log('');
|
|
2656
|
+
}
|
|
2657
|
+
// Process each package in dependency order
|
|
2658
|
+
const results = [];
|
|
2659
|
+
for (const pkgName of filteredOrder) {
|
|
2660
|
+
const pkgInfo = packages.find(p => p.name === pkgName);
|
|
2661
|
+
if (!pkgInfo)
|
|
2662
|
+
continue;
|
|
2663
|
+
console.log(colors.green('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━'));
|
|
2664
|
+
console.log(` Package: ${colors.green(pkgName)}`);
|
|
2665
|
+
console.log(colors.green('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━'));
|
|
2666
|
+
// Merge configs: root config < package config < CLI options
|
|
2667
|
+
const pkgConfig = readConfig(pkgInfo.dir);
|
|
2668
|
+
const mergedConfig = { ...configOptions, ...pkgConfig };
|
|
2669
|
+
const mergedOptions = {
|
|
2670
|
+
...mergedConfig,
|
|
2671
|
+
...options,
|
|
2672
|
+
publishDeps: false, // workspace handles ordering; avoid double-publish
|
|
2673
|
+
_fromWorkspace: true,
|
|
2674
|
+
};
|
|
2675
|
+
try {
|
|
2676
|
+
const success = await globalize(pkgInfo.dir, mergedOptions, mergedConfig);
|
|
2677
|
+
const updatedPkg = readPackageJson(pkgInfo.dir);
|
|
2678
|
+
results.push({
|
|
2679
|
+
name: pkgName,
|
|
2680
|
+
dir: pkgInfo.dir,
|
|
2681
|
+
success,
|
|
2682
|
+
version: updatedPkg.version,
|
|
2683
|
+
});
|
|
2684
|
+
if (!success && !options.continueOnError) {
|
|
2685
|
+
console.error(colors.red(`\nStopping: ${pkgName} failed. Use --continue-on-error to keep going.`));
|
|
2686
|
+
break;
|
|
2687
|
+
}
|
|
2688
|
+
}
|
|
2689
|
+
catch (error) {
|
|
2690
|
+
results.push({
|
|
2691
|
+
name: pkgName,
|
|
2692
|
+
dir: pkgInfo.dir,
|
|
2693
|
+
success: false,
|
|
2694
|
+
error: error.message,
|
|
2695
|
+
});
|
|
2696
|
+
console.error(colors.red(`\nError processing ${pkgName}: ${error.message}`));
|
|
2697
|
+
if (!options.continueOnError) {
|
|
2698
|
+
console.error(colors.red('Use --continue-on-error to continue with remaining packages.'));
|
|
2699
|
+
break;
|
|
2700
|
+
}
|
|
2701
|
+
}
|
|
2702
|
+
console.log('');
|
|
2703
|
+
}
|
|
2704
|
+
// Print workspace summary
|
|
2705
|
+
const allSuccess = results.every(r => r.success);
|
|
2706
|
+
console.log('');
|
|
2707
|
+
console.log(colors.green('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━'));
|
|
2708
|
+
console.log(allSuccess ? colors.green('✓ Workspace Summary') : colors.red('✗ Workspace Summary'));
|
|
2709
|
+
console.log(colors.green('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━'));
|
|
2710
|
+
for (const r of results) {
|
|
2711
|
+
const status = r.success ? colors.green('✓') : colors.red('✗');
|
|
2712
|
+
const ver = r.version ? ` v${r.version}` : '';
|
|
2713
|
+
const err = r.error ? colors.red(` (${r.error})`) : '';
|
|
2714
|
+
console.log(` ${status} ${r.name}${ver}${err}`);
|
|
2715
|
+
}
|
|
2716
|
+
console.log(colors.green('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━'));
|
|
2717
|
+
console.log('');
|
|
2718
|
+
return { success: allSuccess, packages: results, publishOrder };
|
|
2719
|
+
}
|
|
2460
2720
|
export default {
|
|
2461
2721
|
globalize,
|
|
2722
|
+
globalizeWorkspace,
|
|
2462
2723
|
transformDeps,
|
|
2463
2724
|
restoreDeps,
|
|
2464
2725
|
readPackageJson,
|