@bobfrankston/npmglobalize 1.0.219 → 1.0.220

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 (4) hide show
  1. package/README.md +1 -1
  2. package/lib.d.ts +0 -10
  3. package/lib.js +67 -14
  4. 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` 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.
759
+ 2. An immediate sub-directory that is a build of its own: a `package.json` with a `build` script or an import map to regenerate (a sub-package gets its build script set up the same way the root does importgen, `tsc`, its own sub-projects before the root delegates to it), or a `tsconfig.json` the root `tsconfig.json` does not already compile. A `package.json` that only holds dependencies for files the root `tsc` compiles (`whts/pdfToImg`) is not a sub-project. 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
@@ -397,16 +397,6 @@ export declare function missingDeps(pkgDir: string, pkg: any): string[];
397
397
  * re-run" (partial-sync) cases. Also covers `cwd` itself on the first call.
398
398
  * Cycle-safe via the shared `visited` set. */
399
399
  export declare function ensureFileDepModules(cwd: string, verbose?: boolean, visited?: Set<string>): Promise<boolean>;
400
- /** Cheap freshness check so the build cascade can skip packages whose output is
401
- * already current. Returns true only when provably up to date:
402
- * - outDir projects: newest source (.ts/.tsx/.mts/.cts + tsconfig*.json) must
403
- * not be newer than the newest file under outDir.
404
- * - side-by-side projects: every source must have an emitted sibling
405
- * (.js/.jsx/.mjs/.cjs or declaration) at least as new.
406
- * Conservative: project references, allowJs, missing outputs, or unreadable
407
- * tsconfig all report stale (→ build). package.json mtime is deliberately
408
- * ignored — npmglobalize itself rewrites it around every publish, which would
409
- * otherwise force a rebuild on every run. */
410
400
  export declare function isBuildUpToDate(cwd: string): boolean;
411
401
  /** Detect whether a package is an importgen project — a browser app whose HTML
412
402
  * carries a generated `<script type="importmap">`. Signals, in order:
package/lib.js CHANGED
@@ -2888,6 +2888,35 @@ function newestMtimeUnder(dir) {
2888
2888
  * tsconfig all report stale (→ build). package.json mtime is deliberately
2889
2889
  * ignored — npmglobalize itself rewrites it around every publish, which would
2890
2890
  * otherwise force a rebuild on every run. */
2891
+ /** The directories a parsed tsconfig's `exclude` keeps out of the compile, as
2892
+ * lower-cased relative paths. Matches on the literal path prefix (glob tails
2893
+ * like "tests/**" reduce to "tests"). */
2894
+ function tsconfigExcludedDirs(tsconfig) {
2895
+ return (Array.isArray(tsconfig?.exclude) ? tsconfig.exclude : [])
2896
+ .filter((x) => typeof x === 'string')
2897
+ .map((x) => x.replace(/^\.\//, '').replace(/[\\/]?\*.*$/, '').replace(/[\\/]+$/, '').toLowerCase())
2898
+ .filter((x) => x.length > 0 && !x.includes('*'));
2899
+ }
2900
+ /** True when the root tsconfig in `cwd` already compiles the sources under
2901
+ * `rel`: it exists, doesn't narrow the compile with `files`/`include`, and
2902
+ * doesn't `exclude` the directory. Conservative — a root that uses `include`
2903
+ * is treated as not covering the sub-directory. */
2904
+ function rootTsconfigCompiles(cwd, rel) {
2905
+ let tsconfig;
2906
+ try {
2907
+ tsconfig = JSON5.parse(fs.readFileSync(path.join(cwd, 'tsconfig.json'), 'utf-8'));
2908
+ }
2909
+ catch {
2910
+ return false;
2911
+ }
2912
+ if (Array.isArray(tsconfig.files) || Array.isArray(tsconfig.include))
2913
+ return false;
2914
+ const target = normalizeProjectPath(rel).toLowerCase();
2915
+ const parts = target.split('/');
2916
+ const excludes = tsconfigExcludedDirs(tsconfig);
2917
+ // Excluding a parent excludes everything under it.
2918
+ return !parts.some((_, i) => excludes.includes(parts.slice(0, i + 1).join('/')));
2919
+ }
2891
2920
  export function isBuildUpToDate(cwd) {
2892
2921
  let tsconfig;
2893
2922
  try {
@@ -2901,12 +2930,8 @@ export function isBuildUpToDate(cwd) {
2901
2930
  return false;
2902
2931
  const outDir = typeof co.outDir === 'string' ? path.resolve(cwd, co.outDir) : null;
2903
2932
  // tsconfig "exclude" dirs aren't compiled, so their .ts files never get
2904
- // outputs — don't count them as sources. Match on the literal path prefix
2905
- // (glob tails like "tests/**" reduce to "tests").
2906
- const excludes = (Array.isArray(tsconfig.exclude) ? tsconfig.exclude : [])
2907
- .filter((x) => typeof x === 'string')
2908
- .map((x) => x.replace(/^\.\//, '').replace(/[\\/]?\*.*$/, '').replace(/[\\/]+$/, '').toLowerCase())
2909
- .filter((x) => x.length > 0 && !x.includes('*'));
2933
+ // outputs — don't count them as sources.
2934
+ const excludes = tsconfigExcludedDirs(tsconfig);
2910
2935
  const sources = [];
2911
2936
  const collect = (dir, rel) => {
2912
2937
  let entries;
@@ -3198,13 +3223,31 @@ function resolveOnDiskCase(base, rel) {
3198
3223
  }
3199
3224
  return out.length ? out.join('/') : null;
3200
3225
  }
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. */
3226
+ /** Classify `rel` under `cwd`: a package (its own package.json with a build
3227
+ * script, or an importgen project `ensureBuildScript` sets its script up),
3228
+ * a plain tsconfig sub-project the root tsc doesn't compile, or null when it
3229
+ * is neither.
3230
+ * 2026-09-06 — Claude Code (Fable 5.1), at Bob's direction. whts/pdfToImg has
3231
+ * a package.json that only holds puppeteer/sharp for a file the root tsc
3232
+ * already compiles; treating every package.json as a sub-package gave it a
3233
+ * `build: tsc` it never needed and a second compile under its stale tsconfig.
3234
+ * A package.json is a build of its own only when it has something the root
3235
+ * can't do — its own build script, or an import map to regenerate. */
3204
3236
  function subProjectKind(cwd, rel) {
3205
- if (fs.existsSync(path.join(cwd, rel, 'package.json')))
3206
- return 'package';
3207
- return fs.existsSync(path.join(cwd, rel, 'tsconfig.json')) ? 'tsconfig' : null;
3237
+ const dir = path.join(cwd, rel);
3238
+ if (fs.existsSync(path.join(dir, 'package.json'))) {
3239
+ let pkg = {};
3240
+ try {
3241
+ pkg = readPackageJson(dir);
3242
+ }
3243
+ catch { /* unreadable — treat as having no build script */ }
3244
+ const hasBuild = typeof pkg?.scripts?.build === 'string' && pkg.scripts.build.trim();
3245
+ if (hasBuild || detectImportgen(dir))
3246
+ return 'package';
3247
+ }
3248
+ if (!fs.existsSync(path.join(dir, 'tsconfig.json')))
3249
+ return null;
3250
+ return rootTsconfigCompiles(cwd, rel) ? null : 'tsconfig';
3208
3251
  }
3209
3252
  /** The command the root build script runs for a sub-project. */
3210
3253
  function subProjectBuildCommand(s) {
@@ -3505,7 +3548,7 @@ export async function buildProject(cwd, opts = {}) {
3505
3548
  // tsconfig.json, which makes the freshness check correctly read as stale.
3506
3549
  const tsconfigSnapshot = new Map();
3507
3550
  const issueMark = markBuildIssues();
3508
- const migratedUpFront = fs.existsSync(path.join(cwd, 'tsconfig.json')) && migrateTsconfigDeprecations(cwd, tsconfigSnapshot);
3551
+ let migratedUpFront = fs.existsSync(path.join(cwd, 'tsconfig.json')) && migrateTsconfigDeprecations(cwd, tsconfigSnapshot);
3509
3552
  const setup = await ensureBuildScript(cwd);
3510
3553
  if (!setup)
3511
3554
  return true;
@@ -3514,6 +3557,16 @@ export async function buildProject(cwd, opts = {}) {
3514
3557
  // freshness — one the user declined to wire in isn't this build's business.
3515
3558
  const finalBuild = typeof pkg.scripts?.build === 'string' ? pkg.scripts.build : '';
3516
3559
  const wiredSubs = detectSubProjects(cwd).filter(s => scriptBuildsSubProject(finalBuild, s.dir));
3560
+ // 2026-09-06 — Claude Code (Fable 5.1), at Bob's direction.
3561
+ // The sub-projects this build runs get the same TS7 migration as the root.
3562
+ // whts: `tsc -p pdfToImg` failed on pdfToImg/tsconfig.json's
3563
+ // `moduleResolution: node10` (TS5107) and the retry below re-migrated only
3564
+ // the root chain, which had nothing left to fix — so no retry ever happened.
3565
+ const migrateSubProjects = () => wiredSubs
3566
+ .map(s => migrateTsconfigDeprecations(path.join(cwd, s.dir), tsconfigSnapshot))
3567
+ .some(Boolean);
3568
+ if (migrateSubProjects())
3569
+ migratedUpFront = true;
3517
3570
  // A migration just changed how modules resolve — never report that as fresh;
3518
3571
  // the rebuild is what validates it (and what the revert path above needs).
3519
3572
  if (!opts.forceBuild && !migratedUpFront && isBuildUpToDate(cwd) && areSubProjectsUpToDate(cwd, wiredSubs)
@@ -3529,7 +3582,7 @@ export async function buildProject(cwd, opts = {}) {
3529
3582
  // We don't silence with ignoreDeprecations — the retry may now surface real
3530
3583
  // resolution errors, which are genuine bugs to fix, not deprecations.
3531
3584
  const out = (buildResult.stderr || '') + (buildResult.output || '');
3532
- if (/error TS510[17]\b/.test(out) && migrateTsconfigDeprecations(cwd, tsconfigSnapshot)) {
3585
+ if (/error TS510[17]\b/.test(out) && [migrateTsconfigDeprecations(cwd, tsconfigSnapshot), migrateSubProjects()].some(Boolean)) {
3533
3586
  buildResult = await runCommandAsync('npm', ['run', 'build'], { cwd, silent: true });
3534
3587
  }
3535
3588
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bobfrankston/npmglobalize",
3
- "version": "1.0.219",
3
+ "version": "1.0.220",
4
4
  "description": "Transform file: dependencies to npm versions for publishing",
5
5
  "main": "index.js",
6
6
  "type": "module",