Vendor OpenClaw source as Adolf fork baseline
Some checks failed
ClawSweeper Dispatch / dispatch (push) Has been cancelled
CodeQL / Security High (actions) (push) Has been cancelled
CodeQL / Security High (channel-runtime-boundary) (push) Has been cancelled
CodeQL / Security High (core-auth-secrets) (push) Has been cancelled
CodeQL / Security High (mcp-process-tool-boundary) (push) Has been cancelled
CodeQL / Security High (network-ssrf-boundary) (push) Has been cancelled
CodeQL / Security High (plugin-trust-boundary) (push) Has been cancelled
CodeQL / Security High (process-exec-boundary) (push) Has been cancelled
Docs Sync Publish Repo / sync-publish-repo (push) Has been cancelled
Docs / docs (push) Has been cancelled
OpenClaw Stable Main Closeout / Resolve stable release closeout inputs (push) Has been cancelled
OpenClaw Stable Main Closeout / Verify stable main closeout (push) Has been cancelled
Workflow Sanity / no-tabs (push) Has been cancelled
Workflow Sanity / actionlint (push) Has been cancelled
Workflow Sanity / generated-doc-baselines (push) Has been cancelled
CI / runner-admission (push) Has been cancelled
CI / preflight (push) Has been cancelled
CI / security-fast (push) Has been cancelled
CI / pnpm-store-warmup (push) Has been cancelled
CI / build-artifacts (push) Has been cancelled
CI / native-i18n (push) Has been cancelled
CI / ${{ matrix.check_name }} (push) Has been cancelled
CI / ${{ matrix.checkName }} (push) Has been cancelled
CI / checks-node-compat-node22 (push) Has been cancelled
CI / check-bundled-channel-config-metadata (push) Has been cancelled
CI / check-dependencies (push) Has been cancelled
CI / check-guards (push) Has been cancelled
CI / check-lint (push) Has been cancelled
CI / check-prod-types (push) Has been cancelled
CI / check-shrinkwrap (push) Has been cancelled
CI / check-test-types (push) Has been cancelled
CI / check-additional-boundaries-a (push) Has been cancelled
CI / check-additional-boundaries-bcd (push) Has been cancelled
CI / check-additional-extension-bundled (push) Has been cancelled
CI / check-additional-extension-channels (push) Has been cancelled
CI / check-additional-extension-package-boundary (push) Has been cancelled
CI / check-additional-runtime-topology-architecture (push) Has been cancelled
CI / check-session-accessor-boundary (push) Has been cancelled
CI / check-session-transcript-reader-boundary (push) Has been cancelled
CI / check-docs (push) Has been cancelled
CI / skills-python (push) Has been cancelled
CI / macos-swift (push) Has been cancelled
CI / ios-build (push) Has been cancelled
CI / ci-timings-summary (push) Has been cancelled
Native App Locale Refresh / Refresh native fa (push) Has been cancelled
Native App Locale Refresh / Refresh native fr (push) Has been cancelled
Native App Locale Refresh / Refresh native hi (push) Has been cancelled
Native App Locale Refresh / Refresh native id (push) Has been cancelled
Native App Locale Refresh / Refresh native it (push) Has been cancelled
Native App Locale Refresh / Refresh native ja-JP (push) Has been cancelled
Control UI Locale Refresh / plan (push) Has been cancelled
Control UI Locale Refresh / Refresh ${{ matrix.locale }} (push) Has been cancelled
Control UI Locale Refresh / Commit control UI locale refresh (push) Has been cancelled
Live Media Runner Image / Build live media runner image (push) Has been cancelled
Native App Locale Refresh / Refresh native ar (push) Has been cancelled
Native App Locale Refresh / Refresh native de (push) Has been cancelled
Native App Locale Refresh / Refresh native es (push) Has been cancelled
Native App Locale Refresh / Refresh native ko (push) Has been cancelled
Native App Locale Refresh / Refresh native nl (push) Has been cancelled
Native App Locale Refresh / Refresh native pl (push) Has been cancelled
Native App Locale Refresh / Refresh native pt-BR (push) Has been cancelled
Native App Locale Refresh / Refresh native ru (push) Has been cancelled
Native App Locale Refresh / Refresh native sv (push) Has been cancelled
Native App Locale Refresh / Refresh native th (push) Has been cancelled
Native App Locale Refresh / Refresh native tr (push) Has been cancelled
Native App Locale Refresh / Refresh native uk (push) Has been cancelled
Native App Locale Refresh / Refresh native vi (push) Has been cancelled
Native App Locale Refresh / Refresh native zh-CN (push) Has been cancelled
Native App Locale Refresh / Refresh native zh-TW (push) Has been cancelled
Native App Locale Refresh / Commit native locale refresh (push) Has been cancelled
Plugin Init Scaffold Validation / Validate provider scaffold (push) Has been cancelled
Plugin NPM Release / preview_plugins_npm (push) Has been cancelled
Plugin NPM Release / Validate release publish approval (push) Has been cancelled
Plugin NPM Release / preview_plugin_pack (push) Has been cancelled
Plugin NPM Release / publish_plugins_npm (push) Has been cancelled
Sandbox Common Smoke / sandbox-common-smoke (push) Has been cancelled
Website Installer Sync / static (push) Has been cancelled
Website Installer Sync / linux-docker (push) Has been cancelled
Website Installer Sync / macos-installer (push) Has been cancelled
Website Installer Sync / windows-installer (push) Has been cancelled
Website Installer Sync / sync-website (push) Has been cancelled
Some checks failed
ClawSweeper Dispatch / dispatch (push) Has been cancelled
CodeQL / Security High (actions) (push) Has been cancelled
CodeQL / Security High (channel-runtime-boundary) (push) Has been cancelled
CodeQL / Security High (core-auth-secrets) (push) Has been cancelled
CodeQL / Security High (mcp-process-tool-boundary) (push) Has been cancelled
CodeQL / Security High (network-ssrf-boundary) (push) Has been cancelled
CodeQL / Security High (plugin-trust-boundary) (push) Has been cancelled
CodeQL / Security High (process-exec-boundary) (push) Has been cancelled
Docs Sync Publish Repo / sync-publish-repo (push) Has been cancelled
Docs / docs (push) Has been cancelled
OpenClaw Stable Main Closeout / Resolve stable release closeout inputs (push) Has been cancelled
OpenClaw Stable Main Closeout / Verify stable main closeout (push) Has been cancelled
Workflow Sanity / no-tabs (push) Has been cancelled
Workflow Sanity / actionlint (push) Has been cancelled
Workflow Sanity / generated-doc-baselines (push) Has been cancelled
CI / runner-admission (push) Has been cancelled
CI / preflight (push) Has been cancelled
CI / security-fast (push) Has been cancelled
CI / pnpm-store-warmup (push) Has been cancelled
CI / build-artifacts (push) Has been cancelled
CI / native-i18n (push) Has been cancelled
CI / ${{ matrix.check_name }} (push) Has been cancelled
CI / ${{ matrix.checkName }} (push) Has been cancelled
CI / checks-node-compat-node22 (push) Has been cancelled
CI / check-bundled-channel-config-metadata (push) Has been cancelled
CI / check-dependencies (push) Has been cancelled
CI / check-guards (push) Has been cancelled
CI / check-lint (push) Has been cancelled
CI / check-prod-types (push) Has been cancelled
CI / check-shrinkwrap (push) Has been cancelled
CI / check-test-types (push) Has been cancelled
CI / check-additional-boundaries-a (push) Has been cancelled
CI / check-additional-boundaries-bcd (push) Has been cancelled
CI / check-additional-extension-bundled (push) Has been cancelled
CI / check-additional-extension-channels (push) Has been cancelled
CI / check-additional-extension-package-boundary (push) Has been cancelled
CI / check-additional-runtime-topology-architecture (push) Has been cancelled
CI / check-session-accessor-boundary (push) Has been cancelled
CI / check-session-transcript-reader-boundary (push) Has been cancelled
CI / check-docs (push) Has been cancelled
CI / skills-python (push) Has been cancelled
CI / macos-swift (push) Has been cancelled
CI / ios-build (push) Has been cancelled
CI / ci-timings-summary (push) Has been cancelled
Native App Locale Refresh / Refresh native fa (push) Has been cancelled
Native App Locale Refresh / Refresh native fr (push) Has been cancelled
Native App Locale Refresh / Refresh native hi (push) Has been cancelled
Native App Locale Refresh / Refresh native id (push) Has been cancelled
Native App Locale Refresh / Refresh native it (push) Has been cancelled
Native App Locale Refresh / Refresh native ja-JP (push) Has been cancelled
Control UI Locale Refresh / plan (push) Has been cancelled
Control UI Locale Refresh / Refresh ${{ matrix.locale }} (push) Has been cancelled
Control UI Locale Refresh / Commit control UI locale refresh (push) Has been cancelled
Live Media Runner Image / Build live media runner image (push) Has been cancelled
Native App Locale Refresh / Refresh native ar (push) Has been cancelled
Native App Locale Refresh / Refresh native de (push) Has been cancelled
Native App Locale Refresh / Refresh native es (push) Has been cancelled
Native App Locale Refresh / Refresh native ko (push) Has been cancelled
Native App Locale Refresh / Refresh native nl (push) Has been cancelled
Native App Locale Refresh / Refresh native pl (push) Has been cancelled
Native App Locale Refresh / Refresh native pt-BR (push) Has been cancelled
Native App Locale Refresh / Refresh native ru (push) Has been cancelled
Native App Locale Refresh / Refresh native sv (push) Has been cancelled
Native App Locale Refresh / Refresh native th (push) Has been cancelled
Native App Locale Refresh / Refresh native tr (push) Has been cancelled
Native App Locale Refresh / Refresh native uk (push) Has been cancelled
Native App Locale Refresh / Refresh native vi (push) Has been cancelled
Native App Locale Refresh / Refresh native zh-CN (push) Has been cancelled
Native App Locale Refresh / Refresh native zh-TW (push) Has been cancelled
Native App Locale Refresh / Commit native locale refresh (push) Has been cancelled
Plugin Init Scaffold Validation / Validate provider scaffold (push) Has been cancelled
Plugin NPM Release / preview_plugins_npm (push) Has been cancelled
Plugin NPM Release / Validate release publish approval (push) Has been cancelled
Plugin NPM Release / preview_plugin_pack (push) Has been cancelled
Plugin NPM Release / publish_plugins_npm (push) Has been cancelled
Sandbox Common Smoke / sandbox-common-smoke (push) Has been cancelled
Website Installer Sync / static (push) Has been cancelled
Website Installer Sync / linux-docker (push) Has been cancelled
Website Installer Sync / macos-installer (push) Has been cancelled
Website Installer Sync / windows-installer (push) Has been cancelled
Website Installer Sync / sync-website (push) Has been cancelled
Adolf is a fork/vendored clone of github.com/openclaw/openclaw (v2026.6.11), free to diverge. Tree copied sans upstream .git; upstream remote added for future syncs. Node pinned to 24 (.nvmrc); engines already require >=22.19. Preserves docs/ARCHITECTURE.md. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LeqyaxJF2nbRXJtae2kNB2
This commit is contained in:
302
scripts/e2e/parallels/package-artifact.ts
Normal file
302
scripts/e2e/parallels/package-artifact.ts
Normal file
@@ -0,0 +1,302 @@
|
||||
// Package Artifact script supports OpenClaw repository automation.
|
||||
import { randomUUID } from "node:crypto";
|
||||
import { copyFile, mkdir, readFile, rm, stat, writeFile } from "node:fs/promises";
|
||||
import { tmpdir } from "node:os";
|
||||
import path from "node:path";
|
||||
import { sleep as delay } from "../../lib/sleep.mjs";
|
||||
import { readPositiveIntEnv } from "./env-limits.ts";
|
||||
import { exists, readJson } from "./filesystem.ts";
|
||||
import { die, repoRoot, run, say, sh } from "./host-command.ts";
|
||||
import type { PackageArtifact } from "./types.ts";
|
||||
|
||||
export async function extractPackageJsonFromTgz<T>(tgzPath: string, entry: string): Promise<T> {
|
||||
const output = run("tar", ["-xOf", tgzPath, entry], { quiet: true }).stdout;
|
||||
return JSON.parse(output) as T;
|
||||
}
|
||||
|
||||
export async function packageVersionFromTgz(tgzPath: string): Promise<string> {
|
||||
const pkg = await extractPackageJsonFromTgz<{ version: string }>(tgzPath, "package/package.json");
|
||||
return pkg.version;
|
||||
}
|
||||
|
||||
export async function packageBuildCommitFromTgz(tgzPath: string): Promise<string> {
|
||||
const info = await extractPackageJsonFromTgz<{ commit?: string }>(
|
||||
tgzPath,
|
||||
"package/dist/build-info.json",
|
||||
);
|
||||
return info.commit ?? "";
|
||||
}
|
||||
|
||||
function resolveNpmPackTarballFilename(value: unknown): string {
|
||||
const filename = typeof value === "string" ? value.trim() : "";
|
||||
if (
|
||||
!filename.endsWith(".tgz") ||
|
||||
filename.includes("\0") ||
|
||||
filename !== path.basename(filename) ||
|
||||
filename !== path.win32.basename(filename)
|
||||
) {
|
||||
die("npm pack did not report a safe tarball filename");
|
||||
}
|
||||
return filename;
|
||||
}
|
||||
|
||||
export function resolveOpenClawRegistryVersion(specOrAlias: string): string {
|
||||
const rawValue = specOrAlias.trim();
|
||||
const value = rawValue.startsWith("openclaw@") ? rawValue.slice("openclaw@".length) : rawValue;
|
||||
if (!value) {
|
||||
return "";
|
||||
}
|
||||
if (value === "latest" || value === "beta" || /^\d/.test(value)) {
|
||||
return npmViewVersion(`openclaw@${value}`);
|
||||
}
|
||||
const betaMatch = /^beta(\d+)$/u.exec(value);
|
||||
if (betaMatch) {
|
||||
const betaSuffix = `-beta.${betaMatch[1]}`;
|
||||
const versions = JSON.parse(
|
||||
run("npm", ["view", "openclaw", "versions", "--json"], { quiet: true }).stdout,
|
||||
) as string[];
|
||||
const match = versions
|
||||
.filter((version) => version.endsWith(betaSuffix))
|
||||
.toSorted((a, b) => a.localeCompare(b, undefined, { numeric: true }))
|
||||
.at(-1);
|
||||
if (!match) {
|
||||
die(`no openclaw registry version found for alias ${value}`);
|
||||
}
|
||||
return match;
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
function npmViewVersion(spec: string): string {
|
||||
return run("npm", ["view", spec, "version"], { quiet: true }).stdout.trim();
|
||||
}
|
||||
|
||||
async function ensureCurrentBuildUnlocked(input: {
|
||||
requireControlUi?: boolean;
|
||||
checkDirty?: boolean;
|
||||
}): Promise<void> {
|
||||
const head = run("git", ["rev-parse", "HEAD"], { quiet: true }).stdout.trim();
|
||||
const buildInfoPath = path.join(repoRoot, "dist/build-info.json");
|
||||
let buildCommit = "";
|
||||
if (await exists(buildInfoPath)) {
|
||||
buildCommit = (await readJson<{ commit?: string }>(buildInfoPath)).commit ?? "";
|
||||
}
|
||||
const dirty =
|
||||
input.checkDirty !== false &&
|
||||
run(
|
||||
"git",
|
||||
[
|
||||
"status",
|
||||
"--porcelain",
|
||||
"--",
|
||||
"src",
|
||||
"ui",
|
||||
"packages",
|
||||
"extensions",
|
||||
"package.json",
|
||||
"pnpm-lock.yaml",
|
||||
"tsconfig*.json",
|
||||
],
|
||||
{ quiet: true },
|
||||
).stdout.trim() !== "";
|
||||
const controlReady =
|
||||
!input.requireControlUi ||
|
||||
((await exists(path.join(repoRoot, "dist/control-ui/index.html"))) &&
|
||||
sh("compgen -G 'dist/control-ui/assets/*' >/dev/null", { check: false, quiet: true })
|
||||
.status === 0);
|
||||
if (buildCommit === head && !dirty && controlReady) {
|
||||
return;
|
||||
}
|
||||
say("Build dist for current head");
|
||||
run("pnpm", ["build"]);
|
||||
if (input.requireControlUi) {
|
||||
say("Build Control UI for current head");
|
||||
run("pnpm", ["ui:build"]);
|
||||
}
|
||||
const drift = run(
|
||||
"git",
|
||||
["status", "--porcelain", "--", ":(glob)extensions/*/src/host/**/.bundle.hash"],
|
||||
{
|
||||
quiet: true,
|
||||
},
|
||||
).stdout.trim();
|
||||
if (drift) {
|
||||
die(`generated file drift after build; commit or revert before Parallels packaging:\n${drift}`);
|
||||
}
|
||||
}
|
||||
|
||||
export async function packOpenClaw(input: {
|
||||
destination: string;
|
||||
packageSpec?: string;
|
||||
requireControlUi?: boolean;
|
||||
}): Promise<PackageArtifact> {
|
||||
await mkdir(input.destination, { recursive: true });
|
||||
if (input.packageSpec) {
|
||||
say(`Pack target package tgz: ${input.packageSpec}`);
|
||||
const output = run(
|
||||
"npm",
|
||||
[
|
||||
"pack",
|
||||
input.packageSpec,
|
||||
"--ignore-scripts",
|
||||
"--json",
|
||||
"--pack-destination",
|
||||
input.destination,
|
||||
],
|
||||
{ quiet: true },
|
||||
).stdout;
|
||||
const packed = resolveNpmPackTarballFilename(JSON.parse(output).at(-1)?.filename);
|
||||
const tgzPath = path.join(input.destination, packed);
|
||||
const version = await packageVersionFromTgz(tgzPath);
|
||||
say(`Packed ${tgzPath}`);
|
||||
say(`Target package version: ${version}`);
|
||||
return { path: tgzPath, version };
|
||||
}
|
||||
|
||||
return await withPackageLock(path.join(tmpdir(), "openclaw-parallels-build.lock"), async () => {
|
||||
await ensureCurrentBuildUnlocked({
|
||||
checkDirty: true,
|
||||
requireControlUi: input.requireControlUi,
|
||||
});
|
||||
run("node", [
|
||||
"--import",
|
||||
"tsx",
|
||||
"--input-type=module",
|
||||
"--eval",
|
||||
"import { writePackageDistInventory } from './src/infra/package-dist-inventory.ts'; await writePackageDistInventory(process.cwd());",
|
||||
]);
|
||||
const shortHead = run("git", ["rev-parse", "--short", "HEAD"], { quiet: true }).stdout.trim();
|
||||
const output = run(
|
||||
"npm",
|
||||
["pack", "--ignore-scripts", "--json", "--pack-destination", input.destination],
|
||||
{
|
||||
quiet: true,
|
||||
},
|
||||
).stdout;
|
||||
const packed = resolveNpmPackTarballFilename(JSON.parse(output).at(-1)?.filename);
|
||||
const tgzPath = path.join(input.destination, `openclaw-main-${shortHead}.tgz`);
|
||||
await copyFile(path.join(input.destination, packed), tgzPath);
|
||||
const buildCommit = await packageBuildCommitFromTgz(tgzPath);
|
||||
if (!buildCommit) {
|
||||
die(`failed to read packed build commit from ${tgzPath}`);
|
||||
}
|
||||
say(`Packed ${tgzPath}`);
|
||||
return { buildCommit, buildCommitShort: buildCommit.slice(0, 7), path: tgzPath };
|
||||
});
|
||||
}
|
||||
|
||||
async function withPackageLock<T>(lockDir: string, fn: () => Promise<T>): Promise<T> {
|
||||
const ownerToken = randomUUID();
|
||||
await acquirePackageLock(lockDir, ownerToken);
|
||||
try {
|
||||
return await fn();
|
||||
} finally {
|
||||
await releasePackageLock(lockDir, ownerToken);
|
||||
}
|
||||
}
|
||||
|
||||
async function acquirePackageLock(
|
||||
lockDir: string,
|
||||
ownerToken: string,
|
||||
params: { writeOwner?: (lockDir: string, ownerToken: string) => Promise<void> } = {},
|
||||
): Promise<void> {
|
||||
const timeoutMs = readPositiveIntEnv("OPENCLAW_PARALLELS_PACKAGE_LOCK_TIMEOUT_MS", 30 * 60_000);
|
||||
const staleMs = readPositiveIntEnv("OPENCLAW_PARALLELS_PACKAGE_LOCK_STALE_MS", 2 * 60 * 60_000);
|
||||
const startedAt = Date.now();
|
||||
let waitAnnouncementBudget = 1;
|
||||
const consumeWaitAnnouncement = () => waitAnnouncementBudget-- > 0;
|
||||
while (Date.now() - startedAt < timeoutMs) {
|
||||
let createdLockDir = false;
|
||||
try {
|
||||
await mkdir(lockDir);
|
||||
createdLockDir = true;
|
||||
await (params.writeOwner ?? writeLockOwner)(lockDir, ownerToken);
|
||||
return;
|
||||
} catch (error) {
|
||||
if (createdLockDir) {
|
||||
await rm(lockDir, { force: true, recursive: true }).catch(() => undefined);
|
||||
}
|
||||
if (!isErrorCode(error, "EEXIST")) {
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
await removeStalePackageLock(lockDir, staleMs);
|
||||
if (consumeWaitAnnouncement()) {
|
||||
say(`Wait for Parallels package lock: ${lockDir}`);
|
||||
}
|
||||
await delay(1_000);
|
||||
}
|
||||
throw new Error(`timed out waiting for Parallels package lock: ${lockDir}`);
|
||||
}
|
||||
|
||||
async function writeLockOwner(lockDir: string, ownerToken: string): Promise<void> {
|
||||
await writeFile(
|
||||
path.join(lockDir, "owner.json"),
|
||||
`${JSON.stringify(
|
||||
{
|
||||
pid: process.pid,
|
||||
startedAt: new Date().toISOString(),
|
||||
token: ownerToken,
|
||||
},
|
||||
null,
|
||||
2,
|
||||
)}\n`,
|
||||
"utf8",
|
||||
);
|
||||
}
|
||||
|
||||
async function releasePackageLock(lockDir: string, ownerToken: string): Promise<void> {
|
||||
const owner = await readLockOwner(lockDir);
|
||||
if (owner?.token === ownerToken) {
|
||||
await rm(lockDir, { force: true, recursive: true });
|
||||
}
|
||||
}
|
||||
|
||||
async function removeStalePackageLock(lockDir: string, staleMs: number): Promise<void> {
|
||||
const owner = await readLockOwner(lockDir);
|
||||
if (owner?.pid && isProcessAlive(owner.pid)) {
|
||||
return;
|
||||
}
|
||||
const ageMs = Date.now() - ((await stat(lockDir).catch(() => undefined))?.mtimeMs ?? Date.now());
|
||||
if (owner?.pid !== undefined || staleMs <= 0 || ageMs >= staleMs) {
|
||||
await rm(lockDir, { force: true, recursive: true }).catch(() => undefined);
|
||||
}
|
||||
}
|
||||
|
||||
async function readLockOwner(lockDir: string): Promise<{ pid?: number; token?: string } | null> {
|
||||
const text = await readFile(path.join(lockDir, "owner.json"), "utf8").catch(() => "");
|
||||
if (!text) {
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
const parsed = JSON.parse(text) as { pid?: unknown; token?: unknown };
|
||||
return {
|
||||
pid:
|
||||
typeof parsed.pid === "number" && Number.isSafeInteger(parsed.pid) && parsed.pid > 0
|
||||
? parsed.pid
|
||||
: undefined,
|
||||
token: typeof parsed.token === "string" ? parsed.token : undefined,
|
||||
};
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
function isProcessAlive(pid: number): boolean {
|
||||
try {
|
||||
process.kill(pid, 0);
|
||||
return true;
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
function isErrorCode(error: unknown, code: string): boolean {
|
||||
return Boolean(error && typeof error === "object" && "code" in error && error.code === code);
|
||||
}
|
||||
|
||||
export const testing = {
|
||||
acquirePackageLock,
|
||||
removeStalePackageLock,
|
||||
readLockOwner,
|
||||
};
|
||||
Reference in New Issue
Block a user