{"version":3,"file":"launch.d.ts","sourceRoot":"","sources":["../../../src/core/canvas/launch.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AASH,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAKjD,oFAAoF;AACpF,eAAO,MAAM,qBAAqB,KAAK,CAAC;AACxC,0DAA0D;AAC1D,eAAO,MAAM,qBAAqB,IAAI,CAAC;AAEvC,gFAAgF;AAChF,eAAO,MAAM,4BAA4B,OAAQ,CAAC;AAElD,gFAAgF;AAChF,MAAM,MAAM,kBAAkB,GAAG;IAAE,SAAS,EAAE,IAAI,CAAC;IAAC,OAAO,EAAE,aAAa,CAAA;CAAE,GAAG;IAAE,SAAS,EAAE,KAAK,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAAC;AAEpH;;;;;;GAMG;AACH,MAAM,WAAW,mBAAmB;IACnC,sCAAsC;IACtC,GAAG,EAAE,MAAM,CAAC;IACZ,oEAAoE;IACpE,QAAQ,EAAE,MAAM,EAAE,CAAC;CACnB;AAED,uCAAuC;AACvC,MAAM,WAAW,cAAc;IAC9B,mFAAiF;IACjF,QAAQ,EAAE,MAAM,CAAC;IACjB,uCAAuC;IACvC,OAAO,EAAE,MAAM,CAAC;CAChB;AAED,6FAA6F;AAC7F,MAAM,WAAW,eAAe;IAC/B,oDAAoD;IACpD,UAAU,EAAE,MAAM,GAAG,SAAS,CAAC;IAC/B,sFAAoF;IACpF,WAAW,EAAE,MAAM,CAAC;IACpB,0BAA0B;IAC1B,QAAQ,EAAE,MAAM,CAAC;IACjB,iDAAiD;IACjD,cAAc,EAAE,mBAAmB,EAAE,CAAC;IACtC,4CAA4C;IAC5C,MAAM,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,OAAO,CAAC;IACrC,oEAAoE;IACpE,aAAa,EAAE,MAAM,OAAO,CAAC,cAAc,GAAG,SAAS,CAAC,CAAC;CACzD;AA4ED,gCAAgC;AAChC,wBAAgB,sBAAsB,IAAI,eAAe,CASxD;AAWD;;;;;GAKG;AACH,wBAAsB,oBAAoB,CACzC,KAAK,GAAE,eAA0C,GAC/C,OAAO,CAAC,kBAAkB,CAAC,CAmC7B","sourcesContent":["/**\n * Whether canvases can run here, and how to fork one.\n *\n * Design: `docs/canvas-extensions-design.md` §11.1. A canvas child must run under\n * **Node ≥ 20.6** — the version that introduced `module.register`, which\n * `resolver.ts` uses to make the SDK specifier resolvable inside the child without\n * writing into the extension directory.\n *\n * The requirement is on the *child*, not on hoocode. An earlier version of this\n * module conflated the two and refused to run canvases at all under the\n * self-contained build; but a Bun-compiled parent forking a Node child is fine, so\n * the question is \"is a usable Node reachable?\", not \"are we Node?\". That makes\n * canvases available on every install path where Node exists — npm, bun, or the\n * standalone binary with Node on PATH.\n *\n * Two traps found by running it rather than reasoning about it, both of which fail\n * *silently* if you get them wrong:\n *\n *  1. **Bun exports `module.register` but ignores resolve hooks.** The call\n *     succeeds, nothing warns, and the child then resolves the real\n *     `@github/copilot-sdk` out of Bun's global install cache instead of the shim.\n *     So the child may never be Bun, however the parent was launched.\n *  2. **`process.versions.node` does not identify Node.** Bun reports\n *     `process.versions.node = \"24.3.0\"` next to `process.versions.bun`. Deciding\n *     \"we can fork ourselves\" must therefore require `process.versions.bun` to be\n *     absent, never just a satisfying Node version.\n *\n * Resolution is not cached here. It can spawn `node --version`, so the caller should\n * resolve once per session and hold the result rather than asking per open.\n */\n\nimport { execFile } from \"node:child_process\";\nimport { existsSync } from \"node:fs\";\nimport { createRequire } from \"node:module\";\nimport * as path from \"node:path\";\nimport { fileURLToPath, pathToFileURL } from \"node:url\";\nimport { promisify } from \"node:util\";\nimport { getCanvasDir } from \"../../config.js\";\nimport type { CanvasRuntime } from \"./runner.js\";\n\nconst execFileAsync = promisify(execFile);\nconst require = createRequire(import.meta.url);\n\n/** Minimum Node that supports `module.register`, which `resolver.ts` depends on. */\nexport const CANVAS_MIN_NODE_MAJOR = 20;\n/** Minimum minor within {@link CANVAS_MIN_NODE_MAJOR}. */\nexport const CANVAS_MIN_NODE_MINOR = 6;\n\n/** How long to wait for `node --version` before giving up on PATH discovery. */\nexport const CANVAS_NODE_PROBE_TIMEOUT_MS = 5_000;\n\n/** Either a runtime that can fork canvases, or the reason none is available. */\nexport type CanvasAvailability = { available: true; runtime: CanvasRuntime } | { available: false; reason: string };\n\n/**\n * A shim the child could import, paired with whatever argv it needs to do so.\n *\n * The pairing is the point: the built `.js` needs nothing, while the TypeScript source\n * needs a loader. Offering a shim without its prerequisite is how you get an\n * \"available\" that fails at fork time.\n */\nexport interface CanvasShimCandidate {\n\t/** `file:` URL of the shim module. */\n\turl: string;\n\t/** Extra argv prepended for the child, e.g. a TypeScript loader. */\n\texecArgv: string[];\n}\n\n/** A Node executable found on PATH. */\nexport interface DiscoveredNode {\n\t/** Passed straight to `spawn`, so a bare name is fine — it resolves via PATH. */\n\texecPath: string;\n\t/** Version without the leading `v`. */\n\tversion: string;\n}\n\n/** Host facts {@link resolveCanvasRuntime} reads. Injectable so tests need no subprocess. */\nexport interface CanvasHostProbe {\n\t/** `process.versions.bun`, or undefined on Node. */\n\tbunVersion: string | undefined;\n\t/** `process.versions.node` — meaningless as a Node check; see the module header. */\n\tnodeVersion: string;\n\t/** `process.execPath`. */\n\texecPath: string;\n\t/** Candidate shims, highest precedence first. */\n\tshimCandidates: CanvasShimCandidate[];\n\t/** Whether a `file:` URL exists on disk. */\n\texists: (fileUrl: string) => boolean;\n\t/** Locate a Node on PATH. Resolves undefined when there is none. */\n\tprobePathNode: () => Promise<DiscoveredNode | undefined>;\n}\n\n/**\n * Where the child looks for the shim, best first.\n *\n * 1. The built `index.js`, via `getCanvasDir()` so the standalone binary's sidecar\n *    copy is found the same way themes and the HTML export template are. Needs no\n *    extra argv.\n * 2. The TypeScript source, for a checkout run through `tsx` (`hoocode-test.sh`)\n *    where no `dist` exists. Offered **only** when `tsx` actually resolves, so this\n *    is a verified capability rather than a hopeful one — a forked child cannot\n *    import `.ts` on its own, and an \"available\" that fails at fork time is worse\n *    than an honest no.\n *\n * Without (2), the people most likely to be writing canvases — contributors running\n * from source — could not open one.\n */\nfunction defaultShimCandidates(): CanvasShimCandidate[] {\n\tconst canvasDir = getCanvasDir();\n\tconst candidates: CanvasShimCandidate[] = [\n\t\t{ url: pathToFileURL(path.join(canvasDir, \"sdk-shim\", \"index.js\")).href, execArgv: [] },\n\t];\n\tconst loader = typescriptLoaderArg();\n\tif (loader) {\n\t\tcandidates.push({\n\t\t\turl: pathToFileURL(path.join(canvasDir, \"sdk-shim\", \"index.ts\")).href,\n\t\t\texecArgv: [\"--import\", loader],\n\t\t});\n\t}\n\treturn candidates;\n}\n\n/**\n * An absolute `--import` argument for `tsx`, or undefined when it is not installed.\n *\n * Absolute on purpose: Node resolves a bare `--import` specifier against the *child's*\n * working directory, so `tsx/esm` would work only while an extension happened to sit\n * inside this repository and fail elsewhere with ERR_MODULE_NOT_FOUND.\n */\nfunction typescriptLoaderArg(): string | undefined {\n\ttry {\n\t\treturn pathToFileURL(require.resolve(\"tsx/esm\")).href;\n\t} catch {\n\t\treturn undefined;\n\t}\n}\n\nfunction fileUrlExists(fileUrl: string): boolean {\n\ttry {\n\t\treturn existsSync(fileURLToPath(fileUrl));\n\t} catch {\n\t\treturn false;\n\t}\n}\n\n/**\n * Ask PATH for a Node.\n *\n * `spawn` resolves a bare command name through PATH (and PATHEXT on Windows)\n * without a shell, so there is no directory scanning to get wrong here — and unlike\n * `npx`/`npm`, `node` is a real executable rather than a `.cmd` shim, so the Windows\n * caveat in `extensions/core/mcp-loader.ts` does not apply.\n */\nasync function probeNodeOnPath(): Promise<DiscoveredNode | undefined> {\n\ttry {\n\t\tconst { stdout } = await execFileAsync(\"node\", [\"--version\"], {\n\t\t\ttimeout: CANVAS_NODE_PROBE_TIMEOUT_MS,\n\t\t\twindowsHide: true,\n\t\t});\n\t\tconst version = stdout.trim().replace(/^v/, \"\");\n\t\treturn version.length > 0 ? { execPath: \"node\", version } : undefined;\n\t} catch {\n\t\treturn undefined;\n\t}\n}\n\n/** Read the current process. */\nexport function currentCanvasHostProbe(): CanvasHostProbe {\n\treturn {\n\t\tbunVersion: process.versions.bun,\n\t\tnodeVersion: process.versions.node,\n\t\texecPath: process.execPath,\n\t\tshimCandidates: defaultShimCandidates(),\n\t\texists: fileUrlExists,\n\t\tprobePathNode: probeNodeOnPath,\n\t};\n}\n\nfunction meetsMinimum(nodeVersion: string): boolean {\n\tconst [major, minor] = nodeVersion.split(\".\").map((part) => Number.parseInt(part, 10));\n\tif (!Number.isInteger(major) || !Number.isInteger(minor)) return false;\n\tif (major > CANVAS_MIN_NODE_MAJOR) return true;\n\treturn major === CANVAS_MIN_NODE_MAJOR && minor >= CANVAS_MIN_NODE_MINOR;\n}\n\nconst MIN_LABEL = `${CANVAS_MIN_NODE_MAJOR}.${CANVAS_MIN_NODE_MINOR}`;\n\n/**\n * Decide whether canvases can run, and produce the runtime if so.\n *\n * Never throws and never guesses: an unavailable result carries a sentence a person\n * can act on, because \"no Node here\" is a legitimate state rather than a failure.\n */\nexport async function resolveCanvasRuntime(\n\tprobe: CanvasHostProbe = currentCanvasHostProbe(),\n): Promise<CanvasAvailability> {\n\tconst shim = probe.shimCandidates.find((candidate) => probe.exists(candidate.url));\n\tif (shim === undefined) {\n\t\treturn {\n\t\t\tavailable: false,\n\t\t\treason:\n\t\t\t\t\"Canvas extensions need the built canvas shim, which was not found where it ships. \" +\n\t\t\t\t\"This usually means an incomplete install; reinstalling hoocode should restore it.\",\n\t\t};\n\t}\n\n\t// Forking ourselves is only sound when we are genuinely Node: Bun reports a\n\t// satisfying process.versions.node but ignores the resolve hook the child needs.\n\tif (probe.bunVersion === undefined && meetsMinimum(probe.nodeVersion)) {\n\t\treturn { available: true, runtime: { execPath: probe.execPath, execArgv: shim.execArgv, shimUrl: shim.url } };\n\t}\n\n\tconst found = await probe.probePathNode();\n\tif (!found) {\n\t\treturn {\n\t\t\tavailable: false,\n\t\t\treason:\n\t\t\t\t`Canvas extensions run in a Node child process and no \\`node\\` was found on PATH. ` +\n\t\t\t\t`Install Node ${MIN_LABEL} or newer to use canvases.`,\n\t\t};\n\t}\n\tif (!meetsMinimum(found.version)) {\n\t\treturn {\n\t\t\tavailable: false,\n\t\t\treason:\n\t\t\t\t`Canvas extensions need Node ${MIN_LABEL} or newer for module.register(); ` +\n\t\t\t\t`the \\`node\\` on PATH is ${found.version}.`,\n\t\t};\n\t}\n\treturn { available: true, runtime: { execPath: found.execPath, execArgv: shim.execArgv, shimUrl: shim.url } };\n}\n"]}