mirror of
https://github.com/moltbot/moltbot.git
synced 2026-05-13 15:47:28 +00:00
ci: forward-port release validation fixes
This commit is contained in:
@@ -783,6 +783,7 @@ jobs:
|
|||||||
RELEASE_CHECKS_RESULT: ${{ needs.release_checks.result }}
|
RELEASE_CHECKS_RESULT: ${{ needs.release_checks.result }}
|
||||||
NPM_TELEGRAM_RESULT: ${{ needs.npm_telegram.result }}
|
NPM_TELEGRAM_RESULT: ${{ needs.npm_telegram.result }}
|
||||||
TARGET_SHA: ${{ needs.resolve_target.outputs.sha }}
|
TARGET_SHA: ${{ needs.resolve_target.outputs.sha }}
|
||||||
|
CHILD_WORKFLOW_REF: ${{ github.ref_name }}
|
||||||
run: |
|
run: |
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
|
|
||||||
@@ -809,7 +810,7 @@ jobs:
|
|||||||
head_sha="$(jq -r '.headSha // ""' <<< "$run_json")"
|
head_sha="$(jq -r '.headSha // ""' <<< "$run_json")"
|
||||||
echo "${label}: ${status}/${conclusion} attempt ${attempt} head ${head_sha}: ${url}"
|
echo "${label}: ${status}/${conclusion} attempt ${attempt} head ${head_sha}: ${url}"
|
||||||
|
|
||||||
if [[ -n "${TARGET_SHA// }" && "$head_sha" != "$TARGET_SHA" ]]; then
|
if [[ "$CHILD_WORKFLOW_REF" == release-ci/* && -n "${TARGET_SHA// }" && "$head_sha" != "$TARGET_SHA" ]]; then
|
||||||
echo "::error::${label} child run used ${head_sha}, expected ${TARGET_SHA}. Dispatch Full Release Validation from a ref pinned to the target SHA, not a moving branch."
|
echo "::error::${label} child run used ${head_sha}, expected ${TARGET_SHA}. Dispatch Full Release Validation from a ref pinned to the target SHA, not a moving branch."
|
||||||
return 1
|
return 1
|
||||||
fi
|
fi
|
||||||
|
|||||||
21
.github/workflows/openclaw-release-publish.yml
vendored
21
.github/workflows/openclaw-release-publish.yml
vendored
@@ -126,7 +126,7 @@ jobs:
|
|||||||
uses: actions/download-artifact@v8
|
uses: actions/download-artifact@v8
|
||||||
with:
|
with:
|
||||||
name: openclaw-npm-preflight-${{ inputs.tag }}
|
name: openclaw-npm-preflight-${{ inputs.tag }}
|
||||||
path: preflight-manifest
|
path: ${{ runner.temp }}/openclaw-npm-preflight-manifest
|
||||||
repository: ${{ github.repository }}
|
repository: ${{ github.repository }}
|
||||||
run-id: ${{ inputs.preflight_run_id }}
|
run-id: ${{ inputs.preflight_run_id }}
|
||||||
github-token: ${{ github.token }}
|
github-token: ${{ github.token }}
|
||||||
@@ -151,10 +151,11 @@ jobs:
|
|||||||
EXPECTED_SHA: ${{ steps.ref.outputs.sha }}
|
EXPECTED_SHA: ${{ steps.ref.outputs.sha }}
|
||||||
run: |
|
run: |
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
manifest="preflight-manifest/preflight-manifest.json"
|
preflight_dir="${RUNNER_TEMP}/openclaw-npm-preflight-manifest"
|
||||||
|
manifest="${preflight_dir}/preflight-manifest.json"
|
||||||
if [[ ! -f "$manifest" ]]; then
|
if [[ ! -f "$manifest" ]]; then
|
||||||
echo "OpenClaw npm preflight manifest is missing." >&2
|
echo "OpenClaw npm preflight manifest is missing." >&2
|
||||||
ls -la preflight-manifest >&2 || true
|
ls -la "$preflight_dir" >&2 || true
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
release_tag="$(jq -r '.releaseTag // ""' "$manifest")"
|
release_tag="$(jq -r '.releaseTag // ""' "$manifest")"
|
||||||
@@ -174,11 +175,11 @@ jobs:
|
|||||||
echo "Preflight manifest npm dist-tag mismatch: expected $RELEASE_NPM_DIST_TAG, got $npm_dist_tag" >&2
|
echo "Preflight manifest npm dist-tag mismatch: expected $RELEASE_NPM_DIST_TAG, got $npm_dist_tag" >&2
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
if [[ -z "$tarball_name" || ! -f "preflight-manifest/$tarball_name" ]]; then
|
if [[ -z "$tarball_name" || ! -f "${preflight_dir}/${tarball_name}" ]]; then
|
||||||
echo "Preflight manifest tarball is missing: $tarball_name" >&2
|
echo "Preflight manifest tarball is missing: $tarball_name" >&2
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
actual_tarball_sha256="$(sha256sum "preflight-manifest/$tarball_name" | awk '{print $1}')"
|
actual_tarball_sha256="$(sha256sum "${preflight_dir}/${tarball_name}" | awk '{print $1}')"
|
||||||
if [[ "$actual_tarball_sha256" != "$tarball_sha256" ]]; then
|
if [[ "$actual_tarball_sha256" != "$tarball_sha256" ]]; then
|
||||||
echo "Preflight manifest tarball digest mismatch." >&2
|
echo "Preflight manifest tarball digest mismatch." >&2
|
||||||
exit 1
|
exit 1
|
||||||
@@ -222,6 +223,13 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
timeout-minutes: 60
|
timeout-minutes: 60
|
||||||
steps:
|
steps:
|
||||||
|
- name: Checkout release SHA
|
||||||
|
uses: actions/checkout@v6
|
||||||
|
with:
|
||||||
|
ref: ${{ needs.resolve_release_target.outputs.sha }}
|
||||||
|
fetch-depth: 1
|
||||||
|
persist-credentials: false
|
||||||
|
|
||||||
- name: Dispatch publish workflows
|
- name: Dispatch publish workflows
|
||||||
env:
|
env:
|
||||||
GH_TOKEN: ${{ github.token }}
|
GH_TOKEN: ${{ github.token }}
|
||||||
@@ -336,8 +344,7 @@ jobs:
|
|||||||
changelog_file="${RUNNER_TEMP}/CHANGELOG.md"
|
changelog_file="${RUNNER_TEMP}/CHANGELOG.md"
|
||||||
notes_file="${RUNNER_TEMP}/release-notes.md"
|
notes_file="${RUNNER_TEMP}/release-notes.md"
|
||||||
|
|
||||||
gh api "repos/${GITHUB_REPOSITORY}/contents/CHANGELOG.md?ref=${TARGET_SHA}" \
|
git show "${TARGET_SHA}:CHANGELOG.md" > "${changelog_file}"
|
||||||
--jq '.content' | base64 --decode > "${changelog_file}"
|
|
||||||
awk -v version="${notes_version}" '
|
awk -v version="${notes_version}" '
|
||||||
$0 == "## " version { in_section = 1; next }
|
$0 == "## " version { in_section = 1; next }
|
||||||
/^## / && in_section { exit }
|
/^## / && in_section { exit }
|
||||||
|
|||||||
@@ -716,7 +716,6 @@ public struct AgentParams: Codable, Sendable {
|
|||||||
public let bootstrapcontextmode: AnyCodable?
|
public let bootstrapcontextmode: AnyCodable?
|
||||||
public let bootstrapcontextrunkind: AnyCodable?
|
public let bootstrapcontextrunkind: AnyCodable?
|
||||||
public let acpturnsource: String?
|
public let acpturnsource: String?
|
||||||
public let internalruntimehandoffid: String?
|
|
||||||
public let internalevents: [[String: AnyCodable]]?
|
public let internalevents: [[String: AnyCodable]]?
|
||||||
public let inputprovenance: [String: AnyCodable]?
|
public let inputprovenance: [String: AnyCodable]?
|
||||||
public let voicewaketrigger: String?
|
public let voicewaketrigger: String?
|
||||||
@@ -753,7 +752,6 @@ public struct AgentParams: Codable, Sendable {
|
|||||||
bootstrapcontextmode: AnyCodable?,
|
bootstrapcontextmode: AnyCodable?,
|
||||||
bootstrapcontextrunkind: AnyCodable?,
|
bootstrapcontextrunkind: AnyCodable?,
|
||||||
acpturnsource: String?,
|
acpturnsource: String?,
|
||||||
internalruntimehandoffid: String?,
|
|
||||||
internalevents: [[String: AnyCodable]]?,
|
internalevents: [[String: AnyCodable]]?,
|
||||||
inputprovenance: [String: AnyCodable]?,
|
inputprovenance: [String: AnyCodable]?,
|
||||||
voicewaketrigger: String?,
|
voicewaketrigger: String?,
|
||||||
@@ -789,7 +787,6 @@ public struct AgentParams: Codable, Sendable {
|
|||||||
self.bootstrapcontextmode = bootstrapcontextmode
|
self.bootstrapcontextmode = bootstrapcontextmode
|
||||||
self.bootstrapcontextrunkind = bootstrapcontextrunkind
|
self.bootstrapcontextrunkind = bootstrapcontextrunkind
|
||||||
self.acpturnsource = acpturnsource
|
self.acpturnsource = acpturnsource
|
||||||
self.internalruntimehandoffid = internalruntimehandoffid
|
|
||||||
self.internalevents = internalevents
|
self.internalevents = internalevents
|
||||||
self.inputprovenance = inputprovenance
|
self.inputprovenance = inputprovenance
|
||||||
self.voicewaketrigger = voicewaketrigger
|
self.voicewaketrigger = voicewaketrigger
|
||||||
@@ -827,7 +824,6 @@ public struct AgentParams: Codable, Sendable {
|
|||||||
case bootstrapcontextmode = "bootstrapContextMode"
|
case bootstrapcontextmode = "bootstrapContextMode"
|
||||||
case bootstrapcontextrunkind = "bootstrapContextRunKind"
|
case bootstrapcontextrunkind = "bootstrapContextRunKind"
|
||||||
case acpturnsource = "acpTurnSource"
|
case acpturnsource = "acpTurnSource"
|
||||||
case internalruntimehandoffid = "internalRuntimeHandoffId"
|
|
||||||
case internalevents = "internalEvents"
|
case internalevents = "internalEvents"
|
||||||
case inputprovenance = "inputProvenance"
|
case inputprovenance = "inputProvenance"
|
||||||
case voicewaketrigger = "voiceWakeTrigger"
|
case voicewaketrigger = "voiceWakeTrigger"
|
||||||
|
|||||||
@@ -50,7 +50,7 @@ const PUBLISHED_BUNDLED_RUNTIME_SIDECAR_PATHS = BUNDLED_RUNTIME_SIDECAR_PATHS.fi
|
|||||||
);
|
);
|
||||||
const NODE_BUILTIN_MODULES = new Set(builtinModules.map((name) => name.replace(/^node:/u, "")));
|
const NODE_BUILTIN_MODULES = new Set(builtinModules.map((name) => name.replace(/^node:/u, "")));
|
||||||
const MAX_INSTALLED_ROOT_PACKAGE_JSON_BYTES = 1024 * 1024;
|
const MAX_INSTALLED_ROOT_PACKAGE_JSON_BYTES = 1024 * 1024;
|
||||||
const MAX_INSTALLED_ROOT_DIST_JS_BYTES = 4 * 1024 * 1024;
|
const MAX_INSTALLED_ROOT_DIST_JS_BYTES = 6 * 1024 * 1024;
|
||||||
const MAX_INSTALLED_ROOT_DIST_JS_FILES = 5000;
|
const MAX_INSTALLED_ROOT_DIST_JS_FILES = 5000;
|
||||||
const ROOT_DIST_JAVASCRIPT_MODULE_FILE_RE = /\.(?:c|m)?js$/u;
|
const ROOT_DIST_JAVASCRIPT_MODULE_FILE_RE = /\.(?:c|m)?js$/u;
|
||||||
const OPTIONAL_OR_EXTERNALIZED_RUNTIME_IMPORTS = new Set([
|
const OPTIONAL_OR_EXTERNALIZED_RUNTIME_IMPORTS = new Set([
|
||||||
@@ -69,6 +69,9 @@ const OPTIONAL_OR_EXTERNALIZED_RUNTIME_IMPORTS = new Set([
|
|||||||
// Discord voice decoder fallback. The root chunk catches missing decoders and the owning
|
// Discord voice decoder fallback. The root chunk catches missing decoders and the owning
|
||||||
// Discord plugin remains externalized from the root package.
|
// Discord plugin remains externalized from the root package.
|
||||||
"opusscript",
|
"opusscript",
|
||||||
|
// Public plugin SDK contract helpers are intentionally test-only entrypoints.
|
||||||
|
// Consumers importing them run under their own Vitest dev dependency.
|
||||||
|
"vitest",
|
||||||
]);
|
]);
|
||||||
const require = createRequire(import.meta.url);
|
const require = createRequire(import.meta.url);
|
||||||
const acorn = require("acorn") as typeof import("acorn");
|
const acorn = require("acorn") as typeof import("acorn");
|
||||||
|
|||||||
@@ -250,6 +250,12 @@ describe("collectInstalledRootDependencyManifestErrors", () => {
|
|||||||
'import * as lark from "@larksuiteoapi/node-sdk";\nexport { lark };\n',
|
'import * as lark from "@larksuiteoapi/node-sdk";\nexport { lark };\n',
|
||||||
"utf8",
|
"utf8",
|
||||||
);
|
);
|
||||||
|
mkdirSync(join(packageRoot, "dist", "plugin-sdk"), { recursive: true });
|
||||||
|
writeFileSync(
|
||||||
|
join(packageRoot, "dist", "plugin-sdk/channel-test-helpers.js"),
|
||||||
|
'import { expect, it } from "vitest";\nexport { expect, it };\n',
|
||||||
|
"utf8",
|
||||||
|
);
|
||||||
|
|
||||||
expect(collectInstalledRootDependencyManifestErrors(packageRoot)).toStrictEqual([]);
|
expect(collectInstalledRootDependencyManifestErrors(packageRoot)).toStrictEqual([]);
|
||||||
} finally {
|
} finally {
|
||||||
@@ -363,12 +369,12 @@ describe("collectInstalledRootDependencyManifestErrors", () => {
|
|||||||
mkdirSync(join(packageRoot, "dist"), { recursive: true });
|
mkdirSync(join(packageRoot, "dist"), { recursive: true });
|
||||||
writeFileSync(
|
writeFileSync(
|
||||||
join(packageRoot, "dist", "oversized.js"),
|
join(packageRoot, "dist", "oversized.js"),
|
||||||
"x".repeat(4 * 1024 * 1024 + 1),
|
"x".repeat(6 * 1024 * 1024 + 1),
|
||||||
"utf8",
|
"utf8",
|
||||||
);
|
);
|
||||||
|
|
||||||
expect(collectInstalledRootDependencyManifestErrors(packageRoot)).toEqual([
|
expect(collectInstalledRootDependencyManifestErrors(packageRoot)).toEqual([
|
||||||
"installed package root dist file 'oversized.js' is invalid or exceeds 4194304 bytes.",
|
"installed package root dist file 'oversized.js' is invalid or exceeds 6291456 bytes.",
|
||||||
]);
|
]);
|
||||||
} finally {
|
} finally {
|
||||||
rmSync(packageRoot, { recursive: true, force: true });
|
rmSync(packageRoot, { recursive: true, force: true });
|
||||||
|
|||||||
@@ -150,16 +150,20 @@ describe("package acceptance workflow", () => {
|
|||||||
expect(workflow).toContain("Published upgrade survivor scenarios:");
|
expect(workflow).toContain("Published upgrade survivor scenarios:");
|
||||||
});
|
});
|
||||||
|
|
||||||
it("requires full release child workflows to run at the resolved target SHA", () => {
|
it("requires pinned full release child workflows to run at the resolved target SHA", () => {
|
||||||
const workflow = readFileSync(FULL_RELEASE_VALIDATION_WORKFLOW, "utf8");
|
const workflow = readFileSync(FULL_RELEASE_VALIDATION_WORKFLOW, "utf8");
|
||||||
const releaseChecksWorkflow = readFileSync(RELEASE_CHECKS_WORKFLOW, "utf8");
|
const releaseChecksWorkflow = readFileSync(RELEASE_CHECKS_WORKFLOW, "utf8");
|
||||||
|
|
||||||
expect(workflow).toContain("TARGET_SHA: ${{ needs.resolve_target.outputs.sha }}");
|
expect(workflow).toContain("TARGET_SHA: ${{ needs.resolve_target.outputs.sha }}");
|
||||||
|
expect(workflow).toContain("CHILD_WORKFLOW_REF: ${{ github.ref_name }}");
|
||||||
expect(workflow).toContain("package_acceptance_package_spec:");
|
expect(workflow).toContain("package_acceptance_package_spec:");
|
||||||
expect(workflow).toContain(
|
expect(workflow).toContain(
|
||||||
'args+=(-f package_acceptance_package_spec="$PACKAGE_ACCEPTANCE_PACKAGE_SPEC")',
|
'args+=(-f package_acceptance_package_spec="$PACKAGE_ACCEPTANCE_PACKAGE_SPEC")',
|
||||||
);
|
);
|
||||||
expect(workflow).toContain("--json status,conclusion,url,attempt,headSha,jobs");
|
expect(workflow).toContain("--json status,conclusion,url,attempt,headSha,jobs");
|
||||||
|
expect(workflow).toContain(
|
||||||
|
'[[ "$CHILD_WORKFLOW_REF" == release-ci/* && -n "${TARGET_SHA// }" && "$head_sha" != "$TARGET_SHA" ]]',
|
||||||
|
);
|
||||||
expect(workflow).toContain("child run used ${head_sha}, expected ${TARGET_SHA}");
|
expect(workflow).toContain("child run used ${head_sha}, expected ${TARGET_SHA}");
|
||||||
expect(workflow).toContain(
|
expect(workflow).toContain(
|
||||||
"Dispatch Full Release Validation from a ref pinned to the target SHA",
|
"Dispatch Full Release Validation from a ref pinned to the target SHA",
|
||||||
@@ -842,9 +846,8 @@ describe("package artifact reuse", () => {
|
|||||||
expect(workflow).toContain("preflight-manifest.json");
|
expect(workflow).toContain("preflight-manifest.json");
|
||||||
expect(npmWorkflow).toContain("preflight-manifest.json");
|
expect(npmWorkflow).toContain("preflight-manifest.json");
|
||||||
expect(npmWorkflow).toContain("tarballSha256");
|
expect(npmWorkflow).toContain("tarballSha256");
|
||||||
expect(workflow).toContain(
|
expect(workflow).toContain("Checkout release SHA");
|
||||||
'gh api "repos/${GITHUB_REPOSITORY}/contents/CHANGELOG.md?ref=${TARGET_SHA}"',
|
expect(workflow).toContain('git show "${TARGET_SHA}:CHANGELOG.md" > "${changelog_file}"');
|
||||||
);
|
|
||||||
expect(workflow).toContain('$0 == "## Unreleased" { in_section = 1; next }');
|
expect(workflow).toContain('$0 == "## Unreleased" { in_section = 1; next }');
|
||||||
expect(workflow).toContain("Unreleased prerelease fallback");
|
expect(workflow).toContain("Unreleased prerelease fallback");
|
||||||
expect(workflow).not.toContain("gh api --repo");
|
expect(workflow).not.toContain("gh api --repo");
|
||||||
|
|||||||
Reference in New Issue
Block a user