mirror of
https://github.com/harivansh-afk/sandbox-agent.git
synced 2026-04-17 05:00:20 +00:00
fix: dynamically discover packages in release script instead of hardcoding
- sdk.ts: discoverNpmPackages() + topoSort() for library packages - sdk.ts: discoverCrates() via cargo metadata for workspace crates - sdk.ts: publishNpmLibraries replaces publishNpmCliShared + publishNpmSdk - sdk.ts: publishNpmCli now discovers CLI packages from filesystem - update_version.ts: discovers SDK package.json files via glob - update_version.ts: discovers internal crates from Cargo.toml path deps This prevents packages like persist-*, acp-http-client from being silently skipped during releases.
This commit is contained in:
parent
cdbe920070
commit
46193747e6
3 changed files with 240 additions and 268 deletions
|
|
@ -13,7 +13,7 @@ import {
|
||||||
createGitHubRelease,
|
createGitHubRelease,
|
||||||
validateGit,
|
validateGit,
|
||||||
} from "./git";
|
} from "./git";
|
||||||
import { publishCrates, publishNpmCli, publishNpmCliShared, publishNpmSdk } from "./sdk";
|
import { publishCrates, publishNpmCli, publishNpmLibraries } from "./sdk";
|
||||||
import { updateVersion } from "./update_version";
|
import { updateVersion } from "./update_version";
|
||||||
import { assert, assertEquals, fetchGitRef, versionOrCommitToRef } from "./utils";
|
import { assert, assertEquals, fetchGitRef, versionOrCommitToRef } from "./utils";
|
||||||
|
|
||||||
|
|
@ -282,8 +282,7 @@ const STEPS = [
|
||||||
"run-ci-checks",
|
"run-ci-checks",
|
||||||
"build-js-artifacts",
|
"build-js-artifacts",
|
||||||
"publish-crates",
|
"publish-crates",
|
||||||
"publish-npm-cli-shared",
|
"publish-npm-libraries",
|
||||||
"publish-npm-sdk",
|
|
||||||
"publish-npm-cli",
|
"publish-npm-cli",
|
||||||
"tag-docker",
|
"tag-docker",
|
||||||
"promote-artifacts",
|
"promote-artifacts",
|
||||||
|
|
@ -324,8 +323,7 @@ const PHASE_MAP: Record<Phase, Step[]> = {
|
||||||
"complete-ci": [
|
"complete-ci": [
|
||||||
"update-version",
|
"update-version",
|
||||||
"publish-crates",
|
"publish-crates",
|
||||||
"publish-npm-cli-shared",
|
"publish-npm-libraries",
|
||||||
"publish-npm-sdk",
|
|
||||||
"publish-npm-cli",
|
"publish-npm-cli",
|
||||||
"tag-docker",
|
"tag-docker",
|
||||||
"promote-artifacts",
|
"promote-artifacts",
|
||||||
|
|
@ -604,14 +602,9 @@ async function main() {
|
||||||
await publishCrates(releaseOpts);
|
await publishCrates(releaseOpts);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (shouldRunStep("publish-npm-cli-shared")) {
|
if (shouldRunStep("publish-npm-libraries")) {
|
||||||
console.log("==> Publishing NPM CLI Shared");
|
console.log("==> Publishing NPM Libraries");
|
||||||
await publishNpmCliShared(releaseOpts);
|
await publishNpmLibraries(releaseOpts);
|
||||||
}
|
|
||||||
|
|
||||||
if (shouldRunStep("publish-npm-sdk")) {
|
|
||||||
console.log("==> Publishing NPM SDK");
|
|
||||||
await publishNpmSdk(releaseOpts);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (shouldRunStep("publish-npm-cli")) {
|
if (shouldRunStep("publish-npm-cli")) {
|
||||||
|
|
|
||||||
|
|
@ -1,38 +1,13 @@
|
||||||
import { $ } from "execa";
|
import { $ } from "execa";
|
||||||
import * as fs from "node:fs/promises";
|
import * as fs from "node:fs/promises";
|
||||||
import { join } from "node:path";
|
import { dirname, join, relative } from "node:path";
|
||||||
|
import { glob } from "glob";
|
||||||
import type { ReleaseOpts } from "./main";
|
import type { ReleaseOpts } from "./main";
|
||||||
import { downloadFromReleases, PREFIX } from "./utils";
|
import { downloadFromReleases, PREFIX } from "./utils";
|
||||||
|
|
||||||
// Crates to publish in dependency order
|
// ─── Platform binary mapping (npm package → Rust target) ────────────────────
|
||||||
const CRATES = [
|
// Maps CLI platform packages to their Rust build targets.
|
||||||
"error",
|
// Keep in sync when adding new target platforms.
|
||||||
"agent-credentials",
|
|
||||||
"agent-management",
|
|
||||||
"opencode-server-manager",
|
|
||||||
"opencode-adapter",
|
|
||||||
"acp-http-adapter",
|
|
||||||
"sandbox-agent",
|
|
||||||
"gigacode",
|
|
||||||
] as const;
|
|
||||||
|
|
||||||
// NPM CLI packages
|
|
||||||
const CLI_PACKAGES = [
|
|
||||||
"@sandbox-agent/cli",
|
|
||||||
"@sandbox-agent/cli-linux-x64",
|
|
||||||
"@sandbox-agent/cli-linux-arm64",
|
|
||||||
"@sandbox-agent/cli-win32-x64",
|
|
||||||
"@sandbox-agent/cli-darwin-x64",
|
|
||||||
"@sandbox-agent/cli-darwin-arm64",
|
|
||||||
"@sandbox-agent/gigacode",
|
|
||||||
"@sandbox-agent/gigacode-linux-x64",
|
|
||||||
"@sandbox-agent/gigacode-linux-arm64",
|
|
||||||
"@sandbox-agent/gigacode-win32-x64",
|
|
||||||
"@sandbox-agent/gigacode-darwin-x64",
|
|
||||||
"@sandbox-agent/gigacode-darwin-arm64",
|
|
||||||
] as const;
|
|
||||||
|
|
||||||
// Mapping from npm package name to Rust target and binary extension
|
|
||||||
const CLI_PLATFORM_MAP: Record<
|
const CLI_PLATFORM_MAP: Record<
|
||||||
string,
|
string,
|
||||||
{ target: string; binaryExt: string; binaryName: string }
|
{ target: string; binaryExt: string; binaryName: string }
|
||||||
|
|
@ -89,6 +64,8 @@ const CLI_PLATFORM_MAP: Record<
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// ─── Shared helpers ─────────────────────────────────────────────────────────
|
||||||
|
|
||||||
async function npmVersionExists(
|
async function npmVersionExists(
|
||||||
packageName: string,
|
packageName: string,
|
||||||
version: string,
|
version: string,
|
||||||
|
|
@ -131,7 +108,6 @@ async function crateVersionExists(
|
||||||
stdout: "pipe",
|
stdout: "pipe",
|
||||||
stderr: "pipe",
|
stderr: "pipe",
|
||||||
})`cargo search ${crateName} --limit 1`;
|
})`cargo search ${crateName} --limit 1`;
|
||||||
// cargo search output format: "cratename = \"version\" # description"
|
|
||||||
const output = result.stdout;
|
const output = result.stdout;
|
||||||
const match = output.match(new RegExp(`^${crateName}\\s*=\\s*"([^"]+)"`));
|
const match = output.match(new RegExp(`^${crateName}\\s*=\\s*"([^"]+)"`));
|
||||||
if (match && match[1] === version) {
|
if (match && match[1] === version) {
|
||||||
|
|
@ -139,62 +115,148 @@ async function crateVersionExists(
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
// If cargo search fails, assume crate doesn't exist
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ─── Package discovery ──────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
interface NpmPackageInfo {
|
||||||
|
name: string;
|
||||||
|
dir: string;
|
||||||
|
hasBuildScript: boolean;
|
||||||
|
localDeps: string[];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Discover non-private npm packages matching the given glob patterns.
|
||||||
|
*/
|
||||||
|
async function discoverNpmPackages(root: string, patterns: string[]): Promise<NpmPackageInfo[]> {
|
||||||
|
const packages: NpmPackageInfo[] = [];
|
||||||
|
|
||||||
|
for (const pattern of patterns) {
|
||||||
|
const matches = await glob(pattern, { cwd: root });
|
||||||
|
for (const match of matches) {
|
||||||
|
const fullPath = join(root, match);
|
||||||
|
const pkg = JSON.parse(await fs.readFile(fullPath, "utf-8"));
|
||||||
|
if (pkg.private) continue;
|
||||||
|
|
||||||
|
const allDeps = { ...pkg.dependencies, ...pkg.peerDependencies };
|
||||||
|
const localDeps = Object.entries(allDeps || {})
|
||||||
|
.filter(([_, v]) => String(v).startsWith("workspace:"))
|
||||||
|
.map(([k]) => k);
|
||||||
|
|
||||||
|
packages.push({
|
||||||
|
name: pkg.name,
|
||||||
|
dir: dirname(fullPath),
|
||||||
|
hasBuildScript: !!pkg.scripts?.build,
|
||||||
|
localDeps,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return packages;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Topologically sort packages so dependencies are published before dependents.
|
||||||
|
*/
|
||||||
|
function topoSort(packages: NpmPackageInfo[]): NpmPackageInfo[] {
|
||||||
|
const byName = new Map(packages.map(p => [p.name, p]));
|
||||||
|
const visited = new Set<string>();
|
||||||
|
const result: NpmPackageInfo[] = [];
|
||||||
|
|
||||||
|
function visit(pkg: NpmPackageInfo) {
|
||||||
|
if (visited.has(pkg.name)) return;
|
||||||
|
visited.add(pkg.name);
|
||||||
|
for (const dep of pkg.localDeps) {
|
||||||
|
const d = byName.get(dep);
|
||||||
|
if (d) visit(d);
|
||||||
|
}
|
||||||
|
result.push(pkg);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const pkg of packages) visit(pkg);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface CrateInfo {
|
||||||
|
name: string;
|
||||||
|
dir: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Discover workspace crates via `cargo metadata` and return them in dependency order.
|
||||||
|
*/
|
||||||
|
async function discoverCrates(root: string): Promise<CrateInfo[]> {
|
||||||
|
const result = await $({ cwd: root, stdout: "pipe" })`cargo metadata --no-deps --format-version 1`;
|
||||||
|
const metadata = JSON.parse(result.stdout);
|
||||||
|
|
||||||
|
const memberIds = new Set<string>(metadata.workspace_members);
|
||||||
|
const workspacePackages = metadata.packages.filter((p: any) => memberIds.has(p.id));
|
||||||
|
|
||||||
|
// Build name→package map for topo sort
|
||||||
|
const byName = new Map<string, any>(workspacePackages.map((p: any) => [p.name, p]));
|
||||||
|
|
||||||
|
const visited = new Set<string>();
|
||||||
|
const sorted: CrateInfo[] = [];
|
||||||
|
|
||||||
|
function visit(pkg: any) {
|
||||||
|
if (visited.has(pkg.name)) return;
|
||||||
|
visited.add(pkg.name);
|
||||||
|
for (const dep of pkg.dependencies) {
|
||||||
|
const internal = byName.get(dep.name);
|
||||||
|
if (internal) visit(internal);
|
||||||
|
}
|
||||||
|
sorted.push({
|
||||||
|
name: pkg.name,
|
||||||
|
dir: dirname(pkg.manifest_path),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const pkg of workspacePackages) visit(pkg);
|
||||||
|
return sorted;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ─── Crate publishing ───────────────────────────────────────────────────────
|
||||||
|
|
||||||
export async function publishCrates(opts: ReleaseOpts) {
|
export async function publishCrates(opts: ReleaseOpts) {
|
||||||
console.log("==> Publishing crates to crates.io");
|
console.log("==> Discovering workspace crates");
|
||||||
|
const crates = await discoverCrates(opts.root);
|
||||||
|
|
||||||
for (const crate of CRATES) {
|
console.log(`Found ${crates.length} crates to publish:`);
|
||||||
const cratePath = crate === "gigacode"
|
for (const c of crates) console.log(` - ${c.name}`);
|
||||||
? join(opts.root, "gigacode")
|
|
||||||
: join(opts.root, "server/packages", crate);
|
|
||||||
|
|
||||||
// Read Cargo.toml to get the actual crate name
|
for (const crate of crates) {
|
||||||
const cargoTomlPath = join(cratePath, "Cargo.toml");
|
const versionExists = await crateVersionExists(crate.name, opts.version);
|
||||||
const cargoToml = await fs.readFile(cargoTomlPath, "utf-8");
|
|
||||||
const nameMatch = cargoToml.match(/^name\s*=\s*"([^"]+)"/m);
|
|
||||||
const crateName = nameMatch ? nameMatch[1] : `sandbox-agent-${crate}`;
|
|
||||||
|
|
||||||
// Check if version already exists
|
|
||||||
const versionExists = await crateVersionExists(crateName, opts.version);
|
|
||||||
if (versionExists) {
|
if (versionExists) {
|
||||||
console.log(
|
console.log(
|
||||||
`Version ${opts.version} of ${crateName} already exists on crates.io. Skipping...`,
|
`Version ${opts.version} of ${crate.name} already exists on crates.io. Skipping...`,
|
||||||
);
|
);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Publish
|
console.log(`==> Publishing to crates.io: ${crate.name}@${opts.version}`);
|
||||||
// Use --no-verify to skip the verification step because:
|
|
||||||
// 1. Code was already built/checked in the setup phase
|
|
||||||
// 2. Verification downloads published dependencies which may not have the latest
|
|
||||||
// changes yet (crates.io indexing takes time)
|
|
||||||
console.log(`==> Publishing to crates.io: ${crateName}@${opts.version}`);
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await $({
|
await $({
|
||||||
stdout: "pipe",
|
stdout: "pipe",
|
||||||
stderr: "pipe",
|
stderr: "pipe",
|
||||||
cwd: cratePath,
|
cwd: crate.dir,
|
||||||
})`cargo publish --allow-dirty --no-verify`;
|
})`cargo publish --allow-dirty --no-verify`;
|
||||||
console.log(`✅ Published ${crateName}@${opts.version}`);
|
console.log(`✅ Published ${crate.name}@${opts.version}`);
|
||||||
} catch (err: any) {
|
} catch (err: any) {
|
||||||
// Check if error is because crate already exists (from a previous partial run)
|
|
||||||
if (err.stderr?.includes("already exists")) {
|
if (err.stderr?.includes("already exists")) {
|
||||||
console.log(
|
console.log(
|
||||||
`Version ${opts.version} of ${crateName} already exists on crates.io. Skipping...`,
|
`Version ${opts.version} of ${crate.name} already exists on crates.io. Skipping...`,
|
||||||
);
|
);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
console.error(`❌ Failed to publish ${crateName}`);
|
console.error(`❌ Failed to publish ${crate.name}`);
|
||||||
console.error(err.stderr || err.message);
|
console.error(err.stderr || err.message);
|
||||||
throw err;
|
throw err;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Wait a bit for crates.io to index the new version (needed for dependency resolution)
|
|
||||||
console.log("Waiting for crates.io to index...");
|
console.log("Waiting for crates.io to index...");
|
||||||
await new Promise((resolve) => setTimeout(resolve, 30000));
|
await new Promise((resolve) => setTimeout(resolve, 30000));
|
||||||
}
|
}
|
||||||
|
|
@ -202,108 +264,68 @@ export async function publishCrates(opts: ReleaseOpts) {
|
||||||
console.log("✅ All crates published");
|
console.log("✅ All crates published");
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function publishNpmCliShared(opts: ReleaseOpts) {
|
// ─── NPM library publishing ────────────────────────────────────────────────
|
||||||
const cliSharedPath = join(opts.root, "sdks/cli-shared");
|
|
||||||
const packageJsonPath = join(cliSharedPath, "package.json");
|
|
||||||
const packageJson = JSON.parse(await fs.readFile(packageJsonPath, "utf-8"));
|
|
||||||
const name = packageJson.name;
|
|
||||||
|
|
||||||
// Check if version already exists
|
/**
|
||||||
const versionExists = await npmVersionExists(name, opts.version);
|
* Discover and publish all non-private library packages under sdks/.
|
||||||
if (versionExists) {
|
* Excludes CLI/gigacode wrapper and platform packages (handled by publishNpmCli).
|
||||||
console.log(
|
* Publishes in dependency order via topological sort.
|
||||||
`Version ${opts.version} of ${name} already exists. Skipping...`,
|
*/
|
||||||
);
|
export async function publishNpmLibraries(opts: ReleaseOpts) {
|
||||||
return;
|
console.log("==> Discovering library packages");
|
||||||
}
|
const all = await discoverNpmPackages(opts.root, ["sdks/*/package.json"]);
|
||||||
|
|
||||||
// Build cli-shared
|
// Exclude CLI and gigacode directories (handled by publishNpmCli)
|
||||||
console.log(`==> Building @sandbox-agent/cli-shared`);
|
const libraries = all.filter(p => {
|
||||||
await $({
|
const rel = relative(opts.root, p.dir);
|
||||||
stdio: "inherit",
|
return !rel.startsWith("sdks/cli") && !rel.startsWith("sdks/gigacode");
|
||||||
cwd: opts.root,
|
});
|
||||||
})`pnpm --filter @sandbox-agent/cli-shared build`;
|
|
||||||
|
|
||||||
// Publish
|
const sorted = topoSort(libraries);
|
||||||
console.log(`==> Publishing to NPM: ${name}@${opts.version}`);
|
|
||||||
|
console.log(`Found ${sorted.length} library packages to publish:`);
|
||||||
|
for (const pkg of sorted) console.log(` - ${pkg.name}`);
|
||||||
|
|
||||||
// Add --tag flag for release candidates
|
|
||||||
const isReleaseCandidate = opts.version.includes("-rc.");
|
const isReleaseCandidate = opts.version.includes("-rc.");
|
||||||
const tag = isReleaseCandidate ? "rc" : (opts.latest ? "latest" : opts.minorVersionChannel);
|
const tag = isReleaseCandidate ? "rc" : (opts.latest ? "latest" : opts.minorVersionChannel);
|
||||||
|
|
||||||
await $({
|
for (const pkg of sorted) {
|
||||||
stdio: "inherit",
|
const versionExists = await npmVersionExists(pkg.name, opts.version);
|
||||||
cwd: cliSharedPath,
|
|
||||||
})`pnpm publish --access public --tag ${tag} --no-git-checks`;
|
|
||||||
|
|
||||||
console.log(`✅ Published ${name}@${opts.version}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function publishNpmSdk(opts: ReleaseOpts) {
|
|
||||||
const isReleaseCandidate = opts.version.includes("-rc.");
|
|
||||||
const tag = isReleaseCandidate ? "rc" : (opts.latest ? "latest" : opts.minorVersionChannel);
|
|
||||||
|
|
||||||
// Publish acp-http-client (dependency of the SDK)
|
|
||||||
{
|
|
||||||
const acpHttpClientPath = join(opts.root, "sdks/acp-http-client");
|
|
||||||
const packageJsonPath = join(acpHttpClientPath, "package.json");
|
|
||||||
const packageJson = JSON.parse(await fs.readFile(packageJsonPath, "utf-8"));
|
|
||||||
const name = packageJson.name;
|
|
||||||
|
|
||||||
const versionExists = await npmVersionExists(name, opts.version);
|
|
||||||
if (versionExists) {
|
if (versionExists) {
|
||||||
console.log(
|
console.log(`Version ${opts.version} of ${pkg.name} already exists. Skipping...`);
|
||||||
`Version ${opts.version} of ${name} already exists. Skipping...`,
|
continue;
|
||||||
);
|
|
||||||
} else {
|
|
||||||
console.log(`==> Building acp-http-client`);
|
|
||||||
await $({
|
|
||||||
stdio: "inherit",
|
|
||||||
cwd: opts.root,
|
|
||||||
})`pnpm --filter acp-http-client build`;
|
|
||||||
|
|
||||||
console.log(`==> Publishing to NPM: ${name}@${opts.version}`);
|
|
||||||
await $({
|
|
||||||
stdio: "inherit",
|
|
||||||
cwd: acpHttpClientPath,
|
|
||||||
})`pnpm publish --access public --tag ${tag} --no-git-checks`;
|
|
||||||
|
|
||||||
console.log(`✅ Published ${name}@${opts.version}`);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (pkg.hasBuildScript) {
|
||||||
|
console.log(`==> Building ${pkg.name}`);
|
||||||
|
await $({ stdio: "inherit", cwd: opts.root })`pnpm --filter ${pkg.name} build`;
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(`==> Publishing to NPM: ${pkg.name}@${opts.version}`);
|
||||||
|
await $({ stdio: "inherit", cwd: pkg.dir })`pnpm publish --access public --tag ${tag} --no-git-checks`;
|
||||||
|
console.log(`✅ Published ${pkg.name}@${opts.version}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Publish SDK
|
console.log("✅ All library packages published");
|
||||||
const sdkPath = join(opts.root, "sdks/typescript");
|
|
||||||
const packageJsonPath = join(sdkPath, "package.json");
|
|
||||||
const packageJson = JSON.parse(await fs.readFile(packageJsonPath, "utf-8"));
|
|
||||||
const name = packageJson.name;
|
|
||||||
|
|
||||||
const versionExists = await npmVersionExists(name, opts.version);
|
|
||||||
if (versionExists) {
|
|
||||||
console.log(
|
|
||||||
`Version ${opts.version} of ${name} already exists. Skipping...`,
|
|
||||||
);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Build the SDK (cli-shared should already be built by publishNpmCliShared)
|
|
||||||
console.log(`==> Building TypeScript SDK`);
|
|
||||||
await $({
|
|
||||||
stdio: "inherit",
|
|
||||||
cwd: opts.root,
|
|
||||||
})`pnpm --filter sandbox-agent build`;
|
|
||||||
|
|
||||||
console.log(`==> Publishing to NPM: ${name}@${opts.version}`);
|
|
||||||
await $({
|
|
||||||
stdio: "inherit",
|
|
||||||
cwd: sdkPath,
|
|
||||||
})`pnpm publish --access public --tag ${tag} --no-git-checks`;
|
|
||||||
|
|
||||||
console.log(`✅ Published ${name}@${opts.version}`);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ─── NPM CLI publishing ────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Discover and publish CLI wrapper and platform packages.
|
||||||
|
* Platform packages get their binaries downloaded from R2 before publishing.
|
||||||
|
*/
|
||||||
export async function publishNpmCli(opts: ReleaseOpts) {
|
export async function publishNpmCli(opts: ReleaseOpts) {
|
||||||
console.log("==> Publishing CLI packages to NPM");
|
console.log("==> Discovering CLI packages");
|
||||||
|
const packages = await discoverNpmPackages(opts.root, [
|
||||||
|
"sdks/cli/package.json",
|
||||||
|
"sdks/cli/platforms/*/package.json",
|
||||||
|
"sdks/gigacode/package.json",
|
||||||
|
"sdks/gigacode/platforms/*/package.json",
|
||||||
|
]);
|
||||||
|
|
||||||
|
console.log(`Found ${packages.length} CLI packages to publish:`);
|
||||||
|
for (const pkg of packages) console.log(` - ${pkg.name}`);
|
||||||
|
|
||||||
// Determine which commit to use for downloading binaries
|
// Determine which commit to use for downloading binaries
|
||||||
let sourceCommit = opts.commit;
|
let sourceCommit = opts.commit;
|
||||||
|
|
@ -316,65 +338,41 @@ export async function publishNpmCli(opts: ReleaseOpts) {
|
||||||
console.log(`Using binaries from commit: ${sourceCommit}`);
|
console.log(`Using binaries from commit: ${sourceCommit}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const packageName of CLI_PACKAGES) {
|
for (const pkg of packages) {
|
||||||
// Check if version already exists
|
const versionExists = await npmVersionExists(pkg.name, opts.version);
|
||||||
const versionExists = await npmVersionExists(packageName, opts.version);
|
|
||||||
if (versionExists) {
|
if (versionExists) {
|
||||||
console.log(
|
console.log(
|
||||||
`Version ${opts.version} of ${packageName} already exists. Skipping...`,
|
`Version ${opts.version} of ${pkg.name} already exists. Skipping...`,
|
||||||
);
|
);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Determine package path
|
// Download binary for platform-specific packages
|
||||||
let packagePath: string;
|
const platformInfo = CLI_PLATFORM_MAP[pkg.name];
|
||||||
if (packageName === "@sandbox-agent/cli") {
|
|
||||||
packagePath = join(opts.root, "sdks/cli");
|
|
||||||
} else if (packageName === "@sandbox-agent/gigacode") {
|
|
||||||
packagePath = join(opts.root, "sdks/gigacode");
|
|
||||||
} else if (packageName.startsWith("@sandbox-agent/cli-")) {
|
|
||||||
// Platform-specific packages: @sandbox-agent/cli-linux-x64 -> sdks/cli/platforms/linux-x64
|
|
||||||
const platform = packageName.replace("@sandbox-agent/cli-", "");
|
|
||||||
packagePath = join(opts.root, "sdks/cli/platforms", platform);
|
|
||||||
} else if (packageName.startsWith("@sandbox-agent/gigacode-")) {
|
|
||||||
// Platform-specific packages: @sandbox-agent/gigacode-linux-x64 -> sdks/gigacode/platforms/linux-x64
|
|
||||||
const platform = packageName.replace("@sandbox-agent/gigacode-", "");
|
|
||||||
packagePath = join(opts.root, "sdks/gigacode/platforms", platform);
|
|
||||||
} else {
|
|
||||||
throw new Error(`Unknown CLI package: ${packageName}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Download binary from R2 for platform-specific packages
|
|
||||||
const platformInfo = CLI_PLATFORM_MAP[packageName];
|
|
||||||
if (platformInfo) {
|
if (platformInfo) {
|
||||||
const binDir = join(packagePath, "bin");
|
const binDir = join(pkg.dir, "bin");
|
||||||
const binaryName = `${platformInfo.binaryName}${platformInfo.binaryExt}`;
|
const binaryName = `${platformInfo.binaryName}${platformInfo.binaryExt}`;
|
||||||
const localBinaryPath = join(binDir, binaryName);
|
const localBinaryPath = join(binDir, binaryName);
|
||||||
const remoteBinaryPath = `${PREFIX}/${sourceCommit}/binaries/${platformInfo.binaryName}-${platformInfo.target}${platformInfo.binaryExt}`;
|
const remoteBinaryPath = `${PREFIX}/${sourceCommit}/binaries/${platformInfo.binaryName}-${platformInfo.target}${platformInfo.binaryExt}`;
|
||||||
|
|
||||||
console.log(`==> Downloading binary for ${packageName}`);
|
console.log(`==> Downloading binary for ${pkg.name}`);
|
||||||
console.log(` From: ${remoteBinaryPath}`);
|
console.log(` From: ${remoteBinaryPath}`);
|
||||||
console.log(` To: ${localBinaryPath}`);
|
console.log(` To: ${localBinaryPath}`);
|
||||||
|
|
||||||
// Create bin directory
|
|
||||||
await fs.mkdir(binDir, { recursive: true });
|
await fs.mkdir(binDir, { recursive: true });
|
||||||
|
|
||||||
// Download binary
|
|
||||||
await downloadFromReleases(remoteBinaryPath, localBinaryPath);
|
await downloadFromReleases(remoteBinaryPath, localBinaryPath);
|
||||||
|
|
||||||
// Make binary executable (not needed on Windows)
|
|
||||||
if (!platformInfo.binaryExt) {
|
if (!platformInfo.binaryExt) {
|
||||||
await fs.chmod(localBinaryPath, 0o755);
|
await fs.chmod(localBinaryPath, 0o755);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Publish
|
// Publish
|
||||||
console.log(`==> Publishing to NPM: ${packageName}@${opts.version}`);
|
console.log(`==> Publishing to NPM: ${pkg.name}@${opts.version}`);
|
||||||
|
|
||||||
// Add --tag flag for release candidates
|
|
||||||
const isReleaseCandidate = opts.version.includes("-rc.");
|
const isReleaseCandidate = opts.version.includes("-rc.");
|
||||||
const tag = getCliPackageNpmTag({
|
const tag = getCliPackageNpmTag({
|
||||||
packageName,
|
packageName: pkg.name,
|
||||||
isReleaseCandidate,
|
isReleaseCandidate,
|
||||||
latest: opts.latest,
|
latest: opts.latest,
|
||||||
minorVersionChannel: opts.minorVersionChannel,
|
minorVersionChannel: opts.minorVersionChannel,
|
||||||
|
|
@ -383,12 +381,11 @@ export async function publishNpmCli(opts: ReleaseOpts) {
|
||||||
try {
|
try {
|
||||||
await $({
|
await $({
|
||||||
stdio: "inherit",
|
stdio: "inherit",
|
||||||
cwd: packagePath,
|
cwd: pkg.dir,
|
||||||
})`pnpm publish --access public --tag ${tag} --no-git-checks`;
|
})`pnpm publish --access public --tag ${tag} --no-git-checks`;
|
||||||
console.log(`✅ Published ${packageName}@${opts.version}`);
|
console.log(`✅ Published ${pkg.name}@${opts.version}`);
|
||||||
|
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error(`❌ Failed to publish ${packageName}`);
|
console.error(`❌ Failed to publish ${pkg.name}`);
|
||||||
throw err;
|
throw err;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -412,4 +409,3 @@ function getCliPackageNpmTag(opts: {
|
||||||
|
|
||||||
return opts.minorVersionChannel;
|
return opts.minorVersionChannel;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
import * as fs from "node:fs/promises";
|
import * as fs from "node:fs/promises";
|
||||||
|
import { join } from "node:path";
|
||||||
import { $ } from "execa";
|
import { $ } from "execa";
|
||||||
import { glob } from "glob";
|
import { glob } from "glob";
|
||||||
import type { ReleaseOpts } from "./main";
|
import type { ReleaseOpts } from "./main";
|
||||||
|
|
@ -10,70 +11,36 @@ function assert(condition: any, message?: string): asserts condition {
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function updateVersion(opts: ReleaseOpts) {
|
export async function updateVersion(opts: ReleaseOpts) {
|
||||||
// Define substitutions
|
// 1. Update workspace version and internal crate versions in root Cargo.toml
|
||||||
const findReplace = [
|
const cargoTomlPath = join(opts.root, "Cargo.toml");
|
||||||
{
|
|
||||||
path: "Cargo.toml",
|
|
||||||
find: /\[workspace\.package\]\nversion = ".*"/,
|
|
||||||
replace: `[workspace.package]\nversion = "${opts.version}"`,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: "sdks/cli-shared/package.json",
|
|
||||||
find: /"version": ".*"/,
|
|
||||||
replace: `"version": "${opts.version}"`,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: "sdks/acp-http-client/package.json",
|
|
||||||
find: /"version": ".*"/,
|
|
||||||
replace: `"version": "${opts.version}"`,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: "sdks/typescript/package.json",
|
|
||||||
find: /"version": ".*"/,
|
|
||||||
replace: `"version": "${opts.version}"`,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: "sdks/cli/package.json",
|
|
||||||
find: /"version": ".*"/,
|
|
||||||
replace: `"version": "${opts.version}"`,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: "sdks/gigacode/package.json",
|
|
||||||
find: /"version": ".*"/,
|
|
||||||
replace: `"version": "${opts.version}"`,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: "sdks/cli/platforms/*/package.json",
|
|
||||||
find: /"version": ".*"/,
|
|
||||||
replace: `"version": "${opts.version}"`,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: "sdks/gigacode/platforms/*/package.json",
|
|
||||||
find: /"version": ".*"/,
|
|
||||||
replace: `"version": "${opts.version}"`,
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
// Update internal crate versions in workspace dependencies
|
|
||||||
// These need to match the new version so cargo publish can resolve them correctly
|
|
||||||
const internalCrates = [
|
|
||||||
"sandbox-agent",
|
|
||||||
"sandbox-agent-error",
|
|
||||||
"sandbox-agent-agent-management",
|
|
||||||
"sandbox-agent-agent-credentials",
|
|
||||||
"sandbox-agent-opencode-adapter",
|
|
||||||
"sandbox-agent-opencode-server-manager",
|
|
||||||
"acp-http-adapter",
|
|
||||||
];
|
|
||||||
|
|
||||||
const cargoTomlPath = `${opts.root}/Cargo.toml`;
|
|
||||||
let cargoContent = await fs.readFile(cargoTomlPath, "utf-8");
|
let cargoContent = await fs.readFile(cargoTomlPath, "utf-8");
|
||||||
|
|
||||||
|
// Update [workspace.package] version
|
||||||
|
assert(
|
||||||
|
/\[workspace\.package\]\nversion = ".*"/.test(cargoContent),
|
||||||
|
"Could not find workspace.package version in Cargo.toml",
|
||||||
|
);
|
||||||
|
cargoContent = cargoContent.replace(
|
||||||
|
/\[workspace\.package\]\nversion = ".*"/,
|
||||||
|
`[workspace.package]\nversion = "${opts.version}"`,
|
||||||
|
);
|
||||||
|
|
||||||
|
// Discover internal crates from [workspace.dependencies] by matching
|
||||||
|
// lines with both `version = "..."` and `path = "..."` (internal path deps)
|
||||||
|
const internalCratePattern = /^(\S+)\s*=\s*\{[^}]*version\s*=\s*"[^"]+"\s*,[^}]*path\s*=/gm;
|
||||||
|
let match;
|
||||||
|
const internalCrates: string[] = [];
|
||||||
|
while ((match = internalCratePattern.exec(cargoContent)) !== null) {
|
||||||
|
internalCrates.push(match[1]);
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(`Discovered ${internalCrates.length} internal crates to version-bump:`);
|
||||||
|
for (const crate of internalCrates) console.log(` - ${crate}`);
|
||||||
|
|
||||||
for (const crate of internalCrates) {
|
for (const crate of internalCrates) {
|
||||||
// Match: crate-name = { version = "x.y.z", path = "..." }
|
|
||||||
const pattern = new RegExp(
|
const pattern = new RegExp(
|
||||||
`(${crate.replace(/-/g, "-")} = \\{ version = ")[^"]+(",)`,
|
`(${crate.replace(/-/g, "-")} = \\{ version = ")[^"]+(",)`,
|
||||||
"g"
|
"g",
|
||||||
);
|
);
|
||||||
cargoContent = cargoContent.replace(pattern, `$1${opts.version}$2`);
|
cargoContent = cargoContent.replace(pattern, `$1${opts.version}$2`);
|
||||||
}
|
}
|
||||||
|
|
@ -81,18 +48,34 @@ export async function updateVersion(opts: ReleaseOpts) {
|
||||||
await fs.writeFile(cargoTomlPath, cargoContent);
|
await fs.writeFile(cargoTomlPath, cargoContent);
|
||||||
await $({ cwd: opts.root })`git add Cargo.toml`;
|
await $({ cwd: opts.root })`git add Cargo.toml`;
|
||||||
|
|
||||||
// Substitute all files
|
// 2. Discover and update all non-private SDK package.json versions
|
||||||
for (const { path: globPath, find, replace } of findReplace) {
|
const packageJsonPaths = await glob("sdks/**/package.json", {
|
||||||
const paths = await glob(globPath, { cwd: opts.root });
|
cwd: opts.root,
|
||||||
assert(paths.length > 0, `no paths matched: ${globPath}`);
|
ignore: ["**/node_modules/**"],
|
||||||
for (const path of paths) {
|
});
|
||||||
const fullPath = `${opts.root}/${path}`;
|
|
||||||
const file = await fs.readFile(fullPath, "utf-8");
|
|
||||||
assert(find.test(file), `file does not match ${find}: ${fullPath}`);
|
|
||||||
const newFile = file.replace(find, replace);
|
|
||||||
await fs.writeFile(fullPath, newFile);
|
|
||||||
|
|
||||||
await $({ cwd: opts.root })`git add ${path}`;
|
// Filter to non-private packages only
|
||||||
}
|
const toUpdate: string[] = [];
|
||||||
|
for (const relPath of packageJsonPaths) {
|
||||||
|
const fullPath = join(opts.root, relPath);
|
||||||
|
const content = await fs.readFile(fullPath, "utf-8");
|
||||||
|
const pkg = JSON.parse(content);
|
||||||
|
if (pkg.private) continue;
|
||||||
|
toUpdate.push(relPath);
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(`Discovered ${toUpdate.length} SDK package.json files to version-bump:`);
|
||||||
|
for (const relPath of toUpdate) console.log(` - ${relPath}`);
|
||||||
|
|
||||||
|
for (const relPath of toUpdate) {
|
||||||
|
const fullPath = join(opts.root, relPath);
|
||||||
|
const content = await fs.readFile(fullPath, "utf-8");
|
||||||
|
|
||||||
|
const versionPattern = /"version": ".*"/;
|
||||||
|
assert(versionPattern.test(content), `No version field in ${relPath}`);
|
||||||
|
|
||||||
|
const updated = content.replace(versionPattern, `"version": "${opts.version}"`);
|
||||||
|
await fs.writeFile(fullPath, updated);
|
||||||
|
await $({ cwd: opts.root })`git add ${relPath}`;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue