@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.
- package/lib.d.ts +17 -1
- package/lib.js +148 -20
- 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 `
|
|
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 `
|
|
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('
|
|
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
|
-
//
|
|
3285
|
-
|
|
3286
|
-
|
|
3287
|
-
|
|
3288
|
-
|
|
3289
|
-
|
|
3290
|
-
|
|
3291
|
-
|
|
3292
|
-
|
|
3293
|
-
|
|
3294
|
-
|
|
3295
|
-
|
|
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
|
-
|
|
3300
|
-
|
|
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 {
|