@bobfrankston/npmglobalize 1.0.78 → 1.0.79

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, writePackageJson } from './lib.js';
6
6
  import fs from 'fs';
7
7
  import path from 'path';
8
8
  import { fileURLToPath } from 'url';
@@ -78,6 +78,11 @@ Git/npm Visibility:
78
78
  --npm private Mark npm package private (skip publish) (default)
79
79
  --npm public Publish to npm
80
80
 
81
+ Workspace Options:
82
+ -w, --workspace <pkg> Filter to specific package (repeatable)
83
+ --no-workspace Disable workspace mode at a workspace root
84
+ --continue-on-error Continue if a package fails in workspace mode
85
+
81
86
  Other Options:
82
87
  --init Initialize git/npm if needed
83
88
  --force Continue despite git errors
@@ -88,6 +93,7 @@ Other Options:
88
93
  --asis Skip ignore file checks (or set "asis": true in .globalize.json5)
89
94
  --rebase Automatically rebase if local is behind remote
90
95
  --show Show package.json dependency changes
96
+ --package, -pkg Update package.json scripts to use npmglobalize
91
97
  -h, --help Show this help
92
98
  -v, --version Show version number
93
99
 
@@ -107,6 +113,13 @@ Examples:
107
113
  npmglobalize --cleanup Restore original dependencies
108
114
  npmglobalize --init Initialize new git repo + release
109
115
  npmglobalize --dry-run Preview what would happen
116
+ npmglobalize --package Migrate scripts to use npmglobalize
117
+
118
+ Workspace (auto-detected when run from a workspace root):
119
+ npmglobalize Publish all workspace packages in dependency order
120
+ npmglobalize -w msgcommon Publish only msgcommon
121
+ npmglobalize --no-workspace Skip workspace detection, run single-package mode
122
+ npmglobalize --continue-on-error Keep going if a package fails
110
123
  `);
111
124
  }
112
125
  function parseArgs(args) {
@@ -248,6 +261,28 @@ function parseArgs(args) {
248
261
  case '--no-fix':
249
262
  options.fix = false;
250
263
  break;
264
+ case '--workspace':
265
+ case '-w':
266
+ i++;
267
+ if (args[i]) {
268
+ if (!options.workspaceFilter)
269
+ options.workspaceFilter = [];
270
+ options.workspaceFilter.push(args[i]);
271
+ }
272
+ else {
273
+ options.error = '-w/--workspace requires a package name';
274
+ }
275
+ break;
276
+ case '--no-workspace':
277
+ options.noWorkspace = true;
278
+ break;
279
+ case '--continue-on-error':
280
+ options.continueOnError = true;
281
+ break;
282
+ case '--package':
283
+ case '-pkg':
284
+ options.package = true;
285
+ break;
251
286
  default:
252
287
  if (arg.startsWith('-')) {
253
288
  unrecognized.push(arg);
@@ -341,10 +376,43 @@ export async function main() {
341
376
  process.exit(1);
342
377
  }
343
378
  }
379
+ // Handle --package: update scripts in target package.json
380
+ if (cliOptions.package) {
381
+ const pkg = readPackageJson(cwd);
382
+ if (!pkg.scripts)
383
+ pkg.scripts = {};
384
+ const scripts = pkg.scripts;
385
+ const changes = [];
386
+ if (scripts.release) {
387
+ scripts['old-release'] = scripts.release;
388
+ changes.push(`renamed release → old-release`);
389
+ }
390
+ if (scripts.installer) {
391
+ scripts['old-installer'] = scripts.installer;
392
+ changes.push(`renamed installer → old-installer`);
393
+ }
394
+ scripts.release = 'npmglobalize';
395
+ changes.push(`added release: "npmglobalize"`);
396
+ writePackageJson(cwd, pkg);
397
+ console.log(`Updated ${path.join(cwd, 'package.json')} scripts:`);
398
+ changes.forEach(c => console.log(` ${c}`));
399
+ process.exit(0);
400
+ }
344
401
  // Load config file and merge with CLI options (CLI takes precedence)
345
402
  const configOptions = readConfig(cwd);
346
403
  const options = { ...configOptions, ...cliOptions };
347
404
  try {
405
+ // Auto-detect workspace root: private package with workspaces[] field
406
+ if (!options.noWorkspace) {
407
+ const pkgJsonPath = path.join(cwd, 'package.json');
408
+ if (fs.existsSync(pkgJsonPath)) {
409
+ const rootPkg = readPackageJson(cwd);
410
+ if (rootPkg.private && Array.isArray(rootPkg.workspaces)) {
411
+ const result = await globalizeWorkspace(cwd, options, configOptions);
412
+ process.exit(result.success ? 0 : 1);
413
+ }
414
+ }
415
+ }
348
416
  const success = await globalize(cwd, options, configOptions);
349
417
  process.exit(success ? 0 : 1);
350
418
  }
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
@@ -59,6 +59,30 @@ export interface GlobalizeOptions {
59
59
  rebase?: boolean;
60
60
  /** Show package.json dependency changes */
61
61
  show?: boolean;
62
+ /** Filter to specific workspace packages (by name or dir name) */
63
+ workspaceFilter?: string[];
64
+ /** Disable workspace mode even at a workspace root */
65
+ noWorkspace?: boolean;
66
+ /** Continue processing remaining packages if one fails (workspace mode) */
67
+ continueOnError?: boolean;
68
+ /** Update package.json scripts to use npmglobalize */
69
+ package?: boolean;
70
+ /** Internal: signals this call is from workspace orchestrator */
71
+ _fromWorkspace?: boolean;
72
+ }
73
+ /** Result from a single package in workspace mode */
74
+ interface WorkspacePackageResult {
75
+ name: string;
76
+ dir: string;
77
+ success: boolean;
78
+ version?: string;
79
+ error?: string;
80
+ }
81
+ /** Aggregate result from workspace orchestration */
82
+ export interface WorkspaceResult {
83
+ success: boolean;
84
+ packages: WorkspacePackageResult[];
85
+ publishOrder: string[];
62
86
  }
63
87
  /** Read and parse package.json from a directory */
64
88
  export declare function readPackageJson(dir: string): any;
@@ -101,6 +125,20 @@ export declare function getFileRefs(pkg: any): Map<string, {
101
125
  name: string;
102
126
  value: string;
103
127
  }>;
128
+ /** Resolve workspace entries to package info. Skips dirs without package.json or with private:true. */
129
+ export declare function resolveWorkspacePackages(rootDir: string): Array<{
130
+ name: string;
131
+ dir: string;
132
+ pkg: any;
133
+ }>;
134
+ /** Build a dependency graph among workspace packages. Returns Map<name, Set<depName>>. */
135
+ export declare function buildDependencyGraph(packages: Array<{
136
+ name: string;
137
+ dir: string;
138
+ pkg: any;
139
+ }>): Map<string, Set<string>>;
140
+ /** Topological sort with cycle detection. Returns package names in dependency order. */
141
+ export declare function topologicalSort(graph: Map<string, Set<string>>): string[];
104
142
  /** Transform file: dependencies to npm versions */
105
143
  export declare function transformDeps(pkg: any, baseDir: string, verbose?: boolean, forcePublish?: boolean): {
106
144
  transformed: boolean;
@@ -173,8 +211,11 @@ export declare function runNpmAudit(cwd: string, fix?: boolean, verbose?: boolea
173
211
  /** Get the version of npmglobalize itself */
174
212
  export declare function getToolVersion(): string;
175
213
  export declare function globalize(cwd: string, options?: GlobalizeOptions, configOptions?: Partial<GlobalizeOptions>): Promise<boolean>;
214
+ /** Orchestrate globalize across all packages in an npm workspace */
215
+ export declare function globalizeWorkspace(rootDir: string, options?: GlobalizeOptions, configOptions?: Partial<GlobalizeOptions>): Promise<WorkspaceResult>;
176
216
  declare const _default: {
177
217
  globalize: typeof globalize;
218
+ globalizeWorkspace: typeof globalizeWorkspace;
178
219
  transformDeps: typeof transformDeps;
179
220
  restoreDeps: typeof restoreDeps;
180
221
  readPackageJson: typeof readPackageJson;
package/lib.js CHANGED
@@ -415,6 +415,91 @@ export function getFileRefs(pkg) {
415
415
  }
416
416
  return refs;
417
417
  }
418
+ // ─── Workspace helpers ───────────────────────────────────────────────
419
+ /** Resolve workspace entries to package info. Skips dirs without package.json or with private:true. */
420
+ export function resolveWorkspacePackages(rootDir) {
421
+ const rootPkg = readPackageJson(rootDir);
422
+ const workspaces = rootPkg.workspaces;
423
+ if (!Array.isArray(workspaces))
424
+ return [];
425
+ const results = [];
426
+ for (const entry of workspaces) {
427
+ const pkgDir = path.resolve(rootDir, entry);
428
+ const pkgJsonPath = path.join(pkgDir, 'package.json');
429
+ if (!fs.existsSync(pkgJsonPath))
430
+ continue;
431
+ const pkg = readPackageJson(pkgDir);
432
+ if (pkg.private)
433
+ continue; // skip private packages (they can't be published)
434
+ results.push({ name: pkg.name || entry, dir: pkgDir, pkg });
435
+ }
436
+ return results;
437
+ }
438
+ /** Build a dependency graph among workspace packages. Returns Map<name, Set<depName>>. */
439
+ export function buildDependencyGraph(packages) {
440
+ const nameSet = new Set(packages.map(p => p.name));
441
+ const dirToName = new Map();
442
+ for (const p of packages) {
443
+ dirToName.set(p.dir, p.name);
444
+ }
445
+ const graph = new Map();
446
+ for (const p of packages) {
447
+ const deps = new Set();
448
+ for (const depKey of DEP_KEYS) {
449
+ if (!p.pkg[depKey])
450
+ continue;
451
+ for (const [depName, depValue] of Object.entries(p.pkg[depKey])) {
452
+ // Check by npm name
453
+ if (nameSet.has(depName)) {
454
+ deps.add(depName);
455
+ continue;
456
+ }
457
+ // Check file: refs that resolve to a sibling workspace dir
458
+ if (isFileRef(depValue)) {
459
+ const resolved = resolveFilePath(depValue, p.dir);
460
+ const resolvedNorm = path.resolve(resolved);
461
+ const match = dirToName.get(resolvedNorm);
462
+ if (match) {
463
+ deps.add(match);
464
+ }
465
+ }
466
+ }
467
+ }
468
+ graph.set(p.name, deps);
469
+ }
470
+ return graph;
471
+ }
472
+ /** Topological sort with cycle detection. Returns package names in dependency order. */
473
+ export function topologicalSort(graph) {
474
+ const visited = new Set();
475
+ const visiting = new Set(); // cycle detection
476
+ const result = [];
477
+ function visit(node) {
478
+ if (visited.has(node))
479
+ return;
480
+ if (visiting.has(node)) {
481
+ throw new Error(`Circular dependency detected involving: ${node}`);
482
+ }
483
+ visiting.add(node);
484
+ const deps = graph.get(node);
485
+ if (deps) {
486
+ for (const dep of deps) {
487
+ if (dep === node)
488
+ continue; // skip self-references
489
+ if (graph.has(dep)) { // only visit workspace-internal deps
490
+ visit(dep);
491
+ }
492
+ }
493
+ }
494
+ visiting.delete(node);
495
+ visited.add(node);
496
+ result.push(node);
497
+ }
498
+ for (const node of graph.keys()) {
499
+ visit(node);
500
+ }
501
+ return result;
502
+ }
418
503
  /** Transform file: dependencies to npm versions */
419
504
  export function transformDeps(pkg, baseDir, verbose = false, forcePublish = false) {
420
505
  let transformed = false;
@@ -1310,9 +1395,11 @@ export function getToolVersion() {
1310
1395
  export async function globalize(cwd, options = {}, configOptions = {}) {
1311
1396
  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
1312
1397
  forcePublish = false, fix = false, fixTags = false, rebase = false, show = false } = options;
1313
- // Show tool version
1398
+ // Show tool version (skip when called from workspace orchestrator)
1314
1399
  const toolVersion = getToolVersion();
1315
- console.log(colors.italic(`npmglobalize v${toolVersion}`));
1400
+ if (!options._fromWorkspace) {
1401
+ console.log(colors.italic(`npmglobalize v${toolVersion}`));
1402
+ }
1316
1403
  // Show settings from .globalize.json5 if any
1317
1404
  if (Object.keys(configOptions).length > 0) {
1318
1405
  const settings = [];
@@ -1579,6 +1666,26 @@ export async function globalize(cwd, options = {}, configOptions = {}) {
1579
1666
  }
1580
1667
  // Read package.json
1581
1668
  const pkg = readPackageJson(cwd);
1669
+ // Run build step if package.json has a build script
1670
+ if (pkg.scripts?.build) {
1671
+ console.log('Running build...');
1672
+ if (!dryRun) {
1673
+ const buildResult = runCommand('npm', ['run', 'build'], { cwd, silent: !verbose });
1674
+ if (!buildResult.success) {
1675
+ console.error(colors.red('ERROR: Build failed:'), buildResult.stderr || buildResult.output);
1676
+ if (!force) {
1677
+ return false;
1678
+ }
1679
+ console.log(colors.yellow('Continuing with --force despite build failure...'));
1680
+ }
1681
+ else {
1682
+ console.log(colors.green('✓ Build succeeded'));
1683
+ }
1684
+ }
1685
+ else {
1686
+ console.log(' [dry-run] Would run: npm run build');
1687
+ }
1688
+ }
1582
1689
  // Pre-flight check: fix version/tag mismatches silently
1583
1690
  if (!dryRun) {
1584
1691
  fixVersionTagMismatch(cwd, pkg, verbose);
@@ -2457,8 +2564,128 @@ export async function globalize(cwd, options = {}, configOptions = {}) {
2457
2564
  }
2458
2565
  return true;
2459
2566
  }
2567
+ // ─── Workspace orchestration ─────────────────────────────────────────
2568
+ /** Orchestrate globalize across all packages in an npm workspace */
2569
+ export async function globalizeWorkspace(rootDir, options = {}, configOptions = {}) {
2570
+ const toolVersion = getToolVersion();
2571
+ console.log(colors.italic(`npmglobalize v${toolVersion}`) + colors.dim(' (workspace mode)'));
2572
+ console.log('');
2573
+ // Resolve workspace packages
2574
+ const packages = resolveWorkspacePackages(rootDir);
2575
+ if (packages.length === 0) {
2576
+ console.error(colors.red('No publishable workspace packages found.'));
2577
+ return { success: false, packages: [], publishOrder: [] };
2578
+ }
2579
+ // Build dependency graph and determine publish order
2580
+ const graph = buildDependencyGraph(packages);
2581
+ const publishOrder = topologicalSort(graph);
2582
+ console.log(`Workspace: ${colors.green(path.basename(rootDir))}`);
2583
+ console.log(`Packages (${packages.length}): ${packages.map(p => p.name).join(', ')}`);
2584
+ console.log(`Publish order: ${publishOrder.join(' → ')}`);
2585
+ console.log('');
2586
+ // Handle --cleanup: restore deps for all packages and return
2587
+ if (options.cleanup) {
2588
+ console.log('Restoring workspace dependencies...');
2589
+ for (const pkgName of publishOrder) {
2590
+ const pkgInfo = packages.find(p => p.name === pkgName);
2591
+ if (!pkgInfo)
2592
+ continue;
2593
+ const pkg = readPackageJson(pkgInfo.dir);
2594
+ const restored = restoreDeps(pkg);
2595
+ if (restored) {
2596
+ writePackageJson(pkgInfo.dir, pkg);
2597
+ console.log(` ${colors.green('✓')} ${pkgName}: restored`);
2598
+ }
2599
+ else {
2600
+ console.log(` ${colors.dim('–')} ${pkgName}: nothing to restore`);
2601
+ }
2602
+ }
2603
+ return { success: true, packages: [], publishOrder };
2604
+ }
2605
+ // Apply workspace filter if specified
2606
+ let filteredOrder = publishOrder;
2607
+ if (options.workspaceFilter && options.workspaceFilter.length > 0) {
2608
+ const filter = new Set(options.workspaceFilter);
2609
+ filteredOrder = publishOrder.filter(name => {
2610
+ const pkgInfo = packages.find(p => p.name === name);
2611
+ if (!pkgInfo)
2612
+ return false;
2613
+ // Match by package name or directory basename
2614
+ return filter.has(name) || filter.has(path.basename(pkgInfo.dir));
2615
+ });
2616
+ if (filteredOrder.length === 0) {
2617
+ console.error(colors.red(`No packages matched filter: ${options.workspaceFilter.join(', ')}`));
2618
+ return { success: false, packages: [], publishOrder };
2619
+ }
2620
+ console.log(`Filtered to: ${filteredOrder.join(', ')}`);
2621
+ console.log('');
2622
+ }
2623
+ // Process each package in dependency order
2624
+ const results = [];
2625
+ for (const pkgName of filteredOrder) {
2626
+ const pkgInfo = packages.find(p => p.name === pkgName);
2627
+ if (!pkgInfo)
2628
+ continue;
2629
+ console.log(colors.green('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━'));
2630
+ console.log(` Package: ${colors.green(pkgName)}`);
2631
+ console.log(colors.green('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━'));
2632
+ // Merge configs: root config < package config < CLI options
2633
+ const pkgConfig = readConfig(pkgInfo.dir);
2634
+ const mergedConfig = { ...configOptions, ...pkgConfig };
2635
+ const mergedOptions = {
2636
+ ...mergedConfig,
2637
+ ...options,
2638
+ publishDeps: false, // workspace handles ordering; avoid double-publish
2639
+ _fromWorkspace: true,
2640
+ };
2641
+ try {
2642
+ const success = await globalize(pkgInfo.dir, mergedOptions, mergedConfig);
2643
+ const updatedPkg = readPackageJson(pkgInfo.dir);
2644
+ results.push({
2645
+ name: pkgName,
2646
+ dir: pkgInfo.dir,
2647
+ success,
2648
+ version: updatedPkg.version,
2649
+ });
2650
+ if (!success && !options.continueOnError) {
2651
+ console.error(colors.red(`\nStopping: ${pkgName} failed. Use --continue-on-error to keep going.`));
2652
+ break;
2653
+ }
2654
+ }
2655
+ catch (error) {
2656
+ results.push({
2657
+ name: pkgName,
2658
+ dir: pkgInfo.dir,
2659
+ success: false,
2660
+ error: error.message,
2661
+ });
2662
+ console.error(colors.red(`\nError processing ${pkgName}: ${error.message}`));
2663
+ if (!options.continueOnError) {
2664
+ console.error(colors.red('Use --continue-on-error to continue with remaining packages.'));
2665
+ break;
2666
+ }
2667
+ }
2668
+ console.log('');
2669
+ }
2670
+ // Print workspace summary
2671
+ const allSuccess = results.every(r => r.success);
2672
+ console.log('');
2673
+ console.log(colors.green('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━'));
2674
+ console.log(allSuccess ? colors.green('✓ Workspace Summary') : colors.red('✗ Workspace Summary'));
2675
+ console.log(colors.green('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━'));
2676
+ for (const r of results) {
2677
+ const status = r.success ? colors.green('✓') : colors.red('✗');
2678
+ const ver = r.version ? ` v${r.version}` : '';
2679
+ const err = r.error ? colors.red(` (${r.error})`) : '';
2680
+ console.log(` ${status} ${r.name}${ver}${err}`);
2681
+ }
2682
+ console.log(colors.green('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━'));
2683
+ console.log('');
2684
+ return { success: allSuccess, packages: results, publishOrder };
2685
+ }
2460
2686
  export default {
2461
2687
  globalize,
2688
+ globalizeWorkspace,
2462
2689
  transformDeps,
2463
2690
  restoreDeps,
2464
2691
  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.79",
4
4
  "description": "Transform file: dependencies to npm versions for publishing",
5
5
  "main": "index.js",
6
6
  "type": "module",