|
| 1 | +/** |
| 2 | + * Public entry for `@taskless/cli/node/runtimes`. |
| 3 | + * |
| 4 | + * Where the engine binaries this CLI executes actually live on this host, and |
| 5 | + * how to find them. It is the CLI's own resolver and the CLI's own package |
| 6 | + * specs — not a copy kept in step, the actual modules — so a consumer that |
| 7 | + * installs this package resolves the same ast-grep and the same Vale that |
| 8 | + * `taskless check` would run. |
| 9 | + * |
| 10 | + * ## Why this is published |
| 11 | + * |
| 12 | + * A service that verifies a generated rule has to execute it, and a rule's |
| 13 | + * behaviour is a property of the engine version that runs it, not of the |
| 14 | + * version it was written against. ast-grep in particular changes what a capture |
| 15 | + * matches across minors: a rule verified under one and executed under another |
| 16 | + * can verify clean and then match nothing, which is a rule that passes its gate |
| 17 | + * and never fires. Nothing reports that, because both sides succeeded. |
| 18 | + * |
| 19 | + * The alternative to importing this is pinning the engine version by hand |
| 20 | + * alongside the CLI version, in a second place, and keeping the two in step. |
| 21 | + * They drift, and the drift is silent in exactly the direction that matters. |
| 22 | + * Installing this package and asking it where the binary is makes the engine |
| 23 | + * version a consequence of the CLI version rather than a parallel fact: the |
| 24 | + * pinned platform packages arrive as this package's own optional dependencies, |
| 25 | + * and they move when the pin moves. |
| 26 | + * |
| 27 | + * ## This entry requires Node, and says so in its path |
| 28 | + * |
| 29 | + * `node/` is in the specifier deliberately. Resolution spawns each candidate to |
| 30 | + * make it identify itself, reads the filesystem, and consults `PATH`, so this |
| 31 | + * graph reaches `node:child_process`, `node:fs`, `node:module`, `node:path` and |
| 32 | + * `node:url`. It will not run on a Worker or in a browser, and the import path |
| 33 | + * says that before a build error has to. |
| 34 | + * |
| 35 | + * This is the difference between this entry and `@taskless/cli/prompts` and |
| 36 | + * `@taskless/cli/layout`, which are host-free and whose builds fail if their |
| 37 | + * graphs ever reach a host capability. Every published entry is classified as |
| 38 | + * one or the other in `vite.config.ts`, and the build fails on an export |
| 39 | + * belonging to neither, so this exemption is a declaration rather than an |
| 40 | + * omission. |
| 41 | + * |
| 42 | + * ## Resolution reports a miss as a value, and each side decides what it means |
| 43 | + * |
| 44 | + * {@link resolvePlatformBinary} returns `{ path: undefined, tried }` when no |
| 45 | + * candidate verified. It does not throw, and that is deliberate rather than |
| 46 | + * incidental: `taskless check` runs several engines, and a host without Vale |
| 47 | + * installed must lose the Vale rules rather than the whole run. Encoding "not |
| 48 | + * found" as a value is what lets a caller choose. |
| 49 | + * |
| 50 | + * A verifier should choose the opposite. A sandbox whose optional dependency |
| 51 | + * did not install resolves `undefined` for the same reason a developer's laptop |
| 52 | + * does — nothing is there — but the consequence is not a skipped engine, it is |
| 53 | + * a verification that ran no engine and reported success. Fail closed on |
| 54 | + * `undefined`, and use `tried` to say where you looked: |
| 55 | + * |
| 56 | + * ```ts |
| 57 | + * import { |
| 58 | + * AST_GREP_BINARY, |
| 59 | + * resolvePlatformBinary, |
| 60 | + * } from "@taskless/cli/node/runtimes"; |
| 61 | + * |
| 62 | + * const { path, tried } = resolvePlatformBinary(AST_GREP_BINARY); |
| 63 | + * if (path === undefined) { |
| 64 | + * throw new Error(`ast-grep not resolved. Looked in: ${tried.join(", ")}`); |
| 65 | + * } |
| 66 | + * ``` |
| 67 | + * |
| 68 | + * ## What a resolved path is, and is not |
| 69 | + * |
| 70 | + * It is a path that exists and that answered `--version` with output matching |
| 71 | + * the spec's `identity`, so it is the binary and not a placeholder a failed |
| 72 | + * install left behind. It is not a promise about which version answered: the |
| 73 | + * first candidate tried is the pinned platform package, but a host that only |
| 74 | + * has the engine on `PATH` resolves that instead. Read `--version` yourself if |
| 75 | + * the version is what you are relying on. |
| 76 | + */ |
| 77 | + |
| 78 | +// The `.js` extensions are deliberate, for the same reason they are on the |
| 79 | +// prompts and layout entries: this module is a published entry, so `tsc` copies |
| 80 | +// these specifiers verbatim into `dist/node/runtimes/index.d.ts`, where an |
| 81 | +// extensionless specifier fails to resolve for a consumer on |
| 82 | +// `moduleResolution: node16`/`nodenext`. |
| 83 | +export { |
| 84 | + findOnPath, |
| 85 | + isPlatformBinary, |
| 86 | + pathCommandName, |
| 87 | + platformPackageName, |
| 88 | + resolvePlatformBinary, |
| 89 | + type PlatformBinaryResolution, |
| 90 | + type PlatformBinarySpec, |
| 91 | +} from "../../rules/platform-binary.js"; |
| 92 | +export { AST_GREP_BINARY } from "../../rules/ast-grep-binary.js"; |
| 93 | +export { VALE_BINARY } from "../../rules/vale/binary.js"; |
0 commit comments