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
188 lines
5.2 KiB
JavaScript
188 lines
5.2 KiB
JavaScript
#!/usr/bin/env node
|
|
|
|
// Discovers and runs bundled plugin package asset hooks.
|
|
import { spawnSync } from "node:child_process";
|
|
import fs from "node:fs/promises";
|
|
import path from "node:path";
|
|
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
|
|
const rootDir = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
|
|
const VALID_PHASES = new Set(["build", "copy"]);
|
|
|
|
async function readJsonFile(filePath) {
|
|
return JSON.parse(await fs.readFile(filePath, "utf8"));
|
|
}
|
|
|
|
async function pathExists(filePath) {
|
|
try {
|
|
await fs.stat(filePath);
|
|
return true;
|
|
} catch {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
function packagePluginAliases(packageName) {
|
|
if (typeof packageName !== "string") {
|
|
return [];
|
|
}
|
|
const aliases = [packageName];
|
|
const unscopedName = packageName.split("/").at(-1);
|
|
if (unscopedName) {
|
|
aliases.push(unscopedName);
|
|
if (unscopedName.endsWith("-plugin")) {
|
|
aliases.push(unscopedName.slice(0, -"-plugin".length));
|
|
}
|
|
}
|
|
return aliases;
|
|
}
|
|
|
|
async function resolvePluginAliases(pluginDir, packageJson) {
|
|
const aliases = new Set([path.basename(pluginDir), ...packagePluginAliases(packageJson.name)]);
|
|
const manifestPath = path.join(pluginDir, "openclaw.plugin.json");
|
|
if (await pathExists(manifestPath)) {
|
|
const manifest = await readJsonFile(manifestPath);
|
|
if (typeof manifest.id === "string" && manifest.id) {
|
|
aliases.add(manifest.id);
|
|
}
|
|
}
|
|
return aliases;
|
|
}
|
|
|
|
function resolveAssetCommand(packageJson, phase) {
|
|
const assetScripts = packageJson.openclaw?.assetScripts;
|
|
if (!assetScripts || typeof assetScripts !== "object") {
|
|
return null;
|
|
}
|
|
const command = assetScripts[phase];
|
|
return typeof command === "string" && command.trim() ? command.trim() : null;
|
|
}
|
|
|
|
/**
|
|
* Reads bundled plugin asset hook commands for a build or copy phase.
|
|
*/
|
|
export async function readBundledPluginAssetHooks(options = {}) {
|
|
const repoRoot = options.rootDir ?? rootDir;
|
|
const phase = options.phase;
|
|
if (!VALID_PHASES.has(phase)) {
|
|
throw new Error(`Unsupported bundled plugin asset phase: ${String(phase)}`);
|
|
}
|
|
|
|
const pluginFilters = new Set((options.plugins ?? []).filter(Boolean));
|
|
const extensionsDir = path.join(repoRoot, "extensions");
|
|
let entries;
|
|
try {
|
|
entries = await fs.readdir(extensionsDir, { withFileTypes: true });
|
|
} catch {
|
|
return [];
|
|
}
|
|
|
|
const hooks = [];
|
|
for (const entry of entries) {
|
|
if (!entry.isDirectory()) {
|
|
continue;
|
|
}
|
|
const pluginDir = path.join(extensionsDir, entry.name);
|
|
const packagePath = path.join(pluginDir, "package.json");
|
|
if (!(await pathExists(packagePath))) {
|
|
continue;
|
|
}
|
|
|
|
const packageJson = await readJsonFile(packagePath);
|
|
const aliases = await resolvePluginAliases(pluginDir, packageJson);
|
|
if (pluginFilters.size > 0 && ![...pluginFilters].some((plugin) => aliases.has(plugin))) {
|
|
continue;
|
|
}
|
|
|
|
const command = resolveAssetCommand(packageJson, phase);
|
|
if (!command) {
|
|
continue;
|
|
}
|
|
|
|
hooks.push({
|
|
aliases: [...aliases].toSorted(),
|
|
command,
|
|
packageName: packageJson.name,
|
|
phase,
|
|
pluginDir,
|
|
pluginId: aliases.has(entry.name) ? entry.name : [...aliases][0],
|
|
});
|
|
}
|
|
|
|
return hooks.toSorted((left, right) => left.pluginDir.localeCompare(right.pluginDir));
|
|
}
|
|
|
|
/**
|
|
* Runs bundled plugin asset hook commands for the selected phase/plugins.
|
|
*/
|
|
export async function runBundledPluginAssetHooks(options = {}) {
|
|
const phase = options.phase;
|
|
const hooks = await readBundledPluginAssetHooks(options);
|
|
if (hooks.length === 0) {
|
|
const scope = options.plugins?.length ? ` for ${options.plugins.join(", ")}` : "";
|
|
console.log(`No bundled plugin asset ${phase} hooks${scope}; skipping.`);
|
|
return;
|
|
}
|
|
|
|
for (const hook of hooks) {
|
|
console.log(`[${hook.pluginId}] ${phase}: ${hook.command}`);
|
|
const result = spawnSync(hook.command, {
|
|
cwd: hook.pluginDir,
|
|
env: process.env,
|
|
shell: true,
|
|
stdio: "inherit",
|
|
});
|
|
if (result.status !== 0) {
|
|
process.exit(result.status ?? 1);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Parses `--phase` and repeated `--plugin` flags for asset hook scripts.
|
|
*/
|
|
export function parseBundledPluginAssetArgs(argv) {
|
|
const args = [...argv];
|
|
const plugins = [];
|
|
let phase = null;
|
|
|
|
while (args.length > 0) {
|
|
const arg = args.shift();
|
|
if (arg === "--phase") {
|
|
phase = args.shift() ?? null;
|
|
continue;
|
|
}
|
|
if (arg?.startsWith("--phase=")) {
|
|
phase = arg.slice("--phase=".length);
|
|
continue;
|
|
}
|
|
if (arg === "--plugin") {
|
|
const plugin = args.shift();
|
|
if (plugin) {
|
|
plugins.push(plugin);
|
|
}
|
|
continue;
|
|
}
|
|
if (arg?.startsWith("--plugin=")) {
|
|
plugins.push(arg.slice("--plugin=".length));
|
|
continue;
|
|
}
|
|
throw new Error(`Unknown bundled plugin asset argument: ${String(arg)}`);
|
|
}
|
|
|
|
if (!VALID_PHASES.has(phase)) {
|
|
throw new Error(`Expected --phase ${[...VALID_PHASES].join("|")}`);
|
|
}
|
|
|
|
return { phase, plugins };
|
|
}
|
|
|
|
if (import.meta.url === pathToFileURL(process.argv[1] ?? "").href) {
|
|
try {
|
|
await runBundledPluginAssetHooks(parseBundledPluginAssetArgs(process.argv.slice(2)));
|
|
} catch (error) {
|
|
console.error(error instanceof Error ? error.message : String(error));
|
|
process.exit(1);
|
|
}
|
|
}
|