new_pull
This commit is contained in:
@@ -4,14 +4,15 @@
|
||||
# Mirrors .github/workflows/build-binaries.yml
|
||||
#
|
||||
# Usage:
|
||||
# ./scripts/build-binaries.sh [--skip-install] [--skip-deps] [--skip-build] [--platform <platform>] [--out <dir>]
|
||||
# ./scripts/build-binaries.sh [--skip-install] [--skip-deps] [--skip-build] [--offline-model-data] [--platform <platform>] [--out <dir>]
|
||||
#
|
||||
# Options:
|
||||
# --skip-install Skip npm ci
|
||||
# --skip-deps Skip installing cross-platform dependencies
|
||||
# --skip-build Skip npm run build
|
||||
# --platform <name> Build only for specified platform (darwin-arm64, darwin-x64, linux-x64, linux-arm64, windows-x64, windows-arm64)
|
||||
# --out <dir> Output directory (default: packages/coding-agent/binaries)
|
||||
# --skip-install Skip npm ci
|
||||
# --skip-deps Skip installing cross-platform dependencies
|
||||
# --skip-build Skip the package build
|
||||
# --offline-model-data Build with bundled model data instead of refreshing it
|
||||
# --platform <name> Build only for specified platform (darwin-arm64, darwin-x64, linux-x64, linux-arm64, windows-x64, windows-arm64)
|
||||
# --out <dir> Output directory (default: packages/coding-agent/binaries)
|
||||
#
|
||||
# Output:
|
||||
# packages/coding-agent/binaries/
|
||||
@@ -29,6 +30,7 @@ cd "$(dirname "$0")/.."
|
||||
SKIP_INSTALL=false
|
||||
SKIP_DEPS=false
|
||||
SKIP_BUILD=false
|
||||
OFFLINE_MODEL_DATA=false
|
||||
PLATFORM=""
|
||||
OUTPUT_DIR=""
|
||||
|
||||
@@ -46,6 +48,10 @@ while [[ $# -gt 0 ]]; do
|
||||
SKIP_BUILD=true
|
||||
shift
|
||||
;;
|
||||
--offline-model-data)
|
||||
OFFLINE_MODEL_DATA=true
|
||||
shift
|
||||
;;
|
||||
--platform)
|
||||
PLATFORM="$2"
|
||||
shift 2
|
||||
@@ -108,8 +114,13 @@ else
|
||||
fi
|
||||
|
||||
if [[ "$SKIP_BUILD" == "false" ]]; then
|
||||
echo "==> Building all packages..."
|
||||
npm run build
|
||||
if [[ "$OFFLINE_MODEL_DATA" == "true" ]]; then
|
||||
echo "==> Building all packages with bundled model data..."
|
||||
npm run build:offline
|
||||
else
|
||||
echo "==> Building all packages..."
|
||||
npm run build
|
||||
fi
|
||||
else
|
||||
echo "==> Skipping package build (--skip-build)"
|
||||
fi
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
# Create the deterministic source archive uploaded with GitHub releases.
|
||||
#
|
||||
# Usage:
|
||||
# npm run hydrate:model-data
|
||||
# ./scripts/create-source-archive.sh --version <version> --ref <git-ref> --out <archive.tar.gz>
|
||||
|
||||
set -euo pipefail
|
||||
@@ -78,18 +79,47 @@ fi
|
||||
mkdir -p "$(dirname "$output")"
|
||||
output="$(cd "$(dirname "$output")" && pwd)/$(basename "$output")"
|
||||
|
||||
model_data_dir="packages/ai/src/providers/data"
|
||||
if [[ ! -f "${model_data_dir}/.manifest.json" ]]; then
|
||||
echo "Generated model data is missing. Run npm run hydrate:model-data first." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
shopt -s nullglob
|
||||
model_data_files=("${model_data_dir}/.manifest.json" "${model_data_dir}"/*.json)
|
||||
shopt -u nullglob
|
||||
if [[ ${#model_data_files[@]} -eq 1 ]]; then
|
||||
echo "Generated model data is missing from ${model_data_dir}" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
temporary_archive="$(mktemp "${output}.tmp.XXXXXX")"
|
||||
temporary_index="$(mktemp "${output}.index.XXXXXX")"
|
||||
manifest="$(mktemp "${output}.manifest.XXXXXX")"
|
||||
trap 'rm -f "$temporary_archive" "$manifest"' EXIT
|
||||
validation_root="$(mktemp -d "${output}.validation.XXXXXX")"
|
||||
rm -f "$temporary_index"
|
||||
trap 'rm -f "$temporary_archive" "$temporary_index" "$manifest"; rm -rf "$validation_root"' EXIT
|
||||
|
||||
# Add the ignored release model-data snapshot to a temporary index based on the
|
||||
# release commit. Archiving the resulting tree keeps the source artifact
|
||||
# deterministic for the same commit and generated model data.
|
||||
GIT_INDEX_FILE="$temporary_index" git read-tree "$commit"
|
||||
GIT_INDEX_FILE="$temporary_index" git add -f -- "${model_data_files[@]}"
|
||||
archive_tree="$(GIT_INDEX_FILE="$temporary_index" git write-tree)"
|
||||
archive_mtime="$(git show -s --format=%ct "$commit")"
|
||||
|
||||
archive_root="pi-${version}"
|
||||
git archive --format=tar --prefix="${archive_root}/" "$commit" | gzip -n -9 > "$temporary_archive"
|
||||
git archive --format=tar --prefix="${archive_root}/" --mtime="@${archive_mtime}" "$archive_tree" \
|
||||
| gzip -n -9 > "$temporary_archive"
|
||||
tar -tzf "$temporary_archive" > "$manifest"
|
||||
|
||||
required_paths=(
|
||||
"package.json"
|
||||
"package-lock.json"
|
||||
"scripts/build-binaries.sh"
|
||||
"packages/ai/src/models.generated.ts"
|
||||
"packages/ai/src/image-models.generated.ts"
|
||||
"packages/ai/src/providers/data/.manifest.json"
|
||||
"packages/coding-agent/package.json"
|
||||
"packages/coding-agent/src/utils/image-resize-worker.ts"
|
||||
"packages/coding-agent/src/core/export-html/template.css"
|
||||
@@ -112,6 +142,9 @@ if grep -Eq '(^|/)node_modules/|(^|/)packages/coding-agent/binaries/' "$manifest
|
||||
exit 1
|
||||
fi
|
||||
|
||||
tar -xzf "$temporary_archive" -C "$validation_root"
|
||||
node "${validation_root}/${archive_root}/packages/ai/scripts/check-model-data.ts"
|
||||
|
||||
mv "$temporary_archive" "$output"
|
||||
trap 'rm -f "$manifest"' EXIT
|
||||
trap 'rm -f "$temporary_index" "$manifest"; rm -rf "$validation_root"' EXIT
|
||||
printf '%s\n' "$output"
|
||||
|
||||
@@ -15,7 +15,7 @@ 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"],
|
||||
["protobufjs@7.6.5", "postinstall only warns about protobufjs version scheme mismatches"],
|
||||
]);
|
||||
|
||||
const args = new Set(process.argv.slice(2));
|
||||
|
||||
@@ -12,7 +12,7 @@ const shrinkwrapPath = join(codingAgentDir, "npm-shrinkwrap.json");
|
||||
const internalPackagePrefix = "@earendil-works/pi-";
|
||||
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"],
|
||||
["protobufjs@7.6.5", "postinstall only warns about protobufjs version scheme mismatches"],
|
||||
]);
|
||||
|
||||
const args = new Set(process.argv.slice(2));
|
||||
|
||||
+29
-27
@@ -1,29 +1,32 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
/**
|
||||
* Syncs all non-private workspace package dependency versions to match their current versions.
|
||||
* This ensures release packages, including unpublished packages, use lockstep versioning.
|
||||
* Validates lockstep versions for published packages, then synchronizes
|
||||
* internal dependency versions in all workspace packages, including private ones.
|
||||
*/
|
||||
|
||||
import { readFileSync, writeFileSync } from "node:fs";
|
||||
import { join } from "node:path";
|
||||
import { findPackageDirectories } from "./package-workspaces.mjs";
|
||||
|
||||
const packages = findPackageDirectories()
|
||||
const GENERATED_PACKAGE_SUFFIXES = [join("coding-agent", "install-lock")];
|
||||
|
||||
const packageRoot = process.argv[2] ?? "packages";
|
||||
const workspacePackages = findPackageDirectories(packageRoot)
|
||||
.filter((directory) => !GENERATED_PACKAGE_SUFFIXES.some((suffix) => directory.endsWith(suffix)))
|
||||
.map((directory) => {
|
||||
const path = join(directory, "package.json");
|
||||
return { data: JSON.parse(readFileSync(path, "utf8")), path };
|
||||
})
|
||||
.filter((pkg) => pkg.data.private !== true);
|
||||
|
||||
const versionMap = new Map(packages.map((pkg) => [pkg.data.name, pkg.data.version]));
|
||||
});
|
||||
const publishedPackages = workspacePackages.filter((pkg) => pkg.data.private !== true);
|
||||
const versionMap = new Map(workspacePackages.map((pkg) => [pkg.data.name, pkg.data.version]));
|
||||
|
||||
console.log("Current versions:");
|
||||
for (const [name, version] of [...versionMap].sort(([a], [b]) => a.localeCompare(b))) {
|
||||
console.log(` ${name}: ${version}`);
|
||||
for (const pkg of [...publishedPackages].sort((a, b) => a.data.name.localeCompare(b.data.name))) {
|
||||
console.log(` ${pkg.data.name}: ${pkg.data.version}`);
|
||||
}
|
||||
|
||||
const versions = new Set(versionMap.values());
|
||||
const versions = new Set(publishedPackages.map((pkg) => pkg.data.version));
|
||||
if (versions.size > 1) {
|
||||
console.error("\nERROR: Not all non-private packages have the same version.");
|
||||
console.error("Expected lockstep versioning. Run one of:");
|
||||
@@ -36,37 +39,36 @@ if (versions.size > 1) {
|
||||
console.log("\nAll non-private packages are at the same version (lockstep).");
|
||||
|
||||
let totalUpdates = 0;
|
||||
for (const pkg of packages) {
|
||||
let updated = false;
|
||||
|
||||
const updatedPackages = new Set();
|
||||
for (const pkg of workspacePackages) {
|
||||
for (const dependencyType of ["dependencies", "devDependencies"]) {
|
||||
const dependencies = pkg.data[dependencyType];
|
||||
if (!dependencies) {
|
||||
continue;
|
||||
}
|
||||
|
||||
for (const [dependencyName, currentVersion] of Object.entries(dependencies)) {
|
||||
const dependencyVersion = versionMap.get(dependencyName);
|
||||
if (!dependencyVersion) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const newVersion = `^${dependencyVersion}`;
|
||||
if (currentVersion === newVersion) {
|
||||
for (const [dependencyName, currentSpecifier] of Object.entries(dependencies)) {
|
||||
// Registry aliases such as `npm:@earendil-works/pi-ai@0.1.2` are never workspace-linked,
|
||||
// so lockstep bumping them would point at a version that is not published yet.
|
||||
const version = versionMap.get(dependencyName);
|
||||
const newSpecifier = version ? `^${version}` : null;
|
||||
if (!newSpecifier || currentSpecifier === newSpecifier) {
|
||||
continue;
|
||||
}
|
||||
|
||||
console.log(`\n${pkg.data.name}:`);
|
||||
console.log(` ${dependencyName}: ${currentVersion} → ${newVersion}${dependencyType === "devDependencies" ? " (devDependencies)" : ""}`);
|
||||
dependencies[dependencyName] = newVersion;
|
||||
updated = true;
|
||||
console.log(
|
||||
` ${dependencyName}: ${currentSpecifier} → ${newSpecifier}${dependencyType === "devDependencies" ? " (devDependencies)" : ""}`,
|
||||
);
|
||||
dependencies[dependencyName] = newSpecifier;
|
||||
updatedPackages.add(pkg);
|
||||
totalUpdates++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (updated) {
|
||||
writeFileSync(pkg.path, `${JSON.stringify(pkg.data, null, "\t")}\n`);
|
||||
}
|
||||
for (const pkg of updatedPackages) {
|
||||
writeFileSync(pkg.path, `${JSON.stringify(pkg.data, null, "\t")}\n`);
|
||||
}
|
||||
|
||||
if (totalUpdates === 0) {
|
||||
|
||||
@@ -0,0 +1,75 @@
|
||||
import assert from "node:assert/strict";
|
||||
import { spawnSync } from "node:child_process";
|
||||
import { mkdtemp, mkdir, readFile, rm, writeFile } from "node:fs/promises";
|
||||
import { tmpdir } from "node:os";
|
||||
import { join } from "node:path";
|
||||
import { fileURLToPath } from "node:url";
|
||||
import test from "node:test";
|
||||
|
||||
const syncVersionsScript = fileURLToPath(new URL("./sync-versions.js", import.meta.url));
|
||||
|
||||
async function writeManifest(root, relativeDirectory, manifest) {
|
||||
const directory = join(root, relativeDirectory);
|
||||
await mkdir(directory, { recursive: true });
|
||||
await writeFile(join(directory, "package.json"), `${JSON.stringify(manifest, null, "\t")}\n`);
|
||||
}
|
||||
|
||||
async function readManifest(root, relativeDirectory) {
|
||||
return JSON.parse(await readFile(join(root, relativeDirectory, "package.json"), "utf8"));
|
||||
}
|
||||
|
||||
function runSyncVersions(root) {
|
||||
return spawnSync(process.execPath, [syncVersionsScript, join(root, "packages")], {
|
||||
cwd: root,
|
||||
encoding: "utf8",
|
||||
});
|
||||
}
|
||||
|
||||
test("synchronizes private dependencies without touching registry aliases, generated manifests, or published lockstep", async () => {
|
||||
const root = await mkdtemp(join(tmpdir(), "pi-sync-versions-"));
|
||||
try {
|
||||
await writeManifest(root, "packages/ai", {
|
||||
name: "@earendil-works/pi-ai",
|
||||
version: "2.0.0",
|
||||
});
|
||||
await writeManifest(root, "packages/coding-agent", {
|
||||
name: "@earendil-works/pi-coding-agent",
|
||||
version: "2.0.0",
|
||||
});
|
||||
await writeManifest(root, "packages/evals", {
|
||||
name: "@earendil-works/pi-evals",
|
||||
version: "9.9.9",
|
||||
private: true,
|
||||
dependencies: {
|
||||
"@earendil-works/pi-coding-agent": "^1.0.0",
|
||||
"@mariozechner/pi-ai": "npm:@earendil-works/pi-ai@1.0.0",
|
||||
},
|
||||
});
|
||||
await writeManifest(root, "packages/coding-agent/install-lock", {
|
||||
name: "generated-install-lock",
|
||||
version: "0.0.0",
|
||||
private: true,
|
||||
dependencies: {
|
||||
"@earendil-works/pi-coding-agent": "^1.0.0",
|
||||
},
|
||||
});
|
||||
|
||||
const result = runSyncVersions(root);
|
||||
assert.equal(result.status, 0, result.stderr);
|
||||
|
||||
const evalsManifest = await readManifest(root, "packages/evals");
|
||||
assert.equal(evalsManifest.dependencies["@earendil-works/pi-coding-agent"], "^2.0.0");
|
||||
assert.equal(evalsManifest.dependencies["@mariozechner/pi-ai"], "npm:@earendil-works/pi-ai@1.0.0");
|
||||
const generatedManifest = await readManifest(root, "packages/coding-agent/install-lock");
|
||||
assert.equal(generatedManifest.dependencies["@earendil-works/pi-coding-agent"], "^1.0.0");
|
||||
|
||||
await writeManifest(root, "packages/ai", {
|
||||
name: "@earendil-works/pi-ai",
|
||||
version: "3.0.0",
|
||||
});
|
||||
const lockstepFailure = runSyncVersions(root);
|
||||
assert.equal(lockstepFailure.status, 1, lockstepFailure.stderr);
|
||||
} finally {
|
||||
await rm(root, { recursive: true, force: true });
|
||||
}
|
||||
});
|
||||
Reference in New Issue
Block a user