@bobfrankston/npmglobalize 1.0.218 → 1.0.219
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/README.md +1 -1
- package/lib.d.ts +15 -6
- package/lib.js +75 -63
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -756,7 +756,7 @@ As with import maps, these have historically been built only by a second `.vscod
|
|
|
756
756
|
Before building, `npmglobalize` looks for sub-projects, in this order:
|
|
757
757
|
|
|
758
758
|
1. `.vscode/tasks.json` has a task that runs `tsc` or `importgen` with `"options": { "cwd": "${workspaceFolder}/client" }`, or runs `tsc` with `-p`/`--project` naming a sub-directory (authoritative — it's how the project is actually built today; the task's `label` is quoted back in the prompt).
|
|
759
|
-
2. An immediate sub-directory containing its own `package.json`
|
|
759
|
+
2. An immediate sub-directory containing its own `package.json` or `tsconfig.json`. A sub-package without a build script gets one set up the same way the root does (importgen, `tsc`, its own sub-projects) before the root delegates to it. Build output and vendored trees (`node_modules`, `prev`, `dist`, `built`, `out`, `wwwroot`, `coverage`, `temp`, `preflight`, dot-directories) are never scanned.
|
|
760
760
|
|
|
761
761
|
Nothing is detected when the root `tsconfig.json` uses project `references` — that build graph belongs to `tsc -b` and isn't second-guessed.
|
|
762
762
|
|
package/lib.d.ts
CHANGED
|
@@ -430,7 +430,7 @@ export declare function ensureImportgenInBuild(cwd: string): Promise<void>;
|
|
|
430
430
|
/** A sub-directory the root build must build, and how. */
|
|
431
431
|
interface SubProject {
|
|
432
432
|
dir: string; /** Relative directory with its on-disk casing */
|
|
433
|
-
kind: 'package' | 'tsconfig'; /** package: delegate to its build script; tsconfig: `tsc -p` */
|
|
433
|
+
kind: 'package' | 'tsconfig'; /** package: has its own package.json, delegate to its build script; tsconfig: `tsc -p` */
|
|
434
434
|
reason: string; /** The signal that found it, quoted in prompts */
|
|
435
435
|
}
|
|
436
436
|
/** Sub-directories the root build must build itself. A package can hold more
|
|
@@ -452,11 +452,20 @@ export declare function detectSubProjects(cwd: string): SubProject[];
|
|
|
452
452
|
* own idea of what to compile. Declining records `subProjects: false` in
|
|
453
453
|
* .globalize.json5. */
|
|
454
454
|
export declare function ensureSubProjectsInBuild(cwd: string): Promise<void>;
|
|
455
|
-
/**
|
|
456
|
-
*
|
|
457
|
-
*
|
|
458
|
-
*
|
|
459
|
-
*
|
|
455
|
+
/** Make sure `cwd`'s package.json has the build script it needs: importgen
|
|
456
|
+
* for an importgen project, `tsc` for a TypeScript project (prompted), and a
|
|
457
|
+
* step per sub-project. Returns the package plus whether the script runs
|
|
458
|
+
* importgen, or null when there is nothing to build here (no package.json, no
|
|
459
|
+
* tsconfig or `noEmit` and no importgen, or the user declined the prompt). */
|
|
460
|
+
export declare function ensureBuildScript(cwd: string): Promise<{
|
|
461
|
+
pkg: any;
|
|
462
|
+
runsImportgen: boolean;
|
|
463
|
+
} | null>;
|
|
464
|
+
/** Build a single project: set up its build script (see `ensureBuildScript`),
|
|
465
|
+
* run `npm run build`, record failures. Returns true if build succeeded (or
|
|
466
|
+
* was skipped because there was nothing to build / the user declined the
|
|
467
|
+
* build-script prompt / output already up to date). Pass `forceBuild` to
|
|
468
|
+
* skip the freshness check. */
|
|
460
469
|
export declare function buildProject(cwd: string, opts?: {
|
|
461
470
|
verbose?: boolean;
|
|
462
471
|
force?: boolean;
|
package/lib.js
CHANGED
|
@@ -3198,17 +3198,12 @@ function resolveOnDiskCase(base, rel) {
|
|
|
3198
3198
|
}
|
|
3199
3199
|
return out.length ? out.join('/') : null;
|
|
3200
3200
|
}
|
|
3201
|
-
/** Classify `rel` under `cwd`: a package
|
|
3202
|
-
*
|
|
3201
|
+
/** Classify `rel` under `cwd`: a package (its own package.json — its build
|
|
3202
|
+
* script is set up by `ensureBuildScript` if it lacks one), a plain tsconfig
|
|
3203
|
+
* sub-project, or null when it's neither. */
|
|
3203
3204
|
function subProjectKind(cwd, rel) {
|
|
3204
|
-
|
|
3205
|
-
|
|
3206
|
-
if (typeof pkg?.scripts?.build === 'string' && pkg.scripts.build.trim())
|
|
3207
|
-
return 'package';
|
|
3208
|
-
}
|
|
3209
|
-
catch {
|
|
3210
|
-
// No package.json there — fall through to the tsconfig test
|
|
3211
|
-
}
|
|
3205
|
+
if (fs.existsSync(path.join(cwd, rel, 'package.json')))
|
|
3206
|
+
return 'package';
|
|
3212
3207
|
return fs.existsSync(path.join(cwd, rel, 'tsconfig.json')) ? 'tsconfig' : null;
|
|
3213
3208
|
}
|
|
3214
3209
|
/** The command the root build script runs for a sub-project. */
|
|
@@ -3303,7 +3298,7 @@ export function detectSubProjects(cwd) {
|
|
|
3303
3298
|
continue;
|
|
3304
3299
|
const kind = subProjectKind(cwd, e.name);
|
|
3305
3300
|
if (kind)
|
|
3306
|
-
add(e.name, kind === 'package' ? `${e.name}/package.json
|
|
3301
|
+
add(e.name, kind === 'package' ? `${e.name}/package.json` : `${e.name}/tsconfig.json`);
|
|
3307
3302
|
}
|
|
3308
3303
|
}
|
|
3309
3304
|
catch {
|
|
@@ -3328,6 +3323,20 @@ function configuredSubProjects(cwd, config) {
|
|
|
3328
3323
|
}
|
|
3329
3324
|
return detectSubProjects(cwd);
|
|
3330
3325
|
}
|
|
3326
|
+
/** The sub-projects the root build should run, each ready to be run: a
|
|
3327
|
+
* sub-package gets its own build script set up first (importgen, tsc, its own
|
|
3328
|
+
* sub-projects — the same setup the root gets), and one that still has
|
|
3329
|
+
* nothing to build afterwards is left out, since delegating to it would only
|
|
3330
|
+
* fail with "missing script: build". */
|
|
3331
|
+
async function readySubProjects(cwd, config) {
|
|
3332
|
+
const ready = [];
|
|
3333
|
+
for (const s of configuredSubProjects(cwd, config)) {
|
|
3334
|
+
if (s.kind === 'package' && !(await ensureBuildScript(path.join(cwd, s.dir))))
|
|
3335
|
+
continue;
|
|
3336
|
+
ready.push(s);
|
|
3337
|
+
}
|
|
3338
|
+
return ready;
|
|
3339
|
+
}
|
|
3331
3340
|
/** Quote a path for a package.json script only when it needs it. */
|
|
3332
3341
|
function quoteScriptPath(p) {
|
|
3333
3342
|
return /[\s&|<>]/.test(p) ? `"${p}"` : p;
|
|
@@ -3351,14 +3360,8 @@ export async function ensureSubProjectsInBuild(cwd) {
|
|
|
3351
3360
|
const buildScript = typeof pkg.scripts?.build === 'string' ? pkg.scripts.build.trim() : '';
|
|
3352
3361
|
if (!buildScript || !scriptRunsTsc(buildScript))
|
|
3353
3362
|
return;
|
|
3354
|
-
const
|
|
3355
|
-
|
|
3356
|
-
// importgen check the root gets, so delegating to it is enough.
|
|
3357
|
-
for (const s of subs) {
|
|
3358
|
-
if (s.kind === 'package')
|
|
3359
|
-
await ensureImportgenInBuild(path.join(cwd, s.dir));
|
|
3360
|
-
}
|
|
3361
|
-
const missing = subs.filter(s => !scriptBuildsSubProject(buildScript, s.dir));
|
|
3363
|
+
const missing = (await readySubProjects(cwd, config))
|
|
3364
|
+
.filter(s => !scriptBuildsSubProject(buildScript, s.dir));
|
|
3362
3365
|
if (missing.length === 0)
|
|
3363
3366
|
return;
|
|
3364
3367
|
const name = pkg.name || path.basename(cwd);
|
|
@@ -3367,7 +3370,7 @@ export async function ensureSubProjectsInBuild(cwd) {
|
|
|
3367
3370
|
// — a bare nested tsconfig (tests, examples) is a weaker signal, and this
|
|
3368
3371
|
// prompt rewrites a build script that currently works.
|
|
3369
3372
|
const strong = missing.every(s => s.reason.startsWith('.vscode/tasks.json') || s.reason.startsWith('listed in'));
|
|
3370
|
-
console.log(colors.yellow(`${name}'s build script doesn't
|
|
3373
|
+
console.log(colors.yellow(`${name}'s build script doesn't build ${missing.length === 1 ? 'a sub-project' : 'its sub-projects'}:`));
|
|
3371
3374
|
for (const s of missing)
|
|
3372
3375
|
console.log(colors.yellow(` ${s.dir}/ — ${s.reason}`));
|
|
3373
3376
|
const addIt = await confirm(`Set "build": "${newScript}" in ${name}'s package.json?`, strong);
|
|
@@ -3417,76 +3420,61 @@ function areSubProjectsUpToDate(cwd, subs) {
|
|
|
3417
3420
|
}
|
|
3418
3421
|
return true;
|
|
3419
3422
|
}
|
|
3420
|
-
|
|
3421
|
-
|
|
3422
|
-
|
|
3423
|
-
|
|
3424
|
-
|
|
3425
|
-
|
|
3423
|
+
// 2026-09-06 — Claude Code (Fable 5.1), at Bob's direction ("when you add
|
|
3424
|
+
// importgen for a client dir you should set up the proper package.json").
|
|
3425
|
+
// Factored out of buildProject so a sub-package (itemw/client) gets the SAME
|
|
3426
|
+
// build-script setup as the root — importgen wired in, `tsc` added when it has
|
|
3427
|
+
// a tsconfig, its own sub-projects appended — instead of a second recipe.
|
|
3428
|
+
/** Make sure `cwd`'s package.json has the build script it needs: importgen
|
|
3429
|
+
* for an importgen project, `tsc` for a TypeScript project (prompted), and a
|
|
3430
|
+
* step per sub-project. Returns the package plus whether the script runs
|
|
3431
|
+
* importgen, or null when there is nothing to build here (no package.json, no
|
|
3432
|
+
* tsconfig or `noEmit` and no importgen, or the user declined the prompt). */
|
|
3433
|
+
export async function ensureBuildScript(cwd) {
|
|
3426
3434
|
// Do this before the tsconfig check: an importgen project may be plain JS
|
|
3427
3435
|
// with no tsconfig at all, and still need its import map regenerated.
|
|
3428
3436
|
await ensureImportgenInBuild(cwd);
|
|
3429
3437
|
let shouldBuild = false;
|
|
3430
|
-
let hasTsconfig = false;
|
|
3431
3438
|
try {
|
|
3432
|
-
const
|
|
3433
|
-
const content = fs.readFileSync(tsconfigPath, 'utf-8');
|
|
3434
|
-
const tsconfig = JSON5.parse(content);
|
|
3435
|
-
hasTsconfig = true;
|
|
3439
|
+
const tsconfig = JSON5.parse(fs.readFileSync(path.join(cwd, 'tsconfig.json'), 'utf-8'));
|
|
3436
3440
|
shouldBuild = tsconfig.compilerOptions?.noEmit !== true;
|
|
3437
3441
|
}
|
|
3438
3442
|
catch {
|
|
3439
|
-
// No tsconfig —
|
|
3443
|
+
// No tsconfig — nothing for tsc to do
|
|
3440
3444
|
}
|
|
3441
|
-
|
|
3442
|
-
// build to fail on them. A deprecated `moduleResolution` is only a *hard*
|
|
3443
|
-
// error under the tsc that flags it (TS6+, and the editor's bundled
|
|
3444
|
-
// compiler), so a package pinned to an older tsc — or one that never gets
|
|
3445
|
-
// to the build at all (noEmit, no build script, output already fresh) —
|
|
3446
|
-
// would otherwise keep the deprecation until TS7 removes it outright.
|
|
3447
|
-
// Deliberately ahead of every early return below: the tsconfig is worth
|
|
3448
|
-
// fixing whether or not this package builds. Migrating also touches
|
|
3449
|
-
// tsconfig.json, which makes the freshness check correctly read as stale.
|
|
3450
|
-
const tsconfigSnapshot = new Map();
|
|
3451
|
-
const issueMark = markBuildIssues();
|
|
3452
|
-
const migratedUpFront = hasTsconfig && migrateTsconfigDeprecations(cwd, tsconfigSnapshot);
|
|
3453
|
-
let earlyPkg;
|
|
3445
|
+
let pkg;
|
|
3454
3446
|
try {
|
|
3455
|
-
|
|
3447
|
+
pkg = readPackageJson(cwd);
|
|
3456
3448
|
}
|
|
3457
3449
|
catch {
|
|
3458
|
-
return
|
|
3450
|
+
return null;
|
|
3459
3451
|
}
|
|
3460
|
-
const runsImportgen = scriptRunsImportgen(typeof
|
|
3452
|
+
const runsImportgen = scriptRunsImportgen(typeof pkg.scripts?.build === 'string' ? pkg.scripts.build : '');
|
|
3461
3453
|
// A build script that regenerates the import map is worth running even when
|
|
3462
3454
|
// there's nothing for tsc to emit.
|
|
3463
3455
|
if (!shouldBuild && !runsImportgen)
|
|
3464
|
-
return
|
|
3465
|
-
const pkg = earlyPkg;
|
|
3456
|
+
return null;
|
|
3466
3457
|
const existingBuild = typeof pkg.scripts?.build === 'string' ? pkg.scripts.build.trim() : '';
|
|
3467
3458
|
// A build script ensureImportgenInBuild just created for a package that had
|
|
3468
3459
|
// none still needs its tsc pass — treat importgen-only as "no build yet".
|
|
3469
3460
|
const importgenOnly = !!existingBuild && existingBuild.split(/\s*&&\s*/).every(scriptRunsImportgen);
|
|
3470
3461
|
if (shouldBuild && (!existingBuild || importgenOnly)) {
|
|
3471
|
-
//
|
|
3472
|
-
// root tsc excludes them, so a bare "tsc" would leave them unbuilt.
|
|
3473
|
-
const subs =
|
|
3462
|
+
// Build the sub-projects (service worker, client package, …) in the same
|
|
3463
|
+
// breath: the root tsc excludes them, so a bare "tsc" would leave them unbuilt.
|
|
3464
|
+
const subs = await readySubProjects(cwd, readConfig(cwd));
|
|
3474
3465
|
const tscParts = ['tsc', ...subs.map(subProjectBuildCommand)];
|
|
3475
3466
|
const newScript = [...(existingBuild ? [existingBuild] : []), ...tscParts].join(' && ');
|
|
3476
3467
|
console.log(colors.yellow(`TypeScript project has no "build" script in ${pkg.name || cwd}`));
|
|
3477
3468
|
for (const s of subs)
|
|
3478
3469
|
console.log(colors.yellow(` sub-project ${s.dir}/ — ${s.reason}`));
|
|
3479
3470
|
const addIt = await confirm(`Add "build": "${newScript}" to ${pkg.name || path.basename(cwd)}'s package.json?`, true);
|
|
3480
|
-
if (addIt)
|
|
3481
|
-
|
|
3482
|
-
|
|
3483
|
-
pkg.scripts
|
|
3484
|
-
|
|
3485
|
-
|
|
3486
|
-
}
|
|
3487
|
-
else {
|
|
3488
|
-
return true;
|
|
3489
|
-
}
|
|
3471
|
+
if (!addIt)
|
|
3472
|
+
return null;
|
|
3473
|
+
if (!pkg.scripts)
|
|
3474
|
+
pkg.scripts = {};
|
|
3475
|
+
pkg.scripts.build = newScript;
|
|
3476
|
+
writePackageJson(cwd, pkg);
|
|
3477
|
+
console.log(colors.green(`✓ Added "build": "${newScript}" to ${pkg.name || path.basename(cwd)}`));
|
|
3490
3478
|
}
|
|
3491
3479
|
else if (existingBuild) {
|
|
3492
3480
|
await ensureSubProjectsInBuild(cwd);
|
|
@@ -3498,6 +3486,30 @@ export async function buildProject(cwd, opts = {}) {
|
|
|
3498
3486
|
catch { /* keep the script we already have */ }
|
|
3499
3487
|
}
|
|
3500
3488
|
ensureTsconfigNodeTypes(cwd);
|
|
3489
|
+
return { pkg, runsImportgen };
|
|
3490
|
+
}
|
|
3491
|
+
/** Build a single project: set up its build script (see `ensureBuildScript`),
|
|
3492
|
+
* run `npm run build`, record failures. Returns true if build succeeded (or
|
|
3493
|
+
* was skipped because there was nothing to build / the user declined the
|
|
3494
|
+
* build-script prompt / output already up to date). Pass `forceBuild` to
|
|
3495
|
+
* skip the freshness check. */
|
|
3496
|
+
export async function buildProject(cwd, opts = {}) {
|
|
3497
|
+
// Fix removed-in-TS7 tsconfig settings up front rather than waiting for a
|
|
3498
|
+
// build to fail on them. A deprecated `moduleResolution` is only a *hard*
|
|
3499
|
+
// error under the tsc that flags it (TS6+, and the editor's bundled
|
|
3500
|
+
// compiler), so a package pinned to an older tsc — or one that never gets
|
|
3501
|
+
// to the build at all (noEmit, no build script, output already fresh) —
|
|
3502
|
+
// would otherwise keep the deprecation until TS7 removes it outright.
|
|
3503
|
+
// Deliberately ahead of every early return below: the tsconfig is worth
|
|
3504
|
+
// fixing whether or not this package builds. Migrating also touches
|
|
3505
|
+
// tsconfig.json, which makes the freshness check correctly read as stale.
|
|
3506
|
+
const tsconfigSnapshot = new Map();
|
|
3507
|
+
const issueMark = markBuildIssues();
|
|
3508
|
+
const migratedUpFront = fs.existsSync(path.join(cwd, 'tsconfig.json')) && migrateTsconfigDeprecations(cwd, tsconfigSnapshot);
|
|
3509
|
+
const setup = await ensureBuildScript(cwd);
|
|
3510
|
+
if (!setup)
|
|
3511
|
+
return true;
|
|
3512
|
+
const { pkg, runsImportgen } = setup;
|
|
3501
3513
|
// Only the sub-projects the final build script really compiles count toward
|
|
3502
3514
|
// freshness — one the user declined to wire in isn't this build's business.
|
|
3503
3515
|
const finalBuild = typeof pkg.scripts?.build === 'string' ? pkg.scripts.build : '';
|