@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 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 (Windows)
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
@@ -3,4 +3,5 @@
3
3
  * npmglobalize - Main entry point
4
4
  */
5
5
  import './cli.js';
6
+ export { globalize, globalizeWorkspace, GlobalizeOptions, WorkspaceResult } from './lib.js';
6
7
  //# sourceMappingURL=index.d.ts.map
package/index.js CHANGED
@@ -4,6 +4,7 @@
4
4
  */
5
5
  import './cli.js';
6
6
  import { main } from './cli.js';
7
+ export { globalize, globalizeWorkspace } from './lib.js';
7
8
  if (import.meta.main) {
8
9
  main();
9
10
  }
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
- console.log(colors.italic(`npmglobalize v${toolVersion}`));
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 from local directory
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
- if (!pkg.bin && (install || wsl)) {
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 (install) {
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
- const version = pkg.version;
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
- console.log(`Installing ${pkgName} in WSL from local directory...`);
1945
- const wslInstallResult = runCommand('wsl', ['npm', 'install', '-g', '.'], { cwd, silent: false });
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
- const version = pkg.version;
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 (install) {
2372
- console.log(`Installing globally: ${pkgName}@${pkgVersion}...`);
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(`✓ Installed globally: ${pkgName}@${pkgVersion}`));
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
- console.log(`Installing in WSL: ${pkgName}@${pkgVersion}...`);
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
- // Install from local directory in WSL
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 (install) {
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,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bobfrankston/npmglobalize",
3
- "version": "1.0.78",
3
+ "version": "1.0.80",
4
4
  "description": "Transform file: dependencies to npm versions for publishing",
5
5
  "main": "index.js",
6
6
  "type": "module",