mirror of
https://github.com/badlogic/pi-mono.git
synced 2026-07-09 17:28:45 +00:00
feat(coding-agent): add installer lock generation
Some checks are pending
CI / build-check-test (push) Waiting to run
Some checks are pending
CI / build-check-test (push) Waiting to run
This commit is contained in:
parent
87ad8243cb
commit
622eca7608
6 changed files with 2333 additions and 5 deletions
27
.github/workflows/build-binaries.yml
vendored
27
.github/workflows/build-binaries.yml
vendored
|
|
@ -61,10 +61,13 @@ jobs:
|
||||||
VERSION="${RELEASE_TAG}"
|
VERSION="${RELEASE_TAG}"
|
||||||
VERSION="${VERSION#v}" # Remove 'v' prefix
|
VERSION="${VERSION#v}" # Remove 'v' prefix
|
||||||
node scripts/release-notes.mjs extract --version "${VERSION}" --tag "${RELEASE_TAG}" --out release-assets/RELEASE_NOTES.md
|
node scripts/release-notes.mjs extract --version "${VERSION}" --tag "${RELEASE_TAG}" --out release-assets/RELEASE_NOTES.md
|
||||||
|
node scripts/generate-coding-agent-install-lock.mjs --check
|
||||||
|
cp packages/coding-agent/install-lock/package.json release-assets/pi-coding-agent-install-package.json
|
||||||
|
cp packages/coding-agent/install-lock/package-lock.json release-assets/pi-coding-agent-install-package-lock.json
|
||||||
|
|
||||||
cd packages/coding-agent/binaries
|
cd packages/coding-agent/binaries
|
||||||
|
|
||||||
release_assets=(
|
binary_assets=(
|
||||||
pi-darwin-arm64.tar.gz
|
pi-darwin-arm64.tar.gz
|
||||||
pi-darwin-x64.tar.gz
|
pi-darwin-x64.tar.gz
|
||||||
pi-linux-x64.tar.gz
|
pi-linux-x64.tar.gz
|
||||||
|
|
@ -73,12 +76,24 @@ jobs:
|
||||||
pi-windows-arm64.zip
|
pi-windows-arm64.zip
|
||||||
)
|
)
|
||||||
|
|
||||||
for asset in "${release_assets[@]}"; do
|
for asset in "${binary_assets[@]}"; do
|
||||||
test -f "${asset}"
|
test -f "${asset}"
|
||||||
done
|
done
|
||||||
|
|
||||||
sha256sum "${release_assets[@]}" > "${GITHUB_WORKSPACE}/release-assets/SHA256SUMS"
|
cp "${binary_assets[@]}" "${GITHUB_WORKSPACE}/release-assets/"
|
||||||
cp "${release_assets[@]}" "${GITHUB_WORKSPACE}/release-assets/"
|
|
||||||
|
cd "${GITHUB_WORKSPACE}/release-assets"
|
||||||
|
release_assets=(
|
||||||
|
pi-darwin-arm64.tar.gz
|
||||||
|
pi-darwin-x64.tar.gz
|
||||||
|
pi-linux-x64.tar.gz
|
||||||
|
pi-linux-arm64.tar.gz
|
||||||
|
pi-windows-x64.zip
|
||||||
|
pi-windows-arm64.zip
|
||||||
|
pi-coding-agent-install-package.json
|
||||||
|
pi-coding-agent-install-package-lock.json
|
||||||
|
)
|
||||||
|
sha256sum "${release_assets[@]}" > SHA256SUMS
|
||||||
|
|
||||||
- name: Upload GitHub release payload
|
- name: Upload GitHub release payload
|
||||||
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
|
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
|
||||||
|
|
@ -117,6 +132,8 @@ jobs:
|
||||||
pi-linux-arm64.tar.gz
|
pi-linux-arm64.tar.gz
|
||||||
pi-windows-x64.zip
|
pi-windows-x64.zip
|
||||||
pi-windows-arm64.zip
|
pi-windows-arm64.zip
|
||||||
|
pi-coding-agent-install-package.json
|
||||||
|
pi-coding-agent-install-package-lock.json
|
||||||
SHA256SUMS
|
SHA256SUMS
|
||||||
RELEASE_NOTES.md
|
RELEASE_NOTES.md
|
||||||
)
|
)
|
||||||
|
|
@ -142,6 +159,8 @@ jobs:
|
||||||
pi-linux-arm64.tar.gz
|
pi-linux-arm64.tar.gz
|
||||||
pi-windows-x64.zip
|
pi-windows-x64.zip
|
||||||
pi-windows-arm64.zip
|
pi-windows-arm64.zip
|
||||||
|
pi-coding-agent-install-package.json
|
||||||
|
pi-coding-agent-install-package-lock.json
|
||||||
SHA256SUMS
|
SHA256SUMS
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -13,10 +13,11 @@
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"clean": "npm run clean --workspaces",
|
"clean": "npm run clean --workspaces",
|
||||||
"build": "cd packages/tui && npm run build && cd ../ai && npm run build && cd ../agent && npm run build && cd ../coding-agent && npm run build && cd ../orchestrator && npm run build",
|
"build": "cd packages/tui && npm run build && cd ../ai && npm run build && cd ../agent && npm run build && cd ../coding-agent && npm run build && cd ../orchestrator && npm run build",
|
||||||
"check": "biome check --write --error-on-warnings . && npm run check:pinned-deps && npm run check:ts-imports && npm run check:shrinkwrap && tsgo --noEmit && npm run check:browser-smoke",
|
"check": "biome check --write --error-on-warnings . && npm run check:pinned-deps && npm run check:ts-imports && npm run check:shrinkwrap && npm run check:install-lock:coding-agent && tsgo --noEmit && npm run check:browser-smoke",
|
||||||
"check:browser-smoke": "node scripts/check-browser-smoke.mjs",
|
"check:browser-smoke": "node scripts/check-browser-smoke.mjs",
|
||||||
"check:pinned-deps": "node scripts/check-pinned-deps.mjs",
|
"check:pinned-deps": "node scripts/check-pinned-deps.mjs",
|
||||||
"check:shrinkwrap": "node scripts/generate-coding-agent-shrinkwrap.mjs --check",
|
"check:shrinkwrap": "node scripts/generate-coding-agent-shrinkwrap.mjs --check",
|
||||||
|
"check:install-lock:coding-agent": "node scripts/generate-coding-agent-install-lock.mjs --check",
|
||||||
"check:ts-imports": "node scripts/check-ts-relative-imports.mjs",
|
"check:ts-imports": "node scripts/check-ts-relative-imports.mjs",
|
||||||
"profile:tui": "node scripts/profile-coding-agent-node.mjs --mode tui",
|
"profile:tui": "node scripts/profile-coding-agent-node.mjs --mode tui",
|
||||||
"profile:rpc": "node scripts/profile-coding-agent-node.mjs --mode rpc",
|
"profile:rpc": "node scripts/profile-coding-agent-node.mjs --mode rpc",
|
||||||
|
|
@ -30,6 +31,7 @@
|
||||||
"publish:dry": "npm run prepublishOnly && node scripts/publish.mjs --dry-run",
|
"publish:dry": "npm run prepublishOnly && node scripts/publish.mjs --dry-run",
|
||||||
"release:local": "node scripts/local-release.mjs",
|
"release:local": "node scripts/local-release.mjs",
|
||||||
"shrinkwrap:coding-agent": "node scripts/generate-coding-agent-shrinkwrap.mjs",
|
"shrinkwrap:coding-agent": "node scripts/generate-coding-agent-shrinkwrap.mjs",
|
||||||
|
"install-lock:coding-agent": "node scripts/generate-coding-agent-install-lock.mjs",
|
||||||
"release:patch": "node scripts/release.mjs patch",
|
"release:patch": "node scripts/release.mjs patch",
|
||||||
"release:minor": "node scripts/release.mjs minor",
|
"release:minor": "node scripts/release.mjs minor",
|
||||||
"release:major": "node scripts/release.mjs major",
|
"release:major": "node scripts/release.mjs major",
|
||||||
|
|
|
||||||
1849
packages/coding-agent/install-lock/package-lock.json
generated
Normal file
1849
packages/coding-agent/install-lock/package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load diff
18
packages/coding-agent/install-lock/package.json
Normal file
18
packages/coding-agent/install-lock/package.json
Normal file
|
|
@ -0,0 +1,18 @@
|
||||||
|
{
|
||||||
|
"name": "@earendil-works/pi-coding-agent-install",
|
||||||
|
"version": "0.80.2",
|
||||||
|
"private": true,
|
||||||
|
"description": "Lockfile root used by the Pi installer and updater.",
|
||||||
|
"dependencies": {
|
||||||
|
"@earendil-works/pi-coding-agent": "0.80.2"
|
||||||
|
},
|
||||||
|
"overrides": {
|
||||||
|
"rimraf": "6.1.2",
|
||||||
|
"gaxios": {
|
||||||
|
"rimraf": "6.1.2"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=22.19.0"
|
||||||
|
}
|
||||||
|
}
|
||||||
439
scripts/generate-coding-agent-install-lock.mjs
Normal file
439
scripts/generate-coding-agent-install-lock.mjs
Normal file
|
|
@ -0,0 +1,439 @@
|
||||||
|
#!/usr/bin/env node
|
||||||
|
|
||||||
|
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
||||||
|
import { dirname, join, posix, resolve } from "node:path";
|
||||||
|
import { fileURLToPath } from "node:url";
|
||||||
|
|
||||||
|
const scriptDir = dirname(fileURLToPath(import.meta.url));
|
||||||
|
const repoRoot = resolve(scriptDir, "..");
|
||||||
|
const codingAgentDir = join(repoRoot, "packages/coding-agent");
|
||||||
|
const outputDir = join(codingAgentDir, "install-lock");
|
||||||
|
const rootLockfilePath = join(repoRoot, "package-lock.json");
|
||||||
|
const outputPackageJsonPath = join(outputDir, "package.json");
|
||||||
|
const outputLockfilePath = join(outputDir, "package-lock.json");
|
||||||
|
const internalPackagePrefix = "@earendil-works/pi-";
|
||||||
|
const installPackageName = "@earendil-works/pi-coding-agent-install";
|
||||||
|
const allowedInstallScriptPackages = new Map([
|
||||||
|
["@google/genai@1.52.0", "preinstall is a no-op in the published package"],
|
||||||
|
["protobufjs@7.6.4", "postinstall only warns about protobufjs version scheme mismatches"],
|
||||||
|
]);
|
||||||
|
|
||||||
|
const args = new Set(process.argv.slice(2));
|
||||||
|
const checkOnly = args.has("--check");
|
||||||
|
|
||||||
|
for (const arg of args) {
|
||||||
|
if (arg !== "--check") {
|
||||||
|
console.error(`Unknown argument: ${arg}`);
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function readJson(path) {
|
||||||
|
return JSON.parse(readFileSync(path, "utf8"));
|
||||||
|
}
|
||||||
|
|
||||||
|
function packageDependencies(entry) {
|
||||||
|
return {
|
||||||
|
...(entry.dependencies ?? {}),
|
||||||
|
...(entry.optionalDependencies ?? {}),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function sortedObject(object) {
|
||||||
|
return Object.fromEntries(Object.entries(object).sort(([a], [b]) => a.localeCompare(b)));
|
||||||
|
}
|
||||||
|
|
||||||
|
function sortedPackageEntry(entry) {
|
||||||
|
const fieldOrder = [
|
||||||
|
"name",
|
||||||
|
"version",
|
||||||
|
"resolved",
|
||||||
|
"integrity",
|
||||||
|
"license",
|
||||||
|
"dependencies",
|
||||||
|
"optionalDependencies",
|
||||||
|
"peerDependencies",
|
||||||
|
"peerDependenciesMeta",
|
||||||
|
"bin",
|
||||||
|
"engines",
|
||||||
|
"os",
|
||||||
|
"cpu",
|
||||||
|
"libc",
|
||||||
|
"optional",
|
||||||
|
"hasInstallScript",
|
||||||
|
"deprecated",
|
||||||
|
"funding",
|
||||||
|
];
|
||||||
|
const sorted = {};
|
||||||
|
|
||||||
|
for (const field of fieldOrder) {
|
||||||
|
if (entry[field] !== undefined) {
|
||||||
|
sorted[field] = entry[field];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (const [field, value] of Object.entries(entry).sort(([a], [b]) => a.localeCompare(b))) {
|
||||||
|
if (sorted[field] === undefined) {
|
||||||
|
sorted[field] = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return sorted;
|
||||||
|
}
|
||||||
|
|
||||||
|
function copyLockEntry(entry) {
|
||||||
|
const copied = { ...entry };
|
||||||
|
delete copied.dev;
|
||||||
|
delete copied.devOptional;
|
||||||
|
delete copied.extraneous;
|
||||||
|
delete copied.link;
|
||||||
|
return sortedPackageEntry(copied);
|
||||||
|
}
|
||||||
|
|
||||||
|
function copyPackageJsonEntry(packageJson, options) {
|
||||||
|
const entry = options.includeName
|
||||||
|
? { name: packageJson.name, version: packageJson.version }
|
||||||
|
: { version: packageJson.version };
|
||||||
|
|
||||||
|
for (const field of [
|
||||||
|
"license",
|
||||||
|
"dependencies",
|
||||||
|
"optionalDependencies",
|
||||||
|
"peerDependencies",
|
||||||
|
"peerDependenciesMeta",
|
||||||
|
"bin",
|
||||||
|
"engines",
|
||||||
|
"os",
|
||||||
|
"cpu",
|
||||||
|
"libc",
|
||||||
|
]) {
|
||||||
|
if (packageJson[field] !== undefined) {
|
||||||
|
entry[field] = packageJson[field];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return sortedPackageEntry(entry);
|
||||||
|
}
|
||||||
|
|
||||||
|
function packageNameFromLockPath(lockPath) {
|
||||||
|
const marker = "node_modules/";
|
||||||
|
const index = lockPath.lastIndexOf(marker);
|
||||||
|
if (index === -1) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
const parts = lockPath.slice(index + marker.length).split("/");
|
||||||
|
if (parts[0]?.startsWith("@")) {
|
||||||
|
return `${parts[0]}/${parts[1]}`;
|
||||||
|
}
|
||||||
|
return parts[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
function registryTarballUrl(packageName, version) {
|
||||||
|
const tarballName = packageName.startsWith("@") ? packageName.split("/")[1] : packageName;
|
||||||
|
return `https://registry.npmjs.org/${packageName}/-/${tarballName}-${version}.tgz`;
|
||||||
|
}
|
||||||
|
|
||||||
|
function isExactVersionSpec(spec) {
|
||||||
|
return /^\d+\.\d+\.\d+(?:[-+][0-9A-Za-z.-]+)?$/.test(spec);
|
||||||
|
}
|
||||||
|
|
||||||
|
function getInternalWorkspaces(lockPackages) {
|
||||||
|
const workspaces = new Map();
|
||||||
|
|
||||||
|
for (const [lockPath, entry] of Object.entries(lockPackages)) {
|
||||||
|
if (!lockPath.startsWith("packages/") || lockPath.includes("/node_modules/") || !entry.name || !entry.version) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (!entry.name.startsWith(internalPackagePrefix)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
workspaces.set(entry.name, {
|
||||||
|
lockPath,
|
||||||
|
packageJson: readJson(join(repoRoot, lockPath, "package.json")),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return workspaces;
|
||||||
|
}
|
||||||
|
|
||||||
|
function resolveExternalDependency(lockPackages, packageName, fromLockPath) {
|
||||||
|
const candidateDirs = [];
|
||||||
|
let current = fromLockPath;
|
||||||
|
|
||||||
|
while (current) {
|
||||||
|
candidateDirs.push(current);
|
||||||
|
const parent = posix.dirname(current);
|
||||||
|
if (parent === "." || parent === current) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
current = parent;
|
||||||
|
}
|
||||||
|
candidateDirs.push("");
|
||||||
|
|
||||||
|
const tried = new Set();
|
||||||
|
for (const directory of candidateDirs) {
|
||||||
|
const candidate = directory ? `${directory}/node_modules/${packageName}` : `node_modules/${packageName}`;
|
||||||
|
if (tried.has(candidate)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
tried.add(candidate);
|
||||||
|
|
||||||
|
const entry = lockPackages[candidate];
|
||||||
|
if (entry && !entry.link) {
|
||||||
|
return candidate;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const suffix = `node_modules/${packageName}`;
|
||||||
|
const matches = Object.entries(lockPackages)
|
||||||
|
.filter(([lockPath, entry]) => !entry.link && (lockPath === suffix || lockPath.endsWith(`/${suffix}`)))
|
||||||
|
.map(([lockPath]) => lockPath);
|
||||||
|
|
||||||
|
if (matches.length === 1) {
|
||||||
|
return matches[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new Error(
|
||||||
|
`Cannot resolve ${packageName} from ${fromLockPath || "root"}. ` +
|
||||||
|
(matches.length > 1 ? `Matches: ${matches.join(", ")}` : "No matching lockfile entry found."),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function addInternalWorkspace(installLockPackages, addedPaths, queue, name, workspace) {
|
||||||
|
const packageJson = workspace.packageJson;
|
||||||
|
const outputPath = `node_modules/${name}`;
|
||||||
|
const entry = copyPackageJsonEntry(packageJson, { includeName: false });
|
||||||
|
entry.resolved = registryTarballUrl(name, packageJson.version);
|
||||||
|
|
||||||
|
installLockPackages[outputPath] = sortedPackageEntry(entry);
|
||||||
|
addedPaths.add(outputPath);
|
||||||
|
|
||||||
|
for (const dependencyName of Object.keys(packageDependencies(packageJson))) {
|
||||||
|
queue.push({ name: dependencyName, from: outputPath });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function addExternalPackage(lockPackages, installLockPackages, addedPaths, queue, name, from) {
|
||||||
|
const lockPath = resolveExternalDependency(lockPackages, name, from);
|
||||||
|
if (addedPaths.has(lockPath)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const entry = lockPackages[lockPath];
|
||||||
|
installLockPackages[lockPath] = copyLockEntry(entry);
|
||||||
|
addedPaths.add(lockPath);
|
||||||
|
|
||||||
|
for (const dependencyName of Object.keys(packageDependencies(entry))) {
|
||||||
|
queue.push({ name: dependencyName, from: lockPath });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function createInstallerPackageJson(codingAgentPackage) {
|
||||||
|
const packageJson = {
|
||||||
|
name: installPackageName,
|
||||||
|
version: codingAgentPackage.version,
|
||||||
|
private: true,
|
||||||
|
description: "Lockfile root used by the Pi installer and updater.",
|
||||||
|
dependencies: {
|
||||||
|
[codingAgentPackage.name]: codingAgentPackage.version,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
if (codingAgentPackage.overrides) {
|
||||||
|
packageJson.overrides = codingAgentPackage.overrides;
|
||||||
|
}
|
||||||
|
if (codingAgentPackage.engines) {
|
||||||
|
packageJson.engines = codingAgentPackage.engines;
|
||||||
|
}
|
||||||
|
return packageJson;
|
||||||
|
}
|
||||||
|
|
||||||
|
function createRootLockEntry(installerPackageJson) {
|
||||||
|
const entry = {
|
||||||
|
name: installerPackageJson.name,
|
||||||
|
version: installerPackageJson.version,
|
||||||
|
dependencies: installerPackageJson.dependencies,
|
||||||
|
};
|
||||||
|
if (installerPackageJson.engines) {
|
||||||
|
entry.engines = installerPackageJson.engines;
|
||||||
|
}
|
||||||
|
return sortedPackageEntry(entry);
|
||||||
|
}
|
||||||
|
|
||||||
|
function validateGeneratedFiles(installerPackageJson, installLock, internalNames) {
|
||||||
|
const errors = [];
|
||||||
|
const rootEntry = installLock.packages[""];
|
||||||
|
const includedPackageNames = new Set();
|
||||||
|
const seenAllowedInstallScriptPackages = new Set();
|
||||||
|
|
||||||
|
if (installLock.lockfileVersion !== 3) {
|
||||||
|
errors.push("package-lock.json must use lockfileVersion 3");
|
||||||
|
}
|
||||||
|
if (installLock.name !== installerPackageJson.name) {
|
||||||
|
errors.push(`lockfile name ${installLock.name} does not match package.json name ${installerPackageJson.name}`);
|
||||||
|
}
|
||||||
|
if (installLock.version !== installerPackageJson.version) {
|
||||||
|
errors.push(
|
||||||
|
`lockfile version ${installLock.version} does not match package.json version ${installerPackageJson.version}`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (JSON.stringify(rootEntry?.dependencies ?? {}) !== JSON.stringify(installerPackageJson.dependencies)) {
|
||||||
|
errors.push("lockfile root dependencies do not match package.json dependencies");
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const [lockPath, entry] of Object.entries(installLock.packages)) {
|
||||||
|
const packageName = packageNameFromLockPath(lockPath);
|
||||||
|
if (packageName) {
|
||||||
|
includedPackageNames.add(packageName);
|
||||||
|
}
|
||||||
|
if (entry.link) {
|
||||||
|
errors.push(`${lockPath} is a link entry`);
|
||||||
|
}
|
||||||
|
if (typeof entry.resolved === "string" && /^(file:|link:|workspace:|\.\.?\/|\/)/.test(entry.resolved)) {
|
||||||
|
errors.push(`${lockPath} has a local resolved value: ${entry.resolved}`);
|
||||||
|
}
|
||||||
|
if (entry.dev || entry.devOptional || entry.extraneous) {
|
||||||
|
errors.push(`${lockPath || "root"} contains dev/extraneous metadata`);
|
||||||
|
}
|
||||||
|
if (packageName?.startsWith(internalPackagePrefix) && entry.version !== installerPackageJson.version) {
|
||||||
|
errors.push(`${lockPath} internal package version ${entry.version} does not match ${installerPackageJson.version}`);
|
||||||
|
}
|
||||||
|
if (entry.hasInstallScript) {
|
||||||
|
if (!packageName || !entry.version) {
|
||||||
|
errors.push(`${lockPath || "root"} has install scripts but no package name/version`);
|
||||||
|
} else {
|
||||||
|
const packageId = `${packageName}@${entry.version}`;
|
||||||
|
if (allowedInstallScriptPackages.has(packageId)) {
|
||||||
|
seenAllowedInstallScriptPackages.add(packageId);
|
||||||
|
} else {
|
||||||
|
errors.push(
|
||||||
|
`${lockPath} has install scripts (${packageId}). Review it and add it to allowedInstallScriptPackages if intentional.`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const packageId of allowedInstallScriptPackages.keys()) {
|
||||||
|
if (!seenAllowedInstallScriptPackages.has(packageId)) {
|
||||||
|
errors.push(`allowed install-script package ${packageId} is no longer present; remove it from the allowlist`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const name of internalNames) {
|
||||||
|
if (!includedPackageNames.has(name)) {
|
||||||
|
errors.push(`internal dependency ${name} is missing`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const [lockPath, entry] of Object.entries(installLock.packages)) {
|
||||||
|
for (const [dependencyName, dependencySpec] of Object.entries(packageDependencies(entry))) {
|
||||||
|
let dependencyLockPath;
|
||||||
|
try {
|
||||||
|
dependencyLockPath = resolveExternalDependency(installLock.packages, dependencyName, lockPath);
|
||||||
|
} catch {
|
||||||
|
errors.push(`${lockPath || "root"} dependency ${dependencyName} is missing`);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const dependencyEntry = installLock.packages[dependencyLockPath];
|
||||||
|
if (isExactVersionSpec(dependencySpec) && dependencyEntry.version !== dependencySpec) {
|
||||||
|
errors.push(
|
||||||
|
`${lockPath || "root"} dependency ${dependencyName}@${dependencySpec} resolves to ${dependencyEntry.version}`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const platformPackageCount = Object.values(installLock.packages).filter((entry) => entry.os || entry.cpu || entry.libc)
|
||||||
|
.length;
|
||||||
|
if (platformPackageCount === 0) {
|
||||||
|
errors.push("no platform-specific optional dependency entries found");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (errors.length > 0) {
|
||||||
|
throw new Error(`Generated installer lock failed validation:\n${errors.map((error) => ` - ${error}`).join("\n")}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function generateInstallLock() {
|
||||||
|
const rootLock = readJson(rootLockfilePath);
|
||||||
|
if (rootLock.lockfileVersion !== 3 || !rootLock.packages) {
|
||||||
|
throw new Error("package-lock.json must be lockfileVersion 3 and contain a packages map");
|
||||||
|
}
|
||||||
|
|
||||||
|
const lockPackages = rootLock.packages;
|
||||||
|
const codingAgentPackage = readJson(join(codingAgentDir, "package.json"));
|
||||||
|
const installerPackageJson = createInstallerPackageJson(codingAgentPackage);
|
||||||
|
const internalWorkspaces = getInternalWorkspaces(lockPackages);
|
||||||
|
const installLockPackages = {
|
||||||
|
"": createRootLockEntry(installerPackageJson),
|
||||||
|
};
|
||||||
|
const addedPaths = new Set([""]);
|
||||||
|
const internalNames = new Set();
|
||||||
|
const queue = Object.keys(packageDependencies(installerPackageJson)).map((name) => ({ name, from: "" }));
|
||||||
|
|
||||||
|
while (queue.length > 0) {
|
||||||
|
const item = queue.shift();
|
||||||
|
if (!item) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
const workspace = internalWorkspaces.get(item.name);
|
||||||
|
if (workspace) {
|
||||||
|
const outputPath = `node_modules/${item.name}`;
|
||||||
|
internalNames.add(item.name);
|
||||||
|
if (!addedPaths.has(outputPath)) {
|
||||||
|
addInternalWorkspace(installLockPackages, addedPaths, queue, item.name, workspace);
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
addExternalPackage(lockPackages, installLockPackages, addedPaths, queue, item.name, item.from);
|
||||||
|
}
|
||||||
|
|
||||||
|
const installLock = {
|
||||||
|
name: installerPackageJson.name,
|
||||||
|
version: installerPackageJson.version,
|
||||||
|
lockfileVersion: 3,
|
||||||
|
requires: true,
|
||||||
|
packages: sortedObject(installLockPackages),
|
||||||
|
};
|
||||||
|
|
||||||
|
validateGeneratedFiles(installerPackageJson, installLock, internalNames);
|
||||||
|
return { installerPackageJson, installLock };
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const { installerPackageJson, installLock } = generateInstallLock();
|
||||||
|
const packageJsonContent = `${JSON.stringify(installerPackageJson, null, "\t")}\n`;
|
||||||
|
const lockfileContent = `${JSON.stringify(installLock, null, "\t")}\n`;
|
||||||
|
|
||||||
|
if (checkOnly) {
|
||||||
|
if (!existsSync(outputPackageJsonPath) || !existsSync(outputLockfilePath)) {
|
||||||
|
console.error("packages/coding-agent/install-lock is missing generated files.");
|
||||||
|
console.error("Run: npm run install-lock:coding-agent");
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
const currentPackageJson = readFileSync(outputPackageJsonPath, "utf8");
|
||||||
|
const currentLockfile = readFileSync(outputLockfilePath, "utf8");
|
||||||
|
if (currentPackageJson !== packageJsonContent || currentLockfile !== lockfileContent) {
|
||||||
|
console.error("packages/coding-agent/install-lock is out of date.");
|
||||||
|
console.error("Run: npm run install-lock:coding-agent");
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
console.log("packages/coding-agent/install-lock is up to date.");
|
||||||
|
} else {
|
||||||
|
mkdirSync(outputDir, { recursive: true });
|
||||||
|
writeFileSync(outputPackageJsonPath, packageJsonContent);
|
||||||
|
writeFileSync(outputLockfilePath, lockfileContent);
|
||||||
|
const packageCount = Object.keys(installLock.packages).length - 1;
|
||||||
|
const platformPackageCount = Object.values(installLock.packages).filter((entry) => entry.os || entry.cpu || entry.libc)
|
||||||
|
.length;
|
||||||
|
console.log(
|
||||||
|
`Wrote packages/coding-agent/install-lock/package.json and package-lock.json (${packageCount} packages, ${platformPackageCount} platform-specific).`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error(error instanceof Error ? error.message : String(error));
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
|
@ -169,6 +169,7 @@ console.log("Regenerating release artifacts...");
|
||||||
run("npm --prefix packages/ai run generate-models");
|
run("npm --prefix packages/ai run generate-models");
|
||||||
run("npm --prefix packages/ai run generate-image-models");
|
run("npm --prefix packages/ai run generate-image-models");
|
||||||
run("npm run shrinkwrap:coding-agent");
|
run("npm run shrinkwrap:coding-agent");
|
||||||
|
run("npm run install-lock:coding-agent");
|
||||||
console.log();
|
console.log();
|
||||||
|
|
||||||
// 5. Run checks
|
// 5. Run checks
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue