mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-06 05:30:42 +00:00
ci: orchestrate plugin release publishing
This commit is contained in:
257
.github/workflows/openclaw-release-publish.yml
vendored
Normal file
257
.github/workflows/openclaw-release-publish.yml
vendored
Normal file
@@ -0,0 +1,257 @@
|
||||
name: OpenClaw Release Publish
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
tag:
|
||||
description: Release tag to publish, for example v2026.5.1-beta.1
|
||||
required: true
|
||||
type: string
|
||||
preflight_run_id:
|
||||
description: Successful OpenClaw NPM Release preflight run id, required when publish_openclaw_npm=true
|
||||
required: false
|
||||
type: string
|
||||
npm_dist_tag:
|
||||
description: npm dist-tag for the OpenClaw package
|
||||
required: true
|
||||
default: beta
|
||||
type: choice
|
||||
options:
|
||||
- beta
|
||||
- latest
|
||||
plugin_publish_scope:
|
||||
description: Plugin publish scope to run before OpenClaw publish
|
||||
required: true
|
||||
default: all-publishable
|
||||
type: choice
|
||||
options:
|
||||
- selected
|
||||
- all-publishable
|
||||
plugins:
|
||||
description: Comma-separated plugin package names when plugin_publish_scope=selected
|
||||
required: false
|
||||
type: string
|
||||
publish_openclaw_npm:
|
||||
description: Publish the OpenClaw npm package after plugin npm and ClawHub publish complete
|
||||
required: true
|
||||
default: true
|
||||
type: boolean
|
||||
|
||||
permissions:
|
||||
actions: write
|
||||
contents: read
|
||||
|
||||
concurrency:
|
||||
group: openclaw-release-publish-${{ inputs.tag }}
|
||||
cancel-in-progress: false
|
||||
|
||||
env:
|
||||
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: "true"
|
||||
NODE_VERSION: "24.x"
|
||||
PNPM_VERSION: "10.32.1"
|
||||
|
||||
jobs:
|
||||
resolve_release_target:
|
||||
name: Resolve release target
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 20
|
||||
outputs:
|
||||
sha: ${{ steps.ref.outputs.sha }}
|
||||
steps:
|
||||
- name: Validate inputs
|
||||
env:
|
||||
RELEASE_TAG: ${{ inputs.tag }}
|
||||
PREFLIGHT_RUN_ID: ${{ inputs.preflight_run_id }}
|
||||
PUBLISH_OPENCLAW_NPM: ${{ inputs.publish_openclaw_npm && 'true' || 'false' }}
|
||||
PLUGIN_PUBLISH_SCOPE: ${{ inputs.plugin_publish_scope }}
|
||||
PLUGINS: ${{ inputs.plugins }}
|
||||
RELEASE_NPM_DIST_TAG: ${{ inputs.npm_dist_tag }}
|
||||
WORKFLOW_REF: ${{ github.ref }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
if [[ ! "${RELEASE_TAG}" =~ ^v[0-9]{4}\.[1-9][0-9]*\.[1-9][0-9]*((-beta\.[1-9][0-9]*)|(-[1-9][0-9]*))?$ ]]; then
|
||||
echo "Invalid release tag: ${RELEASE_TAG}" >&2
|
||||
exit 1
|
||||
fi
|
||||
if [[ "${RELEASE_TAG}" == *"-beta."* && "${RELEASE_NPM_DIST_TAG}" != "beta" ]]; then
|
||||
echo "Beta prerelease tags must publish OpenClaw to npm dist-tag beta." >&2
|
||||
exit 1
|
||||
fi
|
||||
if [[ "${PUBLISH_OPENCLAW_NPM}" == "true" && -z "${PREFLIGHT_RUN_ID}" ]]; then
|
||||
echo "publish_openclaw_npm=true requires preflight_run_id." >&2
|
||||
exit 1
|
||||
fi
|
||||
if [[ "${PUBLISH_OPENCLAW_NPM}" == "true" && "${WORKFLOW_REF}" != "refs/heads/main" && ! "${WORKFLOW_REF}" =~ ^refs/heads/release/[0-9]{4}\.[1-9][0-9]*\.[1-9][0-9]*$ ]]; then
|
||||
echo "publish_openclaw_npm=true requires dispatching this workflow from main or release/YYYY.M.D." >&2
|
||||
exit 1
|
||||
fi
|
||||
if [[ "${PLUGIN_PUBLISH_SCOPE}" == "selected" && -z "${PLUGINS}" ]]; then
|
||||
echo "plugin_publish_scope=selected requires plugins." >&2
|
||||
exit 1
|
||||
fi
|
||||
if [[ "${PLUGIN_PUBLISH_SCOPE}" == "all-publishable" && -n "${PLUGINS}" ]]; then
|
||||
echo "plugin_publish_scope=all-publishable must not include plugins." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
- name: Checkout release tag
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
ref: refs/tags/${{ inputs.tag }}
|
||||
fetch-depth: 0
|
||||
persist-credentials: false
|
||||
|
||||
- name: Setup Node environment
|
||||
uses: ./.github/actions/setup-node-env
|
||||
with:
|
||||
node-version: ${{ env.NODE_VERSION }}
|
||||
pnpm-version: ${{ env.PNPM_VERSION }}
|
||||
install-bun: "false"
|
||||
|
||||
- name: Resolve checked-out release ref
|
||||
id: ref
|
||||
run: echo "sha=$(git rev-parse HEAD)" >> "$GITHUB_OUTPUT"
|
||||
|
||||
- name: Validate release tag is reachable from main or release branch
|
||||
run: |
|
||||
set -euo pipefail
|
||||
git fetch --no-tags origin \
|
||||
+refs/heads/main:refs/remotes/origin/main \
|
||||
'+refs/heads/release/*:refs/remotes/origin/release/*'
|
||||
if git merge-base --is-ancestor HEAD origin/main; then
|
||||
exit 0
|
||||
fi
|
||||
while IFS= read -r release_ref; do
|
||||
if git merge-base --is-ancestor HEAD "${release_ref}"; then
|
||||
exit 0
|
||||
fi
|
||||
done < <(git for-each-ref --format='%(refname)' refs/remotes/origin/release)
|
||||
echo "Release tag must point to a commit reachable from main or release/*." >&2
|
||||
exit 1
|
||||
|
||||
- name: Verify plugin versions were synced for this release
|
||||
run: pnpm plugins:sync:check
|
||||
|
||||
- name: Summarize release target
|
||||
env:
|
||||
RELEASE_TAG: ${{ inputs.tag }}
|
||||
TARGET_SHA: ${{ steps.ref.outputs.sha }}
|
||||
run: |
|
||||
{
|
||||
echo "### Release target"
|
||||
echo
|
||||
echo "- Tag: \`${RELEASE_TAG}\`"
|
||||
echo "- SHA: \`${TARGET_SHA}\`"
|
||||
} >> "$GITHUB_STEP_SUMMARY"
|
||||
|
||||
publish:
|
||||
name: Publish plugins, then OpenClaw
|
||||
needs: [resolve_release_target]
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 360
|
||||
steps:
|
||||
- name: Dispatch publish workflows
|
||||
env:
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
TARGET_SHA: ${{ needs.resolve_release_target.outputs.sha }}
|
||||
CHILD_WORKFLOW_REF: ${{ github.ref_name }}
|
||||
RELEASE_TAG: ${{ inputs.tag }}
|
||||
PREFLIGHT_RUN_ID: ${{ inputs.preflight_run_id }}
|
||||
RELEASE_NPM_DIST_TAG: ${{ inputs.npm_dist_tag }}
|
||||
PLUGIN_PUBLISH_SCOPE: ${{ inputs.plugin_publish_scope }}
|
||||
PLUGINS: ${{ inputs.plugins }}
|
||||
PUBLISH_OPENCLAW_NPM: ${{ inputs.publish_openclaw_npm && 'true' || 'false' }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
|
||||
dispatch_and_wait() {
|
||||
local workflow="$1"
|
||||
shift
|
||||
|
||||
local before_json dispatch_output run_id status conclusion url
|
||||
before_json="$(gh run list --workflow "$workflow" --event workflow_dispatch --limit 100 --json databaseId --jq '[.[].databaseId]')"
|
||||
|
||||
dispatch_output="$(gh workflow run "$workflow" --ref "$CHILD_WORKFLOW_REF" "$@" 2>&1)"
|
||||
printf '%s\n' "$dispatch_output"
|
||||
run_id="$(
|
||||
printf '%s\n' "$dispatch_output" |
|
||||
sed -nE 's#.*actions/runs/([0-9]+).*#\1#p' |
|
||||
tail -n 1
|
||||
)"
|
||||
|
||||
if [[ -z "$run_id" ]]; then
|
||||
for _ in $(seq 1 60); do
|
||||
run_id="$(
|
||||
BEFORE_IDS="$before_json" gh run list --workflow "$workflow" --event workflow_dispatch --limit 50 --json databaseId,createdAt \
|
||||
--jq 'map(select(.databaseId as $id | (env.BEFORE_IDS | fromjson | index($id) | not))) | sort_by(.createdAt) | reverse | .[0].databaseId // empty'
|
||||
)"
|
||||
if [[ -n "$run_id" ]]; then
|
||||
break
|
||||
fi
|
||||
sleep 5
|
||||
done
|
||||
fi
|
||||
|
||||
if [[ -z "${run_id:-}" ]]; then
|
||||
echo "Could not find dispatched run for ${workflow}." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "Dispatched ${workflow}: https://github.com/${GITHUB_REPOSITORY}/actions/runs/${run_id}"
|
||||
|
||||
cancel_child() {
|
||||
if [[ -n "${run_id:-}" ]]; then
|
||||
echo "Cancelling child workflow ${workflow}: ${run_id}" >&2
|
||||
gh run cancel "$run_id" >/dev/null 2>&1 || true
|
||||
fi
|
||||
}
|
||||
trap cancel_child EXIT INT TERM
|
||||
|
||||
while true; do
|
||||
status="$(gh run view "$run_id" --json status --jq '.status')"
|
||||
if [[ "$status" == "completed" ]]; then
|
||||
break
|
||||
fi
|
||||
sleep 30
|
||||
done
|
||||
trap - EXIT INT TERM
|
||||
|
||||
conclusion="$(gh run view "$run_id" --json conclusion --jq '.conclusion')"
|
||||
url="$(gh run view "$run_id" --json url --jq '.url')"
|
||||
echo "${workflow} finished with ${conclusion}: ${url}"
|
||||
{
|
||||
echo "- ${workflow}: ${conclusion} (${url})"
|
||||
} >> "$GITHUB_STEP_SUMMARY"
|
||||
if [[ "$conclusion" != "success" ]]; then
|
||||
gh run view "$run_id" --json jobs --jq '.jobs[] | select(.conclusion != "success" and .conclusion != "skipped") | {name, conclusion, url}' || true
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
{
|
||||
echo "### Publish sequence"
|
||||
echo
|
||||
echo "- Workflow ref: \`${CHILD_WORKFLOW_REF}\`"
|
||||
echo "- Release tag: \`${RELEASE_TAG}\`"
|
||||
echo "- Release SHA: \`${TARGET_SHA}\`"
|
||||
} >> "$GITHUB_STEP_SUMMARY"
|
||||
|
||||
npm_args=(-f publish_scope="${PLUGIN_PUBLISH_SCOPE}" -f ref="${TARGET_SHA}")
|
||||
clawhub_args=(-f publish_scope="${PLUGIN_PUBLISH_SCOPE}" -f ref="${TARGET_SHA}")
|
||||
if [[ -n "${PLUGINS}" ]]; then
|
||||
npm_args+=(-f plugins="${PLUGINS}")
|
||||
clawhub_args+=(-f plugins="${PLUGINS}")
|
||||
fi
|
||||
|
||||
dispatch_and_wait plugin-npm-release.yml "${npm_args[@]}"
|
||||
dispatch_and_wait plugin-clawhub-release.yml "${clawhub_args[@]}"
|
||||
|
||||
if [[ "${PUBLISH_OPENCLAW_NPM}" == "true" ]]; then
|
||||
dispatch_and_wait openclaw-npm-release.yml \
|
||||
-f tag="${RELEASE_TAG}" \
|
||||
-f preflight_only=false \
|
||||
-f preflight_run_id="${PREFLIGHT_RUN_ID}" \
|
||||
-f npm_dist_tag="${RELEASE_NPM_DIST_TAG}"
|
||||
else
|
||||
echo "- OpenClaw npm publish: skipped by input" >> "$GITHUB_STEP_SUMMARY"
|
||||
fi
|
||||
26
.github/workflows/plugin-clawhub-release.yml
vendored
26
.github/workflows/plugin-clawhub-release.yml
vendored
@@ -15,9 +15,14 @@ on:
|
||||
description: Comma-separated plugin package names to publish when publish_scope=selected
|
||||
required: false
|
||||
type: string
|
||||
ref:
|
||||
description: Commit SHA on main or a release branch to publish from; defaults to the workflow ref
|
||||
required: false
|
||||
default: ""
|
||||
type: string
|
||||
|
||||
concurrency:
|
||||
group: plugin-clawhub-release-${{ github.sha }}
|
||||
group: plugin-clawhub-release-${{ github.event_name == 'workflow_dispatch' && inputs.ref || github.sha }}
|
||||
cancel-in-progress: false
|
||||
|
||||
env:
|
||||
@@ -45,7 +50,7 @@ jobs:
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
persist-credentials: false
|
||||
ref: ${{ github.sha }}
|
||||
ref: ${{ github.event_name == 'workflow_dispatch' && inputs.ref || github.sha }}
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Setup Node environment
|
||||
@@ -59,11 +64,22 @@ jobs:
|
||||
id: ref
|
||||
run: echo "sha=$(git rev-parse HEAD)" >> "$GITHUB_OUTPUT"
|
||||
|
||||
- name: Validate ref is on main
|
||||
- name: Validate ref is on main or a release branch
|
||||
run: |
|
||||
set -euo pipefail
|
||||
git fetch --no-tags origin +refs/heads/main:refs/remotes/origin/main
|
||||
git merge-base --is-ancestor HEAD origin/main
|
||||
git fetch --no-tags origin \
|
||||
+refs/heads/main:refs/remotes/origin/main \
|
||||
'+refs/heads/release/*:refs/remotes/origin/release/*'
|
||||
if git merge-base --is-ancestor HEAD origin/main; then
|
||||
exit 0
|
||||
fi
|
||||
while IFS= read -r release_ref; do
|
||||
if git merge-base --is-ancestor HEAD "${release_ref}"; then
|
||||
exit 0
|
||||
fi
|
||||
done < <(git for-each-ref --format='%(refname)' refs/remotes/origin/release)
|
||||
echo "Plugin ClawHub publishes must target a commit reachable from main or release/*." >&2
|
||||
exit 1
|
||||
|
||||
- name: Validate publishable plugin metadata
|
||||
env:
|
||||
|
||||
19
.github/workflows/plugin-npm-release.yml
vendored
19
.github/workflows/plugin-npm-release.yml
vendored
@@ -24,7 +24,7 @@ on:
|
||||
- selected
|
||||
- all-publishable
|
||||
ref:
|
||||
description: Commit SHA on main to publish from (copy from the preview run)
|
||||
description: Commit SHA on main or a release branch to publish from (copy from the preview run)
|
||||
required: true
|
||||
type: string
|
||||
plugins:
|
||||
@@ -70,11 +70,22 @@ jobs:
|
||||
id: ref
|
||||
run: echo "sha=$(git rev-parse HEAD)" >> "$GITHUB_OUTPUT"
|
||||
|
||||
- name: Validate ref is on main
|
||||
- name: Validate ref is on main or a release branch
|
||||
run: |
|
||||
set -euo pipefail
|
||||
git fetch --no-tags origin +refs/heads/main:refs/remotes/origin/main
|
||||
git merge-base --is-ancestor HEAD origin/main
|
||||
git fetch --no-tags origin \
|
||||
+refs/heads/main:refs/remotes/origin/main \
|
||||
'+refs/heads/release/*:refs/remotes/origin/release/*'
|
||||
if git merge-base --is-ancestor HEAD origin/main; then
|
||||
exit 0
|
||||
fi
|
||||
while IFS= read -r release_ref; do
|
||||
if git merge-base --is-ancestor HEAD "${release_ref}"; then
|
||||
exit 0
|
||||
fi
|
||||
done < <(git for-each-ref --format='%(refname)' refs/remotes/origin/release)
|
||||
echo "Plugin npm publishes must target a commit reachable from main or release/*." >&2
|
||||
exit 1
|
||||
|
||||
- name: Validate publishable plugin metadata
|
||||
env:
|
||||
|
||||
@@ -134,6 +134,13 @@ See [Full release validation](/reference/full-release-validation) for the
|
||||
stage matrix, exact workflow job names, profile differences, artifacts, and
|
||||
focused rerun handles.
|
||||
|
||||
`OpenClaw Release Publish` is the manual mutating release workflow. Dispatch it
|
||||
from `release/YYYY.M.D` or `main` after the release tag exists and after the
|
||||
OpenClaw npm preflight has succeeded. It verifies `pnpm plugins:sync:check`,
|
||||
dispatches `Plugin NPM Release` for all publishable plugin packages, dispatches
|
||||
`Plugin ClawHub Release` for the same release SHA, and only then dispatches
|
||||
`OpenClaw NPM Release` with the saved `preflight_run_id`.
|
||||
|
||||
For pinned commit proof on a fast-moving branch, use the helper instead of
|
||||
`gh workflow run ... --ref main -f ref=<sha>`:
|
||||
|
||||
|
||||
@@ -59,10 +59,12 @@ the maintainer-only release runbook.
|
||||
intentionally carried.
|
||||
4. Create `release/YYYY.M.D` from current `main`; do not do normal release work
|
||||
directly on `main`.
|
||||
5. Bump every required version location for the intended tag, then run the
|
||||
local deterministic preflight:
|
||||
5. Bump every required version location for the intended tag, run
|
||||
`pnpm plugins:sync` so publishable plugin packages share the release
|
||||
version and compatibility metadata, then run the local deterministic preflight:
|
||||
`pnpm check:test-types`, `pnpm check:architecture`,
|
||||
`pnpm build && pnpm ui:build`, and `pnpm release:check`.
|
||||
`pnpm build && pnpm ui:build`, `pnpm plugins:sync:check`, and
|
||||
`pnpm release:check`.
|
||||
6. Run `OpenClaw NPM Release` with `preflight_only=true`. Before a tag exists,
|
||||
a full 40-character release-branch SHA is allowed for validation-only
|
||||
preflight. Save the successful `preflight_run_id`.
|
||||
@@ -73,15 +75,19 @@ the maintainer-only release runbook.
|
||||
file, lane, workflow job, package profile, provider, or model allowlist that
|
||||
proves the fix. Rerun the full umbrella only when the changed surface makes
|
||||
prior evidence stale.
|
||||
9. For beta, tag `vYYYY.M.D-beta.N`, publish with npm dist-tag `beta`, then run
|
||||
post-publish package acceptance against the published `openclaw@YYYY.M.D-beta.N`
|
||||
or `openclaw@beta` package. If a pushed or published beta needs a fix, cut
|
||||
the next `-beta.N`; do not delete or rewrite the old beta.
|
||||
9. For beta, tag `vYYYY.M.D-beta.N`, then run `OpenClaw Release Publish` from
|
||||
the matching `release/YYYY.M.D` branch. It verifies `pnpm plugins:sync:check`,
|
||||
publishes all publishable plugin packages to npm first, publishes the same
|
||||
set to ClawHub second, and then promotes the prepared OpenClaw npm preflight
|
||||
artifact with dist-tag `beta`. After publish, run post-publish package
|
||||
acceptance against the published `openclaw@YYYY.M.D-beta.N` or `openclaw@beta`
|
||||
package. If a pushed or published beta needs a fix, cut the next `-beta.N`;
|
||||
do not delete or rewrite the old beta.
|
||||
10. For stable, continue only after the vetted beta or release candidate has the
|
||||
required validation evidence. Stable npm publish reuses the successful
|
||||
preflight artifact via `preflight_run_id`; stable macOS release readiness
|
||||
also requires the packaged `.zip`, `.dmg`, `.dSYM.zip`, and updated
|
||||
`appcast.xml` on `main`.
|
||||
required validation evidence. Stable npm publish also goes through
|
||||
`OpenClaw Release Publish`, reusing the successful preflight artifact via
|
||||
`preflight_run_id`; stable macOS release readiness also requires the
|
||||
packaged `.zip`, `.dmg`, `.dSYM.zip`, and updated `appcast.xml` on `main`.
|
||||
11. After publish, run the npm post-publish verifier, optional standalone
|
||||
published-npm Telegram E2E when you need post-publish channel proof,
|
||||
dist-tag promotion when needed, GitHub release/prerelease notes from the
|
||||
@@ -143,6 +149,14 @@ the maintainer-only release runbook.
|
||||
span names, bounded attributes, and content/identifier redaction without
|
||||
requiring Opik, Langfuse, or another external collector.
|
||||
- Run `pnpm release:check` before every tagged release
|
||||
- Run `OpenClaw Release Publish` for the mutating publish sequence after the
|
||||
tag exists. Dispatch it from `release/YYYY.M.D` (or `main` when publishing a
|
||||
main-reachable tag), pass the release tag and successful OpenClaw npm
|
||||
`preflight_run_id`, and keep the default plugin publish scope
|
||||
`all-publishable` unless you are deliberately running a focused repair. The
|
||||
workflow serializes plugin npm publish, plugin ClawHub publish, and OpenClaw
|
||||
npm publish so the core package is not published before its externalized
|
||||
plugins.
|
||||
- Release checks now run in a separate manual workflow:
|
||||
`OpenClaw Release Checks`
|
||||
- `OpenClaw Release Checks` also runs the QA Lab mock parity gate plus the fast
|
||||
|
||||
@@ -1412,6 +1412,7 @@
|
||||
"plugins:boundary-report:json": "node --import tsx scripts/plugin-boundary-report.ts --json",
|
||||
"plugins:boundary-report:summary": "node --import tsx scripts/plugin-boundary-report.ts --summary",
|
||||
"plugins:sync": "node --import tsx scripts/sync-plugin-versions.ts",
|
||||
"plugins:sync:check": "node --import tsx scripts/sync-plugin-versions.ts --check",
|
||||
"postinstall": "node scripts/postinstall-bundled-plugins.mjs",
|
||||
"preinstall": "node scripts/preinstall-package-manager-warning.mjs",
|
||||
"prepack": "node --import tsx scripts/openclaw-prepack.ts",
|
||||
|
||||
@@ -19,6 +19,10 @@ type PackageJson = {
|
||||
};
|
||||
};
|
||||
|
||||
type SyncPluginVersionsOptions = {
|
||||
write?: boolean;
|
||||
};
|
||||
|
||||
const OPENCLAW_VERSION_RANGE_RE = /^>=\d{4}\.\d{1,2}\.\d{1,2}(?:[-.][^"\s]+)?$/u;
|
||||
|
||||
function syncOpenClawDependencyRange(
|
||||
@@ -64,7 +68,7 @@ function syncBuildOpenClawVersion(pkg: PackageJson, targetVersion: string): bool
|
||||
return true;
|
||||
}
|
||||
|
||||
function ensureChangelogEntry(changelogPath: string, version: string): boolean {
|
||||
function ensureChangelogEntry(changelogPath: string, version: string, write: boolean): boolean {
|
||||
if (!existsSync(changelogPath)) {
|
||||
return false;
|
||||
}
|
||||
@@ -75,15 +79,23 @@ function ensureChangelogEntry(changelogPath: string, version: string): boolean {
|
||||
const entry = `## ${version}\n\n### Changes\n- Version alignment with core OpenClaw release numbers.\n\n`;
|
||||
if (content.startsWith("# Changelog\n\n")) {
|
||||
const next = content.replace("# Changelog\n\n", `# Changelog\n\n${entry}`);
|
||||
writeFileSync(changelogPath, next);
|
||||
if (write) {
|
||||
writeFileSync(changelogPath, next);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
const next = `# Changelog\n\n${entry}${content.trimStart()}`;
|
||||
writeFileSync(changelogPath, `${next}\n`);
|
||||
if (write) {
|
||||
writeFileSync(changelogPath, `${next}\n`);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
export function syncPluginVersions(rootDir = resolve(".")) {
|
||||
export function syncPluginVersions(
|
||||
rootDir = resolve("."),
|
||||
options: SyncPluginVersionsOptions = {},
|
||||
) {
|
||||
const write = options.write ?? true;
|
||||
const rootPackagePath = join(rootDir, "package.json");
|
||||
const rootPackage = JSON.parse(readFileSync(rootPackagePath, "utf8")) as PackageJson;
|
||||
const targetVersion = rootPackage.version;
|
||||
@@ -115,7 +127,7 @@ export function syncPluginVersions(rootDir = resolve(".")) {
|
||||
}
|
||||
|
||||
const changelogPath = join(extensionsDir, dir.name, "CHANGELOG.md");
|
||||
if (ensureChangelogEntry(changelogPath, targetVersion)) {
|
||||
if (ensureChangelogEntry(changelogPath, targetVersion, write)) {
|
||||
changelogged.push(pkg.name);
|
||||
}
|
||||
|
||||
@@ -140,7 +152,9 @@ export function syncPluginVersions(rootDir = resolve(".")) {
|
||||
if (versionChanged) {
|
||||
pkg.version = targetVersion;
|
||||
}
|
||||
writeFileSync(packagePath, `${JSON.stringify(pkg, null, 2)}\n`);
|
||||
if (write) {
|
||||
writeFileSync(packagePath, `${JSON.stringify(pkg, null, 2)}\n`);
|
||||
}
|
||||
updated.push(pkg.name);
|
||||
}
|
||||
|
||||
@@ -153,8 +167,19 @@ export function syncPluginVersions(rootDir = resolve(".")) {
|
||||
}
|
||||
|
||||
if (import.meta.main) {
|
||||
const summary = syncPluginVersions();
|
||||
const check = process.argv.includes("--check");
|
||||
const summary = syncPluginVersions(resolve("."), { write: !check });
|
||||
console.log(
|
||||
`Synced plugin versions to ${summary.targetVersion}. Updated: ${summary.updated.length}. Changelogged: ${summary.changelogged.length}. Skipped: ${summary.skipped.length}.`,
|
||||
);
|
||||
if (check && (summary.updated.length > 0 || summary.changelogged.length > 0)) {
|
||||
for (const packageName of summary.updated) {
|
||||
console.error(` update required: ${packageName}`);
|
||||
}
|
||||
for (const packageName of summary.changelogged) {
|
||||
console.error(` changelog entry required: ${packageName}`);
|
||||
}
|
||||
console.error("Run `pnpm plugins:sync` and commit the plugin version alignment.");
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -73,4 +73,43 @@ describe("syncPluginVersions", () => {
|
||||
expect(updatedPackage.openclaw?.compat?.pluginApi).toBe(">=2026.4.1");
|
||||
expect(updatedPackage.openclaw?.build?.openclawVersion).toBe("2026.4.1");
|
||||
});
|
||||
|
||||
it("reports pending version sync without writing in check mode", () => {
|
||||
const rootDir = makeTempDir(tempDirs, "openclaw-sync-plugin-versions-check-");
|
||||
|
||||
writeJson(path.join(rootDir, "package.json"), {
|
||||
name: "openclaw",
|
||||
version: "2026.4.2",
|
||||
});
|
||||
writeJson(path.join(rootDir, "extensions/discord/package.json"), {
|
||||
name: "@openclaw/discord",
|
||||
version: "2026.4.1",
|
||||
peerDependencies: {
|
||||
openclaw: ">=2026.4.1",
|
||||
},
|
||||
openclaw: {
|
||||
compat: {
|
||||
pluginApi: ">=2026.4.1",
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const summary = syncPluginVersions(rootDir, { write: false });
|
||||
const unchangedPackage = JSON.parse(
|
||||
fs.readFileSync(path.join(rootDir, "extensions/discord/package.json"), "utf8"),
|
||||
) as {
|
||||
version?: string;
|
||||
peerDependencies?: Record<string, string>;
|
||||
openclaw?: {
|
||||
compat?: {
|
||||
pluginApi?: string;
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
expect(summary.updated).toEqual(["@openclaw/discord"]);
|
||||
expect(unchangedPackage.version).toBe("2026.4.1");
|
||||
expect(unchangedPackage.peerDependencies?.openclaw).toBe(">=2026.4.1");
|
||||
expect(unchangedPackage.openclaw?.compat?.pluginApi).toBe(">=2026.4.1");
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user