mirror of
https://github.com/openclaw/clawhub.git
synced 2026-08-14 00:47:57 +00:00
chore(release): automate cli github releases (#2331)
This commit is contained in:
@@ -0,0 +1,285 @@
|
||||
name: ClawHub CLI GitHub Release
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
tag:
|
||||
description: Release tag to create or repair, for example v0.17.0
|
||||
required: true
|
||||
type: string
|
||||
main_run_id:
|
||||
description: Optional successful main CI run id to include in release proof
|
||||
required: false
|
||||
type: string
|
||||
preflight_run_id:
|
||||
description: Optional successful CLI npm preflight run id to include in release proof
|
||||
required: false
|
||||
type: string
|
||||
publish_run_id:
|
||||
description: Optional successful CLI npm publish run id to include in release proof
|
||||
required: false
|
||||
type: string
|
||||
update_existing:
|
||||
description: Update an existing GitHub Release instead of failing
|
||||
required: true
|
||||
default: false
|
||||
type: boolean
|
||||
|
||||
concurrency:
|
||||
group: clawhub-cli-github-release-${{ inputs.tag }}
|
||||
cancel-in-progress: false
|
||||
|
||||
permissions: {}
|
||||
|
||||
env:
|
||||
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: "true"
|
||||
NODE_VERSION: "24.x"
|
||||
|
||||
jobs:
|
||||
create_or_update_github_release:
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
actions: read
|
||||
contents: write
|
||||
steps:
|
||||
- name: Checkout release tooling
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
ref: ${{ github.ref }}
|
||||
path: release-tools
|
||||
|
||||
- name: Checkout release tag
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
ref: refs/tags/${{ inputs.tag }}
|
||||
fetch-depth: 0
|
||||
path: release
|
||||
|
||||
- name: Setup Node
|
||||
uses: actions/setup-node@v6
|
||||
with:
|
||||
node-version: ${{ env.NODE_VERSION }}
|
||||
registry-url: https://registry.npmjs.org
|
||||
|
||||
- name: Validate release tag and package metadata
|
||||
env:
|
||||
RELEASE_TAG: ${{ inputs.tag }}
|
||||
RELEASE_MAIN_REF: origin/main
|
||||
run: |
|
||||
set -euo pipefail
|
||||
RELEASE_SHA="$(git rev-parse HEAD)"
|
||||
echo "RELEASE_SHA=$RELEASE_SHA" >> "$GITHUB_ENV"
|
||||
git fetch --no-tags origin +refs/heads/main:refs/remotes/origin/main
|
||||
if ! git merge-base --is-ancestor "$RELEASE_SHA" "$RELEASE_MAIN_REF"; then
|
||||
echo "Tagged commit ${RELEASE_SHA} is not contained in ${RELEASE_MAIN_REF}." >&2
|
||||
exit 1
|
||||
fi
|
||||
node --input-type=module <<'EOF'
|
||||
import { readFileSync } from "node:fs";
|
||||
|
||||
const releaseTag = process.env.RELEASE_TAG ?? "";
|
||||
const pkg = JSON.parse(readFileSync("./packages/clawhub/package.json", "utf8"));
|
||||
const version = String(pkg.version ?? "").trim();
|
||||
const errors = [];
|
||||
|
||||
if (pkg.name !== "clawhub") {
|
||||
errors.push(`packages/clawhub/package.json name must be "clawhub"; found "${pkg.name ?? ""}".`);
|
||||
}
|
||||
if (!/^\d+\.\d+\.\d+$/.test(version)) {
|
||||
errors.push(`packages/clawhub/package.json version must be stable semver (X.Y.Z); found "${version || "<missing>"}".`);
|
||||
}
|
||||
if (!/^v\d+\.\d+\.\d+$/.test(releaseTag)) {
|
||||
errors.push(`Release tag must match vX.Y.Z; found "${releaseTag || "<missing>"}".`);
|
||||
}
|
||||
if (releaseTag !== `v${version}`) {
|
||||
errors.push(`Release tag ${releaseTag} does not match packages/clawhub/package.json version ${version}; expected v${version}.`);
|
||||
}
|
||||
|
||||
if (errors.length > 0) {
|
||||
for (const error of errors) console.error(error);
|
||||
process.exit(1);
|
||||
}
|
||||
console.log(`Release metadata OK for clawhub@${version} (${releaseTag}).`);
|
||||
EOF
|
||||
working-directory: release
|
||||
|
||||
- name: Resolve release metadata
|
||||
env:
|
||||
RELEASE_TAG: ${{ inputs.tag }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
PACKAGE_VERSION="$(node --input-type=module <<'EOF'
|
||||
import { readFileSync } from "node:fs";
|
||||
|
||||
const pkg = JSON.parse(readFileSync("./packages/clawhub/package.json", "utf8"));
|
||||
process.stdout.write(String(pkg.version ?? "").trim());
|
||||
EOF
|
||||
)"
|
||||
NPM_DIST_JSON=""
|
||||
for attempt in {1..12}; do
|
||||
if NPM_DIST_JSON="$(npm view "clawhub@${PACKAGE_VERSION}" dist.tarball dist.integrity --json 2>/tmp/npm-view-error)" && [[ -n "$NPM_DIST_JSON" ]]; then
|
||||
break
|
||||
fi
|
||||
if [[ "$attempt" == "12" ]]; then
|
||||
cat /tmp/npm-view-error >&2 || true
|
||||
exit 1
|
||||
fi
|
||||
sleep 5
|
||||
done
|
||||
NPM_TARBALL="$(NPM_DIST_JSON="$NPM_DIST_JSON" node --input-type=module <<'EOF'
|
||||
const dist = JSON.parse(process.env.NPM_DIST_JSON ?? "{}");
|
||||
process.stdout.write(String(dist["dist.tarball"] ?? ""));
|
||||
EOF
|
||||
)"
|
||||
NPM_INTEGRITY="$(NPM_DIST_JSON="$NPM_DIST_JSON" node --input-type=module <<'EOF'
|
||||
const dist = JSON.parse(process.env.NPM_DIST_JSON ?? "{}");
|
||||
process.stdout.write(String(dist["dist.integrity"] ?? ""));
|
||||
EOF
|
||||
)"
|
||||
if [[ -z "$NPM_TARBALL" || -z "$NPM_INTEGRITY" ]]; then
|
||||
echo "npm dist metadata for clawhub@${PACKAGE_VERSION} is incomplete." >&2
|
||||
exit 1
|
||||
fi
|
||||
{
|
||||
echo "PACKAGE_VERSION=$PACKAGE_VERSION"
|
||||
echo "NPM_PACKAGE_URL=https://www.npmjs.com/package/clawhub/v/${PACKAGE_VERSION}"
|
||||
echo "NPM_TARBALL_URL=$NPM_TARBALL"
|
||||
echo "NPM_INTEGRITY=$NPM_INTEGRITY"
|
||||
echo "RELEASE_TITLE=clawhub ${PACKAGE_VERSION}"
|
||||
} >> "$GITHUB_ENV"
|
||||
working-directory: release
|
||||
|
||||
- name: Resolve proof workflow run URLs
|
||||
env:
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
MAIN_RUN_ID: ${{ inputs.main_run_id }}
|
||||
PREFLIGHT_RUN_ID: ${{ inputs.preflight_run_id }}
|
||||
PUBLISH_RUN_ID: ${{ inputs.publish_run_id }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
|
||||
resolve_run_url() {
|
||||
local env_name="$1"
|
||||
local run_id="$2"
|
||||
local expected_workflow="$3"
|
||||
local expected_event="$4"
|
||||
local expected_branch="$5"
|
||||
|
||||
if [[ -z "$run_id" ]]; then
|
||||
return 0
|
||||
fi
|
||||
|
||||
local run_json
|
||||
run_json="$(gh run view "$run_id" --repo "$GITHUB_REPOSITORY" --json conclusion,event,headBranch,headSha,url,workflowName)"
|
||||
RUN_JSON="$run_json" RUN_ID="$run_id" EXPECTED_WORKFLOW="$expected_workflow" EXPECTED_EVENT="$expected_event" EXPECTED_BRANCH="$expected_branch" node --input-type=module <<'EOF'
|
||||
const run = JSON.parse(process.env.RUN_JSON);
|
||||
const expectedWorkflow = process.env.EXPECTED_WORKFLOW;
|
||||
if (expectedWorkflow && run.workflowName !== expectedWorkflow) {
|
||||
console.error(`Run ${process.env.RUN_ID} must be ${expectedWorkflow}; got ${run.workflowName ?? "<missing>"}.`);
|
||||
process.exit(1);
|
||||
}
|
||||
if (run.conclusion !== "success") {
|
||||
console.error(`Run ${process.env.RUN_ID} must have conclusion=success; got ${run.conclusion ?? "<missing>"}.`);
|
||||
process.exit(1);
|
||||
}
|
||||
if (run.headSha !== process.env.RELEASE_SHA) {
|
||||
console.error(`Run ${process.env.RUN_ID} must use release SHA ${process.env.RELEASE_SHA}; got ${run.headSha ?? "<missing>"}.`);
|
||||
process.exit(1);
|
||||
}
|
||||
if (process.env.EXPECTED_EVENT && run.event !== process.env.EXPECTED_EVENT) {
|
||||
console.error(`Run ${process.env.RUN_ID} must have event=${process.env.EXPECTED_EVENT}; got ${run.event ?? "<missing>"}.`);
|
||||
process.exit(1);
|
||||
}
|
||||
if (process.env.EXPECTED_BRANCH && run.headBranch !== process.env.EXPECTED_BRANCH) {
|
||||
console.error(`Run ${process.env.RUN_ID} must have headBranch=${process.env.EXPECTED_BRANCH}; got ${run.headBranch ?? "<missing>"}.`);
|
||||
process.exit(1);
|
||||
}
|
||||
process.stdout.write(run.url);
|
||||
EOF
|
||||
echo "${env_name}=$(RUN_JSON="$run_json" RUN_ID="$run_id" EXPECTED_WORKFLOW="$expected_workflow" node --input-type=module <<'EOF'
|
||||
const run = JSON.parse(process.env.RUN_JSON);
|
||||
process.stdout.write(run.url);
|
||||
EOF
|
||||
)" >> "$GITHUB_ENV"
|
||||
}
|
||||
|
||||
resolve_run_url MAIN_RUN_URL "$MAIN_RUN_ID" "CI" "" ""
|
||||
resolve_run_url PREFLIGHT_RUN_URL "$PREFLIGHT_RUN_ID" "ClawHub CLI NPM Release" "workflow_dispatch" "main"
|
||||
resolve_run_url PUBLISH_RUN_URL "$PUBLISH_RUN_ID" "ClawHub CLI NPM Release" "workflow_dispatch" "main"
|
||||
|
||||
- name: Verify preflight proof artifact
|
||||
if: ${{ inputs.preflight_run_id != '' }}
|
||||
env:
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
PREFLIGHT_RUN_ID: ${{ inputs.preflight_run_id }}
|
||||
RELEASE_TAG: ${{ inputs.tag }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
PROOF_DIR="$RUNNER_TEMP/clawhub-cli-github-release-preflight-proof"
|
||||
rm -rf "$PROOF_DIR"
|
||||
mkdir -p "$PROOF_DIR"
|
||||
gh run download "$PREFLIGHT_RUN_ID" \
|
||||
--repo "$GITHUB_REPOSITORY" \
|
||||
--name "clawhub-cli-npm-preflight-${RELEASE_TAG}" \
|
||||
--dir "$PROOF_DIR"
|
||||
|
||||
if [[ "$(tr -d '\r\n' < "$PROOF_DIR/release-tag.txt")" != "$RELEASE_TAG" ]]; then
|
||||
echo "Preflight artifact tag does not match ${RELEASE_TAG}." >&2
|
||||
exit 1
|
||||
fi
|
||||
if [[ "$(tr -d '\r\n' < "$PROOF_DIR/release-sha.txt")" != "$RELEASE_SHA" ]]; then
|
||||
echo "Preflight artifact SHA does not match ${RELEASE_SHA}." >&2
|
||||
exit 1
|
||||
fi
|
||||
if [[ "$(tr -d '\r\n' < "$PROOF_DIR/package-version.txt")" != "$PACKAGE_VERSION" ]]; then
|
||||
echo "Preflight artifact version does not match ${PACKAGE_VERSION}." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
- name: Build release notes
|
||||
env:
|
||||
RELEASE_TAG: ${{ inputs.tag }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
node ../release-tools/scripts/extract-changelog-release.mjs --tag "$RELEASE_TAG" --changelog CHANGELOG.md > ../release-body.md
|
||||
{
|
||||
echo
|
||||
echo "### Release Proof"
|
||||
echo
|
||||
echo "- npm: ${NPM_PACKAGE_URL}"
|
||||
echo "- tarball: ${NPM_TARBALL_URL}"
|
||||
echo "- integrity: ${NPM_INTEGRITY}"
|
||||
if [[ -n "${MAIN_RUN_URL:-}" ]]; then
|
||||
echo "- main CI: ${MAIN_RUN_URL}"
|
||||
fi
|
||||
if [[ -n "${PREFLIGHT_RUN_URL:-}" ]]; then
|
||||
echo "- npm preflight: ${PREFLIGHT_RUN_URL}"
|
||||
fi
|
||||
if [[ -n "${PUBLISH_RUN_URL:-}" ]]; then
|
||||
echo "- npm publish: ${PUBLISH_RUN_URL}"
|
||||
fi
|
||||
} >> ../release-body.md
|
||||
working-directory: release
|
||||
|
||||
- name: Create or update GitHub Release
|
||||
env:
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
RELEASE_TAG: ${{ inputs.tag }}
|
||||
UPDATE_EXISTING: ${{ inputs.update_existing }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
if gh release view "$RELEASE_TAG" --repo "$GITHUB_REPOSITORY" >/dev/null 2>&1; then
|
||||
if [[ "$UPDATE_EXISTING" != "true" ]]; then
|
||||
echo "GitHub Release ${RELEASE_TAG} already exists. Rerun with update_existing=true to repair it." >&2
|
||||
exit 1
|
||||
fi
|
||||
gh release edit "$RELEASE_TAG" \
|
||||
--repo "$GITHUB_REPOSITORY" \
|
||||
--title "$RELEASE_TITLE" \
|
||||
--notes-file release-body.md
|
||||
else
|
||||
gh release create "$RELEASE_TAG" \
|
||||
--repo "$GITHUB_REPOSITORY" \
|
||||
--title "$RELEASE_TITLE" \
|
||||
--notes-file release-body.md
|
||||
fi
|
||||
@@ -47,6 +47,12 @@ jobs:
|
||||
ref: refs/tags/${{ inputs.tag }}
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Checkout release tooling
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
ref: ${{ github.ref }}
|
||||
path: release-tools
|
||||
|
||||
- name: Setup Node
|
||||
uses: actions/setup-node@v6
|
||||
with:
|
||||
@@ -108,6 +114,11 @@ jobs:
|
||||
git fetch --no-tags origin +refs/heads/main:refs/remotes/origin/main
|
||||
node scripts/clawhub-cli-npm-release-check.mjs
|
||||
|
||||
- name: Validate GitHub Release notes
|
||||
env:
|
||||
RELEASE_TAG: ${{ inputs.tag }}
|
||||
run: node release-tools/scripts/extract-changelog-release.mjs --tag "$RELEASE_TAG" >/tmp/clawhub-cli-release-notes.md
|
||||
|
||||
- name: Verify CLI package
|
||||
run: bun run --cwd "$PACKAGE_DIR" verify
|
||||
|
||||
@@ -183,7 +194,7 @@ jobs:
|
||||
environment: npm-release
|
||||
permissions:
|
||||
actions: read
|
||||
contents: read
|
||||
contents: write
|
||||
id-token: write
|
||||
steps:
|
||||
- name: Checkout
|
||||
@@ -192,6 +203,12 @@ jobs:
|
||||
ref: refs/tags/${{ inputs.tag }}
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Checkout release tooling
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
ref: ${{ github.ref }}
|
||||
path: release-tools
|
||||
|
||||
- name: Setup Node
|
||||
uses: actions/setup-node@v6
|
||||
with:
|
||||
@@ -258,6 +275,11 @@ jobs:
|
||||
git fetch --no-tags origin +refs/heads/main:refs/remotes/origin/main
|
||||
node scripts/clawhub-cli-npm-release-check.mjs
|
||||
|
||||
- name: Validate GitHub Release notes
|
||||
env:
|
||||
RELEASE_TAG: ${{ inputs.tag }}
|
||||
run: node release-tools/scripts/extract-changelog-release.mjs --tag "$RELEASE_TAG" >/tmp/clawhub-cli-release-notes.md
|
||||
|
||||
- name: Verify prepared tarball provenance
|
||||
env:
|
||||
RELEASE_TAG: ${{ inputs.tag }}
|
||||
@@ -315,3 +337,81 @@ jobs:
|
||||
publish_target="./${publish_target}"
|
||||
fi
|
||||
bash scripts/clawhub-cli-npm-publish.sh --publish "${publish_target}"
|
||||
|
||||
- name: Resolve npm release metadata
|
||||
run: |
|
||||
set -euo pipefail
|
||||
PACKAGE_VERSION="$(node --input-type=module <<'EOF'
|
||||
import { readFileSync } from "node:fs";
|
||||
|
||||
const pkg = JSON.parse(readFileSync(`./${process.env.PACKAGE_DIR}/package.json`, "utf8"));
|
||||
process.stdout.write(String(pkg.version ?? "").trim());
|
||||
EOF
|
||||
)"
|
||||
NPM_DIST_JSON=""
|
||||
for attempt in {1..12}; do
|
||||
if NPM_DIST_JSON="$(npm view "clawhub@${PACKAGE_VERSION}" dist.tarball dist.integrity --json 2>/tmp/npm-view-error)" && [[ -n "$NPM_DIST_JSON" ]]; then
|
||||
break
|
||||
fi
|
||||
if [[ "$attempt" == "12" ]]; then
|
||||
cat /tmp/npm-view-error >&2 || true
|
||||
exit 1
|
||||
fi
|
||||
sleep 5
|
||||
done
|
||||
NPM_TARBALL="$(NPM_DIST_JSON="$NPM_DIST_JSON" node --input-type=module <<'EOF'
|
||||
const dist = JSON.parse(process.env.NPM_DIST_JSON ?? "{}");
|
||||
process.stdout.write(String(dist["dist.tarball"] ?? ""));
|
||||
EOF
|
||||
)"
|
||||
NPM_INTEGRITY="$(NPM_DIST_JSON="$NPM_DIST_JSON" node --input-type=module <<'EOF'
|
||||
const dist = JSON.parse(process.env.NPM_DIST_JSON ?? "{}");
|
||||
process.stdout.write(String(dist["dist.integrity"] ?? ""));
|
||||
EOF
|
||||
)"
|
||||
if [[ -z "$NPM_TARBALL" || -z "$NPM_INTEGRITY" ]]; then
|
||||
echo "npm dist metadata for clawhub@${PACKAGE_VERSION} is incomplete." >&2
|
||||
exit 1
|
||||
fi
|
||||
{
|
||||
echo "PACKAGE_VERSION=$PACKAGE_VERSION"
|
||||
echo "NPM_PACKAGE_URL=https://www.npmjs.com/package/clawhub/v/${PACKAGE_VERSION}"
|
||||
echo "NPM_TARBALL_URL=$NPM_TARBALL"
|
||||
echo "NPM_INTEGRITY=$NPM_INTEGRITY"
|
||||
echo "RELEASE_TITLE=clawhub ${PACKAGE_VERSION}"
|
||||
} >> "$GITHUB_ENV"
|
||||
|
||||
- name: Build GitHub Release notes
|
||||
env:
|
||||
RELEASE_TAG: ${{ inputs.tag }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
node release-tools/scripts/extract-changelog-release.mjs --tag "$RELEASE_TAG" > release-body.md
|
||||
{
|
||||
echo
|
||||
echo "### Release Proof"
|
||||
echo
|
||||
echo "- npm: ${NPM_PACKAGE_URL}"
|
||||
echo "- tarball: ${NPM_TARBALL_URL}"
|
||||
echo "- integrity: ${NPM_INTEGRITY}"
|
||||
echo "- npm preflight: https://github.com/${GITHUB_REPOSITORY}/actions/runs/${{ inputs.preflight_run_id }}"
|
||||
echo "- npm publish: https://github.com/${GITHUB_REPOSITORY}/actions/runs/${GITHUB_RUN_ID}"
|
||||
} >> release-body.md
|
||||
|
||||
- name: Create or update GitHub Release
|
||||
env:
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
RELEASE_TAG: ${{ inputs.tag }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
if gh release view "$RELEASE_TAG" --repo "$GITHUB_REPOSITORY" >/dev/null 2>&1; then
|
||||
gh release edit "$RELEASE_TAG" \
|
||||
--repo "$GITHUB_REPOSITORY" \
|
||||
--title "$RELEASE_TITLE" \
|
||||
--notes-file release-body.md
|
||||
else
|
||||
gh release create "$RELEASE_TAG" \
|
||||
--repo "$GITHUB_REPOSITORY" \
|
||||
--title "$RELEASE_TITLE" \
|
||||
--notes-file release-body.md
|
||||
fi
|
||||
|
||||
@@ -50,6 +50,7 @@
|
||||
"preview": "bun --bun vite preview",
|
||||
"proof:publish": "node scripts/ui-proof-publish.mjs",
|
||||
"proof:ui": "node scripts/ui-proof.mjs",
|
||||
"release:clawhub:cli:changelog": "node scripts/extract-changelog-release.mjs",
|
||||
"release:clawhub:cli:npm:check": "node scripts/clawhub-cli-npm-release-check.mjs",
|
||||
"security:codex-worker": "bun scripts/security/run-codex-scan-worker.ts",
|
||||
"seed:dev": "bun run setup:worktree -- --quiet && bun scripts/dev-worktree.ts --seed-only",
|
||||
|
||||
@@ -0,0 +1,90 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
import { readFileSync } from "node:fs";
|
||||
|
||||
function parseArgs(argv) {
|
||||
const options = {};
|
||||
for (let index = 0; index < argv.length; index += 1) {
|
||||
const arg = argv[index];
|
||||
const next = argv[index + 1];
|
||||
if ((arg === "--version" || arg === "-v") && next) {
|
||||
options.version = next;
|
||||
index += 1;
|
||||
continue;
|
||||
}
|
||||
if ((arg === "--tag" || arg === "--release-tag") && next) {
|
||||
options.tag = next;
|
||||
index += 1;
|
||||
continue;
|
||||
}
|
||||
if (arg === "--changelog" && next) {
|
||||
options.changelog = next;
|
||||
index += 1;
|
||||
continue;
|
||||
}
|
||||
if (arg === "--help" || arg === "-h") {
|
||||
options.help = true;
|
||||
}
|
||||
}
|
||||
return options;
|
||||
}
|
||||
|
||||
function usage() {
|
||||
return `Usage: node scripts/extract-changelog-release.mjs --version X.Y.Z
|
||||
node scripts/extract-changelog-release.mjs --tag vX.Y.Z
|
||||
|
||||
Options:
|
||||
--version, -v Stable release version to extract.
|
||||
--tag Stable release tag to extract.
|
||||
--changelog Changelog path. Defaults to CHANGELOG.md.
|
||||
`;
|
||||
}
|
||||
|
||||
function versionFromOptions(options) {
|
||||
const raw = options.version ?? options.tag?.replace(/^v/, "") ?? "";
|
||||
const version = raw.trim();
|
||||
if (!/^\d+\.\d+\.\d+$/.test(version)) {
|
||||
throw new Error(`Release version must match X.Y.Z; found "${raw || "<missing>"}".`);
|
||||
}
|
||||
return version;
|
||||
}
|
||||
|
||||
function extractReleaseSection(changelog, version) {
|
||||
const lines = changelog.replace(/\r\n/g, "\n").split("\n");
|
||||
const heading = new RegExp(`^##\\s+${version.replaceAll(".", "\\.")}(?:\\s+-\\s+.*)?\\s*$`);
|
||||
const start = lines.findIndex((line) => heading.test(line));
|
||||
if (start === -1) {
|
||||
throw new Error(`CHANGELOG.md does not contain a section for ${version}.`);
|
||||
}
|
||||
|
||||
const end = lines.findIndex((line, index) => index > start && /^##\s+\S/.test(line));
|
||||
const section = lines
|
||||
.slice(start, end === -1 ? lines.length : end)
|
||||
.join("\n")
|
||||
.trim();
|
||||
const content = lines
|
||||
.slice(start + 1, end === -1 ? lines.length : end)
|
||||
.join("\n")
|
||||
.trim();
|
||||
if (!content) {
|
||||
throw new Error(`CHANGELOG.md section for ${version} is empty.`);
|
||||
}
|
||||
return `${section}\n`;
|
||||
}
|
||||
|
||||
const options = parseArgs(process.argv.slice(2));
|
||||
|
||||
if (options.help) {
|
||||
console.log(usage());
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
try {
|
||||
const version = versionFromOptions(options);
|
||||
const changelogPath = options.changelog ?? "CHANGELOG.md";
|
||||
const changelog = readFileSync(changelogPath, "utf8");
|
||||
process.stdout.write(extractReleaseSection(changelog, version));
|
||||
} catch (error) {
|
||||
console.error(error instanceof Error ? error.message : String(error));
|
||||
process.exit(1);
|
||||
}
|
||||
@@ -68,6 +68,26 @@ CLI release notes:
|
||||
- The publish job waits at the GitHub `npm-release` environment for approval.
|
||||
- npm auth is handled through npm trusted publishing, not an `NPM_TOKEN`.
|
||||
- npm trusted publisher must be configured for package `clawhub` with repository `openclaw/clawhub`, workflow `clawhub-cli-npm-release.yml`, and environment `npm-release`.
|
||||
- After a successful npm publish, the workflow creates or updates the matching GitHub Release from the `CHANGELOG.md` section and appends npm tarball/integrity proof.
|
||||
|
||||
If npm publish succeeds but GitHub Release creation needs repair, rerun the
|
||||
GitHub Release workflow without publishing to npm again:
|
||||
|
||||
```bash
|
||||
gh workflow run clawhub-cli-github-release.yml \
|
||||
--repo openclaw/clawhub \
|
||||
--ref main \
|
||||
-f tag=v0.11.0 \
|
||||
-f preflight_run_id=<successful preflight run id> \
|
||||
-f update_existing=false
|
||||
```
|
||||
|
||||
If the original publish workflow failed after npm publish while creating the
|
||||
GitHub Release, omit `publish_run_id`; the repair workflow accepts only
|
||||
successful proof run ids.
|
||||
|
||||
Use `update_existing=true` only when intentionally replacing the body for an
|
||||
existing GitHub Release.
|
||||
|
||||
That workflow assumes Vercel Git integration is enabled for this repo. It does
|
||||
not run `vercel deploy` directly; frontend-related steps wait for the GitHub
|
||||
|
||||
Reference in New Issue
Block a user