fix(release): run plugin tooling against source checkout (#121762)

This commit is contained in:
Patrick Erichsen
2026-08-10 16:16:33 -07:00
committed by GitHub
parent a287c2d96b
commit c6b9be2646
5 changed files with 97 additions and 131 deletions
+10 -64
View File
@@ -333,43 +333,14 @@ jobs:
ref: ${{ needs.preview_plugins_npm.outputs.ref_revision }}
fetch-depth: 1
- name: Checkout trusted packaging helper
- name: Checkout trusted packaging tooling
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
with:
persist-credentials: false
ref: ${{ github.workflow_sha }}
path: .release-tooling
fetch-depth: 1
sparse-checkout: |
scripts/generate-npm-package-lock.mjs
scripts/generate-npm-package-lock.mts
scripts/lib/npm-json-output.mts
scripts/lib/plugin-npm-package-manifest.mjs
scripts/lib/plugin-npm-package-manifest.mts
scripts/lib/tsx-cli-shim.mjs
sparse-checkout-cone-mode: false
- name: Overlay trusted packaging helper
run: |
set -euo pipefail
cp \
.release-tooling/scripts/generate-npm-package-lock.mjs \
scripts/generate-npm-package-lock.mjs
cp \
.release-tooling/scripts/generate-npm-package-lock.mts \
scripts/generate-npm-package-lock.mts
cp \
.release-tooling/scripts/lib/npm-json-output.mts \
scripts/lib/npm-json-output.mts
cp \
.release-tooling/scripts/lib/plugin-npm-package-manifest.mjs \
scripts/lib/plugin-npm-package-manifest.mjs
cp \
.release-tooling/scripts/lib/plugin-npm-package-manifest.mts \
scripts/lib/plugin-npm-package-manifest.mts
cp \
.release-tooling/scripts/lib/tsx-cli-shim.mjs \
scripts/lib/tsx-cli-shim.mjs
sparse-checkout: scripts
- name: Setup Node environment
uses: ./.github/actions/setup-node-env
@@ -380,12 +351,12 @@ jobs:
- name: Preview publish command
env:
OPENCLAW_PLUGIN_NPM_PUBLISH_TAG: ${{ inputs.npm_dist_tag == 'extended-stable' && inputs.npm_dist_tag || '' }}
run: bash scripts/plugin-npm-publish.sh --dry-run "${{ matrix.plugin.packageDir }}"
run: bash .release-tooling/scripts/plugin-npm-publish.sh --repo-root "$GITHUB_WORKSPACE" --dry-run "${{ matrix.plugin.packageDir }}"
- name: Preview npm pack contents
env:
OPENCLAW_PLUGIN_NPM_PUBLISH_TAG: ${{ inputs.npm_dist_tag == 'extended-stable' && inputs.npm_dist_tag || '' }}
run: bash scripts/plugin-npm-publish.sh --pack-dry-run "${{ matrix.plugin.packageDir }}"
run: bash .release-tooling/scripts/plugin-npm-publish.sh --repo-root "$GITHUB_WORKSPACE" --pack-dry-run "${{ matrix.plugin.packageDir }}"
- name: Prepare immutable npm preflight artifact
id: preflight_artifact
@@ -416,11 +387,13 @@ jobs:
OPENCLAW_PLUGIN_NPM_RUNTIME_BUILD=0 \
OPENCLAW_PLUGIN_NPM_PACK_OUTPUT_DIR="${artifact_dir}" \
bash scripts/plugin-npm-publish.sh --pack "${PACKAGE_DIR}" > "${pack_output}"
bash .release-tooling/scripts/plugin-npm-publish.sh \
--repo-root "$GITHUB_WORKSPACE" \
--pack "${PACKAGE_DIR}" > "${pack_output}"
node --import tsx --input-type=module - "${pack_output}" "${pack_json}" <<'NODE'
import fs from "node:fs";
import { resolveNpmJsonEntries } from "./scripts/lib/npm-json-output.mts";
import { resolveNpmJsonEntries } from "./.release-tooling/scripts/lib/npm-json-output.mts";
const raw = fs.readFileSync(process.argv[2], "utf8").trim();
let pack;
@@ -1252,33 +1225,7 @@ jobs:
path: .publication-target
fetch-depth: 1
- name: Overlay trusted OIDC packaging helper
if: steps.publication_evidence.outputs.publish_route == 'npm-oidc'
run: |
set -euo pipefail
cp \
scripts/generate-npm-package-lock.mjs \
.publication-target/scripts/generate-npm-package-lock.mjs
cp \
scripts/generate-npm-package-lock.mts \
.publication-target/scripts/generate-npm-package-lock.mts
cp \
scripts/lib/npm-json-output.mts \
.publication-target/scripts/lib/npm-json-output.mts
cp \
scripts/lib/plugin-npm-package-manifest.mjs \
.publication-target/scripts/lib/plugin-npm-package-manifest.mjs
cp \
scripts/lib/plugin-npm-package-manifest.mts \
.publication-target/scripts/lib/plugin-npm-package-manifest.mts
cp \
scripts/lib/tsx-cli-shim.mjs \
.publication-target/scripts/lib/tsx-cli-shim.mjs
cp \
scripts/lib/tsx-cli-shim.mjs \
.publication-target/scripts/lib/tsx-cli-shim.mjs
- name: Setup OIDC publication target
- name: Setup trusted OIDC packaging dependencies
if: steps.publication_evidence.outputs.publish_route == 'npm-oidc'
uses: ./.github/actions/setup-node-env
with:
@@ -1302,11 +1249,10 @@ jobs:
- name: Publish with trusted publisher
if: steps.publication_evidence.outputs.publish_route == 'npm-oidc' && steps.npm_package_version.outputs.already_published != 'true'
working-directory: .publication-target
env:
OPENCLAW_NPM_PUBLISH_AUTH_MODE: trusted-publisher
OPENCLAW_PLUGIN_NPM_PUBLISH_TAG: ${{ inputs.npm_dist_tag == 'extended-stable' && inputs.npm_dist_tag || '' }}
run: bash scripts/plugin-npm-publish.sh --publish "${{ matrix.plugin.packageDir }}"
run: bash scripts/plugin-npm-publish.sh --repo-root .publication-target --publish "${{ matrix.plugin.packageDir }}"
- name: Verify OIDC published runtime
if: steps.publication_evidence.outputs.publish_route == 'npm-oidc'
+4 -1
View File
@@ -34,7 +34,10 @@ type NpmLockOptions = {
installStrategy?: "hoisted" | "nested" | "shallow" | "linked" | "" | null;
};
const ROOT_DIR = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
const SCRIPT_ROOT_DIR = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
const ROOT_DIR = path.resolve(
process.env.OPENCLAW_NPM_PACKAGE_LOCK_REPO_ROOT?.trim() || SCRIPT_ROOT_DIR,
);
const EXACT_VERSION_PATTERN = /^\d+\.\d+\.\d+(?:[-+][0-9A-Za-z.-]+)?$/u;
const STABLE_VERSION_PATTERN = /^(\d+)\.(\d+)\.(\d+)$/u;
const NPM_LOCK_COMMAND_TIMEOUT_MS = 10 * 60 * 1000;
+43 -9
View File
@@ -3,7 +3,7 @@
set -euo pipefail
usage() {
echo "usage: bash scripts/plugin-npm-publish.sh [--dry-run|--pack|--pack-dry-run|--publish] <package-dir>"
echo "usage: bash scripts/plugin-npm-publish.sh [--repo-root <dir>] [--dry-run|--pack|--pack-dry-run|--publish] <package-dir>"
}
if [[ "${1:-}" == "--help" || "${1:-}" == "-h" ]]; then
@@ -11,6 +11,20 @@ if [[ "${1:-}" == "--help" || "${1:-}" == "-h" ]]; then
exit 0
fi
tooling_root="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
repo_root="${tooling_root}"
if [[ "${1:-}" == "--repo-root" ]]; then
if [[ -z "${2:-}" || "${2:-}" == -* ]]; then
echo "--repo-root requires a directory" >&2
exit 2
fi
repo_root="$(cd "$2" && pwd)" || {
echo "repository root is not a directory: $2" >&2
exit 2
}
shift 2
fi
mode="${1:-}"
if [[ "${mode}" != "--dry-run" && "${mode}" != "--pack" && "${mode}" != "--pack-dry-run" && "${mode}" != "--publish" ]]; then
usage >&2
@@ -25,7 +39,8 @@ package_dir=""
if [[ "$#" -gt 0 ]]; then
case "$1" in
-*) echo "unexpected plugin npm package-dir option: $1" >&2; exit 2 ;;
*) package_dir="$1"; shift ;;
/*) package_dir="$1"; shift ;;
*) package_dir="${repo_root}/$1"; shift ;;
esac
fi
if [[ -z "${package_dir}" ]]; then
@@ -41,6 +56,7 @@ if [[ "${mode}" == "--pack" && -z "${OPENCLAW_PLUGIN_NPM_PACK_OUTPUT_DIR:-}" ]];
exit 2
fi
cd "${tooling_root}"
package_name="$(node -e 'const pkg = require(require("node:path").resolve(process.argv[1], "package.json")); console.log(pkg.name)' "${package_dir}")"
package_version="$(node -e 'const pkg = require(require("node:path").resolve(process.argv[1], "package.json")); console.log(pkg.version)' "${package_dir}")"
current_beta_version="$(npm view "${package_name}" dist-tags.beta 2>/dev/null || true)"
@@ -93,6 +109,7 @@ if [[ "${OPENCLAW_NPM_PUBLISH_PROVENANCE:-1}" != "0" && "${OPENCLAW_NPM_PUBLISH_
fi
log "Resolved package dir: ${package_dir}"
log "Resolved repository root: ${repo_root}"
log "Resolved package name: ${package_name}"
log "Resolved package version: ${package_version}"
log "Current beta dist-tag: ${current_beta_version:-<missing>}"
@@ -108,12 +125,20 @@ build_package_runtime() {
return
fi
log "Package-local runtime build: ${package_dir}"
node scripts/lib/plugin-npm-runtime-build.mjs "${package_dir}" >&2
(
cd "${repo_root}"
node "${tooling_root}/scripts/lib/plugin-npm-runtime-build.mjs" "${package_dir}" >&2
)
}
check_package_npm_lock() {
log "Package-local npm package-lock check: ${package_dir}"
node scripts/generate-npm-package-lock.mjs --package-dir "${package_dir}" >&2
(
cd "${repo_root}"
OPENCLAW_NPM_PACKAGE_LOCK_REPO_ROOT="${repo_root}" \
node "${tooling_root}/scripts/generate-npm-package-lock.mjs" \
--package-dir "${package_dir}" >&2
)
}
mirror_auth_token=""
@@ -175,9 +200,13 @@ if [[ "${mode}" == "--pack" || "${mode}" == "--pack-dry-run" ]]; then
pack_output_dir="$(cd "${OPENCLAW_PLUGIN_NPM_PACK_OUTPUT_DIR}" && pwd)"
pack_args+=(--pack-destination "${pack_output_dir}")
fi
OPENCLAW_PLUGIN_NPM_BUNDLE_DEPENDENCIES=1 \
node scripts/lib/plugin-npm-package-manifest.mjs --run "${package_dir}" -- \
"${pack_args[@]}"
(
cd "${repo_root}"
OPENCLAW_NPM_PACKAGE_LOCK_REPO_ROOT="${repo_root}" \
OPENCLAW_PLUGIN_NPM_BUNDLE_DEPENDENCIES=1 \
node "${tooling_root}/scripts/lib/plugin-npm-package-manifest.mjs" \
--run "${package_dir}" -- "${pack_args[@]}"
)
exit 0
fi
@@ -185,8 +214,13 @@ fi
cleanup_files=()
trap 'rm -f "${cleanup_files[@]}"' EXIT
run_with_manifest_overlay() {
OPENCLAW_PLUGIN_NPM_BUNDLE_DEPENDENCIES=1 \
node scripts/lib/plugin-npm-package-manifest.mjs --run "${package_dir}" -- "$@"
(
cd "${repo_root}"
OPENCLAW_NPM_PACKAGE_LOCK_REPO_ROOT="${repo_root}" \
OPENCLAW_PLUGIN_NPM_BUNDLE_DEPENDENCIES=1 \
node "${tooling_root}/scripts/lib/plugin-npm-package-manifest.mjs" \
--run "${package_dir}" -- "$@"
)
}
publish_userconfig=""
if [[ -n "${publish_auth_token}" ]]; then
@@ -105,50 +105,28 @@ describe("plugin npm extended-stable workflow", () => {
}
});
it("overlays the complete trusted packaging helper dependency set", () => {
it("runs complete trusted packaging tooling against the frozen source checkout", () => {
const parsed = workflow();
const lockGenerator = readFileSync("scripts/generate-npm-package-lock.mts", "utf8");
expect(lockGenerator).toContain(
'path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..")',
);
expect(lockGenerator).not.toContain("./lib/repo-root.mjs");
const preflightCheckout = step(
parsed.jobs?.preview_plugin_pack,
"Checkout trusted packaging helper",
"Checkout trusted packaging tooling",
);
expect(preflightCheckout.with?.["sparse-checkout"]).toContain(
"scripts/generate-npm-package-lock.mjs",
);
expect(preflightCheckout.with?.["sparse-checkout"]).toContain(
"scripts/generate-npm-package-lock.mts",
);
expect(preflightCheckout.with?.["sparse-checkout"]).toContain(
"scripts/lib/npm-json-output.mts",
);
expect(preflightCheckout.with?.["sparse-checkout"]).toContain(
"scripts/lib/plugin-npm-package-manifest.mjs",
);
expect(preflightCheckout.with?.["sparse-checkout"]).toContain(
"scripts/lib/plugin-npm-package-manifest.mts",
);
expect(preflightCheckout.with?.["sparse-checkout"]).toContain("scripts/lib/tsx-cli-shim.mjs");
expect(preflightCheckout.with).toMatchObject({
ref: "${{ github.workflow_sha }}",
path: ".release-tooling",
"sparse-checkout": "scripts",
});
const previewCommand = step(parsed.jobs?.preview_plugin_pack, "Preview publish command").run;
expect(previewCommand).toContain(".release-tooling/scripts/plugin-npm-publish.sh");
expect(previewCommand).toContain('--repo-root "$GITHUB_WORKSPACE"');
expect(
step(parsed.jobs?.preview_plugin_pack, "Prepare immutable npm preflight artifact").run,
).toContain(".release-tooling/scripts/plugin-npm-publish.sh");
const expectedCopies = [
"scripts/generate-npm-package-lock.mjs",
"scripts/generate-npm-package-lock.mts",
"scripts/lib/npm-json-output.mts",
"scripts/lib/plugin-npm-package-manifest.mjs",
"scripts/lib/plugin-npm-package-manifest.mts",
"scripts/lib/tsx-cli-shim.mjs",
];
for (const helperPath of expectedCopies) {
expect(
step(parsed.jobs?.preview_plugin_pack, "Overlay trusted packaging helper").run,
).toContain(helperPath);
expect(
step(parsed.jobs?.publish_plugins_npm, "Overlay trusted OIDC packaging helper").run,
).toContain(helperPath);
}
const publish = step(parsed.jobs?.publish_plugins_npm, "Publish with trusted publisher");
expect(publish.run).toContain("scripts/plugin-npm-publish.sh");
expect(publish.run).toContain("--repo-root .publication-target");
expect(publish["working-directory"]).toBeUndefined();
});
it("trusts only the canonical monthly branch at the exact checked-out SHA", () => {
@@ -220,9 +198,11 @@ describe("plugin npm extended-stable workflow", () => {
"plugin-npm-package-source-${{ needs.preview_plugins_npm.outputs.ref_revision }}-${{ matrix.plugin.extensionId }}",
);
expect(prepare.if).toBeUndefined();
expect(prepare.run).toContain('bash scripts/plugin-npm-publish.sh --pack "${PACKAGE_DIR}"');
expect(prepare.run).toContain("bash .release-tooling/scripts/plugin-npm-publish.sh");
expect(prepare.run).toContain('--repo-root "$GITHUB_WORKSPACE"');
expect(prepare.run).toContain('--pack "${PACKAGE_DIR}"');
expect(prepare.run).toContain(
'import { resolveNpmJsonEntries } from "./scripts/lib/npm-json-output.mts";',
'import { resolveNpmJsonEntries } from "./.release-tooling/scripts/lib/npm-json-output.mts";',
);
expect(prepare.run).toContain('raw[index] !== "[" && raw[index] !== "{"');
expect(prepare.run).toContain("const entries = resolveNpmJsonEntries(candidate)");
@@ -449,14 +429,11 @@ describe("plugin npm extended-stable workflow", () => {
step(parsed.jobs?.publish_plugins_npm, "Checkout trusted publication tooling").with?.ref,
).toBe("${{ github.workflow_sha }}");
expect(
step(parsed.jobs?.preview_plugin_pack, "Checkout trusted packaging helper").with,
step(parsed.jobs?.preview_plugin_pack, "Checkout trusted packaging tooling").with,
).toMatchObject({
ref: "${{ github.workflow_sha }}",
path: ".release-tooling",
});
expect(
step(parsed.jobs?.preview_plugin_pack, "Overlay trusted packaging helper").run,
).toContain(".release-tooling/scripts/lib/plugin-npm-package-manifest.mjs");
expect(
step(parsed.jobs?.publish_plugins_npm, "Setup trusted publication dependencies").if,
).toContain("npm-token-bootstrap");
@@ -470,14 +447,8 @@ describe("plugin npm extended-stable workflow", () => {
step(parsed.jobs?.publish_plugins_npm, "Checkout OIDC publication target").with?.path,
).toBe(".publication-target");
expect(
step(parsed.jobs?.publish_plugins_npm, "Overlay trusted OIDC packaging helper").run,
).toContain(".publication-target/scripts/lib/plugin-npm-package-manifest.mjs");
expect(step(parsed.jobs?.publish_plugins_npm, "Setup OIDC publication target").uses).toBe(
"./.github/actions/setup-node-env",
);
expect(
step(parsed.jobs?.publish_plugins_npm, "Publish with trusted publisher")["working-directory"],
).toBe(".publication-target");
step(parsed.jobs?.publish_plugins_npm, "Setup trusted OIDC packaging dependencies").uses,
).toBe("./.github/actions/setup-node-env");
expect(parsed.jobs?.reconcile_plugins_npm).toBeUndefined();
expect(readFileSync(workflowPath, "utf8")).not.toContain(
'npm dist-tag add "${PACKAGE_NAME}@${PACKAGE_VERSION}" extended-stable',
+16 -4
View File
@@ -23,7 +23,7 @@ function runPluginPublishWrapper(args: string[], env: NodeJS.ProcessEnv = {}) {
});
}
function makePackage(version: string): { packageDir: string; path: string } {
function makePackage(version: string): { packageDir: string; path: string; root: string } {
const root = mkdtempSync(join(tmpdir(), "openclaw-plugin-publish-test-"));
tempDirs.push(root);
const packageDir = join(root, "plugin");
@@ -37,7 +37,7 @@ function makePackage(version: string): { packageDir: string; path: string } {
const npmPath = join(binDir, "npm");
writeFileSync(npmPath, "#!/bin/sh\nexit 1\n");
chmodSync(npmPath, 0o755);
return { packageDir, path: `${binDir}${delimiter}${process.env.PATH ?? ""}` };
return { packageDir, path: `${binDir}${delimiter}${process.env.PATH ?? ""}`, root };
}
describe("plugin npm publish wrapper", () => {
@@ -46,7 +46,7 @@ describe("plugin npm publish wrapper", () => {
expect(result.status).toBe(0);
expect(result.stdout.trim()).toBe(
"usage: bash scripts/plugin-npm-publish.sh [--dry-run|--pack|--pack-dry-run|--publish] <package-dir>",
"usage: bash scripts/plugin-npm-publish.sh [--repo-root <dir>] [--dry-run|--pack|--pack-dry-run|--publish] <package-dir>",
);
expect(result.stderr).toBe("");
});
@@ -57,10 +57,22 @@ describe("plugin npm publish wrapper", () => {
expect(result.status).toBe(2);
expect(result.stdout).toBe("");
expect(result.stderr.trim()).toBe(
"usage: bash scripts/plugin-npm-publish.sh [--dry-run|--pack|--pack-dry-run|--publish] <package-dir>",
"usage: bash scripts/plugin-npm-publish.sh [--repo-root <dir>] [--dry-run|--pack|--pack-dry-run|--publish] <package-dir>",
);
});
it("runs trusted tooling against an explicit repository root", () => {
const fixture = makePackage("2026.8.1-beta.1");
const result = runPluginPublishWrapper(["--repo-root", fixture.root, "--dry-run", "plugin"], {
PATH: fixture.path,
});
expect(result.status).toBe(0);
expect(result.stdout).toContain(`Resolved repository root: ${fixture.root}`);
expect(result.stdout).toContain(`Resolved package dir: ${fixture.packageDir}`);
expect(result.stdout).toContain("Resolved package name: @openclaw/demo");
});
it("requires an explicit artifact directory for real pack mode", () => {
const result = runPluginPublishWrapper(["--pack", "extensions/telegram"]);