Skip to content

Commit e3c58e0

Browse files
committed
fix(release): verify packaged workspace templates
1 parent 1558a35 commit e3c58e0

4 files changed

Lines changed: 126 additions & 1 deletion

File tree

Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
import { execFileSync } from "node:child_process";
2+
import { existsSync, mkdtempSync, mkdirSync, rmSync } from "node:fs";
3+
import { tmpdir } from "node:os";
4+
import { join } from "node:path";
5+
6+
export const WORKSPACE_TEMPLATE_PACK_PATHS = [
7+
"docs/reference/templates/AGENTS.md",
8+
"docs/reference/templates/SOUL.md",
9+
"docs/reference/templates/TOOLS.md",
10+
"docs/reference/templates/IDENTITY.md",
11+
"docs/reference/templates/USER.md",
12+
"docs/reference/templates/HEARTBEAT.md",
13+
"docs/reference/templates/BOOTSTRAP.md",
14+
];
15+
16+
const REQUIRED_BOOTSTRAP_WORKSPACE_FILES = [
17+
"AGENTS.md",
18+
"SOUL.md",
19+
"TOOLS.md",
20+
"IDENTITY.md",
21+
"USER.md",
22+
"HEARTBEAT.md",
23+
"BOOTSTRAP.md",
24+
];
25+
26+
function collectMissingBootstrapWorkspaceFiles(workspaceDir) {
27+
return REQUIRED_BOOTSTRAP_WORKSPACE_FILES.filter(
28+
(filename) => !existsSync(join(workspaceDir, filename)),
29+
);
30+
}
31+
32+
function describeExecFailure(error) {
33+
if (!(error instanceof Error)) {
34+
return String(error);
35+
}
36+
const stdout =
37+
typeof error.stdout === "string"
38+
? error.stdout.trim()
39+
: error.stdout instanceof Uint8Array
40+
? Buffer.from(error.stdout).toString("utf8").trim()
41+
: "";
42+
const stderr =
43+
typeof error.stderr === "string"
44+
? error.stderr.trim()
45+
: error.stderr instanceof Uint8Array
46+
? Buffer.from(error.stderr).toString("utf8").trim()
47+
: "";
48+
return [error.message, stdout, stderr].filter(Boolean).join(" | ");
49+
}
50+
51+
export function runInstalledWorkspaceBootstrapSmoke(params) {
52+
const tempRoot = mkdtempSync(join(tmpdir(), "openclaw-workspace-bootstrap-smoke-"));
53+
const homeDir = join(tempRoot, "home");
54+
const cwd = join(tempRoot, "cwd");
55+
mkdirSync(homeDir, { recursive: true });
56+
mkdirSync(cwd, { recursive: true });
57+
58+
let combinedOutput = "";
59+
try {
60+
try {
61+
execFileSync(
62+
process.execPath,
63+
[
64+
join(params.packageRoot, "openclaw.mjs"),
65+
"agent",
66+
"--message",
67+
"workspace bootstrap smoke",
68+
"--session-id",
69+
"workspace-bootstrap-smoke",
70+
"--local",
71+
"--timeout",
72+
"1",
73+
"--json",
74+
],
75+
{
76+
cwd,
77+
encoding: "utf8",
78+
maxBuffer: 1024 * 1024 * 16,
79+
stdio: ["ignore", "pipe", "pipe"],
80+
env: {
81+
...process.env,
82+
HOME: homeDir,
83+
OPENCLAW_HOME: homeDir,
84+
OPENCLAW_SUPPRESS_NOTES: "1",
85+
},
86+
},
87+
);
88+
} catch (error) {
89+
combinedOutput = describeExecFailure(error);
90+
}
91+
92+
if (combinedOutput.includes("Missing workspace template:")) {
93+
throw new Error(
94+
`installed workspace bootstrap failed before agent execution: ${combinedOutput}`,
95+
);
96+
}
97+
98+
const workspaceDir = join(homeDir, ".openclaw", "workspace");
99+
const missingFiles = collectMissingBootstrapWorkspaceFiles(workspaceDir);
100+
if (missingFiles.length > 0) {
101+
throw new Error(
102+
`installed workspace bootstrap did not create required files in ${workspaceDir}: ${missingFiles.join(", ")}`,
103+
);
104+
}
105+
} finally {
106+
try {
107+
rmSync(tempRoot, { force: true, recursive: true });
108+
} catch {
109+
// best effort cleanup only
110+
}
111+
}
112+
}

scripts/openclaw-npm-postpublish-verify.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import {
2121
collectRuntimeDependencySpecs,
2222
} from "./lib/bundled-plugin-root-runtime-mirrors.mjs";
2323
import { NPM_UPDATE_COMPAT_SIDECAR_PATHS } from "./lib/npm-update-compat-sidecars.mjs";
24+
import { runInstalledWorkspaceBootstrapSmoke } from "./lib/workspace-bootstrap-smoke.mjs";
2425
import { parseReleaseVersion, resolveNpmCommandInvocation } from "./openclaw-npm-release-check.ts";
2526

2627
type InstalledPackageJson = {
@@ -371,6 +372,10 @@ function verifyScenario(version: string, scenario: PublishedInstallScenario): vo
371372
);
372373
}
373374

375+
if (errors.length === 0) {
376+
runInstalledWorkspaceBootstrapSmoke({ packageRoot });
377+
}
378+
374379
if (errors.length > 0) {
375380
throw new Error(`${scenario.name} failed:\n- ${errors.join("\n- ")}`);
376381
}

scripts/openclaw-npm-release-check.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import {
1010
parseReleaseVersion as parseReleaseVersionBase,
1111
} from "./lib/npm-publish-plan.mjs";
1212
import { NPM_UPDATE_COMPAT_SIDECAR_PATHS } from "./lib/npm-update-compat-sidecars.mjs";
13+
import { WORKSPACE_TEMPLATE_PACK_PATHS } from "./lib/workspace-bootstrap-smoke.mjs";
1314

1415
type PackageJson = {
1516
name?: string;
@@ -55,7 +56,7 @@ export type NpmDistTagMirrorAuth = {
5556
};
5657
const EXPECTED_REPOSITORY_URL = "https://github.com/openclaw/openclaw";
5758
const MAX_CALVER_DISTANCE_DAYS = 2;
58-
const REQUIRED_PACKED_PATHS = ["dist/control-ui/index.html"];
59+
const REQUIRED_PACKED_PATHS = ["dist/control-ui/index.html", ...WORKSPACE_TEMPLATE_PACK_PATHS];
5960
const CONTROL_UI_ASSET_PREFIX = "dist/control-ui/assets/";
6061
const FORBIDDEN_PACKED_PATH_RULES = [
6162
{

scripts/release-check.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,10 @@ import {
1919
} from "./lib/bundled-plugin-root-runtime-mirrors.mjs";
2020
import { collectPackUnpackedSizeErrors as collectNpmPackUnpackedSizeErrors } from "./lib/npm-pack-budget.mjs";
2121
import { listPluginSdkDistArtifacts } from "./lib/plugin-sdk-entries.mjs";
22+
import {
23+
runInstalledWorkspaceBootstrapSmoke,
24+
WORKSPACE_TEMPLATE_PACK_PATHS,
25+
} from "./lib/workspace-bootstrap-smoke.mjs";
2226
import { listStaticExtensionAssetOutputs } from "./runtime-postbuild.mjs";
2327
import { sparkleBuildFloorsFromShortVersion, type SparkleBuildFloors } from "./sparkle-build.ts";
2428

@@ -39,6 +43,7 @@ const requiredPathGroups = [
3943
...listPluginSdkDistArtifacts(),
4044
...listBundledPluginPackArtifacts(),
4145
...listStaticExtensionAssetOutputs(),
46+
...WORKSPACE_TEMPLATE_PACK_PATHS,
4247
...listRequiredQaScenarioPackPaths(),
4348
"scripts/npm-runner.mjs",
4449
"scripts/postinstall-bundled-plugins.mjs",
@@ -235,6 +240,8 @@ function runPackedBundledChannelEntrySmoke(): void {
235240
if (completionFiles.length === 0) {
236241
throw new Error("release-check: packed completion smoke produced no completion files.");
237242
}
243+
244+
runInstalledWorkspaceBootstrapSmoke({ packageRoot });
238245
} finally {
239246
rmSync(tmpRoot, { recursive: true, force: true });
240247
}

0 commit comments

Comments
 (0)