chore(vendor): import pi 1.3.0

Imported as a linear vendor commit so forge rebases do not replay the upstream root tree.

git-subtree-dir: third_party/pi

git-subtree-split: 71023aec1546d6d677a65669c5665c0927ce0dd0
This commit is contained in:
2026-10-15 17:58:48 -04:00
parent 719370c8f7
commit 6081e3e325
1743 changed files with 205460 additions and 131705 deletions

View File

@@ -1,4 +1,5 @@
import { accessSync, constants, existsSync, readFileSync, realpathSync } from "fs";
import { createRequire } from "module";
import { homedir } from "os";
import { basename, dirname, join, resolve, sep, win32 } from "path";
import { fileURLToPath } from "url";
@@ -172,6 +173,9 @@ function getSelfUpdateCommandForMethod(
const [command = "npm", ...npmArgs] = npmCommand ?? [];
const inferred = npmCommand?.length ? undefined : getInferredNpmInstall();
const prefixArgs = [...npmArgs, ...(inferred ? ["--prefix", inferred.prefix] : [])];
// pi.dev advertises releases immediately, so a configured npm age gate would
// block the update. npm has no per-package age gate, so this also lets new
// transitive dependency releases through. Managed installs avoid this.
const installStep = makeSelfUpdateCommandStep(command, [
...prefixArgs,
"install",
@@ -338,7 +342,7 @@ export function getSelfUpdateUnavailableInstruction(
const method = detectInstallMethod();
const target = normalizeSelfUpdatePackageTarget(updatePackageTarget);
if (method === "bun-binary") {
return `Download from: https://github.com/earendil-works/pi-mono/releases/latest`;
return `Download from: https://github.com/earendil-works/pi/releases/latest`;
}
const command = getSelfUpdateCommandForMethod(method, packageName, target, npmCommand);
if (command) {
@@ -366,7 +370,7 @@ export function getUpdateInstruction(packageName: string): string {
/**
* Get the base directory for resolving package assets (themes, package.json, README.md, CHANGELOG.md).
* - For Bun binary: returns the directory containing the executable
* - For Node.js and tsx: returns the package root containing package.json
* - For Node.js: returns the package root containing package.json
* - Ignores Bun binary metadata copied into dist/ when the package root is available
*/
export function findNodePackageDir(startDir: string): string {
@@ -404,7 +408,7 @@ export function getPackageDir(): string {
* Get path to built-in themes directory (shipped with package)
* - For Bun binary: theme/ next to executable
* - For Node.js (dist/): dist/modes/interactive/theme/
* - For tsx (src/): src/modes/interactive/theme/
* - For source (src/): src/modes/interactive/theme/
*/
export function getThemesDir(): string {
if (isBunBinary) {
@@ -420,7 +424,7 @@ export function getThemesDir(): string {
* Get path to HTML export template directory (shipped with package)
* - For Bun binary: export-html/ next to executable
* - For Node.js (dist/): dist/core/export-html/
* - For tsx (src/): src/core/export-html/
* - For source (src/): src/core/export-html/
*/
export function getExportTemplateDir(): string {
if (isBunBinary) {
@@ -460,7 +464,7 @@ export function getChangelogPath(): string {
* Get path to built-in interactive assets directory.
* - For Bun binary: assets/ next to executable
* - For Node.js (dist/): dist/modes/interactive/assets/
* - For tsx (src/): src/modes/interactive/assets/
* - For source (src/): src/modes/interactive/assets/
*/
export function getInteractiveAssetsDir(): string {
if (isBunBinary) {
@@ -476,6 +480,75 @@ export function getBundledInteractiveAssetPath(name: string): string {
return join(getInteractiveAssetsDir(), name);
}
let quickJSWasmPath: string | undefined;
/** Called by the Bun entry with the path of the QuickJS wasm file embedded in the compiled executable. */
export function setEmbeddedQuickJSWasmPath(path: string): void {
quickJSWasmPath = path;
}
/**
* Get path to `quickjs-wasi/quickjs.wasm`, the VM that runs codemode scripts. Resolved once so the
* compiled module cached per path keeps working after an update removes this install (#10439).
*/
export function getQuickJSWasmPath(): string {
quickJSWasmPath ??= createRequire(import.meta.url).resolve("quickjs-wasi/quickjs.wasm");
return quickJSWasmPath;
}
/** Resolve the codemode worker entry for a release runtime. */
export function resolveCodemodeWorkerSpecifier(
runtime: "bun-binary" | "bundled-node" | "unbundled",
moduleUrl: string,
): string | URL | undefined {
// Bun embeds explicit source entrypoints, but on Windows Bun 1.3 cannot map an absolute
// B:\~BUN URL back to one. A relative string with the original source extension works on
// every Bun platform.
if (runtime === "bun-binary") return "./src/extensions/codemode/worker.ts";
if (runtime === "bundled-node") return new URL("./codemode-worker.js", moduleUrl);
return undefined;
}
let codemodeWorkerDataUrl: URL | undefined;
/**
* Get the codemode worker entry, or undefined to use the worker that ships next to pi-codemode.
* The Bun and Node release builds both pass the worker as an extra entrypoint.
*/
export function getCodemodeWorkerSpecifier(): string | URL | undefined {
const runtime = isBunBinary ? "bun-binary" : isBundledNode ? "bundled-node" : "unbundled";
const specifier = resolveCodemodeWorkerSpecifier(runtime, import.meta.url);
if (runtime !== "bundled-node" || !(specifier instanceof URL)) return specifier;
// Spawn workers from an in-memory copy. An update replaces or deletes the file while this
// process keeps running (#10439). The bundle build keeps the worker free of relative imports
// and import.meta, so it runs from a data: URL.
codemodeWorkerDataUrl ??= new URL(`data:text/javascript;base64,${readFileSync(specifier).toString("base64")}`);
return codemodeWorkerDataUrl;
}
export type InstallChange = { kind: "updated"; version: string } | { kind: "removed" };
/**
* Detect that the package this process runs from changed on disk, for example after `pi update`
* in another terminal. Code loaded on demand can then be missing or from another version.
*
* Checks the package.json read at startup. Resolving it again would walk up past a deleted install
* and could find an unrelated package.json, such as one in the home directory.
*/
export function detectInstallChange(packageJsonPath = startupPackageJsonPath): InstallChange | undefined {
// The Bun binary embeds its code, so replacing the executable does not affect this process.
if (isBunBinary || !packageJsonPath) return undefined;
let installed: PackageJson;
try {
installed = JSON.parse(stripBom(readFileSync(packageJsonPath, "utf-8"))) as PackageJson;
} catch (error) {
return (error as NodeJS.ErrnoException).code === "ENOENT" ? { kind: "removed" } : undefined;
}
return installed.version && installed.version !== VERSION
? { kind: "updated", version: installed.version }
: undefined;
}
// =============================================================================
// App Config (from package.json piConfig)
// =============================================================================
@@ -490,8 +563,12 @@ interface PackageJson {
}
let pkg: PackageJson = {};
/** The package.json this process started from, if one existed. */
let startupPackageJsonPath: string | undefined;
try {
pkg = JSON.parse(stripBom(readFileSync(getPackageJsonPath(), "utf-8"))) as PackageJson;
const packageJsonPath = getPackageJsonPath();
pkg = JSON.parse(stripBom(readFileSync(packageJsonPath, "utf-8"))) as PackageJson;
startupPackageJsonPath = packageJsonPath;
} catch (e: unknown) {
const err = e as NodeJS.ErrnoException;
if (err.code !== "ENOENT") throw e;