mirror of
https://github.com/openclaw/openclaw.git
synced 2026-07-09 15:59:30 +00:00
fix(ci): install workspace packages for Kova
This commit is contained in:
parent
686b6b51da
commit
b63317d07e
5 changed files with 219 additions and 0 deletions
17
.github/workflows/openclaw-performance.yml
vendored
17
.github/workflows/openclaw-performance.yml
vendored
|
|
@ -246,6 +246,23 @@ jobs:
|
|||
chmod 0755 "$HOME/.local/bin/kova"
|
||||
echo "$HOME/.local/bin" >> "$GITHUB_PATH"
|
||||
|
||||
- name: Configure OCM local workspace dependencies
|
||||
if: steps.lane.outputs.run == 'true'
|
||||
shell: bash
|
||||
run: |
|
||||
set -euo pipefail
|
||||
npm_wrapper="$PERFORMANCE_HELPER_DIR/scripts/ocm-npm-workspace-deps.mjs"
|
||||
workspace_dependency_dirs=""
|
||||
if [[ -f "${GITHUB_WORKSPACE}/packages/ai/package.json" ]]; then
|
||||
workspace_dependency_dirs="${GITHUB_WORKSPACE}/packages/ai"
|
||||
fi
|
||||
chmod 0755 "$npm_wrapper"
|
||||
{
|
||||
echo "OCM_INTERNAL_NPM_BIN=$npm_wrapper"
|
||||
echo "OPENCLAW_OCM_REAL_NPM_BIN=$(command -v npm)"
|
||||
echo "OPENCLAW_OCM_WORKSPACE_DEPENDENCY_DIRS=$workspace_dependency_dirs"
|
||||
} >> "$GITHUB_ENV"
|
||||
|
||||
- name: Pin Kova OpenAI model to GPT 5.5
|
||||
if: steps.lane.outputs.run == 'true'
|
||||
shell: bash
|
||||
|
|
|
|||
123
scripts/ocm-npm-workspace-deps.mjs
Executable file
123
scripts/ocm-npm-workspace-deps.mjs
Executable file
|
|
@ -0,0 +1,123 @@
|
|||
#!/usr/bin/env node
|
||||
|
||||
import { spawnSync } from "node:child_process";
|
||||
import { mkdtempSync, mkdirSync, readFileSync, readdirSync, rmSync, writeFileSync } from "node:fs";
|
||||
import { tmpdir } from "node:os";
|
||||
import { delimiter, join, resolve } from "node:path";
|
||||
import { pathToFileURL } from "node:url";
|
||||
|
||||
const WORKSPACE_DIRS_ENV = "OPENCLAW_OCM_WORKSPACE_DEPENDENCY_DIRS";
|
||||
const REAL_NPM_ENV = "OPENCLAW_OCM_REAL_NPM_BIN";
|
||||
|
||||
export function parseWorkspaceDependencyDirs(
|
||||
raw = process.env[WORKSPACE_DIRS_ENV],
|
||||
cwd = process.cwd(),
|
||||
) {
|
||||
return (raw ?? "")
|
||||
.split(delimiter)
|
||||
.map((entry) => entry.trim())
|
||||
.filter(Boolean)
|
||||
.map((entry) => resolve(cwd, entry));
|
||||
}
|
||||
|
||||
function optionValue(args, name) {
|
||||
const index = args.indexOf(name);
|
||||
return index >= 0 ? args[index + 1] : undefined;
|
||||
}
|
||||
|
||||
export function resolveWorkspaceInstallPlan(args, workspaceDirs, cwd = process.cwd()) {
|
||||
if (args[0] !== "install" || workspaceDirs.length === 0) {
|
||||
return null;
|
||||
}
|
||||
const prefixDir = optionValue(args, "--prefix");
|
||||
const rootArchive = args.at(-1);
|
||||
if (!prefixDir || !rootArchive?.endsWith(".tgz")) {
|
||||
throw new Error("OCM workspace dependency install requires --prefix and a root .tgz archive");
|
||||
}
|
||||
return {
|
||||
installArgs: args.slice(0, -1),
|
||||
prefixDir: resolve(cwd, prefixDir),
|
||||
rootArchive: resolve(cwd, rootArchive),
|
||||
};
|
||||
}
|
||||
|
||||
export function buildInstallManifest(rootArchive, workspacePackages) {
|
||||
return {
|
||||
private: true,
|
||||
dependencies: {
|
||||
openclaw: pathToFileURL(rootArchive).href,
|
||||
...Object.fromEntries(
|
||||
workspacePackages.map(({ name, tarball }) => [name, pathToFileURL(tarball).href]),
|
||||
),
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
function runNpm(npm, args, options = {}) {
|
||||
const result = spawnSync(npm, args, {
|
||||
...options,
|
||||
env: process.env,
|
||||
});
|
||||
if (result.error) {
|
||||
throw result.error;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
function packWorkspaceDependencies(npm, workspaceDirs, outputDir) {
|
||||
return workspaceDirs.map((packageDir) => {
|
||||
const packageJson = JSON.parse(readFileSync(join(packageDir, "package.json"), "utf8"));
|
||||
if (typeof packageJson.name !== "string" || packageJson.name.trim() === "") {
|
||||
throw new Error(`workspace dependency has no package name: ${packageDir}`);
|
||||
}
|
||||
const before = new Set(readdirSync(outputDir));
|
||||
const result = runNpm(npm, ["pack", packageDir, "--pack-destination", outputDir, "--silent"], {
|
||||
encoding: "utf8",
|
||||
stdio: ["ignore", "pipe", "inherit"],
|
||||
});
|
||||
if (result.status !== 0) {
|
||||
throw new Error(`npm pack failed for ${packageJson.name} with status ${result.status ?? 1}`);
|
||||
}
|
||||
const tarballs = readdirSync(outputDir).filter(
|
||||
(entry) => entry.endsWith(".tgz") && !before.has(entry),
|
||||
);
|
||||
if (tarballs.length !== 1) {
|
||||
throw new Error(
|
||||
`expected npm pack to create one archive for ${packageJson.name}, found ${tarballs.length}`,
|
||||
);
|
||||
}
|
||||
return {
|
||||
name: packageJson.name,
|
||||
tarball: join(outputDir, tarballs[0]),
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
function main() {
|
||||
const args = process.argv.slice(2);
|
||||
const npm = process.env[REAL_NPM_ENV]?.trim() || "npm";
|
||||
const workspaceDirs = parseWorkspaceDependencyDirs();
|
||||
const plan = resolveWorkspaceInstallPlan(args, workspaceDirs);
|
||||
if (!plan) {
|
||||
const result = runNpm(npm, args, { stdio: "inherit" });
|
||||
return result.status ?? 1;
|
||||
}
|
||||
|
||||
const packDir = mkdtempSync(join(tmpdir(), "openclaw-ocm-workspace-deps-"));
|
||||
try {
|
||||
const workspacePackages = packWorkspaceDependencies(npm, workspaceDirs, packDir);
|
||||
mkdirSync(plan.prefixDir, { recursive: true });
|
||||
writeFileSync(
|
||||
join(plan.prefixDir, "package.json"),
|
||||
`${JSON.stringify(buildInstallManifest(plan.rootArchive, workspacePackages), null, 2)}\n`,
|
||||
);
|
||||
const result = runNpm(npm, plan.installArgs, { stdio: "inherit" });
|
||||
return result.status ?? 1;
|
||||
} finally {
|
||||
rmSync(packDir, { force: true, recursive: true });
|
||||
}
|
||||
}
|
||||
|
||||
if (import.meta.url === pathToFileURL(process.argv[1] ?? "").href) {
|
||||
process.exitCode = main();
|
||||
}
|
||||
|
|
@ -1418,6 +1418,7 @@ const TOOLING_SOURCE_TEST_TARGETS = new Map([
|
|||
["scripts/create-dmg.sh", ["test/scripts/create-dmg.test.ts"]],
|
||||
["scripts/kova-ci-summary.mjs", ["test/scripts/kova-ci-summary.test.ts"]],
|
||||
["scripts/make_appcast.sh", ["test/scripts/make-appcast.test.ts"]],
|
||||
["scripts/ocm-npm-workspace-deps.mjs", ["test/scripts/ocm-npm-workspace-deps.test.ts"]],
|
||||
["scripts/openclaw-npm-prepublish-verify.ts", ["test/openclaw-npm-prepublish-verify.test.ts"]],
|
||||
["scripts/openclaw-npm-postpublish-verify.ts", ["test/openclaw-npm-postpublish-verify.test.ts"]],
|
||||
["scripts/openclaw-npm-release-check.ts", ["test/openclaw-npm-release-check.test.ts"]],
|
||||
|
|
|
|||
63
test/scripts/ocm-npm-workspace-deps.test.ts
Normal file
63
test/scripts/ocm-npm-workspace-deps.test.ts
Normal file
|
|
@ -0,0 +1,63 @@
|
|||
import { delimiter, join } from "node:path";
|
||||
import { describe, expect, it } from "vitest";
|
||||
import {
|
||||
buildInstallManifest,
|
||||
parseWorkspaceDependencyDirs,
|
||||
resolveWorkspaceInstallPlan,
|
||||
} from "../../scripts/ocm-npm-workspace-deps.mjs";
|
||||
|
||||
describe("OCM npm workspace dependency adapter", () => {
|
||||
it("resolves workspace package directories", () => {
|
||||
expect(
|
||||
parseWorkspaceDependencyDirs(["packages/ai", "extensions/example"].join(delimiter), "/repo"),
|
||||
).toEqual(["/repo/packages/ai", "/repo/extensions/example"]);
|
||||
});
|
||||
|
||||
it("replaces the root archive argument with a prepared install manifest", () => {
|
||||
expect(
|
||||
resolveWorkspaceInstallPlan(
|
||||
[
|
||||
"install",
|
||||
"--prefix",
|
||||
"runtime",
|
||||
"--omit=dev",
|
||||
"--no-save",
|
||||
"--package-lock=false",
|
||||
"openclaw.tgz",
|
||||
],
|
||||
["/repo/packages/ai"],
|
||||
"/repo",
|
||||
),
|
||||
).toEqual({
|
||||
installArgs: [
|
||||
"install",
|
||||
"--prefix",
|
||||
"runtime",
|
||||
"--omit=dev",
|
||||
"--no-save",
|
||||
"--package-lock=false",
|
||||
],
|
||||
prefixDir: "/repo/runtime",
|
||||
rootArchive: "/repo/openclaw.tgz",
|
||||
});
|
||||
});
|
||||
|
||||
it("keeps normal npm commands unchanged", () => {
|
||||
expect(resolveWorkspaceInstallPlan(["pack", "--silent"], ["/repo/packages/ai"])).toBeNull();
|
||||
expect(resolveWorkspaceInstallPlan(["install", "openclaw.tgz"], [])).toBeNull();
|
||||
});
|
||||
|
||||
it("builds a manifest with the root and local workspace tarballs", () => {
|
||||
expect(
|
||||
buildInstallManifest("/tmp/openclaw.tgz", [
|
||||
{ name: "@openclaw/ai", tarball: "/tmp/openclaw-ai.tgz" },
|
||||
]),
|
||||
).toEqual({
|
||||
private: true,
|
||||
dependencies: {
|
||||
"@openclaw/ai": "file:///tmp/openclaw-ai.tgz",
|
||||
openclaw: "file:///tmp/openclaw.tgz",
|
||||
},
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
@ -104,6 +104,21 @@ describe("OpenClaw performance workflow", () => {
|
|||
expect(runKova.run).not.toContain("report.summary?.statuses ?? {}");
|
||||
});
|
||||
|
||||
it("installs local workspace packages beside the OCM root tarball", () => {
|
||||
const configure = findStep("Configure OCM local workspace dependencies");
|
||||
|
||||
expect(configure.run).toContain(
|
||||
'npm_wrapper="$PERFORMANCE_HELPER_DIR/scripts/ocm-npm-workspace-deps.mjs"',
|
||||
);
|
||||
expect(configure.run).toContain("OCM_INTERNAL_NPM_BIN=$npm_wrapper");
|
||||
expect(configure.run).toContain(
|
||||
'if [[ -f "${GITHUB_WORKSPACE}/packages/ai/package.json" ]]; then',
|
||||
);
|
||||
expect(configure.run).toContain(
|
||||
"OPENCLAW_OCM_WORKSPACE_DEPENDENCY_DIRS=$workspace_dependency_dirs",
|
||||
);
|
||||
});
|
||||
|
||||
it("fails selected live Kova lanes when live auth is missing", () => {
|
||||
const configureAuth = findStep("Configure live OpenAI auth");
|
||||
const runKova = findStep("Run Kova");
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue