mirror of
https://github.com/openclaw/openclaw.git
synced 2026-08-28 05:16:23 -06:00
fix(release): restore direct package Telegram acceptance (#129912)
* fix(release): provision package acceptance plugin registry * fix(release): accept registry tuple in package dispatch * fix(release): default Docker advisory input * fix(release): bind artifact registry to package source * fix(release): preserve package advisory dispatch * fix(ci): bound hosted release gate checks Co-authored-by: Tak Hoffman <781889+Takhoffman@users.noreply.github.com> * test(ci): update release-gate manifest fixture * fix(ci): avoid duplicate release gate lint * test(release): restore Docker helper fixture path --------- Co-authored-by: Tak Hoffman <781889+Takhoffman@users.noreply.github.com>
This commit is contained in:
@@ -99,6 +99,7 @@ function evaluateWorkflowExpression(
|
||||
headRepository?: string;
|
||||
matrix?: Record<string, unknown>;
|
||||
repository: string;
|
||||
runCheck?: boolean;
|
||||
runnerBackend?: "" | "blacksmith" | "github" | "hybrid";
|
||||
runAttempt: number;
|
||||
},
|
||||
@@ -136,6 +137,13 @@ function evaluateWorkflowExpression(
|
||||
: {},
|
||||
},
|
||||
matrix: context.matrix ?? {},
|
||||
needs: {
|
||||
preflight: {
|
||||
outputs: {
|
||||
run_check: String(context.runCheck ?? true),
|
||||
},
|
||||
},
|
||||
},
|
||||
vars: {
|
||||
OPENCLAW_CI_RUNNER_BACKEND: context.runnerBackend ?? "",
|
||||
},
|
||||
@@ -216,6 +224,7 @@ function runCiManifestFixture(options: {
|
||||
qaSmokePlan?: boolean;
|
||||
formatCheck?: boolean;
|
||||
releaseCandidateCompatibility?: boolean;
|
||||
releaseGate?: boolean;
|
||||
targetContextCompatibility?: boolean;
|
||||
nodeFastOnly?: boolean;
|
||||
nodeFastPluginContracts?: boolean;
|
||||
@@ -404,6 +413,7 @@ function runCiManifestFixture(options: {
|
||||
(options.eventName ?? "workflow_dispatch") === "workflow_dispatch"
|
||||
? "true"
|
||||
: "false",
|
||||
OPENCLAW_CI_RELEASE_GATE: String(options.releaseGate ?? false),
|
||||
OPENCLAW_CI_RELEASE_CANDIDATE_TARGET:
|
||||
options.releaseCandidateCompatibility === true ? "true" : "false",
|
||||
OPENCLAW_CI_TARGET_CONTEXT_TARGET:
|
||||
@@ -6468,6 +6478,13 @@ printf '%s\n' "\${CURL_SUCCESS_IP:-203.0.113.7}"
|
||||
);
|
||||
const checkShardRun = checkShardStep.run;
|
||||
const hostedCoreLint = workflow.jobs["check-lint-hosted-core-shard"];
|
||||
const hostedCoreTypes = workflow.jobs["check-test-types-hosted-core-shard"];
|
||||
const manualDispatch = {
|
||||
eventName: "workflow_dispatch",
|
||||
repository: "openclaw/openclaw",
|
||||
runnerBackend: "blacksmith",
|
||||
runAttempt: 1,
|
||||
} as const;
|
||||
const untrustedForkPullRequest = {
|
||||
authorAssociation: "NONE",
|
||||
eventName: "pull_request",
|
||||
@@ -6478,8 +6495,11 @@ printf '%s\n' "\${CURL_SUCCESS_IP:-203.0.113.7}"
|
||||
} as const;
|
||||
|
||||
expect(manifestStep.env.OPENCLAW_CI_RUNNER_BACKEND).toBe(
|
||||
"${{ (github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name != github.repository) && 'github' || vars.OPENCLAW_CI_RUNNER_BACKEND }}",
|
||||
"${{ (github.event_name == 'workflow_dispatch' || (github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name != github.repository)) && 'github' || vars.OPENCLAW_CI_RUNNER_BACKEND }}",
|
||||
);
|
||||
expect(
|
||||
evaluateWorkflowExpression(manifestStep.env.OPENCLAW_CI_RUNNER_BACKEND, manualDispatch),
|
||||
).toBe("github");
|
||||
expect(
|
||||
evaluateWorkflowExpression(
|
||||
manifestStep.env.OPENCLAW_CI_RUNNER_BACKEND,
|
||||
@@ -6489,9 +6509,15 @@ printf '%s\n' "\${CURL_SUCCESS_IP:-203.0.113.7}"
|
||||
expect(
|
||||
evaluateWorkflowExpression(checkShardStep.env.RUNNER_BACKEND, untrustedForkPullRequest),
|
||||
).toBe("github");
|
||||
expect(evaluateWorkflowExpression(checkShardStep.env.RUNNER_BACKEND, manualDispatch)).toBe(
|
||||
"github",
|
||||
);
|
||||
expect(checkShardStep.env.RELEASE_GATE).toBe("${{ inputs.release_gate && 'true' || 'false' }}");
|
||||
expect(manifestStep.run).toContain("runnerBackend: process.env.OPENCLAW_CI_RUNNER_BACKEND");
|
||||
expect(checkShardRun).toContain('if [ "$RUNNER_BACKEND" = "github" ]; then');
|
||||
expect(checkShardRun).toContain("lint_args=(--only=extensions --only=scripts --threads=1)");
|
||||
expect(checkShardRun).toContain('if [ "$RELEASE_GATE" = "true" ]; then');
|
||||
expect(checkShardRun).toContain("lint_args=(--only=scripts --threads=1)");
|
||||
expect(checkShardRun).toContain('elif [ "$(nproc)" -lt 8 ]; then');
|
||||
expect(checkShardRun).toContain("lint_args=(--threads=1)");
|
||||
expect(checkShardRun).not.toContain("lint_args=(--split-core --threads=1)");
|
||||
@@ -6504,9 +6530,11 @@ printf '%s\n' "\${CURL_SUCCESS_IP:-203.0.113.7}"
|
||||
expect(hostedCoreLint.if).toContain(
|
||||
"github.event.pull_request.head.repo.full_name != github.repository",
|
||||
);
|
||||
expect(workflow.jobs["check-test-types-hosted-core-shard"].if).toContain(
|
||||
expect(hostedCoreTypes.if).toContain(
|
||||
"github.event.pull_request.head.repo.full_name != github.repository",
|
||||
);
|
||||
expect(evaluateWorkflowExpression(hostedCoreLint.if, manualDispatch)).toBe(true);
|
||||
expect(evaluateWorkflowExpression(hostedCoreTypes.if, manualDispatch)).toBe(true);
|
||||
expect(hostedCoreLint["runs-on"]).toBe("ubuntu-24.04");
|
||||
expect(hostedCoreLint.strategy).toEqual({
|
||||
"fail-fast": false,
|
||||
@@ -6544,14 +6572,14 @@ printf '%s\n' "\${CURL_SUCCESS_IP:-203.0.113.7}"
|
||||
"pull-requests": "read",
|
||||
});
|
||||
expect(checksFastJob.env.CHECKOUT_BASE_SHA).toBe(
|
||||
"${{ matrix.task == 'baseline-ratchets' && needs.preflight.outputs.diff_base_revision || '' }}",
|
||||
"${{ (matrix.task == 'baseline-ratchets' || startsWith(matrix.task, 'release-lint-')) && needs.preflight.outputs.diff_base_revision || '' }}",
|
||||
);
|
||||
expect(checkout.run).toContain(
|
||||
'fetch_refs+=("+${CHECKOUT_BASE_SHA}:refs/remotes/origin/ci-ratchet-base")',
|
||||
);
|
||||
expect(checkout.run).toContain('"${fetch_refs[@]}" || return 1');
|
||||
expect(releaseGateMerge.if).toBe(
|
||||
"matrix.task == 'baseline-ratchets' && github.event_name == 'workflow_dispatch' && inputs.release_gate",
|
||||
"(matrix.task == 'baseline-ratchets' || startsWith(matrix.task, 'release-lint-')) && github.event_name == 'workflow_dispatch' && inputs.release_gate",
|
||||
);
|
||||
expect(checksFastRun.run).toContain("baseline-ratchets)");
|
||||
expect(checksFastRun.run).toContain("coercion-helpers)");
|
||||
@@ -6603,9 +6631,6 @@ printf '%s\n' "\${CURL_SUCCESS_IP:-203.0.113.7}"
|
||||
expect(releaseGateMerge.run).toContain(
|
||||
'echo "RATCHET_BASE_REF=${frozen_base_sha}" >> "$GITHUB_ENV"',
|
||||
);
|
||||
expect(releaseGateMerge.run).toContain(
|
||||
'echo "RATCHET_RELEASE_MERGE_TREE=true" >> "$GITHUB_ENV"',
|
||||
);
|
||||
expect(checksFastRun.run).not.toContain("PROTOCOL_MANUAL_BASE_SHA");
|
||||
expect(checksFastRun.run).toContain(
|
||||
'"+${PROTOCOL_SINCE_BASE_SHA}:refs/remotes/origin/protocol-since-base"',
|
||||
@@ -6631,10 +6656,10 @@ printf '%s\n' "\${CURL_SUCCESS_IP:-203.0.113.7}"
|
||||
);
|
||||
expect(maxLinesRatchet).toContain("checkEnvVarCount(envVarCountArgs(argv), root);");
|
||||
expect(checksFastRun.run).toContain(
|
||||
'if [[ "${RATCHET_RELEASE_MERGE_TREE:-}" == "true" ]]; then',
|
||||
'--only=core --split-core --core-stripe="${stripe}/5" --threads=1',
|
||||
);
|
||||
expect(checksFastRun.run).toContain(
|
||||
"node --import tsx scripts/run-oxlint-shards.mts --only=core --only=extensions --threads=1",
|
||||
"node --import tsx scripts/run-oxlint-shards.mts --only=extensions --threads=1",
|
||||
);
|
||||
expect(checksFastRun.run).not.toContain(
|
||||
"node scripts/run-oxlint.mjs src ui/src packages extensions",
|
||||
@@ -6665,6 +6690,34 @@ printf '%s\n' "\${CURL_SUCCESS_IP:-203.0.113.7}"
|
||||
task: "coercion-helpers",
|
||||
},
|
||||
]);
|
||||
|
||||
const releaseGate = runCiManifestFixture({
|
||||
bundledPlanner: true,
|
||||
eventName: "workflow_dispatch",
|
||||
historicalCompatibility: false,
|
||||
releaseGate: true,
|
||||
});
|
||||
expect(releaseGate.status, releaseGate.output).toBe(0);
|
||||
expect(
|
||||
JSON.parse(
|
||||
expectDefined(releaseGate.outputs.checks_fast_core_matrix, "release-gate checks matrix"),
|
||||
).include.filter((entry: { task: string }) => entry.task.startsWith("release-lint-")),
|
||||
).toEqual([
|
||||
...Array.from({ length: 5 }, (_, index) => {
|
||||
const stripe = index + 1;
|
||||
return {
|
||||
check_name: `checks-fast-release-lint-core-${stripe}`,
|
||||
runtime: "node",
|
||||
stripe,
|
||||
task: `release-lint-core-${stripe}`,
|
||||
};
|
||||
}),
|
||||
{
|
||||
check_name: "checks-fast-release-lint-extensions",
|
||||
runtime: "node",
|
||||
task: "release-lint-extensions",
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
it("uses target-owned CI plans and capabilities for older release checkouts", () => {
|
||||
|
||||
@@ -4759,6 +4759,29 @@ source "$ROOT_DIR/scripts/lib/docker-e2e-logs.sh"
|
||||
'-v "$harness_root/test/e2e/qa-lab:/app/test/e2e/qa-lab:ro"',
|
||||
'-v "$harness_root/test/helpers:/app/test/helpers:ro"',
|
||||
]);
|
||||
|
||||
const script = repoRootShell`
|
||||
export DOCKER_E2E_HARNESS_ROOT_DIR=/trusted-harness
|
||||
source "$ROOT_DIR/scripts/lib/docker-e2e-package.sh"
|
||||
docker_e2e_harness_mount_args
|
||||
for ((index = 1; index < \${#DOCKER_E2E_HARNESS_ARGS[@]}; index += 2)); do
|
||||
printf "%s\\n" "\${DOCKER_E2E_HARNESS_ARGS[$index]}"
|
||||
done
|
||||
`;
|
||||
const mounts = execFileSync("bash", ["-lc", script], { encoding: "utf8" }).trim().split("\n");
|
||||
|
||||
expect(mounts).toEqual([
|
||||
"/trusted-harness/scripts/e2e:/app/scripts/e2e:ro",
|
||||
"/trusted-harness/scripts/lib:/app/scripts/lib:ro",
|
||||
"/trusted-harness/packages/gateway-client/src:/app/packages/gateway-client/src:ro",
|
||||
"/trusted-harness/packages/normalization-core/package.json:/app/packages/normalization-core/package.json:ro",
|
||||
"/trusted-harness/packages/normalization-core/src:/app/packages/normalization-core/src:ro",
|
||||
"/trusted-harness/tsconfig.json:/app/tsconfig.json:ro",
|
||||
"/trusted-harness/test/e2e/qa-lab:/app/test/e2e/qa-lab:ro",
|
||||
"/trusted-harness/test/helpers:/app/test/helpers:ro",
|
||||
"/trusted-harness/scripts/prepublish-plugin-registry-artifact.mjs:/app/scripts/prepublish-plugin-registry-artifact.mjs:ro",
|
||||
"/trusted-harness/scripts/windows-cmd-helpers.mjs:/app/scripts/windows-cmd-helpers.mjs:ro",
|
||||
]);
|
||||
});
|
||||
|
||||
it("preserves pnpm lookup paths for scheduled Docker child lanes", () => {
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
// Package Acceptance Workflow tests cover package acceptance workflow script behavior.
|
||||
import { execFileSync, spawnSync } from "node:child_process";
|
||||
import { createHash } from "node:crypto";
|
||||
import {
|
||||
chmodSync,
|
||||
mkdirSync,
|
||||
@@ -813,6 +814,101 @@ function runPackageAcceptanceProfile(params: {
|
||||
return { outputs, result };
|
||||
}
|
||||
|
||||
function runPackageAcceptanceRegistryInputValidation(params: {
|
||||
candidateArtifactJson?: string;
|
||||
prepublishPluginRegistryJson?: string;
|
||||
}) {
|
||||
const job = workflowJob(PACKAGE_ACCEPTANCE_WORKFLOW, "resolve_package");
|
||||
const script = workflowStep(job, "Validate prerelease plugin registry input").run;
|
||||
if (!script) {
|
||||
throw new Error("Expected package acceptance registry input validation script");
|
||||
}
|
||||
const workdir = tempDirs.make("package-acceptance-registry-input-");
|
||||
const outputPath = resolve(workdir, "github-output");
|
||||
const result = spawnSync("bash", ["-c", script], {
|
||||
encoding: "utf8",
|
||||
env: {
|
||||
CANDIDATE_ARTIFACT_JSON: params.candidateArtifactJson ?? "",
|
||||
GITHUB_OUTPUT: outputPath,
|
||||
PATH: process.env.PATH,
|
||||
PREPUBLISH_PLUGIN_REGISTRY_JSON: params.prepublishPluginRegistryJson ?? "",
|
||||
},
|
||||
});
|
||||
const output = result.status === 0 ? readFileSync(outputPath, "utf8") : "";
|
||||
return { output, result };
|
||||
}
|
||||
|
||||
function packageAcceptanceRegistryTuple(overrides: Record<string, string> = {}) {
|
||||
return {
|
||||
prepublishPluginRegistryArtifactName: "docker-e2e-prepublish-plugin-registry-123-2",
|
||||
prepublishPluginRegistryArtifactId: "456",
|
||||
prepublishPluginRegistryArtifactDigest: "a".repeat(64),
|
||||
prepublishPluginRegistryArtifactRunId: "123",
|
||||
prepublishPluginRegistryArtifactRunAttempt: "2",
|
||||
prepublishPluginRegistryManifestSha256: "b".repeat(64),
|
||||
...overrides,
|
||||
};
|
||||
}
|
||||
|
||||
function runPackageAcceptanceResolveScript(params: {
|
||||
prepublishPluginRegistryJson?: string;
|
||||
source: "artifact" | "npm" | "ref" | "trusted-url" | "url";
|
||||
telegramMode: "mock-openai" | "none";
|
||||
}) {
|
||||
const job = workflowJob(PACKAGE_ACCEPTANCE_WORKFLOW, "resolve_package");
|
||||
const script = workflowStep(job, "Resolve package candidate").run;
|
||||
if (!script) {
|
||||
throw new Error("Expected package acceptance resolve script");
|
||||
}
|
||||
const workdir = tempDirs.make("package-acceptance-resolve-");
|
||||
const binDir = resolve(workdir, "bin");
|
||||
const capturePath = resolve(workdir, "node-args");
|
||||
const outputPath = resolve(workdir, "github-output");
|
||||
const artifactDir = resolve(workdir, ".artifacts/package-candidate-input");
|
||||
mkdirSync(binDir, { recursive: true });
|
||||
mkdirSync(artifactDir, { recursive: true });
|
||||
const nodePath = resolve(binDir, "node");
|
||||
const artifactPath = resolve(artifactDir, "openclaw-current.tgz");
|
||||
const artifactBody = "package acceptance artifact";
|
||||
writeFileSync(artifactPath, artifactBody);
|
||||
writeFileSync(
|
||||
nodePath,
|
||||
`#!/bin/sh
|
||||
printf "%s\\n" "$@" > "$CAPTURE_PATH"
|
||||
if [ "$SOURCE" = "artifact" ]; then
|
||||
mkdir -p .artifacts/docker-e2e-package
|
||||
printf '{"name":"openclaw","sha256":"%s","packageSourceSha":"%s","version":"%s"}\\n' \
|
||||
"$PACKAGE_SHA256" "$PACKAGE_SOURCE_SHA" "$PACKAGE_VERSION" \
|
||||
> .artifacts/docker-e2e-package/package-candidate.json
|
||||
fi
|
||||
`,
|
||||
);
|
||||
chmodSync(nodePath, 0o755);
|
||||
const result = spawnSync("bash", ["-c", script], {
|
||||
cwd: workdir,
|
||||
encoding: "utf8",
|
||||
env: {
|
||||
CAPTURE_PATH: capturePath,
|
||||
GITHUB_OUTPUT: outputPath,
|
||||
OPENCLAW_TRUSTED_PACKAGE_TOKEN: "",
|
||||
PACKAGE_FILE_NAME: "openclaw-current.tgz",
|
||||
PACKAGE_REF: "HEAD",
|
||||
PACKAGE_SHA256: createHash("sha256").update(artifactBody).digest("hex"),
|
||||
PACKAGE_SOURCE_SHA: "a".repeat(40),
|
||||
PACKAGE_SPEC: "openclaw@beta",
|
||||
PACKAGE_URL: "https://example.invalid/openclaw.tgz",
|
||||
PACKAGE_VERSION: "2026.8.26",
|
||||
PATH: `${binDir}:${process.env.PATH}`,
|
||||
PREPUBLISH_PLUGIN_REGISTRY_JSON: params.prepublishPluginRegistryJson ?? "",
|
||||
SOURCE: params.source,
|
||||
TELEGRAM_MODE: params.telegramMode,
|
||||
TRUSTED_SOURCE_ID: "",
|
||||
},
|
||||
});
|
||||
const args = result.status === 0 ? readFileSync(capturePath, "utf8") : "";
|
||||
return { args, result };
|
||||
}
|
||||
|
||||
function runNpmTelegramInputValidation(overrides: Record<string, string>) {
|
||||
const job = workflowJob(NPM_TELEGRAM_WORKFLOW, "run_package_telegram_e2e");
|
||||
const script = workflowStep(job, "Validate inputs and secrets").run;
|
||||
@@ -2605,6 +2701,17 @@ describe("package acceptance workflow", () => {
|
||||
description: "Acceptance profile: smoke, package, telegram, product, full, or custom",
|
||||
options: ["smoke", "package", "telegram", "product", "full", "custom"],
|
||||
});
|
||||
const dispatchInputs = parsedWorkflow.on?.workflow_dispatch?.inputs;
|
||||
const callInputs = parsedWorkflow.on?.workflow_call?.inputs;
|
||||
expect(dispatchInputs?.prepublish_plugin_registry_json).toBeUndefined();
|
||||
expect(dispatchInputs?.advisory).toEqual(callInputs?.advisory);
|
||||
expect(callInputs?.advisory).toEqual({
|
||||
description: "Treat acceptance failures as advisory for the caller",
|
||||
required: false,
|
||||
default: false,
|
||||
type: "boolean",
|
||||
});
|
||||
expect(Object.keys(dispatchInputs ?? {})).toHaveLength(25);
|
||||
expect(parsedWorkflow.on?.workflow_dispatch?.inputs?.telegram_advisory).toBeUndefined();
|
||||
expect(parsedWorkflow.on?.workflow_call?.inputs?.suite_profile).toMatchObject({
|
||||
default: "package",
|
||||
@@ -2684,17 +2791,17 @@ describe("package acceptance workflow", () => {
|
||||
);
|
||||
const registryInputs = {
|
||||
prepublish_plugin_registry_artifact_name:
|
||||
"${{ fromJSON(inputs.candidate_artifact_json || '{}').prepublishPluginRegistryArtifactName || '' }}",
|
||||
"${{ fromJSON(needs.resolve_package.outputs.prepublish_plugin_registry_json || '{}').prepublishPluginRegistryArtifactName || '' }}",
|
||||
prepublish_plugin_registry_artifact_id:
|
||||
"${{ fromJSON(inputs.candidate_artifact_json || '{}').prepublishPluginRegistryArtifactId || '' }}",
|
||||
"${{ fromJSON(needs.resolve_package.outputs.prepublish_plugin_registry_json || '{}').prepublishPluginRegistryArtifactId || '' }}",
|
||||
prepublish_plugin_registry_artifact_digest:
|
||||
"${{ fromJSON(inputs.candidate_artifact_json || '{}').prepublishPluginRegistryArtifactDigest || '' }}",
|
||||
"${{ fromJSON(needs.resolve_package.outputs.prepublish_plugin_registry_json || '{}').prepublishPluginRegistryArtifactDigest || '' }}",
|
||||
prepublish_plugin_registry_artifact_run_id:
|
||||
"${{ fromJSON(inputs.candidate_artifact_json || '{}').prepublishPluginRegistryArtifactRunId || '' }}",
|
||||
"${{ fromJSON(needs.resolve_package.outputs.prepublish_plugin_registry_json || '{}').prepublishPluginRegistryArtifactRunId || '' }}",
|
||||
prepublish_plugin_registry_artifact_run_attempt:
|
||||
"${{ fromJSON(inputs.candidate_artifact_json || '{}').prepublishPluginRegistryArtifactRunAttempt || '' }}",
|
||||
"${{ fromJSON(needs.resolve_package.outputs.prepublish_plugin_registry_json || '{}').prepublishPluginRegistryArtifactRunAttempt || '' }}",
|
||||
prepublish_plugin_registry_manifest_sha256:
|
||||
"${{ fromJSON(inputs.candidate_artifact_json || '{}').prepublishPluginRegistryManifestSha256 || '' }}",
|
||||
"${{ fromJSON(needs.resolve_package.outputs.prepublish_plugin_registry_json || '{}').prepublishPluginRegistryManifestSha256 || '' }}",
|
||||
};
|
||||
expect(packageTelegram.with).toMatchObject(registryInputs);
|
||||
const registryInputSchema = Object.fromEntries(
|
||||
@@ -2713,7 +2820,11 @@ describe("package acceptance workflow", () => {
|
||||
"Prerelease plugin registry inputs require an artifact-backed OpenClaw package.",
|
||||
);
|
||||
expect(npmTelegramWorkflow).toContain(
|
||||
'expected_registry_name="docker-e2e-prepublish-plugin-registry-${PREPUBLISH_PLUGIN_REGISTRY_ARTIFACT_RUN_ID}-${PREPUBLISH_PLUGIN_REGISTRY_ARTIFACT_RUN_ATTEMPT}"',
|
||||
'expected_registry_suffix="-${PREPUBLISH_PLUGIN_REGISTRY_ARTIFACT_RUN_ID}-${PREPUBLISH_PLUGIN_REGISTRY_ARTIFACT_RUN_ATTEMPT}"',
|
||||
);
|
||||
expect(npmTelegramWorkflow).toContain(
|
||||
'"docker-e2e-prepublish-plugin-registry${expected_registry_suffix}" | \\\n' +
|
||||
' "package-acceptance-telegram-plugin-registry${expected_registry_suffix}"',
|
||||
);
|
||||
expect(npmTelegramWorkflow).not.toContain(
|
||||
"Prerelease plugin registry and package artifacts must come from the same workflow run attempt.",
|
||||
@@ -2729,6 +2840,17 @@ describe("package acceptance workflow", () => {
|
||||
expect(dockerAcceptance.with?.ref).toBe(
|
||||
"${{ needs.resolve_package.outputs.package_source_sha || inputs.workflow_ref }}",
|
||||
);
|
||||
expect(dockerAcceptance.with?.advisory).toBe("${{ inputs.advisory || false }}");
|
||||
expect(dockerAcceptanceRegistry.with?.advisory).toBe("${{ inputs.advisory || false }}");
|
||||
expect(dockerAcceptance.with?.prepublish_plugin_registry_artifact_name).toContain(
|
||||
"startsWith(",
|
||||
);
|
||||
expect(dockerAcceptance.with?.prepublish_plugin_registry_artifact_name).toContain(
|
||||
"'docker-e2e-prepublish-plugin-registry-'",
|
||||
);
|
||||
expect(packageTelegram.with?.prepublish_plugin_registry_artifact_name).not.toContain(
|
||||
"startsWith(",
|
||||
);
|
||||
expect(npm12Install.if).toBe("inputs.suite_profile != 'telegram'");
|
||||
expect(dockerAcceptance.if).toBe(
|
||||
"inputs.suite_profile != 'telegram' && inputs.shared_image_policy == 'no-push-artifact'",
|
||||
@@ -2771,6 +2893,101 @@ describe("package acceptance workflow", () => {
|
||||
expect(workflow).toContain("Published upgrade survivor scenarios:");
|
||||
});
|
||||
|
||||
it("normalizes one closed prerelease registry tuple before child workflows", () => {
|
||||
const tuple = packageAcceptanceRegistryTuple();
|
||||
const direct = runPackageAcceptanceRegistryInputValidation({
|
||||
prepublishPluginRegistryJson: JSON.stringify(tuple),
|
||||
});
|
||||
expect(direct.result.status, direct.result.stderr).toBe(0);
|
||||
expect(direct.output).toContain(`json=${JSON.stringify(tuple)}\n`);
|
||||
|
||||
const candidate = runPackageAcceptanceRegistryInputValidation({
|
||||
candidateArtifactJson: JSON.stringify({
|
||||
imageArtifactName: "image-123-2",
|
||||
...tuple,
|
||||
}),
|
||||
});
|
||||
expect(candidate.result.status, candidate.result.stderr).toBe(0);
|
||||
expect(candidate.output).toContain(`json=${JSON.stringify(tuple)}\n`);
|
||||
|
||||
const identical = runPackageAcceptanceRegistryInputValidation({
|
||||
candidateArtifactJson: JSON.stringify(tuple),
|
||||
prepublishPluginRegistryJson: JSON.stringify(tuple),
|
||||
});
|
||||
expect(identical.result.status, identical.result.stderr).toBe(0);
|
||||
expect(identical.output).toContain(`json=${JSON.stringify(tuple)}\n`);
|
||||
});
|
||||
|
||||
it("rejects partial or ambiguous prerelease registry tuples before child workflows", () => {
|
||||
const tuple = packageAcceptanceRegistryTuple();
|
||||
const partial = runPackageAcceptanceRegistryInputValidation({
|
||||
prepublishPluginRegistryJson: JSON.stringify({
|
||||
prepublishPluginRegistryArtifactId: tuple.prepublishPluginRegistryArtifactId,
|
||||
}),
|
||||
});
|
||||
expect(partial.result.status).toBe(1);
|
||||
expect(partial.result.stderr).toContain(
|
||||
"Prerelease plugin registry JSON must contain one complete immutable tuple.",
|
||||
);
|
||||
|
||||
const ambiguous = runPackageAcceptanceRegistryInputValidation({
|
||||
candidateArtifactJson: JSON.stringify(tuple),
|
||||
prepublishPluginRegistryJson: JSON.stringify(
|
||||
packageAcceptanceRegistryTuple({
|
||||
prepublishPluginRegistryArtifactId: "789",
|
||||
}),
|
||||
),
|
||||
});
|
||||
expect(ambiguous.result.status).toBe(1);
|
||||
expect(ambiguous.result.stderr).toContain("Prerelease plugin registry inputs disagree.");
|
||||
});
|
||||
|
||||
it("generates a Telegram-only registry only for direct ref or artifact candidates", () => {
|
||||
for (const source of ["ref", "artifact"] as const) {
|
||||
const generated = runPackageAcceptanceResolveScript({
|
||||
source,
|
||||
telegramMode: "mock-openai",
|
||||
});
|
||||
expect(generated.result.status, generated.result.stderr).toBe(0);
|
||||
expect(generated.args).toContain("--plugin-registry-output-dir\n");
|
||||
expect(generated.args).toContain(".artifacts/package-acceptance-telegram-plugin-registry\n");
|
||||
expect(generated.args).toContain('--required-plugin-packages-json\n["@openclaw/codex"]\n');
|
||||
}
|
||||
|
||||
for (const source of ["npm", "url", "trusted-url"] as const) {
|
||||
const skipped = runPackageAcceptanceResolveScript({
|
||||
source,
|
||||
telegramMode: "mock-openai",
|
||||
});
|
||||
expect(skipped.result.status, skipped.result.stderr).toBe(0);
|
||||
expect(skipped.args).not.toContain("--plugin-registry-output-dir");
|
||||
}
|
||||
|
||||
const telegramDisabled = runPackageAcceptanceResolveScript({
|
||||
source: "ref",
|
||||
telegramMode: "none",
|
||||
});
|
||||
expect(telegramDisabled.result.status, telegramDisabled.result.stderr).toBe(0);
|
||||
expect(telegramDisabled.args).not.toContain("--plugin-registry-output-dir");
|
||||
});
|
||||
|
||||
it("reuses a supplied registry and keeps generated artifact names distinct from Docker", () => {
|
||||
const tuple = packageAcceptanceRegistryTuple();
|
||||
const reused = runPackageAcceptanceResolveScript({
|
||||
prepublishPluginRegistryJson: JSON.stringify(tuple),
|
||||
source: "ref",
|
||||
telegramMode: "mock-openai",
|
||||
});
|
||||
expect(reused.result.status, reused.result.stderr).toBe(0);
|
||||
expect(reused.args).not.toContain("--plugin-registry-output-dir");
|
||||
|
||||
const workflow = readFileSync(PACKAGE_ACCEPTANCE_WORKFLOW, "utf8");
|
||||
expect(workflow).toContain(
|
||||
"package-acceptance-telegram-plugin-registry-${{ github.run_id }}-${{ github.run_attempt }}",
|
||||
);
|
||||
expect(workflow).toContain('"docker-e2e-prepublish-plugin-registry-" +');
|
||||
});
|
||||
|
||||
it("selects one normalized Telegram scenario without enabling broad acceptance lanes", () => {
|
||||
const { outputs, result } = runPackageAcceptanceProfile({
|
||||
suiteProfile: "telegram",
|
||||
@@ -4544,6 +4761,8 @@ describe("package artifact reuse", () => {
|
||||
"${{ (needs.resolve_target.outputs.package_acceptance_package_spec == '' && needs.resolve_target.outputs.package_mode != 'published') && needs.prepare_release_package.outputs.package_sha256 || '' }}",
|
||||
package_source_sha: "${{ needs.prepare_release_package.outputs.source_sha }}",
|
||||
package_version: "${{ needs.prepare_release_package.outputs.package_version }}",
|
||||
prepublish_plugin_registry_json:
|
||||
"${{ needs.prepare_release_package.outputs.prepublish_plugin_registry_json }}",
|
||||
suite_profile: "custom",
|
||||
});
|
||||
expect(releaseChecksTargetSummary.env).toMatchObject({
|
||||
@@ -4556,7 +4775,7 @@ describe("package artifact reuse", () => {
|
||||
enable_prepublish_plugin_registry:
|
||||
'${{ contains(fromJSON(\'["artifact","ref"]\'), inputs.source) }}',
|
||||
prepublish_plugin_registry_manifest_sha256:
|
||||
"${{ fromJSON(inputs.candidate_artifact_json || '{}').prepublishPluginRegistryManifestSha256 || '' }}",
|
||||
"${{ startsWith(fromJSON(needs.resolve_package.outputs.prepublish_plugin_registry_json || '{}').prepublishPluginRegistryArtifactName || '', 'docker-e2e-prepublish-plugin-registry-') && fromJSON(needs.resolve_package.outputs.prepublish_plugin_registry_json || '{}').prepublishPluginRegistryManifestSha256 || '' }}",
|
||||
});
|
||||
expect(workflow).toContain(
|
||||
"candidate_artifact_json cannot be combined with release package specs.",
|
||||
@@ -5872,6 +6091,14 @@ describe("package artifact reuse", () => {
|
||||
|
||||
expect(runNpmTelegramInputValidation(packageTuple).status).toBe(0);
|
||||
expect(runNpmTelegramInputValidation({ ...packageTuple, ...registryTuple }).status).toBe(0);
|
||||
expect(
|
||||
runNpmTelegramInputValidation({
|
||||
...packageTuple,
|
||||
...registryTuple,
|
||||
PREPUBLISH_PLUGIN_REGISTRY_ARTIFACT_NAME:
|
||||
"package-acceptance-telegram-plugin-registry-123-1",
|
||||
}).status,
|
||||
).toBe(0);
|
||||
|
||||
const partial = runNpmTelegramInputValidation({
|
||||
...packageTuple,
|
||||
|
||||
@@ -588,13 +588,14 @@ describe("scripts/lib/plugin-prerelease-test-plan.mts", () => {
|
||||
"${{ github.event_name == 'workflow_dispatch' && 'false' || steps.docs_scope.outputs.docs_only }}",
|
||||
OPENCLAW_CI_EVENT_NAME: "${{ github.event_name }}",
|
||||
OPENCLAW_CI_HISTORICAL_TARGET: "${{ steps.historical_target.outputs.eligible || 'false' }}",
|
||||
OPENCLAW_CI_RELEASE_GATE: "${{ inputs.release_gate && 'true' || 'false' }}",
|
||||
OPENCLAW_CI_RELEASE_CANDIDATE_TARGET:
|
||||
"${{ steps.release_candidate_target.outputs.eligible || 'false' }}",
|
||||
OPENCLAW_CI_TARGET_CONTEXT_TARGET:
|
||||
"${{ steps.target_context_target.outputs.eligible || 'false' }}",
|
||||
OPENCLAW_CI_REPOSITORY: "${{ github.repository }}",
|
||||
OPENCLAW_CI_RUNNER_BACKEND:
|
||||
"${{ (github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name != github.repository) && 'github' || vars.OPENCLAW_CI_RUNNER_BACKEND }}",
|
||||
"${{ (github.event_name == 'workflow_dispatch' || (github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name != github.repository)) && 'github' || vars.OPENCLAW_CI_RUNNER_BACKEND }}",
|
||||
OPENCLAW_CI_RUN_ANDROID:
|
||||
"${{ github.event_name == 'workflow_dispatch' && (inputs.release_gate || inputs.include_android) && 'true' || steps.changed_scope.outputs.run_android || 'false' }}",
|
||||
OPENCLAW_CI_RUN_CONTROL_UI_I18N:
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
// Resolve Openclaw Package Candidate tests cover resolve openclaw package candidate script behavior.
|
||||
import { execFile, spawn } from "node:child_process";
|
||||
import { existsSync, readFileSync } from "node:fs";
|
||||
import { access, mkdir, mkdtemp, readFile, rm, writeFile } from "node:fs/promises";
|
||||
import { access, chmod, mkdir, mkdtemp, readFile, rm, writeFile } from "node:fs/promises";
|
||||
import { tmpdir } from "node:os";
|
||||
import path from "node:path";
|
||||
import { pathToFileURL } from "node:url";
|
||||
@@ -14,6 +14,7 @@ import {
|
||||
downloadUrl,
|
||||
findSingleTarballForTest,
|
||||
loadTrustedPackageSource,
|
||||
main,
|
||||
moveNewestPackedTarballForTest,
|
||||
parseArgs,
|
||||
readArtifactPackageCandidateMetadata,
|
||||
@@ -22,8 +23,10 @@ import {
|
||||
runCommandForTest,
|
||||
validateOpenClawPackageSpec,
|
||||
} from "../../scripts/resolve-openclaw-package-candidate.mts";
|
||||
import { useAutoCleanupTempDirTracker } from "../helpers/temp-dir.js";
|
||||
|
||||
const tempDirs: string[] = [];
|
||||
const autoTempDirs = useAutoCleanupTempDirTracker(afterEach);
|
||||
|
||||
type LookupAddress = { address: string; family: number };
|
||||
|
||||
@@ -42,6 +45,99 @@ async function missing(file: string): Promise<boolean> {
|
||||
);
|
||||
}
|
||||
|
||||
async function createPackageTarball(
|
||||
dir: string,
|
||||
buildInfo?: string | { commit: string },
|
||||
): Promise<string> {
|
||||
const root = path.join(dir, "package");
|
||||
await mkdir(path.join(root, "dist"), { recursive: true });
|
||||
await writeFile(
|
||||
path.join(root, "package.json"),
|
||||
JSON.stringify({ name: "openclaw", version: "2026.8.1" }),
|
||||
);
|
||||
if (buildInfo !== undefined) {
|
||||
await writeFile(
|
||||
path.join(root, "dist", "build-info.json"),
|
||||
typeof buildInfo === "string" ? buildInfo : JSON.stringify(buildInfo),
|
||||
);
|
||||
}
|
||||
const tarball = path.join(dir, "openclaw.tgz");
|
||||
await new Promise<void>((resolve, reject) => {
|
||||
execFile("tar", ["-czf", tarball, "-C", dir, "package"], (error) => {
|
||||
if (error) {
|
||||
reject(toLintErrorObject(error, "Non-Error rejection"));
|
||||
return;
|
||||
}
|
||||
resolve();
|
||||
});
|
||||
});
|
||||
return tarball;
|
||||
}
|
||||
|
||||
async function createArtifactFixture(
|
||||
prefix: string,
|
||||
{
|
||||
buildInfo,
|
||||
packageSourceSha,
|
||||
}: { buildInfo?: string | { commit: string }; packageSourceSha?: string },
|
||||
) {
|
||||
const dir = autoTempDirs.make(prefix);
|
||||
const artifactDir = path.join(dir, "artifact");
|
||||
const binDir = path.join(dir, "bin");
|
||||
const gitLog = path.join(dir, "git.log");
|
||||
const nodeLog = path.join(dir, "node.log");
|
||||
await mkdir(artifactDir);
|
||||
await mkdir(binDir);
|
||||
await createPackageTarball(artifactDir, buildInfo);
|
||||
if (packageSourceSha !== undefined) {
|
||||
await writeFile(
|
||||
path.join(artifactDir, "package-candidate.json"),
|
||||
JSON.stringify({ packageSourceSha }),
|
||||
);
|
||||
}
|
||||
await writeFile(
|
||||
path.join(binDir, "git"),
|
||||
`#!/bin/sh
|
||||
printf '%s\\n' "$*" >> "$FAKE_GIT_LOG"
|
||||
exit 99
|
||||
`,
|
||||
);
|
||||
await writeFile(
|
||||
path.join(binDir, "node"),
|
||||
`#!/bin/sh
|
||||
printf '%s\\n' "$*" >> "$FAKE_NODE_LOG"
|
||||
exit 0
|
||||
`,
|
||||
);
|
||||
await chmod(path.join(binDir, "git"), 0o755);
|
||||
await chmod(path.join(binDir, "node"), 0o755);
|
||||
return {
|
||||
artifactDir,
|
||||
binDir,
|
||||
dir,
|
||||
gitLog,
|
||||
nodeLog,
|
||||
registryDir: path.join(dir, "registry"),
|
||||
};
|
||||
}
|
||||
|
||||
async function withArtifactFixtureCommands<T>(
|
||||
fixture: Awaited<ReturnType<typeof createArtifactFixture>>,
|
||||
run: () => Promise<T>,
|
||||
): Promise<T> {
|
||||
const previousPath = process.env.PATH;
|
||||
process.env.FAKE_GIT_LOG = fixture.gitLog;
|
||||
process.env.FAKE_NODE_LOG = fixture.nodeLog;
|
||||
process.env.PATH = `${fixture.binDir}:${previousPath}`;
|
||||
try {
|
||||
return await run();
|
||||
} finally {
|
||||
process.env.PATH = previousPath;
|
||||
delete process.env.FAKE_GIT_LOG;
|
||||
delete process.env.FAKE_NODE_LOG;
|
||||
}
|
||||
}
|
||||
|
||||
function isProcessAlive(pid: number): boolean {
|
||||
try {
|
||||
process.kill(pid, 0);
|
||||
@@ -1267,6 +1363,191 @@ describe("resolve-openclaw-package-candidate", () => {
|
||||
});
|
||||
});
|
||||
|
||||
it.each([
|
||||
["without a registry", false],
|
||||
["with a registry", true],
|
||||
])("rejects artifact provenance mismatches %s before side effects", async (_label, registry) => {
|
||||
const metadataSha = "66ce632b9b7c5c7fdd3e66c739687d51638ad6e2";
|
||||
const buildInfoSha = "77df743c0c8d6d80ee4f77d84a798e62749be7f3";
|
||||
const fixture = await createArtifactFixture("openclaw-artifact-provenance-mismatch-", {
|
||||
buildInfo: { commit: buildInfoSha.toUpperCase() },
|
||||
packageSourceSha: metadataSha.toUpperCase(),
|
||||
});
|
||||
|
||||
await withArtifactFixtureCommands(fixture, async () => {
|
||||
await expect(
|
||||
main([
|
||||
"--source",
|
||||
"artifact",
|
||||
"--artifact-dir",
|
||||
fixture.artifactDir,
|
||||
"--output-dir",
|
||||
path.join(fixture.dir, "output"),
|
||||
...(registry
|
||||
? [
|
||||
"--plugin-registry-output-dir",
|
||||
fixture.registryDir,
|
||||
"--required-plugin-packages-json",
|
||||
'["@openclaw/codex"]',
|
||||
]
|
||||
: []),
|
||||
]),
|
||||
).rejects.toThrow(
|
||||
`artifact packageSourceSha ${metadataSha} does not match package build-info commit ${buildInfoSha}`,
|
||||
);
|
||||
});
|
||||
await expect(missing(fixture.gitLog)).resolves.toBe(true);
|
||||
await expect(missing(fixture.registryDir)).resolves.toBe(true);
|
||||
});
|
||||
|
||||
it("uses normalized artifact build-info provenance when metadata is absent", async () => {
|
||||
const sourceSha = "66ce632b9b7c5c7fdd3e66c739687d51638ad6e2";
|
||||
const fixture = await createArtifactFixture("openclaw-artifact-build-info-fallback-", {
|
||||
buildInfo: { commit: sourceSha.toUpperCase() },
|
||||
});
|
||||
const metadataPath = path.join(fixture.dir, "resolved.json");
|
||||
|
||||
await withArtifactFixtureCommands(fixture, async () => {
|
||||
await main([
|
||||
"--source",
|
||||
"artifact",
|
||||
"--artifact-dir",
|
||||
fixture.artifactDir,
|
||||
"--output-dir",
|
||||
path.join(fixture.dir, "output"),
|
||||
"--metadata",
|
||||
metadataPath,
|
||||
]);
|
||||
});
|
||||
|
||||
const metadata = JSON.parse(await readFile(metadataPath, "utf8")) as Record<string, unknown>;
|
||||
expect(metadata.packageSourceSha).toBe(sourceSha);
|
||||
expect(metadata.packageTrustedReason).toBe("package-build-info");
|
||||
});
|
||||
|
||||
it("requires artifact build-info only when preparing a registry", async () => {
|
||||
const sourceSha = "66ce632b9b7c5c7fdd3e66c739687d51638ad6e2";
|
||||
const fixture = await createArtifactFixture("openclaw-artifact-missing-build-info-", {
|
||||
packageSourceSha: sourceSha,
|
||||
});
|
||||
const metadataPath = path.join(fixture.dir, "resolved.json");
|
||||
|
||||
await withArtifactFixtureCommands(fixture, async () => {
|
||||
await main([
|
||||
"--source",
|
||||
"artifact",
|
||||
"--artifact-dir",
|
||||
fixture.artifactDir,
|
||||
"--output-dir",
|
||||
path.join(fixture.dir, "output-without-registry"),
|
||||
"--metadata",
|
||||
metadataPath,
|
||||
]);
|
||||
await expect(
|
||||
main([
|
||||
"--source",
|
||||
"artifact",
|
||||
"--artifact-dir",
|
||||
fixture.artifactDir,
|
||||
"--output-dir",
|
||||
path.join(fixture.dir, "output-with-registry"),
|
||||
"--plugin-registry-output-dir",
|
||||
fixture.registryDir,
|
||||
"--required-plugin-packages-json",
|
||||
'["@openclaw/codex"]',
|
||||
]),
|
||||
).rejects.toThrow(
|
||||
"source=artifact requires a valid package build-info commit for prerelease plugin registry creation",
|
||||
);
|
||||
});
|
||||
|
||||
const metadata = JSON.parse(await readFile(metadataPath, "utf8")) as Record<string, unknown>;
|
||||
expect(metadata.packageSourceSha).toBe(sourceSha);
|
||||
await expect(missing(fixture.gitLog)).resolves.toBe(true);
|
||||
await expect(missing(fixture.registryDir)).resolves.toBe(true);
|
||||
});
|
||||
|
||||
it("rejects malformed artifact build-info before package validation", async () => {
|
||||
const fixture = await createArtifactFixture("openclaw-artifact-malformed-build-info-", {
|
||||
buildInfo: "{not-json",
|
||||
packageSourceSha: "66ce632b9b7c5c7fdd3e66c739687d51638ad6e2",
|
||||
});
|
||||
|
||||
await withArtifactFixtureCommands(fixture, async () => {
|
||||
await expect(
|
||||
main([
|
||||
"--source",
|
||||
"artifact",
|
||||
"--artifact-dir",
|
||||
fixture.artifactDir,
|
||||
"--output-dir",
|
||||
path.join(fixture.dir, "output"),
|
||||
]),
|
||||
).rejects.toBeInstanceOf(SyntaxError);
|
||||
});
|
||||
await expect(missing(fixture.nodeLog)).resolves.toBe(true);
|
||||
});
|
||||
|
||||
it("validates the normalized artifact source SHA before registry preparation", async () => {
|
||||
const dir = autoTempDirs.make("openclaw-artifact-registry-source-");
|
||||
const artifactDir = path.join(dir, "artifact");
|
||||
const binDir = path.join(dir, "bin");
|
||||
const gitLog = path.join(dir, "git.log");
|
||||
const sourceSha = "66ce632b9b7c5c7fdd3e66c739687d51638ad6e2";
|
||||
await mkdir(artifactDir);
|
||||
await mkdir(binDir);
|
||||
await createPackageTarball(artifactDir, { commit: sourceSha });
|
||||
await writeFile(
|
||||
path.join(artifactDir, "package-candidate.json"),
|
||||
JSON.stringify({ packageSourceSha: sourceSha.toUpperCase() }),
|
||||
);
|
||||
const fakeGit = path.join(binDir, "git");
|
||||
await writeFile(
|
||||
fakeGit,
|
||||
`#!/bin/sh
|
||||
printf '%s\\n' "$*" >> "$FAKE_GIT_LOG"
|
||||
case "$1" in
|
||||
fetch) exit 0 ;;
|
||||
rev-parse) printf '%s\\n' "$FAKE_SOURCE_SHA" ;;
|
||||
merge-base) exit 1 ;;
|
||||
tag | for-each-ref) exit 0 ;;
|
||||
*) exit 99 ;;
|
||||
esac
|
||||
`,
|
||||
);
|
||||
await chmod(fakeGit, 0o755);
|
||||
|
||||
const previousPath = process.env.PATH;
|
||||
process.env.FAKE_GIT_LOG = gitLog;
|
||||
process.env.FAKE_SOURCE_SHA = sourceSha;
|
||||
process.env.PATH = `${binDir}:${previousPath}`;
|
||||
try {
|
||||
await expect(
|
||||
main([
|
||||
"--source",
|
||||
"artifact",
|
||||
"--artifact-dir",
|
||||
artifactDir,
|
||||
"--output-dir",
|
||||
path.join(dir, "output"),
|
||||
"--plugin-registry-output-dir",
|
||||
path.join(dir, "registry"),
|
||||
"--required-plugin-packages-json",
|
||||
'["@openclaw/codex"]',
|
||||
]),
|
||||
).rejects.toThrow(
|
||||
`package_ref ${sourceSha} resolved to ${sourceSha}, which is not reachable from an OpenClaw branch or release tag`,
|
||||
);
|
||||
} finally {
|
||||
process.env.PATH = previousPath;
|
||||
delete process.env.FAKE_GIT_LOG;
|
||||
delete process.env.FAKE_SOURCE_SHA;
|
||||
}
|
||||
await expect(readFile(gitLog, "utf8")).resolves.toContain(
|
||||
`rev-parse --verify ${sourceSha}^{commit}`,
|
||||
);
|
||||
});
|
||||
|
||||
it("normalizes whitespace-only artifact package source SHAs to absent", async () => {
|
||||
const dir = await mkdtemp(path.join(tmpdir(), "openclaw-package-candidate-empty-sha-"));
|
||||
tempDirs.push(dir);
|
||||
@@ -1341,22 +1622,8 @@ describe("resolve-openclaw-package-candidate", () => {
|
||||
it("reads the source SHA from packed npm build metadata", async () => {
|
||||
const dir = await mkdtemp(path.join(tmpdir(), "openclaw-package-build-info-"));
|
||||
tempDirs.push(dir);
|
||||
const root = path.join(dir, "package");
|
||||
await mkdir(path.join(root, "dist"), { recursive: true });
|
||||
await writeFile(path.join(root, "package.json"), JSON.stringify({ name: "openclaw" }));
|
||||
await writeFile(
|
||||
path.join(root, "dist", "build-info.json"),
|
||||
JSON.stringify({ commit: "66CE632B9B7C5C7FDD3E66C739687D51638AD6E2" }),
|
||||
);
|
||||
const tarball = path.join(dir, "openclaw.tgz");
|
||||
await new Promise<void>((resolve, reject) => {
|
||||
execFile("tar", ["-czf", tarball, "-C", dir, "package"], (error) => {
|
||||
if (error) {
|
||||
reject(toLintErrorObject(error, "Non-Error rejection"));
|
||||
return;
|
||||
}
|
||||
resolve();
|
||||
});
|
||||
const tarball = await createPackageTarball(dir, {
|
||||
commit: "66CE632B9B7C5C7FDD3E66C739687D51638AD6E2",
|
||||
});
|
||||
|
||||
await expect(readPackageBuildSourceSha(tarball)).resolves.toBe(
|
||||
|
||||
Reference in New Issue
Block a user