mirror of
https://github.com/openclaw/openclaw.git
synced 2026-04-10 16:51:13 +00:00
ci: use oidc token for npm promotion
This commit is contained in:
@@ -116,6 +116,9 @@ node --import tsx scripts/openclaw-npm-postpublish-verify.ts <published-version>
|
||||
## Use the right auth flow
|
||||
|
||||
- OpenClaw publish uses GitHub trusted publishing.
|
||||
- Stable npm promotion from `beta` to `latest` also uses GitHub Actions OIDC by
|
||||
exchanging the workflow token for a short-lived npm registry token; it should
|
||||
not depend on a stored `NPM_TOKEN`.
|
||||
- The publish run must be started manually with `workflow_dispatch`.
|
||||
- The npm workflow and the private mac publish workflow accept
|
||||
`preflight_only=true` to run validation/build/package steps without uploading
|
||||
|
||||
@@ -23,6 +23,7 @@ jobs:
|
||||
environment: npm-release
|
||||
permissions:
|
||||
contents: read
|
||||
id-token: write
|
||||
steps:
|
||||
- name: Validate version input format
|
||||
env:
|
||||
@@ -69,16 +70,15 @@ jobs:
|
||||
|
||||
- name: Promote beta to latest
|
||||
env:
|
||||
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
|
||||
RELEASE_VERSION: ${{ inputs.version }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
registry_token="$(node scripts/npm-oidc-exchange-token.mjs openclaw)"
|
||||
userconfig="$(mktemp)"
|
||||
trap 'rm -f "${userconfig}"' EXIT
|
||||
chmod 0600 "${userconfig}"
|
||||
printf '%s\n' "//registry.npmjs.org/:_authToken=${NODE_AUTH_TOKEN}" > "${userconfig}"
|
||||
printf '%s\n' "//registry.npmjs.org/:_authToken=${registry_token}" > "${userconfig}"
|
||||
|
||||
NPM_CONFIG_USERCONFIG="${userconfig}" npm whoami >/dev/null
|
||||
NPM_CONFIG_USERCONFIG="${userconfig}" \
|
||||
npm dist-tag add "openclaw@${RELEASE_VERSION}" latest
|
||||
promoted_latest="$(npm view openclaw dist-tags.latest)"
|
||||
|
||||
@@ -52,6 +52,7 @@ OpenClaw has three public release lanes:
|
||||
- stable npm releases default to `beta`
|
||||
- stable npm publish can target `latest` explicitly via workflow input
|
||||
- stable npm promotion from `beta` to `latest` is still available as a separate manual workflow step
|
||||
- that promotion workflow exchanges the GitHub Actions OIDC token for a short-lived npm registry token instead of depending on a stored `NPM_TOKEN`
|
||||
- public `macOS Release` is validation-only
|
||||
- real private mac publish must pass successful private mac
|
||||
`preflight_run_id` and `validate_run_id`
|
||||
@@ -108,6 +109,9 @@ When cutting a stable npm release:
|
||||
the exact stable version when you want to move that published build to
|
||||
`latest`
|
||||
|
||||
The promotion workflow still requires the `npm-release` environment approval,
|
||||
but it no longer depends on a long-lived npm publish token.
|
||||
|
||||
That keeps the direct publish path and the beta-first promotion path both
|
||||
documented and operator-visible.
|
||||
|
||||
|
||||
68
scripts/npm-oidc-exchange-token.mjs
Normal file
68
scripts/npm-oidc-exchange-token.mjs
Normal file
@@ -0,0 +1,68 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
const packageName = process.argv[2];
|
||||
|
||||
if (!packageName) {
|
||||
console.error("usage: node scripts/npm-oidc-exchange-token.mjs <package-name>");
|
||||
process.exit(2);
|
||||
}
|
||||
|
||||
const requestUrl = process.env.ACTIONS_ID_TOKEN_REQUEST_URL;
|
||||
const requestToken = process.env.ACTIONS_ID_TOKEN_REQUEST_TOKEN;
|
||||
|
||||
if (!requestUrl || !requestToken) {
|
||||
console.error(
|
||||
"GitHub OIDC request environment is missing. ACTIONS_ID_TOKEN_REQUEST_URL and ACTIONS_ID_TOKEN_REQUEST_TOKEN are required.",
|
||||
);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
async function readJson(response, context) {
|
||||
const text = await response.text();
|
||||
if (!response.ok) {
|
||||
throw new Error(`${context} failed (${response.status}): ${text}`);
|
||||
}
|
||||
try {
|
||||
return JSON.parse(text);
|
||||
} catch (error) {
|
||||
throw new Error(
|
||||
`${context} returned invalid JSON: ${error instanceof Error ? error.message : String(error)}`,
|
||||
{ cause: error },
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const oidcUrl = new URL(requestUrl);
|
||||
oidcUrl.searchParams.set("audience", "npm:registry.npmjs.org");
|
||||
|
||||
const oidcResponse = await fetch(oidcUrl, {
|
||||
headers: {
|
||||
Authorization: `Bearer ${requestToken}`,
|
||||
},
|
||||
});
|
||||
const oidcPayload = await readJson(oidcResponse, "GitHub OIDC token request");
|
||||
const idToken = oidcPayload && typeof oidcPayload.value === "string" ? oidcPayload.value : "";
|
||||
|
||||
if (!idToken) {
|
||||
throw new Error("GitHub OIDC token response did not include a token value.");
|
||||
}
|
||||
|
||||
const exchangeUrl = new URL(
|
||||
`https://registry.npmjs.org/-/npm/v1/oidc/token/exchange/package/${encodeURIComponent(packageName)}`,
|
||||
);
|
||||
|
||||
const exchangeResponse = await fetch(exchangeUrl, {
|
||||
method: "POST",
|
||||
headers: {
|
||||
Authorization: `Bearer ${idToken}`,
|
||||
},
|
||||
});
|
||||
const exchangePayload = await readJson(exchangeResponse, "npm OIDC exchange");
|
||||
const registryToken =
|
||||
exchangePayload && typeof exchangePayload.token === "string" ? exchangePayload.token : "";
|
||||
|
||||
if (!registryToken) {
|
||||
throw new Error("npm OIDC exchange response did not include a registry token.");
|
||||
}
|
||||
|
||||
process.stdout.write(registryToken);
|
||||
Reference in New Issue
Block a user