@bobfrankston/npmglobalize 1.0.215 → 1.0.216

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 +17 -0
  2. package/lib.d.ts +9 -0
  3. package/lib.js +52 -1
  4. package/package.json +1 -1
package/README.md CHANGED
@@ -562,6 +562,23 @@ Settings can be saved in `.globalize.json5`:
562
562
 
563
563
  Configuration persists across runs. CLI flags override config file.
564
564
 
565
+ ### `.stale` — retired directories
566
+
567
+ A directory that is no longer live or maintained can be retired in place by
568
+ dropping a `.stale` file into it — empty, or better holding one line saying why
569
+ and when. It is a marker file, not a setting in `.globalize.json5`, because
570
+ stale is a property of the directory (most retired directories never had a
571
+ config) and a file is findable without parsing.
572
+
573
+ npmglobalize refuses to build, publish or install a directory marked `.stale`,
574
+ and refuses to cascade into a `file:` dependency that is marked. Both are
575
+ reported as **errors**, not skips: a live package that depends on a retired one
576
+ has a reference that needs fixing. Remove the marker to make the directory live
577
+ again.
578
+
579
+ `cleanprev.ps1` (in `%OneDrive%\xfer\bin`) strips `node_modules`, lock files and
580
+ tsc output from every `.stale` directory the same way it does for `prev/`.
581
+
565
582
  ### `usePaths` — Standalone packages
566
583
 
567
584
  Default: `true`. Set to `false` (or pass `-no-use-paths` / `-nup`) to mark a
package/lib.d.ts CHANGED
@@ -297,6 +297,15 @@ export type UnpublishedDep = {
297
297
  * NOT `fs.realpathSync.native`: that would also resolve a junction the user is
298
298
  * standing inside, which is a behaviour change nobody asked for. */
299
299
  export declare function canonicalCase(target: string): string;
300
+ /** A directory retired in place carries a `.stale` marker file (Bob's convention,
301
+ * 2026-09-06; `cleanprev.ps1` strips such directories like a `prev/`). The file
302
+ * is empty or holds a line saying why and when. Returns that line (or a fixed
303
+ * phrase when empty), or null when the directory is not marked.
304
+ *
305
+ * 2026-09-06 — Claude Code (Fable 5.1), at Bob's direction. A marker file rather
306
+ * than a flag in .globalize.json5: stale is a property of a directory, most stale
307
+ * directories never had a config, and a file is findable without parsing. */
308
+ export declare function staleMarker(dir: string): string;
300
309
  /** Vet the casing of every `file:` dependency path, in BOTH places it is recorded:
301
310
  * the spec in package.json and the junction npm created in node_modules.
302
311
  *
package/lib.js CHANGED
@@ -1370,6 +1370,25 @@ function caseDiffers(a, b) {
1370
1370
  const strip = (p) => p.replace(/[\\/]+$/, '');
1371
1371
  return strip(a) !== strip(b);
1372
1372
  }
1373
+ /** A directory retired in place carries a `.stale` marker file (Bob's convention,
1374
+ * 2026-09-06; `cleanprev.ps1` strips such directories like a `prev/`). The file
1375
+ * is empty or holds a line saying why and when. Returns that line (or a fixed
1376
+ * phrase when empty), or null when the directory is not marked.
1377
+ *
1378
+ * 2026-09-06 — Claude Code (Fable 5.1), at Bob's direction. A marker file rather
1379
+ * than a flag in .globalize.json5: stale is a property of a directory, most stale
1380
+ * directories never had a config, and a file is findable without parsing. */
1381
+ export function staleMarker(dir) {
1382
+ const marker = path.join(dir, '.stale');
1383
+ if (!fs.existsSync(marker))
1384
+ return null;
1385
+ let note = '';
1386
+ try {
1387
+ note = fs.readFileSync(marker, 'utf8').split(/\r?\n/).map(l => l.trim()).find(l => l) || '';
1388
+ }
1389
+ catch { /* unreadable note: still stale */ }
1390
+ return note || 'no reason recorded in .stale';
1391
+ }
1373
1392
  /** Vet the casing of every `file:` dependency path, in BOTH places it is recorded:
1374
1393
  * the spec in package.json and the junction npm created in node_modules.
1375
1394
  *
@@ -2328,6 +2347,15 @@ export async function ensureFileDepModules(cwd, verbose = false, visited = new S
2328
2347
  const target = path.resolve(cwd, spec.slice('file:'.length));
2329
2348
  if (!fs.existsSync(path.join(target, 'package.json')))
2330
2349
  continue;
2350
+ // 2026-09-06 — Claude Code (Fable 5.1): never install into, or walk
2351
+ // through, a dep that is retired (`.stale`). Reported as an error so the
2352
+ // stale reference in package.json gets fixed rather than papered over.
2353
+ const targetStale = staleMarker(target);
2354
+ if (targetStale) {
2355
+ console.error(colors.red(`✗ file: dependency ${name} -> ${target} is marked .stale (${targetStale}); not installing into a retired directory.`));
2356
+ recordBuildIssue(pkg?.name || abs, 'error', `file: dependency ${name} (${target}) is marked .stale (${targetStale}).`);
2357
+ continue;
2358
+ }
2331
2359
  let targetPkg;
2332
2360
  try {
2333
2361
  targetPkg = readPackageJson(target);
@@ -3505,7 +3533,7 @@ export async function buildFileDepsTopologically(cwd, opts = {}, visited = new S
3505
3533
  const deps = pkg?.[key];
3506
3534
  if (!deps || typeof deps !== 'object')
3507
3535
  continue;
3508
- for (const [, spec] of Object.entries(deps)) {
3536
+ for (const [name, spec] of Object.entries(deps)) {
3509
3537
  if (typeof spec !== 'string' || !spec.startsWith('file:'))
3510
3538
  continue;
3511
3539
  const target = path.resolve(cwd, spec.slice('file:'.length));
@@ -3514,6 +3542,18 @@ export async function buildFileDepsTopologically(cwd, opts = {}, visited = new S
3514
3542
  const targetAbs = path.resolve(target);
3515
3543
  if (visited.has(targetAbs))
3516
3544
  continue;
3545
+ // 2026-09-06 — Claude Code (Fable 5.1): a retired (`.stale`) dep is not
3546
+ // built. Counts as a failed dep so the consumer's build stops here too,
3547
+ // unless -force says to carry on.
3548
+ const targetStale = staleMarker(target);
3549
+ if (targetStale) {
3550
+ console.error(colors.red(`✗ file: dependency ${name} -> ${target} is marked .stale (${targetStale}); not building a retired directory.`));
3551
+ recordBuildIssue(pkg?.name || abs, 'error', `file: dependency ${name} (${target}) is marked .stale (${targetStale}).`);
3552
+ allOk = false;
3553
+ if (!opts.force)
3554
+ return false;
3555
+ continue;
3556
+ }
3517
3557
  // Recurse first (deps before consumer)
3518
3558
  const childOk = await buildFileDepsTopologically(target, opts, visited);
3519
3559
  if (!childOk)
@@ -6141,6 +6181,17 @@ export async function globalize(cwd, options = {}, configOptions = {}) {
6141
6181
  publishDepsYes = false, // -pd: auto-yes to dep-cascade prompts (private only)
6142
6182
  publicDeps = false, // -public-deps: cascade public visibility to all deps
6143
6183
  noPrescan = false, forcePublish = false, fix = true, fixTags = false, rebase = false, show = false, local = false, freeze = false, usePaths = true, allowTs, adopt = false, importCheck = false } = options;
6184
+ // 2026-09-06 — Claude Code (Fable 5.1), at Bob's direction: a directory marked
6185
+ // `.stale` is retired. Refuse to build, publish or install it — and because the
6186
+ // dep cascade reaches deps through this same function, refuse to cascade into
6187
+ // one too. The refusal is an error, not a skip: a live package depending on a
6188
+ // stale one is a fact the user has to act on.
6189
+ const staleNote = staleMarker(cwd);
6190
+ if (staleNote) {
6191
+ console.error(colors.red(`✗ ${cwd} is marked .stale (${staleNote}). Not publishing a retired directory.`));
6192
+ recordBuildIssue(path.basename(cwd), 'error', `Directory is marked .stale (${staleNote}); remove the marker to publish it again.`);
6193
+ return false;
6194
+ }
6144
6195
  // Show tool version only for recursive dep calls (CLI already prints it at startup)
6145
6196
  const toolVersion = getToolVersion();
6146
6197
  if (!options._fromWorkspace && !options._fromCli) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bobfrankston/npmglobalize",
3
- "version": "1.0.215",
3
+ "version": "1.0.216",
4
4
  "description": "Transform file: dependencies to npm versions for publishing",
5
5
  "main": "index.js",
6
6
  "type": "module",