mirror of
https://github.com/openclaw/openclaw.git
synced 2026-08-27 12:56:01 -06:00
ci: split plugin ClawHub publishing paths
* feat: partition clawhub plugin release candidates * fix: read clawhub trusted publisher config endpoint * feat: split clawhub plugin bootstrap workflow * ci: split plugin clawhub publish paths * ci: pin clawhub package publish workflow * ci: keep clawhub bootstrap token out of builds * ci: fix clawhub release dry-run gating * ci: align clawhub oidc publish refs * ci: make clawhub bootstrap recovery idempotent * ci: route clawhub repair candidates through bootstrap * ci: preserve tideclaw alpha clawhub guards * ci: simplify clawhub release ref handling * ci: extract clawhub release routing plan * ci: extract clawhub release runtime state * test: guard clawhub release helper executability * ci: pin ClawHub CLI for plugin publishing * ci: allow historical ClawHub dry-run validation * ci: fix ClawHub bootstrap token handoff
This commit is contained in:
@@ -0,0 +1,314 @@
|
||||
// OpenClaw release ClawHub plan script supports release workflow routing.
|
||||
import { resolve } from "node:path";
|
||||
import {
|
||||
collectPluginClawHubReleasePlan,
|
||||
type PublishablePluginPackage,
|
||||
} from "./plugin-clawhub-release.ts";
|
||||
import {
|
||||
parsePluginReleaseSelection,
|
||||
parsePluginReleaseSelectionMode,
|
||||
type PluginReleaseSelectionMode,
|
||||
} from "./plugin-npm-release.ts";
|
||||
|
||||
type ClawHubPlanPackage = Pick<PublishablePluginPackage, "packageName">;
|
||||
|
||||
type ClawHubDispatchInputs = Record<string, string>;
|
||||
|
||||
type ClawHubDispatchTarget = {
|
||||
workflow: "plugin-clawhub-release.yml" | "plugin-clawhub-new.yml";
|
||||
ref: string;
|
||||
shouldDispatch: boolean;
|
||||
packages: string[];
|
||||
inputs: ClawHubDispatchInputs;
|
||||
};
|
||||
|
||||
export type OpenClawReleaseClawHubPlanArgs = {
|
||||
releaseTag: string;
|
||||
releasePublishBranch: string;
|
||||
releasePublishRunId: string;
|
||||
pluginPublishScope: PluginReleaseSelectionMode;
|
||||
plugins: string[];
|
||||
};
|
||||
|
||||
export type OpenClawReleaseClawHubPlan = {
|
||||
clawHubWorkflowRef: string;
|
||||
releasePublishBranch: string;
|
||||
normal: ClawHubDispatchTarget;
|
||||
bootstrap: ClawHubDispatchTarget;
|
||||
summary: {
|
||||
normalCount: number;
|
||||
bootstrapCount: number;
|
||||
missingTrustedPublisherCount: number;
|
||||
normalPlugins: string;
|
||||
bootstrapPlugins: string;
|
||||
missingTrustedPlugins: string;
|
||||
};
|
||||
verifier: {
|
||||
clawHubWorkflowRef: string;
|
||||
};
|
||||
};
|
||||
|
||||
export type OpenClawReleaseClawHubRuntimeStateArgs = {
|
||||
repository: string;
|
||||
waitForClawHub: boolean;
|
||||
forceSkipClawHub: boolean;
|
||||
normalRunId?: string;
|
||||
bootstrapRunId?: string;
|
||||
bootstrapCompleted: boolean;
|
||||
};
|
||||
|
||||
export type OpenClawReleaseClawHubRuntimeState = {
|
||||
verifierArgs: string[];
|
||||
proofLines: {
|
||||
normal: string;
|
||||
bootstrap: string;
|
||||
};
|
||||
};
|
||||
|
||||
function requireArg(value: string | undefined, label: string): string {
|
||||
const trimmed = value?.trim();
|
||||
if (!trimmed) {
|
||||
throw new Error(`${label} is required.`);
|
||||
}
|
||||
return trimmed;
|
||||
}
|
||||
|
||||
function packageNames(packages: readonly ClawHubPlanPackage[]): string[] {
|
||||
return packages.map((plugin) => plugin.packageName);
|
||||
}
|
||||
|
||||
function joinPackageNames(packages: readonly string[]): string {
|
||||
return packages.join(",");
|
||||
}
|
||||
|
||||
function optionalArg(value: string | undefined): string | undefined {
|
||||
const trimmed = value?.trim();
|
||||
return trimmed ? trimmed : undefined;
|
||||
}
|
||||
|
||||
function runUrl(repository: string, runId: string): string {
|
||||
return `https://github.com/${repository}/actions/runs/${runId}`;
|
||||
}
|
||||
|
||||
function assertNoPackageOverlap(
|
||||
normalPackages: readonly string[],
|
||||
bootstrapPackages: readonly string[],
|
||||
) {
|
||||
const normalPackageSet = new Set(normalPackages);
|
||||
const overlap = bootstrapPackages.filter((packageName) => normalPackageSet.has(packageName));
|
||||
if (overlap.length > 0) {
|
||||
throw new Error(
|
||||
`ClawHub release plan routed package(s) to both normal and bootstrap workflows: ${overlap.join(", ")}.`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
function createDispatchTarget(params: {
|
||||
workflow: ClawHubDispatchTarget["workflow"];
|
||||
ref: string;
|
||||
packages: readonly string[];
|
||||
releasePublishRunId: string;
|
||||
releasePublishBranch: string;
|
||||
includePublishScope: boolean;
|
||||
}): ClawHubDispatchTarget {
|
||||
if (params.packages.length === 0) {
|
||||
return {
|
||||
workflow: params.workflow,
|
||||
ref: params.ref,
|
||||
shouldDispatch: false,
|
||||
packages: [],
|
||||
inputs: {},
|
||||
};
|
||||
}
|
||||
|
||||
const plugins = joinPackageNames(params.packages);
|
||||
return {
|
||||
workflow: params.workflow,
|
||||
ref: params.ref,
|
||||
shouldDispatch: true,
|
||||
packages: [...params.packages],
|
||||
inputs: {
|
||||
...(params.includePublishScope ? { publish_scope: "selected" } : {}),
|
||||
plugins,
|
||||
release_publish_run_id: params.releasePublishRunId,
|
||||
release_publish_branch: params.releasePublishBranch,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
export function buildOpenClawReleaseClawHubRuntimeState(
|
||||
args: OpenClawReleaseClawHubRuntimeStateArgs,
|
||||
): OpenClawReleaseClawHubRuntimeState {
|
||||
const repository = requireArg(args.repository, "repository");
|
||||
const normalRunId = optionalArg(args.normalRunId);
|
||||
const bootstrapRunId = optionalArg(args.bootstrapRunId);
|
||||
|
||||
const shouldIncludeNormalRun =
|
||||
!args.forceSkipClawHub && normalRunId !== undefined && args.waitForClawHub;
|
||||
const shouldIncludeBootstrapRun =
|
||||
!args.forceSkipClawHub && bootstrapRunId !== undefined && args.bootstrapCompleted;
|
||||
const shouldVerifyClawHubPackages =
|
||||
bootstrapRunId !== undefined &&
|
||||
args.bootstrapCompleted &&
|
||||
(normalRunId === undefined || args.waitForClawHub);
|
||||
const shouldSkipClawHubPackages =
|
||||
args.forceSkipClawHub || !(shouldIncludeNormalRun || shouldVerifyClawHubPackages);
|
||||
|
||||
const verifierArgs = shouldSkipClawHubPackages ? ["--skip-clawhub"] : [];
|
||||
if (shouldIncludeNormalRun) {
|
||||
verifierArgs.push("--plugin-clawhub-run", normalRunId);
|
||||
}
|
||||
if (shouldIncludeBootstrapRun) {
|
||||
verifierArgs.push("--plugin-clawhub-bootstrap-run", bootstrapRunId);
|
||||
}
|
||||
|
||||
let normalProofLine = "- plugin ClawHub publish: no normal OIDC candidates";
|
||||
if (normalRunId !== undefined && args.waitForClawHub) {
|
||||
normalProofLine = `- plugin ClawHub publish: ${runUrl(repository, normalRunId)}`;
|
||||
} else if (normalRunId !== undefined) {
|
||||
normalProofLine = `- plugin ClawHub publish: dispatched separately, not awaited by this proof: ${runUrl(repository, normalRunId)}`;
|
||||
}
|
||||
|
||||
let bootstrapProofLine = "- plugin ClawHub bootstrap: not needed";
|
||||
if (bootstrapRunId !== undefined && (args.bootstrapCompleted || args.waitForClawHub)) {
|
||||
bootstrapProofLine = `- plugin ClawHub bootstrap: ${runUrl(repository, bootstrapRunId)}`;
|
||||
} else if (bootstrapRunId !== undefined) {
|
||||
bootstrapProofLine = `- plugin ClawHub bootstrap: dispatched separately, not awaited by this proof: ${runUrl(repository, bootstrapRunId)}`;
|
||||
}
|
||||
|
||||
return {
|
||||
verifierArgs,
|
||||
proofLines: {
|
||||
normal: normalProofLine,
|
||||
bootstrap: bootstrapProofLine,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
export function parseOpenClawReleaseClawHubPlanArgs(
|
||||
argv: string[],
|
||||
): OpenClawReleaseClawHubPlanArgs {
|
||||
const values = [...argv];
|
||||
if (values[0] === "--") {
|
||||
values.shift();
|
||||
}
|
||||
|
||||
let releaseTag: string | undefined;
|
||||
let releasePublishBranch: string | undefined;
|
||||
let releasePublishRunId: string | undefined;
|
||||
let pluginPublishScope: PluginReleaseSelectionMode | undefined;
|
||||
let plugins: string[] = [];
|
||||
let pluginsFlagProvided = false;
|
||||
|
||||
for (let index = 0; index < values.length; index += 1) {
|
||||
const arg = values[index];
|
||||
const next = () => {
|
||||
const value = values[index + 1];
|
||||
if (value === undefined || value.startsWith("-")) {
|
||||
throw new Error(`${arg} requires a value.`);
|
||||
}
|
||||
index += 1;
|
||||
return value;
|
||||
};
|
||||
|
||||
switch (arg) {
|
||||
case "--release-tag":
|
||||
releaseTag = next();
|
||||
break;
|
||||
case "--release-publish-branch":
|
||||
releasePublishBranch = next();
|
||||
break;
|
||||
case "--release-publish-run-id":
|
||||
releasePublishRunId = next();
|
||||
break;
|
||||
case "--plugin-publish-scope":
|
||||
pluginPublishScope = parsePluginReleaseSelectionMode(next());
|
||||
break;
|
||||
case "--plugins":
|
||||
plugins = parsePluginReleaseSelection(next());
|
||||
pluginsFlagProvided = true;
|
||||
break;
|
||||
default:
|
||||
throw new Error(`Unknown argument: ${arg}`);
|
||||
}
|
||||
}
|
||||
|
||||
const resolvedPluginPublishScope = pluginPublishScope ?? "all-publishable";
|
||||
if (pluginsFlagProvided && plugins.length === 0) {
|
||||
throw new Error("--plugins must include at least one package name.");
|
||||
}
|
||||
if (resolvedPluginPublishScope === "selected" && !pluginsFlagProvided) {
|
||||
throw new Error("plugin-publish-scope=selected requires --plugins.");
|
||||
}
|
||||
if (resolvedPluginPublishScope === "all-publishable" && pluginsFlagProvided) {
|
||||
throw new Error("plugin-publish-scope=all-publishable must not be combined with --plugins.");
|
||||
}
|
||||
|
||||
return {
|
||||
releaseTag: requireArg(releaseTag, "--release-tag"),
|
||||
releasePublishBranch: requireArg(releasePublishBranch, "--release-publish-branch"),
|
||||
releasePublishRunId: requireArg(releasePublishRunId, "--release-publish-run-id"),
|
||||
pluginPublishScope: resolvedPluginPublishScope,
|
||||
plugins,
|
||||
};
|
||||
}
|
||||
|
||||
export async function buildOpenClawReleaseClawHubPlan(
|
||||
args: OpenClawReleaseClawHubPlanArgs,
|
||||
options: {
|
||||
rootDir?: string;
|
||||
fetchImpl?: typeof fetch;
|
||||
registryBaseUrl?: string;
|
||||
} = {},
|
||||
): Promise<OpenClawReleaseClawHubPlan> {
|
||||
const releaseTag = requireArg(args.releaseTag, "releaseTag");
|
||||
const releasePublishBranch = requireArg(args.releasePublishBranch, "releasePublishBranch");
|
||||
const releasePublishRunId = requireArg(args.releasePublishRunId, "releasePublishRunId");
|
||||
const plan = await collectPluginClawHubReleasePlan({
|
||||
rootDir: options.rootDir ?? resolve("."),
|
||||
selection: args.plugins,
|
||||
selectionMode: args.pluginPublishScope,
|
||||
fetchImpl: options.fetchImpl,
|
||||
registryBaseUrl: options.registryBaseUrl,
|
||||
});
|
||||
|
||||
const normalPackages = packageNames(plan.candidates);
|
||||
const bootstrapPackages = [
|
||||
...packageNames(plan.bootstrapCandidates),
|
||||
...packageNames(plan.missingTrustedPublisher),
|
||||
];
|
||||
const missingTrustedPlugins = packageNames(plan.missingTrustedPublisher);
|
||||
assertNoPackageOverlap(normalPackages, bootstrapPackages);
|
||||
|
||||
return {
|
||||
clawHubWorkflowRef: releaseTag,
|
||||
releasePublishBranch,
|
||||
normal: createDispatchTarget({
|
||||
workflow: "plugin-clawhub-release.yml",
|
||||
ref: releaseTag,
|
||||
packages: normalPackages,
|
||||
releasePublishRunId,
|
||||
releasePublishBranch,
|
||||
includePublishScope: true,
|
||||
}),
|
||||
bootstrap: createDispatchTarget({
|
||||
workflow: "plugin-clawhub-new.yml",
|
||||
ref: releaseTag,
|
||||
packages: bootstrapPackages,
|
||||
releasePublishRunId,
|
||||
releasePublishBranch,
|
||||
includePublishScope: false,
|
||||
}),
|
||||
summary: {
|
||||
normalCount: normalPackages.length,
|
||||
bootstrapCount: bootstrapPackages.length,
|
||||
missingTrustedPublisherCount: missingTrustedPlugins.length,
|
||||
normalPlugins: joinPackageNames(normalPackages),
|
||||
bootstrapPlugins: joinPackageNames(bootstrapPackages),
|
||||
missingTrustedPlugins: joinPackageNames(missingTrustedPlugins),
|
||||
},
|
||||
verifier: {
|
||||
clawHubWorkflowRef: releaseTag,
|
||||
},
|
||||
};
|
||||
}
|
||||
@@ -60,15 +60,34 @@ type PluginReleasePlanItem = PublishablePluginPackage & {
|
||||
type PluginReleasePlan = {
|
||||
all: PluginReleasePlanItem[];
|
||||
candidates: PluginReleasePlanItem[];
|
||||
bootstrapCandidates: PluginReleasePlanItem[];
|
||||
missingTrustedPublisher: PluginReleasePlanItem[];
|
||||
skippedPublished: PluginReleasePlanItem[];
|
||||
};
|
||||
|
||||
type ClawHubTrustedPublisherDetail = {
|
||||
trustedPublisher?: unknown;
|
||||
};
|
||||
|
||||
type ClawHubTrustedPublisherConfig = {
|
||||
repository?: unknown;
|
||||
workflowFilename?: unknown;
|
||||
environment?: unknown;
|
||||
};
|
||||
|
||||
type PluginReleasePlanItemWithPackageState = PluginReleasePlanItem & {
|
||||
packageExists: boolean;
|
||||
hasTrustedPublisher: boolean;
|
||||
};
|
||||
|
||||
type ClawHubPublishablePluginPackageFilters = {
|
||||
extensionIds?: readonly string[];
|
||||
packageNames?: readonly string[];
|
||||
};
|
||||
|
||||
const CLAWHUB_DEFAULT_REGISTRY = "https://clawhub.ai";
|
||||
const OPENCLAW_PLUGIN_CLAWHUB_REPOSITORY = "openclaw/openclaw";
|
||||
const OPENCLAW_PLUGIN_CLAWHUB_WORKFLOW_FILENAME = "plugin-clawhub-release.yml";
|
||||
const SAFE_EXTENSION_ID_RE = /^[a-z0-9][a-z0-9._-]*$/;
|
||||
const CLAWHUB_SHARED_RELEASE_INPUT_PATHS = [
|
||||
".github/workflows/plugin-clawhub-release.yml",
|
||||
@@ -357,6 +376,97 @@ async function isPluginVersionPublishedOnClawHub(
|
||||
);
|
||||
}
|
||||
|
||||
async function doesClawHubPackageExist(
|
||||
packageName: string,
|
||||
options: {
|
||||
fetchImpl?: typeof fetch;
|
||||
registryBaseUrl?: string;
|
||||
} = {},
|
||||
): Promise<boolean> {
|
||||
const fetchImpl = options.fetchImpl ?? fetch;
|
||||
const url = new URL(
|
||||
`/api/v1/packages/${encodeURIComponent(packageName)}`,
|
||||
getRegistryBaseUrl(options.registryBaseUrl),
|
||||
);
|
||||
const response = await fetchImpl(url, {
|
||||
method: "GET",
|
||||
headers: {
|
||||
Accept: "application/json",
|
||||
},
|
||||
});
|
||||
|
||||
if (response.status === 404) {
|
||||
return false;
|
||||
}
|
||||
if (!response.ok) {
|
||||
throw new Error(
|
||||
`Failed to query ClawHub package ${packageName}: ${response.status} ${response.statusText}`,
|
||||
);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
async function hasClawHubTrustedPublisher(
|
||||
packageName: string,
|
||||
options: {
|
||||
fetchImpl?: typeof fetch;
|
||||
registryBaseUrl?: string;
|
||||
} = {},
|
||||
): Promise<boolean> {
|
||||
const fetchImpl = options.fetchImpl ?? fetch;
|
||||
const url = new URL(
|
||||
`/api/v1/packages/${encodeURIComponent(packageName)}/trusted-publisher`,
|
||||
getRegistryBaseUrl(options.registryBaseUrl),
|
||||
);
|
||||
const response = await fetchImpl(url, {
|
||||
method: "GET",
|
||||
headers: {
|
||||
Accept: "application/json",
|
||||
},
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(
|
||||
`Failed to query ClawHub trusted publisher for ${packageName}: ${response.status} ${response.statusText}`,
|
||||
);
|
||||
}
|
||||
|
||||
let trustedPublisherDetail: ClawHubTrustedPublisherDetail;
|
||||
try {
|
||||
trustedPublisherDetail = (await response.json()) as ClawHubTrustedPublisherDetail;
|
||||
} catch (error) {
|
||||
throw new Error(`Failed to parse ClawHub trusted publisher ${packageName} response.`, {
|
||||
cause: error,
|
||||
});
|
||||
}
|
||||
|
||||
return isOpenClawPluginTrustedPublisher(trustedPublisherDetail.trustedPublisher);
|
||||
}
|
||||
|
||||
function isOpenClawPluginTrustedPublisher(value: unknown): boolean {
|
||||
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
||||
return false;
|
||||
}
|
||||
const trustedPublisher = value as ClawHubTrustedPublisherConfig;
|
||||
return (
|
||||
trustedPublisher.repository === OPENCLAW_PLUGIN_CLAWHUB_REPOSITORY &&
|
||||
trustedPublisher.workflowFilename === OPENCLAW_PLUGIN_CLAWHUB_WORKFLOW_FILENAME &&
|
||||
trustedPublisher.environment == null
|
||||
);
|
||||
}
|
||||
|
||||
function stripPackageReleaseState(
|
||||
item: PluginReleasePlanItemWithPackageState,
|
||||
): PluginReleasePlanItem {
|
||||
const {
|
||||
packageExists: _packageExists,
|
||||
hasTrustedPublisher: _hasTrustedPublisher,
|
||||
...planItem
|
||||
} = item;
|
||||
return planItem;
|
||||
}
|
||||
|
||||
export async function collectPluginClawHubReleasePlan(params?: {
|
||||
rootDir?: string;
|
||||
selection?: string[];
|
||||
@@ -395,22 +505,56 @@ export async function collectPluginClawHubReleasePlan(params?: {
|
||||
assertPluginReleaseVersionFloors(selectedPublishable, "Plugin ClawHub release plan");
|
||||
}
|
||||
|
||||
const all = await Promise.all(
|
||||
selectedPublishable.map(async (plugin) =>
|
||||
Object.assign({}, plugin, {
|
||||
alreadyPublished: await isPluginVersionPublishedOnClawHub(
|
||||
plugin.packageName,
|
||||
plugin.version,
|
||||
{ registryBaseUrl: params?.registryBaseUrl, fetchImpl: params?.fetchImpl },
|
||||
),
|
||||
const planned = await Promise.all(
|
||||
selectedPublishable.map(async (plugin): Promise<PluginReleasePlanItemWithPackageState> => {
|
||||
const packageExists = await doesClawHubPackageExist(plugin.packageName, {
|
||||
registryBaseUrl: params?.registryBaseUrl,
|
||||
fetchImpl: params?.fetchImpl,
|
||||
});
|
||||
const hasTrustedPublisher = packageExists
|
||||
? await hasClawHubTrustedPublisher(plugin.packageName, {
|
||||
registryBaseUrl: params?.registryBaseUrl,
|
||||
fetchImpl: params?.fetchImpl,
|
||||
})
|
||||
: false;
|
||||
const alreadyPublished = packageExists
|
||||
? await isPluginVersionPublishedOnClawHub(plugin.packageName, plugin.version, {
|
||||
registryBaseUrl: params?.registryBaseUrl,
|
||||
fetchImpl: params?.fetchImpl,
|
||||
})
|
||||
: false;
|
||||
|
||||
return {
|
||||
extensionId: plugin.extensionId,
|
||||
packageDir: plugin.packageDir,
|
||||
packageName: plugin.packageName,
|
||||
version: plugin.version,
|
||||
channel: plugin.channel,
|
||||
publishTag: plugin.publishTag,
|
||||
packageExists,
|
||||
hasTrustedPublisher,
|
||||
alreadyPublished,
|
||||
artifactName: formatClawHubPackageArtifactName(plugin),
|
||||
}),
|
||||
),
|
||||
};
|
||||
}),
|
||||
);
|
||||
const all = planned.map(stripPackageReleaseState);
|
||||
|
||||
return {
|
||||
all,
|
||||
candidates: all.filter((plugin) => !plugin.alreadyPublished),
|
||||
skippedPublished: all.filter((plugin) => plugin.alreadyPublished),
|
||||
candidates: planned
|
||||
.filter(
|
||||
(plugin) => plugin.packageExists && plugin.hasTrustedPublisher && !plugin.alreadyPublished,
|
||||
)
|
||||
.map(stripPackageReleaseState),
|
||||
bootstrapCandidates: planned
|
||||
.filter((plugin) => !plugin.packageExists)
|
||||
.map(stripPackageReleaseState),
|
||||
missingTrustedPublisher: planned
|
||||
.filter((plugin) => plugin.packageExists && !plugin.hasTrustedPublisher)
|
||||
.map(stripPackageReleaseState),
|
||||
skippedPublished: planned
|
||||
.filter((plugin) => plugin.alreadyPublished)
|
||||
.map(stripPackageReleaseState),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -18,6 +18,7 @@ export type ReleaseVerifyBetaArgs = {
|
||||
repo: string;
|
||||
registry: string;
|
||||
workflowRef?: string;
|
||||
clawHubWorkflowRef?: string;
|
||||
pluginSelection: string[];
|
||||
evidenceOut?: string;
|
||||
skipPostpublish: boolean;
|
||||
@@ -29,6 +30,7 @@ export type ReleaseVerifyBetaArgs = {
|
||||
openclawNpm?: string;
|
||||
pluginNpm?: string;
|
||||
pluginClawHub?: string;
|
||||
pluginClawHubBootstrap?: string;
|
||||
npmTelegram?: string;
|
||||
};
|
||||
};
|
||||
@@ -119,7 +121,7 @@ export function parseReleaseVerifyBetaArgs(argv: string[]): ReleaseVerifyBetaArg
|
||||
const version = values.shift();
|
||||
if (!version || version.startsWith("-")) {
|
||||
throw new Error(
|
||||
"Usage: pnpm release:verify-beta -- <version> [--workflow-ref REF] [--full-release-validation-run ID] [--openclaw-npm-run ID] [--plugin-npm-run ID] [--plugin-clawhub-run ID] [--npm-telegram-run ID] [--skip-github-release] [--skip-clawhub]",
|
||||
"Usage: pnpm release:verify-beta -- <version> [--workflow-ref REF] [--clawhub-workflow-ref REF] [--full-release-validation-run ID] [--openclaw-npm-run ID] [--plugin-npm-run ID] [--plugin-clawhub-run ID] [--plugin-clawhub-bootstrap-run ID] [--npm-telegram-run ID] [--skip-github-release] [--skip-clawhub]",
|
||||
);
|
||||
}
|
||||
|
||||
@@ -130,6 +132,7 @@ export function parseReleaseVerifyBetaArgs(argv: string[]): ReleaseVerifyBetaArg
|
||||
repo: DEFAULT_REPO,
|
||||
registry: DEFAULT_CLAWHUB_REGISTRY,
|
||||
workflowRef: undefined,
|
||||
clawHubWorkflowRef: undefined,
|
||||
pluginSelection: [],
|
||||
evidenceOut: undefined,
|
||||
skipPostpublish: false,
|
||||
@@ -166,6 +169,9 @@ export function parseReleaseVerifyBetaArgs(argv: string[]): ReleaseVerifyBetaArg
|
||||
case "--workflow-ref":
|
||||
parsed.workflowRef = next();
|
||||
break;
|
||||
case "--clawhub-workflow-ref":
|
||||
parsed.clawHubWorkflowRef = next();
|
||||
break;
|
||||
case "--plugins":
|
||||
parsed.pluginSelection = parsePluginReleaseSelection(next());
|
||||
if (parsed.pluginSelection.length === 0) {
|
||||
@@ -187,6 +193,9 @@ export function parseReleaseVerifyBetaArgs(argv: string[]): ReleaseVerifyBetaArg
|
||||
case "--plugin-clawhub-run":
|
||||
parsed.workflowRuns.pluginClawHub = next();
|
||||
break;
|
||||
case "--plugin-clawhub-bootstrap-run":
|
||||
parsed.workflowRuns.pluginClawHubBootstrap = next();
|
||||
break;
|
||||
case "--npm-telegram-run":
|
||||
parsed.workflowRuns.npmTelegram = next();
|
||||
break;
|
||||
@@ -567,17 +576,31 @@ export async function verifyBetaRelease(
|
||||
);
|
||||
}
|
||||
if (args.workflowRuns.pluginClawHub !== undefined) {
|
||||
const clawHubWorkflowRef = args.clawHubWorkflowRef ?? args.workflowRef;
|
||||
workflowRuns.push(
|
||||
verifyWorkflowRun({
|
||||
id: args.workflowRuns.pluginClawHub,
|
||||
label: "Plugin ClawHub Release",
|
||||
repo: args.repo,
|
||||
expectedWorkflowName: "Plugin ClawHub Release",
|
||||
expectedHeadBranch: args.workflowRef,
|
||||
expectedHeadBranch: clawHubWorkflowRef,
|
||||
rerunFailed: args.rerunFailedClawHub,
|
||||
}),
|
||||
);
|
||||
}
|
||||
if (args.workflowRuns.pluginClawHubBootstrap !== undefined) {
|
||||
const clawHubWorkflowRef = args.clawHubWorkflowRef ?? args.workflowRef;
|
||||
workflowRuns.push(
|
||||
verifyWorkflowRun({
|
||||
id: args.workflowRuns.pluginClawHubBootstrap,
|
||||
label: "Plugin ClawHub New",
|
||||
repo: args.repo,
|
||||
expectedWorkflowName: "Plugin ClawHub New",
|
||||
expectedHeadBranch: clawHubWorkflowRef,
|
||||
rerunFailed: false,
|
||||
}),
|
||||
);
|
||||
}
|
||||
if (args.workflowRuns.openclawNpm !== undefined) {
|
||||
workflowRuns.push(
|
||||
verifyWorkflowRun({
|
||||
|
||||
Executable
+14
@@ -0,0 +1,14 @@
|
||||
#!/usr/bin/env -S node --import tsx
|
||||
// OpenClaw release ClawHub plan CLI emits release workflow routing as JSON.
|
||||
|
||||
import { pathToFileURL } from "node:url";
|
||||
import {
|
||||
buildOpenClawReleaseClawHubPlan,
|
||||
parseOpenClawReleaseClawHubPlanArgs,
|
||||
} from "./lib/openclaw-release-clawhub-plan.ts";
|
||||
|
||||
if (import.meta.url === pathToFileURL(process.argv[1] ?? "").href) {
|
||||
const args = parseOpenClawReleaseClawHubPlanArgs(process.argv.slice(2));
|
||||
const plan = await buildOpenClawReleaseClawHubPlan(args);
|
||||
console.log(JSON.stringify(plan, null, 2));
|
||||
}
|
||||
+92
@@ -0,0 +1,92 @@
|
||||
#!/usr/bin/env -S node --import tsx
|
||||
import { buildOpenClawReleaseClawHubRuntimeState } from "./lib/openclaw-release-clawhub-plan.ts";
|
||||
|
||||
function parseBoolean(value: string, label: string): boolean {
|
||||
if (value === "true") {
|
||||
return true;
|
||||
}
|
||||
if (value === "false") {
|
||||
return false;
|
||||
}
|
||||
throw new Error(`${label} must be true or false.`);
|
||||
}
|
||||
|
||||
function parseArgs(argv: string[]) {
|
||||
const values = [...argv];
|
||||
if (values[0] === "--") {
|
||||
values.shift();
|
||||
}
|
||||
|
||||
let repository: string | undefined;
|
||||
let waitForClawHub: boolean | undefined;
|
||||
let forceSkipClawHub: boolean | undefined;
|
||||
let normalRunId: string | undefined;
|
||||
let bootstrapRunId: string | undefined;
|
||||
let bootstrapCompleted: boolean | undefined;
|
||||
|
||||
for (let index = 0; index < values.length; index += 1) {
|
||||
const arg = values[index];
|
||||
const next = () => {
|
||||
const value = values[index + 1];
|
||||
if (value === undefined || value.startsWith("-")) {
|
||||
throw new Error(`${arg} requires a value.`);
|
||||
}
|
||||
index += 1;
|
||||
return value;
|
||||
};
|
||||
|
||||
switch (arg) {
|
||||
case "--repository":
|
||||
repository = next();
|
||||
break;
|
||||
case "--wait-for-clawhub":
|
||||
waitForClawHub = parseBoolean(next(), "--wait-for-clawhub");
|
||||
break;
|
||||
case "--force-skip-clawhub":
|
||||
forceSkipClawHub = parseBoolean(next(), "--force-skip-clawhub");
|
||||
break;
|
||||
case "--normal-run-id":
|
||||
normalRunId = next();
|
||||
break;
|
||||
case "--bootstrap-run-id":
|
||||
bootstrapRunId = next();
|
||||
break;
|
||||
case "--bootstrap-completed":
|
||||
bootstrapCompleted = parseBoolean(next(), "--bootstrap-completed");
|
||||
break;
|
||||
default:
|
||||
throw new Error(`Unknown argument: ${arg}`);
|
||||
}
|
||||
}
|
||||
|
||||
if (!repository?.trim()) {
|
||||
throw new Error("--repository is required.");
|
||||
}
|
||||
if (waitForClawHub === undefined) {
|
||||
throw new Error("--wait-for-clawhub is required.");
|
||||
}
|
||||
if (forceSkipClawHub === undefined) {
|
||||
throw new Error("--force-skip-clawhub is required.");
|
||||
}
|
||||
if (bootstrapCompleted === undefined) {
|
||||
throw new Error("--bootstrap-completed is required.");
|
||||
}
|
||||
|
||||
return {
|
||||
repository,
|
||||
waitForClawHub,
|
||||
forceSkipClawHub,
|
||||
normalRunId,
|
||||
bootstrapRunId,
|
||||
bootstrapCompleted,
|
||||
};
|
||||
}
|
||||
|
||||
try {
|
||||
const state = buildOpenClawReleaseClawHubRuntimeState(parseArgs(process.argv.slice(2)));
|
||||
process.stdout.write(`${JSON.stringify(state, null, 2)}\n`);
|
||||
} catch (error) {
|
||||
const message = error instanceof Error ? error.message : String(error);
|
||||
console.error(message);
|
||||
process.exit(1);
|
||||
}
|
||||
@@ -42,6 +42,7 @@ source_repo="${SOURCE_REPO:-${GITHUB_REPOSITORY:-openclaw/openclaw}}"
|
||||
source_commit="${SOURCE_COMMIT:-$(git -C "${invocation_root}" rev-parse HEAD)}"
|
||||
source_ref="${SOURCE_REF:-$(git -C "${invocation_root}" symbolic-ref -q HEAD || true)}"
|
||||
clawhub_workdir="${CLAWDHUB_WORKDIR:-${CLAWHUB_WORKDIR:-${invocation_root}}}"
|
||||
manual_override_reason="${OPENCLAW_CLAWHUB_MANUAL_OVERRIDE_REASON:-}"
|
||||
|
||||
pack_dir="$(mktemp -d "${RUNNER_TEMP:-/tmp}/openclaw-clawhub-pack.XXXXXX")"
|
||||
cleanup() {
|
||||
@@ -158,6 +159,13 @@ if [[ -n "${source_ref}" ]]; then
|
||||
)
|
||||
fi
|
||||
|
||||
if [[ -n "${manual_override_reason}" ]]; then
|
||||
publish_cmd+=(
|
||||
--manual-override-reason
|
||||
"${manual_override_reason}"
|
||||
)
|
||||
fi
|
||||
|
||||
printf 'Publish command: CLAWHUB_WORKDIR=%q' "${clawhub_workdir}"
|
||||
printf ' %q' "${publish_cmd[@]}"
|
||||
printf '\n'
|
||||
|
||||
Reference in New Issue
Block a user