@bobfrankston/npmglobalize 1.0.161 → 1.0.162

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/lib.d.ts +17 -1
  2. package/lib.js +148 -20
  3. package/package.json +1 -1
package/lib.d.ts CHANGED
@@ -186,12 +186,25 @@ export declare function getFileRefs(pkg: any): Map<string, {
186
186
  name: string;
187
187
  value: string;
188
188
  }>;
189
+ /** Expand workspace entries (which may include globs like 'packages/*') to relative
190
+ * dir paths from rootDir, normalized to forward slashes. Only `<base>/*` is expanded;
191
+ * more exotic globs (`**`, `?`, character classes) are passed through unchanged. */
192
+ export declare function expandWorkspaceEntries(rootDir: string, entries: string[]): string[];
189
193
  /** Resolve workspace entries to package info. Skips dirs without package.json or with private:true. */
190
194
  export declare function resolveWorkspacePackages(rootDir: string): Array<{
191
195
  name: string;
192
196
  dir: string;
193
197
  pkg: any;
194
198
  }>;
199
+ /** Like resolveWorkspacePackages but INCLUDES private packages and exposes the
200
+ * workspaces[] entry (relative path) for each. Use for build ordering, where
201
+ * a private workspace package is still part of the dep graph. */
202
+ export declare function resolveAllWorkspacePackages(rootDir: string): Array<{
203
+ name: string;
204
+ dir: string;
205
+ pkg: any;
206
+ entry: string;
207
+ }>;
195
208
  /** Build a dependency graph among workspace packages. Returns Map<name, Set<depName>>. */
196
209
  export declare function buildDependencyGraph(packages: Array<{
197
210
  name: string;
@@ -244,7 +257,10 @@ export declare function compareVersions(a: number[], b: number[]): number;
244
257
  /** Fix version/tag mismatches */
245
258
  export declare function fixVersionTagMismatch(cwd: string, pkg: any, verbose?: boolean): boolean;
246
259
  /** Return declared deps (dependencies + devDependencies) that don't resolve from
247
- * `pkgDir`. Skips `file:`/`workspace:`/`link:` specs those are handled elsewhere.
260
+ * `pkgDir`. Skips `workspace:`/`link:` specs (handled by workspace tooling /
261
+ * rarely used). `file:` deps are checked: npm installs them as junctions
262
+ * (Windows) or symlinks, and `fs.existsSync` traverses both, so a missing
263
+ * file: dep is a real out-of-sync case worth flagging.
248
264
  * A declared dep that doesn't resolve means `package.json` and installed
249
265
  * `node_modules/` are out of sync (e.g. dep added but `npm install` not re-run). */
250
266
  export declare function missingDeps(pkgDir: string, pkg: any): string[];
package/lib.js CHANGED
@@ -685,6 +685,41 @@ export function getFileRefs(pkg) {
685
685
  return refs;
686
686
  }
687
687
  // ─── Workspace helpers ───────────────────────────────────────────────
688
+ /** Expand workspace entries (which may include globs like 'packages/*') to relative
689
+ * dir paths from rootDir, normalized to forward slashes. Only `<base>/*` is expanded;
690
+ * more exotic globs (`**`, `?`, character classes) are passed through unchanged. */
691
+ export function expandWorkspaceEntries(rootDir, entries) {
692
+ const expanded = [];
693
+ const seen = new Set();
694
+ const push = (rel) => { if (!seen.has(rel)) {
695
+ seen.add(rel);
696
+ expanded.push(rel);
697
+ } };
698
+ for (const entry of entries) {
699
+ const norm = entry.replace(/\\/g, '/');
700
+ const m = norm.match(/^(.+?)\/\*$/);
701
+ if (m && !m[1].includes('*')) {
702
+ const baseDir = path.resolve(rootDir, m[1]);
703
+ if (!fs.existsSync(baseDir) || !fs.statSync(baseDir).isDirectory())
704
+ continue;
705
+ const subs = fs.readdirSync(baseDir).sort();
706
+ for (const sub of subs) {
707
+ if (sub.startsWith('.'))
708
+ continue;
709
+ const subPath = path.join(baseDir, sub);
710
+ if (!fs.statSync(subPath).isDirectory())
711
+ continue;
712
+ if (!fs.existsSync(path.join(subPath, 'package.json')))
713
+ continue;
714
+ push(`${m[1]}/${sub}`);
715
+ }
716
+ }
717
+ else {
718
+ push(norm);
719
+ }
720
+ }
721
+ return expanded;
722
+ }
688
723
  /** Resolve workspace entries to package info. Skips dirs without package.json or with private:true. */
689
724
  export function resolveWorkspacePackages(rootDir) {
690
725
  const rootPkg = readPackageJson(rootDir);
@@ -692,7 +727,7 @@ export function resolveWorkspacePackages(rootDir) {
692
727
  if (!Array.isArray(workspaces))
693
728
  return [];
694
729
  const results = [];
695
- for (const entry of workspaces) {
730
+ for (const entry of expandWorkspaceEntries(rootDir, workspaces)) {
696
731
  const pkgDir = path.resolve(rootDir, entry);
697
732
  const pkgJsonPath = path.join(pkgDir, 'package.json');
698
733
  if (!fs.existsSync(pkgJsonPath))
@@ -704,6 +739,87 @@ export function resolveWorkspacePackages(rootDir) {
704
739
  }
705
740
  return results;
706
741
  }
742
+ /** Like resolveWorkspacePackages but INCLUDES private packages and exposes the
743
+ * workspaces[] entry (relative path) for each. Use for build ordering, where
744
+ * a private workspace package is still part of the dep graph. */
745
+ export function resolveAllWorkspacePackages(rootDir) {
746
+ const rootPkg = readPackageJson(rootDir);
747
+ const workspaces = rootPkg.workspaces;
748
+ if (!Array.isArray(workspaces))
749
+ return [];
750
+ const results = [];
751
+ for (const entry of expandWorkspaceEntries(rootDir, workspaces)) {
752
+ const pkgDir = path.resolve(rootDir, entry);
753
+ const pkgJsonPath = path.join(pkgDir, 'package.json');
754
+ if (!fs.existsSync(pkgJsonPath))
755
+ continue;
756
+ const pkg = readPackageJson(pkgDir);
757
+ results.push({ name: pkg.name || entry, dir: pkgDir, pkg, entry });
758
+ }
759
+ return results;
760
+ }
761
+ /** Workspace-root build with `npm run ... --workspaces` runs sub-packages in
762
+ * workspaces[] array order. If that order doesn't match topological dep order,
763
+ * a consumer can be compiled against a sibling whose .d.ts/.js is still stale.
764
+ * Temporarily rewrites workspaces[] to topological order for the build, then
765
+ * restores it. Returns undefined if no rewrite is needed (or no build script
766
+ * uses --workspaces). */
767
+ function reorderWorkspacesForBuild(cwd, pkg, verbose) {
768
+ if (!Array.isArray(pkg.workspaces) || pkg.workspaces.length < 2)
769
+ return undefined;
770
+ const buildScript = typeof pkg.scripts?.build === 'string' ? pkg.scripts.build : '';
771
+ if (!buildScript.includes('--workspaces') && !buildScript.includes('-ws'))
772
+ return undefined;
773
+ const all = resolveAllWorkspacePackages(cwd);
774
+ if (all.length < 2)
775
+ return undefined;
776
+ const graph = buildDependencyGraph(all);
777
+ let order;
778
+ try {
779
+ order = topologicalSort(graph);
780
+ }
781
+ catch (e) {
782
+ console.error(colors.yellow(` Skipping workspace reorder: ${e.message}`));
783
+ return undefined;
784
+ }
785
+ const nameToEntry = new Map();
786
+ for (const p of all)
787
+ nameToEntry.set(p.name, p.entry);
788
+ const newEntries = order
789
+ .map(n => nameToEntry.get(n))
790
+ .filter((e) => !!e);
791
+ // If the expanded current order already matches topological order AND has no
792
+ // globs, nothing to do. (When globs are present, npm expands them in fs order
793
+ // — we always need to write an explicit list.)
794
+ const original = pkg.workspaces.slice();
795
+ const hasGlob = original.some(e => e.includes('*'));
796
+ if (!hasGlob) {
797
+ const cur = original.map(e => e.replace(/\\/g, '/'));
798
+ if (cur.length === newEntries.length && cur.every((v, i) => v === newEntries[i])) {
799
+ return undefined;
800
+ }
801
+ }
802
+ pkg.workspaces = newEntries;
803
+ writePackageJson(cwd, pkg);
804
+ if (verbose) {
805
+ console.log(colors.dim(` Reordered workspaces[] for topological build: ${newEntries.join(' → ')}`));
806
+ }
807
+ let restored = false;
808
+ return () => {
809
+ if (restored)
810
+ return;
811
+ restored = true;
812
+ try {
813
+ pkg.workspaces = original;
814
+ const onDisk = readPackageJson(cwd);
815
+ onDisk.workspaces = original;
816
+ writePackageJson(cwd, onDisk);
817
+ }
818
+ catch (e) {
819
+ console.error(colors.yellow(` Warning: failed to restore workspaces[]: ${e.message}`));
820
+ }
821
+ };
822
+ }
707
823
  /** Build a dependency graph among workspace packages. Returns Map<name, Set<depName>>. */
708
824
  export function buildDependencyGraph(packages) {
709
825
  const nameSet = new Set(packages.map(p => p.name));
@@ -1338,7 +1454,10 @@ function depResolves(startDir, depName) {
1338
1454
  }
1339
1455
  }
1340
1456
  /** Return declared deps (dependencies + devDependencies) that don't resolve from
1341
- * `pkgDir`. Skips `file:`/`workspace:`/`link:` specs those are handled elsewhere.
1457
+ * `pkgDir`. Skips `workspace:`/`link:` specs (handled by workspace tooling /
1458
+ * rarely used). `file:` deps are checked: npm installs them as junctions
1459
+ * (Windows) or symlinks, and `fs.existsSync` traverses both, so a missing
1460
+ * file: dep is a real out-of-sync case worth flagging.
1342
1461
  * A declared dep that doesn't resolve means `package.json` and installed
1343
1462
  * `node_modules/` are out of sync (e.g. dep added but `npm install` not re-run). */
1344
1463
  export function missingDeps(pkgDir, pkg) {
@@ -1350,7 +1469,7 @@ export function missingDeps(pkgDir, pkg) {
1350
1469
  for (const [name, spec] of Object.entries(deps)) {
1351
1470
  if (typeof spec !== 'string')
1352
1471
  continue;
1353
- if (spec.startsWith('file:') || spec.startsWith('workspace:') || spec.startsWith('link:'))
1472
+ if (spec.startsWith('workspace:') || spec.startsWith('link:'))
1354
1473
  continue;
1355
1474
  if (!depResolves(pkgDir, name))
1356
1475
  missing.push(name);
@@ -3281,25 +3400,34 @@ export async function globalize(cwd, options = {}, configOptions = {}) {
3281
3400
  ensureFileDepModules(cwd, verbose);
3282
3401
  console.log(`${timestamp()} Running build...`);
3283
3402
  if (!dryRun) {
3284
- // Always capture output so we can extract tsc errors for the summary
3285
- const buildResult = runCommand('npm', ['run', 'build'], { cwd, silent: true });
3286
- if (!buildResult.success) {
3287
- const buildOutput = buildResult.stderr || buildResult.output;
3288
- if (buildOutput)
3289
- console.error(buildOutput);
3290
- console.error(colors.red('ERROR: Build failed'));
3291
- diagnoseBuildFailure(buildOutput || '', cwd);
3292
- const firstErr = extractFirstTscError(buildOutput || '');
3293
- recordBuildIssue(pkg.name || path.basename(cwd), 'error', firstErr || 'Build failed');
3294
- if (!force) {
3295
- return false;
3403
+ // For workspace roots whose build invokes `--workspaces`, npm uses
3404
+ // workspaces[] array order; rewrite to topological order so deps build
3405
+ // before consumers (avoids stale-.d.ts errors in cross-package imports).
3406
+ const restoreWorkspaces = reorderWorkspacesForBuild(cwd, pkg, verbose);
3407
+ try {
3408
+ // Always capture output so we can extract tsc errors for the summary
3409
+ const buildResult = runCommand('npm', ['run', 'build'], { cwd, silent: true });
3410
+ if (!buildResult.success) {
3411
+ const buildOutput = buildResult.stderr || buildResult.output;
3412
+ if (buildOutput)
3413
+ console.error(buildOutput);
3414
+ console.error(colors.red('ERROR: Build failed'));
3415
+ diagnoseBuildFailure(buildOutput || '', cwd);
3416
+ const firstErr = extractFirstTscError(buildOutput || '');
3417
+ recordBuildIssue(pkg.name || path.basename(cwd), 'error', firstErr || 'Build failed');
3418
+ if (!force) {
3419
+ return false;
3420
+ }
3421
+ console.log(colors.yellow('Continuing with --force despite build failure...'));
3422
+ }
3423
+ else {
3424
+ if (verbose && buildResult.output)
3425
+ process.stdout.write(buildResult.output);
3426
+ console.log(`${timestamp()} ${colors.green('✓ Build succeeded')}`);
3296
3427
  }
3297
- console.log(colors.yellow('Continuing with --force despite build failure...'));
3298
3428
  }
3299
- else {
3300
- if (verbose && buildResult.output)
3301
- process.stdout.write(buildResult.output);
3302
- console.log(`${timestamp()} ${colors.green('✓ Build succeeded')}`);
3429
+ finally {
3430
+ restoreWorkspaces?.();
3303
3431
  }
3304
3432
  }
3305
3433
  else {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bobfrankston/npmglobalize",
3
- "version": "1.0.161",
3
+ "version": "1.0.162",
4
4
  "description": "Transform file: dependencies to npm versions for publishing",
5
5
  "main": "index.js",
6
6
  "type": "module",