|
28 | 28 | * the probe against synthetic fixtures covering legacy edge cases. |
29 | 29 | */ |
30 | 30 |
|
| 31 | +import { realpathSync } from 'node:fs'; |
| 32 | +import { join, resolve } from 'node:path'; |
31 | 33 | import { hashSpec } from '@objectstack/metadata-core'; |
32 | 34 |
|
33 | 35 | export interface LegacyMetadataRow { |
@@ -257,8 +259,57 @@ export function formatReport(report: DryRunReport): string { |
257 | 259 | return lines.join('\n'); |
258 | 260 | } |
259 | 261 |
|
260 | | -// CLI entrypoint — only runs when invoked directly. |
261 | | -if (typeof process !== 'undefined' && process.argv[1] && /dry-run-hash-compat\.ts$/.test(process.argv[1])) { |
| 262 | +// ─── entry guard ─────────────────────────────────────────────────────── |
| 263 | +// ⛔ NOT ``import.meta.url === `file://${process.argv[1]}` ``. Node symlink-resolves |
| 264 | +// `import.meta.url` but leaves `process.argv[1]` exactly as the caller typed it, and |
| 265 | +// the template also skips the percent-encoding `pathToFileURL` applies — so that |
| 266 | +// spelling goes INERT (exit 0, no output) through a symlink AND on any checkout path |
| 267 | +// containing a character that needs encoding (a `#` in a parent directory name is |
| 268 | +// enough, with no symlink involved). Compare RESOLVED PATHS, never URL strings. |
| 269 | +// |
| 270 | +// Same predicate as `packages/cli/src/utils/invocation.ts` (`isProcessEntry`) and |
| 271 | +// `scripts/invoked-as.mjs` (`invokedAs`) — both legs identical: realpath for the |
| 272 | +// symlink, directory resolution for `node <dir>`. Spelled out rather than imported |
| 273 | +// because neither home is legally reachable from this file — the PR for #10269 |
| 274 | +// carries the boundary measurement. ⚠️ Two predicates answering this question |
| 275 | +// differently IS the defect this closes; change one, change all of them. |
| 276 | +// |
| 277 | +// ⚠️ ONE spelling DIVERGES from those two, and the divergence is FORCED — do not |
| 278 | +// "restore consistency" here: the self-path seed is `__filename`, NOT |
| 279 | +// `fileURLToPath(import.meta.url)`. `packages/objectql/package.json` declares no |
| 280 | +// `"type"`, so under the repo-wide `module: NodeNext` every file in this package |
| 281 | +// compiles as COMMONJS, and `import.meta` in a CommonJS-format file is a hard |
| 282 | +// compile error (TS1470). This file IS inside a tsc program despite the package's |
| 283 | +// own `include` naming only `src/**/*`: `src/dry-run-hash-compat.test.ts` imports |
| 284 | +// it, and the TEST_DEBT re-measure in `scripts/check-type-check-coverage.mjs` |
| 285 | +// type-checks the tests — so the ESM seed costs a ratchet failure on a ledger that |
| 286 | +// may only shrink. The PREDICATE is untouched by this: `invokedAs(entryArg, |
| 287 | +// selfPath)` is the shared core and it takes a PATH, `isEntrypoint(import.meta.url)` |
| 288 | +// is merely the ESM way to seed it, and `__filename` is the CommonJS way — node's |
| 289 | +// CJS loader hands it an absolute path that is ALREADY symlink-resolved and |
| 290 | +// percent-decoded, which is exactly the property the guard rests on. |
| 291 | +function isProcessEntry(): boolean { |
| 292 | + const entryArg = process.argv[1]; |
| 293 | + if (!entryArg) return false; // `node --eval` / the REPL |
| 294 | + const self = resolve(__filename); |
| 295 | + const entry = resolve(entryArg); |
| 296 | + // `node <dir>` gives the ENTRY ARGUMENT, and only it, directory resolution. |
| 297 | + const candidates = [entry, join(entry, 'index.js'), join(entry, 'index.mjs'), join(entry, 'index.ts')]; |
| 298 | + if (candidates.includes(self)) return true; |
| 299 | + const realSelf = realOrSelf(self); |
| 300 | + return candidates.some((candidate) => realOrSelf(candidate) === realSelf); |
| 301 | +} |
| 302 | + |
| 303 | +/** `realpathSync`, degrading to the input for a path that cannot be read. */ |
| 304 | +function realOrSelf(p: string): string { |
| 305 | + try { |
| 306 | + return realpathSync(p); |
| 307 | + } catch { |
| 308 | + return p; |
| 309 | + } |
| 310 | +} |
| 311 | + |
| 312 | +if (isProcessEntry()) { |
262 | 313 | const path = process.argv[2]; |
263 | 314 | if (!path) { |
264 | 315 | console.error('Usage: pnpm tsx packages/objectql/scripts/dry-run-hash-compat.ts <snapshot.json>'); |
|
0 commit comments