@bobfrankston/npmglobalize 1.0.214 → 1.0.215

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/cli.js +14 -1
  2. package/lib.d.ts +10 -0
  3. package/lib.js +22 -5
  4. package/package.json +1 -1
package/cli.js CHANGED
@@ -2,7 +2,7 @@
2
2
  /**
3
3
  * npmglobalize CLI - Transform file: dependencies to npm versions for publishing
4
4
  */
5
- import { globalize, globalizeWorkspace, installCleanupHandlers, readConfig, readPackageJson, readUserNpmConfig, writeConfig, writePackageJson, getBuildIssues, clearBuildIssues, ensureFileDepModules, buildProject, buildFileDepsTopologically, reportTs7Deprecations, fixTs7Deprecations } from './lib.js';
5
+ import { globalize, globalizeWorkspace, installCleanupHandlers, readConfig, readPackageJson, readUserNpmConfig, writeConfig, writePackageJson, getBuildIssues, clearBuildIssues, ensureFileDepModules, buildProject, buildFileDepsTopologically, reportTs7Deprecations, fixTs7Deprecations, canonicalCase } from './lib.js';
6
6
  import fs from 'fs';
7
7
  import path from 'path';
8
8
  import { colors } from './colors.js';
@@ -549,6 +549,19 @@ export async function main() {
549
549
  process.exit(1);
550
550
  }
551
551
  }
552
+ // 2026-09-06 — Claude Code (Fable 5.1), at Bob's direction.
553
+ // `process.cwd()` carries whatever letter case the shell was started with
554
+ // (`y:\dev\...` from a lowercase prompt). Every `file:` junction npm writes
555
+ // inherits that case, and mixed `y:`/`Y:` junction targets are exactly what
556
+ // makes arborist crash with "Cannot read properties of null (reading
557
+ // 'package')" — see `caseDiffers` in lib.ts. Canonicalizing once up front
558
+ // means every npm the cascade spawns writes junctions with the on-disk
559
+ // spelling, so the repair in verifyDepPathCase stops being needed again.
560
+ const canonicalCwd = canonicalCase(cwd);
561
+ if (canonicalCwd && canonicalCwd !== cwd) {
562
+ cwd = canonicalCwd;
563
+ process.chdir(cwd);
564
+ }
552
565
  // Report-only TS7-readiness scan (no transform / build / publish).
553
566
  if (cliOptions.ts7Report) {
554
567
  printTs7Report(cwd);
package/lib.d.ts CHANGED
@@ -287,6 +287,16 @@ export type UnpublishedDep = {
287
287
  path: string;
288
288
  reason: 'new' | 'update';
289
289
  };
290
+ /** The canonical on-disk spelling of `target`, or null if it does not exist.
291
+ * Windows answers path queries case-insensitively, so the only way to learn how
292
+ * a directory is really spelled is to ask its parent to list it — a segment at a
293
+ * time, from the root down. The drive letter is always uppercased.
294
+ *
295
+ * 2026-09-06 — Claude Code (Fable 5.1), at Bob's direction: exported so cli.ts can
296
+ * canonicalize the working directory at startup (see there for why). Deliberately
297
+ * NOT `fs.realpathSync.native`: that would also resolve a junction the user is
298
+ * standing inside, which is a behaviour change nobody asked for. */
299
+ export declare function canonicalCase(target: string): string;
290
300
  /** Vet the casing of every `file:` dependency path, in BOTH places it is recorded:
291
301
  * the spec in package.json and the junction npm created in node_modules.
292
302
  *
package/lib.js CHANGED
@@ -1323,8 +1323,13 @@ function hasLocalChanges(packageName, version, targetPath, verbose) {
1323
1323
  /** The canonical on-disk spelling of `target`, or null if it does not exist.
1324
1324
  * Windows answers path queries case-insensitively, so the only way to learn how
1325
1325
  * a directory is really spelled is to ask its parent to list it — a segment at a
1326
- * time, from the root down. */
1327
- function canonicalCase(target) {
1326
+ * time, from the root down. The drive letter is always uppercased.
1327
+ *
1328
+ * 2026-09-06 — Claude Code (Fable 5.1), at Bob's direction: exported so cli.ts can
1329
+ * canonicalize the working directory at startup (see there for why). Deliberately
1330
+ * NOT `fs.realpathSync.native`: that would also resolve a junction the user is
1331
+ * standing inside, which is a behaviour change nobody asked for. */
1332
+ export function canonicalCase(target) {
1328
1333
  const full = path.resolve(target);
1329
1334
  if (!fs.existsSync(full))
1330
1335
  return null;
@@ -1347,10 +1352,22 @@ function canonicalCase(target) {
1347
1352
  return cur;
1348
1353
  }
1349
1354
  /** True when two paths name the same place but are spelled with different case
1350
- * in a directory name. A bare drive-letter difference (`y:` vs `Y:`) doesn't
1351
- * count — it resolves identically everywhere, including WSL's /mnt/y. */
1355
+ * anywhere the drive letter included.
1356
+ *
1357
+ * 2026-09-06 — Claude Code (Fable 5.1), at Bob's direction. This used to strip the
1358
+ * root before comparing, on the theory that `y:` vs `Y:` "resolves identically
1359
+ * everywhere". It does on the filesystem; it does NOT inside npm's arborist.
1360
+ * Traced with an instrumented `loadActual` on dbitemx / pings / itemw: arborist
1361
+ * keeps the junction target's case in `realpath` (an exact-string index) but
1362
+ * computes `location` with `path.win32.relative`, which folds drive-letter case.
1363
+ * One directory reached as `Y:\...` and `y:\...` becomes two target nodes at one
1364
+ * location; the second clobbers the first, every Link into the first is left with
1365
+ * `target === null`, and the next same-case Link crashes on `target.package` —
1366
+ * the "Cannot read properties of null (reading 'package')" failure. npm writes
1367
+ * the junction target with whatever drive case the cwd had, so a lowercase shell
1368
+ * is all it takes. Full trace: `.llm/arborist-trace.md`. */
1352
1369
  function caseDiffers(a, b) {
1353
- const strip = (p) => p.replace(/[\\/]+$/, '').slice(path.parse(p).root.length);
1370
+ const strip = (p) => p.replace(/[\\/]+$/, '');
1354
1371
  return strip(a) !== strip(b);
1355
1372
  }
1356
1373
  /** Vet the casing of every `file:` dependency path, in BOTH places it is recorded:
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bobfrankston/npmglobalize",
3
- "version": "1.0.214",
3
+ "version": "1.0.215",
4
4
  "description": "Transform file: dependencies to npm versions for publishing",
5
5
  "main": "index.js",
6
6
  "type": "module",