|
13 | 13 | // shape you would ship in a `*.object.ts` to a full ObjectStack backend. One |
14 | 14 | // object model, two hosts; only the installed capability set differs. |
15 | 15 |
|
| 16 | +import { realpathSync } from 'node:fs'; |
| 17 | +import { join, resolve } from 'node:path'; |
| 18 | +import { fileURLToPath } from 'node:url'; |
16 | 19 | import { ObjectQL } from '@objectstack/objectql/core'; |
17 | 20 | import { InMemoryDriver } from '@objectstack/driver-memory'; |
18 | 21 | import { ObjectSchema, Field, type ServiceObject } from '@objectstack/spec/data'; |
@@ -56,8 +59,41 @@ export async function runEmbeddedEngine(): Promise<AccountRow[]> { |
56 | 59 | }) as Promise<AccountRow[]>; |
57 | 60 | } |
58 | 61 |
|
59 | | -// Allow `node`/`tsx`-style direct execution to print the result. |
60 | | -if (import.meta.url === `file://${process.argv[1]}`) { |
| 62 | +// ─── entry guard ─────────────────────────────────────────────────────── |
| 63 | +// ⛔ NOT ``import.meta.url === `file://${process.argv[1]}` ``. Node symlink-resolves |
| 64 | +// `import.meta.url` but leaves `process.argv[1]` exactly as the caller typed it, and |
| 65 | +// the template also skips the percent-encoding `pathToFileURL` applies — so that |
| 66 | +// spelling goes INERT (exit 0, no output) through a symlink AND on any checkout path |
| 67 | +// containing a character that needs encoding (a `#` in a parent directory name is |
| 68 | +// enough, with no symlink involved). Compare RESOLVED PATHS, never URL strings. |
| 69 | +// |
| 70 | +// Same predicate as `packages/cli/src/utils/invocation.ts` (`isProcessEntry`) and |
| 71 | +// `scripts/invoked-as.mjs` (`invokedAs`). Spelled out rather than imported because |
| 72 | +// neither home is legally reachable from this file — the PR for #10269 carries the |
| 73 | +// boundary measurement. ⚠️ Two predicates answering this question differently IS the |
| 74 | +// defect this closes; change one, change all of them. |
| 75 | +function isProcessEntry(): boolean { |
| 76 | + const entryArg = process.argv[1]; |
| 77 | + if (!entryArg) return false; // `node --eval` / the REPL |
| 78 | + const self = resolve(fileURLToPath(import.meta.url)); |
| 79 | + const entry = resolve(entryArg); |
| 80 | + // `node <dir>` gives the ENTRY ARGUMENT, and only it, directory resolution. |
| 81 | + const candidates = [entry, join(entry, 'index.js'), join(entry, 'index.mjs'), join(entry, 'index.ts')]; |
| 82 | + if (candidates.includes(self)) return true; |
| 83 | + const realSelf = realOrSelf(self); |
| 84 | + return candidates.some((candidate) => realOrSelf(candidate) === realSelf); |
| 85 | +} |
| 86 | + |
| 87 | +/** `realpathSync`, degrading to the input for a path that cannot be read. */ |
| 88 | +function realOrSelf(p: string): string { |
| 89 | + try { |
| 90 | + return realpathSync(p); |
| 91 | + } catch { |
| 92 | + return p; |
| 93 | + } |
| 94 | +} |
| 95 | + |
| 96 | +if (isProcessEntry()) { |
61 | 97 | runEmbeddedEngine() |
62 | 98 | .then((rows) => { |
63 | 99 | // eslint-disable-next-line no-console |
|
0 commit comments