mirror of
https://github.com/openclaw/openclaw.git
synced 2026-07-21 02:06:43 +00:00
ci: speed up release preparation
This commit is contained in:
@@ -465,6 +465,7 @@ jobs:
|
||||
ref: ${{ inputs.ref }}
|
||||
path: source
|
||||
fetch-depth: 0
|
||||
filter: blob:none
|
||||
persist-credentials: false
|
||||
submodules: recursive
|
||||
|
||||
|
||||
@@ -105,6 +105,7 @@ jobs:
|
||||
with:
|
||||
ref: ${{ inputs.tag }}
|
||||
fetch-depth: 0
|
||||
filter: blob:none
|
||||
|
||||
- name: Validate Tideclaw alpha preflight target
|
||||
if: startsWith(github.ref, 'refs/heads/tideclaw/alpha/')
|
||||
@@ -586,6 +587,7 @@ jobs:
|
||||
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6
|
||||
with:
|
||||
fetch-depth: 0
|
||||
filter: blob:none
|
||||
persist-credentials: false
|
||||
|
||||
- name: Validate npm release request
|
||||
|
||||
@@ -203,6 +203,7 @@ jobs:
|
||||
ref: ${{ inputs.ref }}
|
||||
path: source
|
||||
fetch-depth: 0
|
||||
filter: blob:none
|
||||
|
||||
- name: Resolve checked-out fallback SHA
|
||||
if: steps.fast_ref.outputs.fallback == 'true'
|
||||
@@ -565,6 +566,7 @@ jobs:
|
||||
persist-credentials: false
|
||||
ref: ${{ github.sha }}
|
||||
fetch-depth: 0
|
||||
filter: blob:none
|
||||
|
||||
- name: Set artifact metadata
|
||||
id: artifact
|
||||
|
||||
@@ -401,6 +401,7 @@ jobs:
|
||||
with:
|
||||
ref: refs/tags/${{ inputs.tag }}
|
||||
fetch-depth: 0
|
||||
filter: blob:none
|
||||
persist-credentials: false
|
||||
|
||||
- name: Resolve checked-out release ref
|
||||
|
||||
@@ -447,6 +447,7 @@ jobs:
|
||||
with:
|
||||
ref: ${{ inputs.workflow_ref }}
|
||||
fetch-depth: 0
|
||||
filter: blob:none
|
||||
persist-credentials: false
|
||||
|
||||
- name: Setup Node environment
|
||||
|
||||
@@ -68,6 +68,7 @@ jobs:
|
||||
persist-credentials: false
|
||||
ref: ${{ github.ref }}
|
||||
fetch-depth: 0
|
||||
filter: blob:none
|
||||
|
||||
- name: Resolve checked-out ref
|
||||
id: ref
|
||||
@@ -321,6 +322,7 @@ jobs:
|
||||
persist-credentials: false
|
||||
ref: ${{ github.ref }}
|
||||
fetch-depth: 0
|
||||
filter: blob:none
|
||||
|
||||
- name: Checkout target revision
|
||||
env:
|
||||
|
||||
@@ -82,6 +82,7 @@ jobs:
|
||||
persist-credentials: false
|
||||
ref: ${{ github.event_name == 'workflow_dispatch' && inputs.ref || github.sha }}
|
||||
fetch-depth: 0
|
||||
filter: blob:none
|
||||
|
||||
- name: Resolve checked-out ref
|
||||
id: ref
|
||||
|
||||
@@ -4,6 +4,7 @@ import { resolve } from "node:path";
|
||||
import { truncateUtf16Safe } from "../../packages/normalization-core/src/utf16-slice.js";
|
||||
import { validateExternalCodePluginPackageJson } from "../../packages/plugin-package-contract/src/index.ts";
|
||||
import { retryClawHubRead } from "../../src/infra/clawhub-retry.js";
|
||||
import { runTasksWithConcurrency } from "../../src/utils/run-with-concurrency.js";
|
||||
import { readBoundedResponseText } from "./bounded-response.ts";
|
||||
import {
|
||||
assertPluginReleaseDependencyFreshness,
|
||||
@@ -102,6 +103,9 @@ const CLAWHUB_REQUEST_TIMEOUT_MS = 30_000;
|
||||
const CLAWHUB_RESPONSE_BODY_MAX_BYTES = 64 * 1024;
|
||||
const CLAWHUB_ERROR_BODY_MAX_BYTES = 8 * 1024;
|
||||
const CLAWHUB_ERROR_BODY_MAX_CHARS = 400;
|
||||
// All-publishable releases query dozens of packages. Bound registry pressure while
|
||||
// allowing independent package state reads to leave the core publish critical path quickly.
|
||||
const CLAWHUB_RELEASE_PLAN_CONCURRENCY = 8;
|
||||
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._-]*$/;
|
||||
@@ -652,32 +656,22 @@ export async function collectPluginClawHubReleasePlan(params?: {
|
||||
params?.resolveLatestVersion,
|
||||
);
|
||||
|
||||
const planned: PluginReleasePlanItemWithPackageState[] = [];
|
||||
for (const plugin of selectedPublishable) {
|
||||
const packageExists = await doesClawHubPackageExist(plugin.packageName, {
|
||||
const planTasks = selectedPublishable.map((plugin) => async () => {
|
||||
const queryOptions = {
|
||||
registryBaseUrl: params?.registryBaseUrl,
|
||||
fetchImpl: params?.fetchImpl,
|
||||
requestTimeoutMs: params?.requestTimeoutMs,
|
||||
sleep: params?.sleep,
|
||||
});
|
||||
};
|
||||
const packageExists = await doesClawHubPackageExist(plugin.packageName, queryOptions);
|
||||
const hasTrustedPublisher = packageExists
|
||||
? await hasClawHubTrustedPublisher(plugin.packageName, {
|
||||
registryBaseUrl: params?.registryBaseUrl,
|
||||
fetchImpl: params?.fetchImpl,
|
||||
requestTimeoutMs: params?.requestTimeoutMs,
|
||||
sleep: params?.sleep,
|
||||
})
|
||||
? await hasClawHubTrustedPublisher(plugin.packageName, queryOptions)
|
||||
: false;
|
||||
const alreadyPublished = packageExists
|
||||
? await isPluginVersionPublishedOnClawHub(plugin.packageName, plugin.version, {
|
||||
registryBaseUrl: params?.registryBaseUrl,
|
||||
fetchImpl: params?.fetchImpl,
|
||||
requestTimeoutMs: params?.requestTimeoutMs,
|
||||
sleep: params?.sleep,
|
||||
})
|
||||
? await isPluginVersionPublishedOnClawHub(plugin.packageName, plugin.version, queryOptions)
|
||||
: false;
|
||||
|
||||
planned.push({
|
||||
return {
|
||||
extensionId: plugin.extensionId,
|
||||
packageDir: plugin.packageDir,
|
||||
packageName: plugin.packageName,
|
||||
@@ -688,8 +682,17 @@ export async function collectPluginClawHubReleasePlan(params?: {
|
||||
hasTrustedPublisher,
|
||||
alreadyPublished,
|
||||
artifactName: formatClawHubPackageArtifactName(plugin),
|
||||
});
|
||||
} satisfies PluginReleasePlanItemWithPackageState;
|
||||
});
|
||||
const planResult = await runTasksWithConcurrency({
|
||||
tasks: planTasks,
|
||||
limit: CLAWHUB_RELEASE_PLAN_CONCURRENCY,
|
||||
errorMode: "stop",
|
||||
});
|
||||
if (planResult.hasError) {
|
||||
throw planResult.firstError;
|
||||
}
|
||||
const planned = planResult.results;
|
||||
const all = planned.map(stripPackageReleaseState);
|
||||
|
||||
return {
|
||||
|
||||
@@ -431,6 +431,59 @@ describe("resolveSelectedClawHubPublishablePluginPackages", () => {
|
||||
});
|
||||
|
||||
describe("collectPluginClawHubReleasePlan", () => {
|
||||
it("bounds parallel ClawHub package-state reads and preserves plan order", async () => {
|
||||
const extraExtensionIds = Array.from({ length: 11 }, (_, index) => `demo-${index + 2}`);
|
||||
const repoDir = createTempPluginRepo({ extraExtensionIds });
|
||||
const packageNames = ["demo-plugin", ...extraExtensionIds].map(
|
||||
(extensionId) => `@openclaw/${extensionId}`,
|
||||
);
|
||||
const baseFetch = createClawHubPlanFetch({
|
||||
packages: Object.fromEntries(
|
||||
packageNames.map((packageName) => [packageName, { status: 200 }]),
|
||||
),
|
||||
trustedPublishers: Object.fromEntries(
|
||||
packageNames.map((packageName) => [
|
||||
packageName,
|
||||
{
|
||||
status: 200,
|
||||
body: {
|
||||
trustedPublisher: {
|
||||
repository: "openclaw/openclaw",
|
||||
workflowFilename: "plugin-clawhub-release.yml",
|
||||
},
|
||||
},
|
||||
},
|
||||
]),
|
||||
),
|
||||
versions: Object.fromEntries(
|
||||
packageNames.map((packageName) => [`${packageName}@2026.4.1`, 404]),
|
||||
),
|
||||
}).fetchImpl;
|
||||
let activeRequests = 0;
|
||||
let maxActiveRequests = 0;
|
||||
const fetchImpl: typeof fetch = async (...args) => {
|
||||
activeRequests += 1;
|
||||
maxActiveRequests = Math.max(maxActiveRequests, activeRequests);
|
||||
try {
|
||||
await new Promise((resolve) => setTimeout(resolve, 5));
|
||||
return await baseFetch(...args);
|
||||
} finally {
|
||||
activeRequests -= 1;
|
||||
}
|
||||
};
|
||||
|
||||
const plan = await collectPluginClawHubReleasePlan({
|
||||
rootDir: repoDir,
|
||||
selectionMode: "all-publishable",
|
||||
fetchImpl,
|
||||
registryBaseUrl: "https://clawhub.ai",
|
||||
});
|
||||
|
||||
expect(maxActiveRequests).toBe(8);
|
||||
expect(plan.all.map((plugin) => plugin.packageName)).toEqual(packageNames.toSorted());
|
||||
expect(plan.candidates.map((plugin) => plugin.packageName)).toEqual(packageNames.toSorted());
|
||||
});
|
||||
|
||||
it("rejects stale required dependencies before querying ClawHub", async () => {
|
||||
const repoDir = createTempPluginRepo({
|
||||
requiredLatestDependencyVersion: "1.2.3",
|
||||
|
||||
@@ -4603,6 +4603,39 @@ wait_for_run plugin-clawhub-new.yml 123 "${expectedSha}" || status=$?
|
||||
expect(readFileSync(LIVE_E2E_WORKFLOW, "utf8")).toContain("live-cache attempt ${attempt}/2");
|
||||
});
|
||||
|
||||
it("keeps release history checks blobless", () => {
|
||||
const fullHistoryCheckouts: Array<[string, string, string]> = [
|
||||
[RELEASE_PUBLISH_WORKFLOW, "resolve_release_target", "Checkout release tag"],
|
||||
[
|
||||
RELEASE_CHECKS_WORKFLOW,
|
||||
"resolve_target",
|
||||
"Checkout selected ref for reachability fallback",
|
||||
],
|
||||
[RELEASE_CHECKS_WORKFLOW, "prepare_release_package", "Checkout trusted workflow ref"],
|
||||
[PACKAGE_ACCEPTANCE_WORKFLOW, "resolve_package", "Checkout package workflow ref"],
|
||||
[PLUGIN_NPM_RELEASE_WORKFLOW, "preview_plugins_npm", "Checkout"],
|
||||
[PLUGIN_CLAWHUB_RELEASE_WORKFLOW, "preview_plugins_clawhub", "Checkout"],
|
||||
[PLUGIN_CLAWHUB_RELEASE_WORKFLOW, "pack_plugins_clawhub_artifacts", "Checkout"],
|
||||
[OPENCLAW_NPM_RELEASE_WORKFLOW, "preflight_openclaw_npm", "Checkout"],
|
||||
[OPENCLAW_NPM_RELEASE_WORKFLOW, "validate_publish_request", "Checkout"],
|
||||
[
|
||||
".github/workflows/openclaw-cross-os-release-checks-reusable.yml",
|
||||
"prepare",
|
||||
"Checkout public source ref",
|
||||
],
|
||||
];
|
||||
|
||||
for (const [workflowPath, jobName, stepName] of fullHistoryCheckouts) {
|
||||
expect(
|
||||
workflowStep(workflowJob(workflowPath, jobName), stepName).with,
|
||||
workflowPath,
|
||||
).toMatchObject({
|
||||
"fetch-depth": 0,
|
||||
filter: "blob:none",
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
it("validates the macOS release handoff before the GitHub release page exists", () => {
|
||||
const macosRelease = readWorkflow(".github/workflows/macos-release.yml");
|
||||
const validateJob = workflowJob(
|
||||
|
||||
Reference in New Issue
Block a user