Upgrade the bundled Pi runtime closure to version 0.84.1. - Pin direct and transitive Pi runtime packages to a coherent 0.84.1 closure. - Adapt runtime interfaces and test assertions for Pi 0.84.1 behavior. - Guard the added Pi client, protocol, and telemetry packages in desktop packaging checks. Files changed: .changeset/fn-9007-pi-0-84-1.md | 7 + packages/cli/package.json | 4 +- packages/cli/src/__tests__/package-config.test.ts | 6 +- packages/cli/vitest.config.ts | 2 +- packages/core/package.json | 2 +- packages/dashboard/package.json | 4 +- packages/dashboard/src/routes.ts | 6 +- packages/engine/package.json | 4 +- packages/engine/src/__tests__/pi.test.ts | 45 ++-- .../src/__tests__/provider-registration.test.ts | 4 +- packages/engine/src/auth/auth-storage.ts | 20 +- packages/engine/src/pi.ts | 5 + packages/pi-claude-cli/package.json | 8 +- pnpm-lock.yaml | 283 ++++++++++++--------- pnpm-workspace.yaml | 14 +- .../__tests__/check-pi-versions-pinned.test.mjs | 27 +- scripts/check-pi-versions-pinned.mjs | 8 + 17 files changed, 279 insertions(+), 170 deletions(-) Fusion-Task-Id: FN-9007 Fusion-Task-Lineage: 89045e86-d4c8-4fc3-bbf6-5ba38fc786e7 Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
193 lines
7.3 KiB
JavaScript
193 lines
7.3 KiB
JavaScript
#!/usr/bin/env node
|
|
/*
|
|
FNXC:DependencyPinning 2026-07-17-12:00:
|
|
FN-8201 / Runfusion/Fusion#2270 requires the pi-ai and pi-coding-agent runtime
|
|
set to remain version-locked. npm global installs ignore pnpm-lock.yaml and can
|
|
independently resolve ranges to incompatible pi-mono patches, so every guarded
|
|
manifest declaration must be an exact semver and all declarations must agree.
|
|
*/
|
|
import { readFileSync } from "node:fs";
|
|
import { spawnSync } from "node:child_process";
|
|
import { fileURLToPath } from "node:url";
|
|
import { parse as parseYaml } from "yaml";
|
|
|
|
export const PI_DEPENDENCIES = [
|
|
"@earendil-works/pi-ai",
|
|
"@earendil-works/pi-coding-agent",
|
|
];
|
|
|
|
/*
|
|
FNXC:DesktopPackaging 2026-07-25-17:15:
|
|
The legacy desktop deploy resolves pi-coding-agent's transitive pi-mono ranges
|
|
without the workspace lockfile. Keep every Pi package in that staged runtime
|
|
closure on one exact override so a new agent-core or tui patch cannot be hoisted
|
|
beside older direct ai/coding-agent dependencies.
|
|
|
|
FNXC:DesktopPackaging 2026-08-12-20:46:
|
|
pi-coding-agent@0.84.1 transitively adds pi-client and pi-protocol while pi-ai
|
|
adds pi-telemetry. Guard these siblings too so electron-builder's lockfile-free
|
|
production walk cannot hoist them into a split Pi runtime closure.
|
|
*/
|
|
export const PI_RUNTIME_PACKAGES = [
|
|
"@earendil-works/pi-agent-core",
|
|
"@earendil-works/pi-ai",
|
|
"@earendil-works/pi-client",
|
|
"@earendil-works/pi-coding-agent",
|
|
"@earendil-works/pi-protocol",
|
|
"@earendil-works/pi-telemetry",
|
|
"@earendil-works/pi-tui",
|
|
];
|
|
|
|
export const GUARDED_MANIFESTS = [
|
|
"packages/cli/package.json",
|
|
"packages/core/package.json",
|
|
"packages/engine/package.json",
|
|
"packages/dashboard/package.json",
|
|
"packages/pi-claude-cli/package.json",
|
|
];
|
|
|
|
const DEPENDENCY_BLOCKS = ["dependencies", "devDependencies", "peerDependencies", "optionalDependencies"];
|
|
const EXACT_SEMVER = /^\d+\.\d+\.\d+(?:-[0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*)?(?:\+[0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*)?$/;
|
|
|
|
export function isExactSemver(version) {
|
|
return typeof version === "string" && EXACT_SEMVER.test(version);
|
|
}
|
|
|
|
export function validateWorkspaceOverrides(overrides = {}) {
|
|
const violations = [];
|
|
const versions = [];
|
|
|
|
for (const packageName of PI_RUNTIME_PACKAGES) {
|
|
const version = overrides?.[packageName];
|
|
if (version === undefined) {
|
|
violations.push(`pnpm-workspace.yaml: overrides.${packageName} must be pinned for the staged desktop closure`);
|
|
continue;
|
|
}
|
|
if (!isExactSemver(version)) {
|
|
violations.push(`pnpm-workspace.yaml: overrides.${packageName} must be an exact semver, found ${JSON.stringify(version)}`);
|
|
continue;
|
|
}
|
|
versions.push(version);
|
|
}
|
|
|
|
const uniqueVersions = [...new Set(versions)];
|
|
if (uniqueVersions.length > 1) {
|
|
violations.push(`${PI_RUNTIME_PACKAGES.join(", ")} must use one exact version in pnpm-workspace.yaml overrides; found ${uniqueVersions.join(", ")}`);
|
|
}
|
|
|
|
return violations;
|
|
}
|
|
|
|
function listTrackedManifests() {
|
|
const result = spawnSync("git", ["ls-files", "--", ...GUARDED_MANIFESTS], {
|
|
encoding: "utf8",
|
|
stdio: ["ignore", "pipe", "pipe"],
|
|
});
|
|
if (result.status !== 0) throw new Error(result.stderr?.trim() || "git ls-files failed");
|
|
return result.stdout.split("\n").map((line) => line.trim()).filter(Boolean);
|
|
}
|
|
|
|
export function validateManifestSet(manifests) {
|
|
const violations = [];
|
|
const declaredVersions = new Map();
|
|
|
|
for (const { filePath, manifest } of manifests) {
|
|
for (const blockName of DEPENDENCY_BLOCKS) {
|
|
const dependencies = manifest?.[blockName];
|
|
if (!dependencies || typeof dependencies !== "object") continue;
|
|
|
|
for (const packageName of PI_DEPENDENCIES) {
|
|
if (!(packageName in dependencies)) continue;
|
|
const version = dependencies[packageName];
|
|
if (!isExactSemver(version)) {
|
|
violations.push(`${filePath}: ${blockName}.${packageName} must be an exact semver, found ${JSON.stringify(version)}`);
|
|
continue;
|
|
}
|
|
const declarations = declaredVersions.get(packageName) ?? [];
|
|
declarations.push({ filePath, blockName, version });
|
|
declaredVersions.set(packageName, declarations);
|
|
}
|
|
}
|
|
}
|
|
|
|
for (const packageName of PI_DEPENDENCIES) {
|
|
const declarations = declaredVersions.get(packageName) ?? [];
|
|
const versions = [...new Set(declarations.map(({ version }) => version))];
|
|
if (versions.length > 1) {
|
|
violations.push(`${packageName} must use one exact version across guarded manifests; found ${versions.join(", ")} (${declarations.map(({ filePath, blockName, version }) => `${filePath}:${blockName}=${version}`).join(", ")})`);
|
|
}
|
|
}
|
|
|
|
const allVersions = [...new Set(
|
|
PI_DEPENDENCIES.flatMap((packageName) =>
|
|
(declaredVersions.get(packageName) ?? []).map(({ version }) => version),
|
|
),
|
|
)];
|
|
if (allVersions.length > 1) {
|
|
violations.push(`${PI_DEPENDENCIES.join(" and ")} must resolve to the same exact version; found ${allVersions.join(", ")}`);
|
|
}
|
|
|
|
return violations;
|
|
}
|
|
|
|
export function scanTrackedManifests(files = listTrackedManifests(), options = {}) {
|
|
const readFile = options.readFile ?? readFileSync;
|
|
const manifests = [];
|
|
for (const filePath of files) {
|
|
let source;
|
|
try {
|
|
source = readFile(filePath, "utf8");
|
|
} catch (error) {
|
|
if (error && typeof error === "object" && "code" in error && error.code === "ENOENT") continue;
|
|
throw error;
|
|
}
|
|
try {
|
|
manifests.push({ filePath, manifest: JSON.parse(source) });
|
|
} catch (error) {
|
|
return [`${filePath}: invalid JSON (${error instanceof Error ? error.message : String(error)})`];
|
|
}
|
|
}
|
|
const violations = validateManifestSet(manifests);
|
|
let workspaceConfig;
|
|
try {
|
|
workspaceConfig = parseYaml(readFile("pnpm-workspace.yaml", "utf8"));
|
|
} catch (error) {
|
|
return [...violations, `pnpm-workspace.yaml: invalid YAML (${error instanceof Error ? error.message : String(error)})`];
|
|
}
|
|
|
|
const workspaceOverrides = workspaceConfig?.overrides ?? {};
|
|
violations.push(...validateWorkspaceOverrides(workspaceOverrides));
|
|
|
|
const manifestVersions = new Set(
|
|
manifests.flatMap(({ manifest }) =>
|
|
DEPENDENCY_BLOCKS.flatMap((blockName) =>
|
|
PI_DEPENDENCIES.map((packageName) => manifest?.[blockName]?.[packageName]).filter(isExactSemver),
|
|
),
|
|
),
|
|
);
|
|
const overrideVersions = new Set(PI_RUNTIME_PACKAGES.map((packageName) => workspaceOverrides[packageName]).filter(isExactSemver));
|
|
const allRuntimeVersions = [...new Set([...manifestVersions, ...overrideVersions])];
|
|
if (allRuntimeVersions.length > 1) {
|
|
violations.push(`guarded Pi manifests and workspace overrides must use one exact runtime version; found ${allRuntimeVersions.join(", ")}`);
|
|
}
|
|
|
|
return violations;
|
|
}
|
|
|
|
export function formatFailureMessage(violations) {
|
|
return [
|
|
"[check-pi-versions-pinned] Pi runtime dependencies and workspace overrides must be exact, matched versions.",
|
|
"npm global installs and legacy desktop deploy do not reliably use pnpm-lock.yaml; ranges can resolve an incompatible pi-mono patch set.",
|
|
...violations.map((violation) => `- ${violation}`),
|
|
].join("\n");
|
|
}
|
|
|
|
export function main() {
|
|
const violations = scanTrackedManifests();
|
|
if (!violations.length) return 0;
|
|
console.error(formatFailureMessage(violations));
|
|
return 1;
|
|
}
|
|
|
|
if (process.argv[1] && fileURLToPath(import.meta.url) === process.argv[1]) process.exitCode = main();
|