MemOS Local Plugin (V2) — Legacy Standalone Publisher #55
Workflow file for this run
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: MemOS Local Plugin (V2) — Legacy Standalone Publisher | |
| on: | |
| workflow_dispatch: | |
| inputs: | |
| version: | |
| description: "Version to publish (e.g. 2.0.2 or 2.0.2-beta.1)" | |
| required: true | |
| tag: | |
| description: "npm dist-tag (latest for production, beta/next/alpha for testing)" | |
| required: true | |
| default: "latest" | |
| git_ref: | |
| description: "Git ref to build from (branch, tag, or SHA). Leave blank to use the branch selected above." | |
| required: false | |
| default: "" | |
| release_notes: | |
| description: "Optional Markdown release notes. latest uses Doc Agent evidence; beta/non-latest can use package-only notes." | |
| required: false | |
| default: "" | |
| dry_run: | |
| description: "Draft release notes and build artifacts only. Skip npm publish, tag, GitHub Release, and release PR." | |
| required: true | |
| type: boolean | |
| default: true | |
| recover_existing_npm_release: | |
| description: "Allow reconstructing a missing tag/Release for an npm version. Keep false for normal releases." | |
| required: true | |
| type: boolean | |
| default: false | |
| legacy_publish_confirmation: | |
| description: "Required only when dry_run=false. Must exactly equal: LEGACY PUBLISH memos-local-plugin-v<version>" | |
| required: false | |
| type: string | |
| default: "" | |
| workflow_call: | |
| inputs: | |
| version: | |
| description: "Version to publish or dry-run (e.g. 2.0.10 or 2.0.11-beta.1)" | |
| required: true | |
| type: string | |
| tag: | |
| description: "npm dist-tag (latest for production, beta/next/alpha for testing)" | |
| required: false | |
| type: string | |
| default: "latest" | |
| git_ref: | |
| description: "Git ref to build from. Leave blank to use the caller ref." | |
| required: false | |
| type: string | |
| default: "" | |
| release_notes: | |
| description: "Optional Markdown release notes. latest uses Doc Agent evidence; beta/non-latest can use package-only notes." | |
| required: false | |
| type: string | |
| default: "" | |
| dry_run: | |
| description: "Draft release notes and build artifacts only. Skip npm publish, tag, GitHub Release, and release PR." | |
| required: false | |
| type: boolean | |
| default: true | |
| recover_existing_npm_release: | |
| description: "Allow reconstructing a missing tag/Release for an npm version. Keep false for normal releases." | |
| required: false | |
| type: boolean | |
| default: false | |
| legacy_publish_confirmation: | |
| description: "Required only when dry_run=false. Must exactly equal: LEGACY PUBLISH memos-local-plugin-v<version>" | |
| required: false | |
| type: string | |
| default: "" | |
| concurrency: | |
| group: memos-local-plugin-publish | |
| cancel-in-progress: false | |
| defaults: | |
| run: | |
| working-directory: apps/memos-local-plugin | |
| permissions: | |
| contents: write | |
| pull-requests: write | |
| jobs: | |
| guard-legacy-publish: | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 5 | |
| steps: | |
| - name: Validate legacy standalone publish confirmation | |
| shell: bash | |
| working-directory: . | |
| env: | |
| DRY_RUN: ${{ inputs.dry_run }} | |
| RELEASE_VERSION: ${{ inputs.version }} | |
| LEGACY_PUBLISH_CONFIRMATION: ${{ inputs.legacy_publish_confirmation }} | |
| run: | | |
| set -euo pipefail | |
| if [ "${DRY_RUN}" = "true" ]; then | |
| echo "dry_run=true; legacy standalone publish confirmation is not required." | |
| exit 0 | |
| fi | |
| expected="LEGACY PUBLISH memos-local-plugin-v${RELEASE_VERSION}" | |
| if [ "${LEGACY_PUBLISH_CONFIRMATION}" != "${expected}" ]; then | |
| echo "::error::This workflow is the standalone local-plugin npm publisher for beta or latest package releases." | |
| echo "::error::MemOS Release — Publish remains the weekly whole-repo release path and can also update the Plugin tab from apps/memos-local-plugin/** changes." | |
| echo "::error::To intentionally run this legacy publisher, set legacy_publish_confirmation exactly to: ${expected}" | |
| exit 1 | |
| fi | |
| build-prebuilds: | |
| needs: guard-legacy-publish | |
| strategy: | |
| matrix: | |
| include: | |
| - os: macos-14 | |
| platform: darwin-arm64 | |
| - os: macos-15-intel | |
| platform: darwin-x64 | |
| - os: ubuntu-latest | |
| platform: linux-x64 | |
| - os: windows-latest | |
| platform: win32-x64 | |
| runs-on: ${{ matrix.os }} | |
| steps: | |
| - uses: actions/checkout@v4 | |
| with: | |
| ref: ${{ inputs.git_ref || github.ref }} | |
| fetch-depth: 0 | |
| - uses: actions/setup-node@v4 | |
| with: | |
| node-version: 22 | |
| - name: Install dependencies | |
| shell: bash | |
| run: bash ../../.github/scripts/retry.sh --label "npm ci" -- npm ci | |
| - name: Rebuild for x64 under Rosetta (darwin-x64 only) | |
| if: matrix.platform == 'darwin-x64' | |
| shell: bash | |
| run: bash ../../.github/scripts/retry.sh --label "npm rebuild better-sqlite3 under Rosetta" -- arch -x86_64 npm rebuild better-sqlite3 | |
| - name: Collect prebuild | |
| shell: bash | |
| run: | | |
| bash ../../.github/scripts/retry.sh --label "collect ${{ matrix.platform }} prebuild" -- bash -euo pipefail -c ' | |
| mkdir -p prebuilds/${{ matrix.platform }} | |
| cp node_modules/better-sqlite3/build/Release/better_sqlite3.node prebuilds/${{ matrix.platform }}/ | |
| ' | |
| - name: Upload prebuild artifact | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: prebuild-${{ matrix.platform }} | |
| path: apps/memos-local-plugin/prebuilds/${{ matrix.platform }}/better_sqlite3.node | |
| publish: | |
| needs: build-prebuilds | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@v4 | |
| with: | |
| ref: ${{ inputs.git_ref || github.ref }} | |
| fetch-depth: 0 | |
| - uses: actions/setup-node@v4 | |
| with: | |
| node-version: 22 | |
| registry-url: https://registry.npmjs.org | |
| - name: Test npm publish helper | |
| working-directory: . | |
| run: node --test .github/scripts/publish-local-plugin.test.mjs | |
| - name: Download all prebuilds | |
| uses: actions/download-artifact@v4 | |
| with: | |
| path: apps/memos-local-plugin/prebuilds | |
| pattern: prebuild-* | |
| merge-multiple: false | |
| - name: Organize prebuilds | |
| run: | | |
| bash ../../.github/scripts/retry.sh --label "organize prebuild artifacts" -- bash -euo pipefail -c ' | |
| cd prebuilds | |
| for dir in prebuild-*; do | |
| platform="${dir#prebuild-}" | |
| mkdir -p "$platform" | |
| mv "$dir/better_sqlite3.node" "$platform/" | |
| rmdir "$dir" | |
| done | |
| echo "Prebuilds collected:" | |
| find . -name "*.node" -exec ls -lh {} \; | |
| ' | |
| - name: Install dependencies (skip native build) | |
| run: bash ../../.github/scripts/retry.sh --label "npm ci --ignore-scripts" -- npm ci --ignore-scripts | |
| - name: Install Linux sqlite binding for validation | |
| run: | | |
| bash ../../.github/scripts/retry.sh --label "install linux sqlite binding for validation" -- bash -euo pipefail -c ' | |
| test -s prebuilds/linux-x64/better_sqlite3.node | |
| mkdir -p node_modules/better-sqlite3/build/Release | |
| cp prebuilds/linux-x64/better_sqlite3.node node_modules/better-sqlite3/build/Release/better_sqlite3.node | |
| ' | |
| - name: Generate telemetry credentials | |
| run: bash ../../.github/scripts/retry.sh --label "generate telemetry credentials" -- node scripts/generate-telemetry-credentials.cjs | |
| env: | |
| MEMOS_ARMS_ENDPOINT: ${{ secrets.MEMOS_ARMS_ENDPOINT }} | |
| MEMOS_ARMS_PID: ${{ secrets.MEMOS_ARMS_PID }} | |
| MEMOS_ARMS_ENV: ${{ secrets.MEMOS_ARMS_ENV }} | |
| - name: Bump version | |
| env: | |
| RELEASE_VERSION: ${{ inputs.version }} | |
| run: | | |
| if [[ "${RELEASE_VERSION}" == v* ]]; then | |
| echo "::error::version must not include a leading v." | |
| exit 1 | |
| fi | |
| bash ../../.github/scripts/retry.sh --label "bump package version" -- npm version "${RELEASE_VERSION}" --no-git-tag-version --allow-same-version | |
| npm run sync:hermes-version | |
| npm run check:hermes-version | |
| - name: Validate and build package | |
| env: | |
| RELEASE_VERSION: ${{ inputs.version }} | |
| run: | | |
| set -euo pipefail | |
| bash ../../.github/scripts/retry.sh --attempts 2 --label "npm run lint" -- npm run lint | |
| bash ../../.github/scripts/retry.sh --attempts 2 --label "npm test" -- npm test | |
| release_dir="${RUNNER_TEMP}/memos-local-plugin-release" | |
| pack_json="${RUNNER_TEMP}/memos-local-plugin-pack.json" | |
| mkdir -p "${release_dir}" | |
| bash ../../.github/scripts/retry.sh --label "npm pack" -- \ | |
| bash -euo pipefail -c 'npm pack --json --silent --pack-destination "$1" > "$2"' \ | |
| _ "${release_dir}" "${pack_json}" | |
| pack_filename="$( | |
| node -e ' | |
| const fs = require("node:fs"); | |
| const raw = fs.readFileSync(process.argv[1], "utf8"); | |
| const jsonStart = raw.match(/^\[/m); | |
| if (!jsonStart || jsonStart.index === undefined) { | |
| throw new Error("npm pack output did not contain a JSON report"); | |
| } | |
| const report = JSON.parse(raw.slice(jsonStart.index)); | |
| if (!Array.isArray(report) || report.length !== 1 || !report[0].filename) { | |
| throw new Error("npm pack did not report exactly one tarball"); | |
| } | |
| fs.writeFileSync(process.argv[1], `${JSON.stringify(report, null, 2)}\n`); | |
| process.stdout.write(report[0].filename); | |
| ' "${pack_json}" | |
| )" | |
| release_tarball="${release_dir}/${pack_filename}" | |
| test -s "${release_tarball}" | |
| package_version="$( | |
| tar -xOf "${release_tarball}" package/package.json \ | |
| | node -e ' | |
| const fs = require("node:fs"); | |
| process.stdout.write(JSON.parse(fs.readFileSync(0, "utf8")).version); | |
| ' | |
| )" | |
| manifest_version="$( | |
| tar -xOf "${release_tarball}" package/adapters/hermes/plugin.yaml \ | |
| | awk '$1 == "version:" { print $2; exit }' | |
| )" | |
| if [ "${package_version}" != "${RELEASE_VERSION}" ]; then | |
| echo "::error::Tarball package.json version ${package_version} does not match ${RELEASE_VERSION}." | |
| exit 1 | |
| fi | |
| if [ "${manifest_version}" != "${RELEASE_VERSION}" ]; then | |
| echo "::error::Tarball Hermes manifest version ${manifest_version} does not match ${RELEASE_VERSION}." | |
| exit 1 | |
| fi | |
| sha256sum "${release_tarball}" > "${release_tarball}.sha256" | |
| { | |
| echo "RELEASE_TARBALL=${release_tarball}" | |
| echo "RELEASE_TARBALL_SHA256=${release_tarball}.sha256" | |
| } >> "${GITHUB_ENV}" | |
| - name: Draft GitHub Release notes | |
| id: release_notes | |
| working-directory: . | |
| env: | |
| RELEASE_VERSION: ${{ inputs.version }} | |
| RELEASE_TAG: memos-local-plugin-v${{ inputs.version }} | |
| NPM_DIST_TAG: ${{ inputs.tag }} | |
| MANUAL_RELEASE_NOTES: ${{ inputs.release_notes }} | |
| DOC_AGENT_RELEASE_NOTES_DRAFT_URL: ${{ secrets.DOC_AGENT_RELEASE_NOTES_DRAFT_URL }} | |
| DOC_AGENT_RELEASE_FAILURE_URL: ${{ secrets.DOC_AGENT_RELEASE_FAILURE_URL }} | |
| DOC_AGENT_RELEASE_NOTES_DRAFT_TOKEN: ${{ secrets.DOC_AGENT_RELEASE_NOTES_DRAFT_TOKEN }} | |
| run: bash .github/scripts/retry.sh --attempts 2 --label "draft GitHub Release notes" -- node .github/scripts/draft-local-plugin-release-notes.mjs | |
| - name: Prepare release notes inspection artifact | |
| working-directory: . | |
| env: | |
| DRY_RUN: ${{ inputs.dry_run }} | |
| RELEASE_VERSION: ${{ inputs.version }} | |
| RELEASE_NOTES_FILE: ${{ steps.release_notes.outputs.release_notes_file }} | |
| EVIDENCE_FILE: ${{ steps.release_notes.outputs.evidence_file }} | |
| DRAFT_FILE: ${{ steps.release_notes.outputs.draft_file }} | |
| DRAFT_USED: ${{ steps.release_notes.outputs.draft_used }} | |
| PREVIOUS_TAG: ${{ steps.release_notes.outputs.previous_tag }} | |
| CURRENT_TAG: ${{ steps.release_notes.outputs.current_tag }} | |
| CURRENT_REF: ${{ steps.release_notes.outputs.current_ref }} | |
| DRAFT_CONFIDENCE: ${{ steps.release_notes.outputs.draft_confidence }} | |
| MISSING_REQUIRED_COUNT: ${{ steps.release_notes.outputs.missing_required_count }} | |
| VALIDATION_ATTEMPT_COUNT: ${{ steps.release_notes.outputs.validation_attempt_count }} | |
| REPAIR_ATTEMPT_COUNT: ${{ steps.release_notes.outputs.repair_attempt_count }} | |
| run: | | |
| set -euo pipefail | |
| inspection_dir="${RUNNER_TEMP}/memos-local-plugin-release-notes-inspection" | |
| mkdir -p "${inspection_dir}" | |
| if [ -z "${RELEASE_NOTES_FILE}" ] || [ ! -s "${RELEASE_NOTES_FILE}" ]; then | |
| echo "::error::Release notes file was not generated." | |
| exit 1 | |
| fi | |
| cp "${RELEASE_NOTES_FILE}" "${inspection_dir}/release-notes.md" | |
| cp "${RUNNER_TEMP}/memos-local-plugin-pack.json" "${inspection_dir}/npm-pack.json" | |
| cp "${RELEASE_TARBALL}" "${inspection_dir}/" | |
| cp "${RELEASE_TARBALL_SHA256}" "${inspection_dir}/" | |
| if [ -n "${EVIDENCE_FILE}" ] && [ -s "${EVIDENCE_FILE}" ]; then | |
| cp "${EVIDENCE_FILE}" "${inspection_dir}/evidence.json" | |
| fi | |
| if [ "${DRAFT_USED:-}" = "true" ]; then | |
| if [ -z "${DRAFT_FILE}" ] || [ ! -s "${DRAFT_FILE}" ]; then | |
| echo "::error::Draft JSON file was not generated." | |
| exit 1 | |
| fi | |
| cp "${DRAFT_FILE}" "${inspection_dir}/release-notes-draft.json" | |
| elif [ -n "${DRAFT_FILE}" ] && [ -s "${DRAFT_FILE}" ]; then | |
| cp "${DRAFT_FILE}" "${inspection_dir}/release-notes-draft.json" | |
| fi | |
| { | |
| echo "# MemOS local plugin release notes inspection" | |
| echo | |
| echo "- dry_run: ${DRY_RUN}" | |
| echo "- version: ${RELEASE_VERSION}" | |
| echo "- draft_used: ${DRAFT_USED:-unknown}" | |
| echo "- previous_tag: ${PREVIOUS_TAG:-n/a}" | |
| echo "- current_tag: ${CURRENT_TAG:-n/a}" | |
| echo "- current_ref: ${CURRENT_REF:-n/a}" | |
| echo "- draft_confidence: ${DRAFT_CONFIDENCE:-n/a}" | |
| echo "- missing_required_count: ${MISSING_REQUIRED_COUNT:-n/a}" | |
| echo "- validation_attempt_count: ${VALIDATION_ATTEMPT_COUNT:-n/a}" | |
| echo "- repair_attempt_count: ${REPAIR_ATTEMPT_COUNT:-n/a}" | |
| echo | |
| echo "Files:" | |
| echo | |
| echo "- release-notes.md" | |
| echo "- npm-pack.json" | |
| if [ -n "${EVIDENCE_FILE}" ] && [ -s "${EVIDENCE_FILE}" ]; then | |
| echo "- evidence.json (redacted; no full diff or prompt guidance)" | |
| fi | |
| if [ -f "${inspection_dir}/release-notes-draft.json" ]; then | |
| echo "- release-notes-draft.json" | |
| fi | |
| } > "${inspection_dir}/README.md" | |
| { | |
| echo "### MemOS local plugin release notes" | |
| echo | |
| echo "- dry_run: \`${DRY_RUN}\`" | |
| echo "- draft_used: \`${DRAFT_USED:-unknown}\`" | |
| echo "- previous_tag: \`${PREVIOUS_TAG:-n/a}\`" | |
| echo "- current_tag: \`${CURRENT_TAG:-n/a}\`" | |
| echo "- current_ref: \`${CURRENT_REF:-n/a}\`" | |
| echo "- draft_confidence: \`${DRAFT_CONFIDENCE:-n/a}\`" | |
| echo "- missing_required_count: \`${MISSING_REQUIRED_COUNT:-n/a}\`" | |
| echo "- validation_attempt_count: \`${VALIDATION_ATTEMPT_COUNT:-n/a}\`" | |
| echo "- repair_attempt_count: \`${REPAIR_ATTEMPT_COUNT:-n/a}\`" | |
| echo | |
| echo "Download the workflow artifact \`memos-local-plugin-release-notes-inspection\` to review release-notes.md, release-notes-draft.json, and redacted evidence.json." | |
| } >> "${GITHUB_STEP_SUMMARY}" | |
| - name: Upload release notes inspection artifact | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: memos-local-plugin-release-notes-inspection | |
| path: ${{ runner.temp }}/memos-local-plugin-release-notes-inspection | |
| if-no-files-found: error | |
| - name: Stop before publish in dry run | |
| if: ${{ inputs.dry_run == true }} | |
| run: | | |
| echo "::notice::dry_run=true; skipped npm publish, tag, GitHub Release, and release PR." | |
| - name: Publish to npm | |
| if: ${{ inputs.dry_run != true }} | |
| env: | |
| NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} | |
| PACKAGE_NAME: "@memtensor/memos-local-plugin" | |
| RELEASE_VERSION: ${{ inputs.version }} | |
| RELEASE_TAG: memos-local-plugin-v${{ inputs.version }} | |
| NPM_DIST_TAG: ${{ inputs.tag }} | |
| RECOVER_EXISTING_NPM_RELEASE: ${{ inputs.recover_existing_npm_release }} | |
| DOC_AGENT_RELEASE_FAILURE_URL: ${{ secrets.DOC_AGENT_RELEASE_FAILURE_URL }} | |
| DOC_AGENT_RELEASE_NOTES_DRAFT_TOKEN: ${{ secrets.DOC_AGENT_RELEASE_NOTES_DRAFT_TOKEN }} | |
| run: bash ../../.github/scripts/publish-local-plugin.sh | |
| - name: Create release tag and PR | |
| if: ${{ inputs.dry_run != true }} | |
| working-directory: . | |
| env: | |
| GH_TOKEN: ${{ github.token }} | |
| RELEASE_VERSION: ${{ inputs.version }} | |
| NPM_DIST_TAG: ${{ inputs.tag }} | |
| RELEASE_NOTES_FILE: ${{ steps.release_notes.outputs.release_notes_file }} | |
| DEFAULT_BRANCH: ${{ github.event.repository.default_branch }} | |
| run: | | |
| set -euo pipefail | |
| if [ -z "${RELEASE_NOTES_FILE}" ] || [ ! -s "${RELEASE_NOTES_FILE}" ]; then | |
| echo "::error::Release notes file was not generated." | |
| exit 1 | |
| fi | |
| git config user.name "github-actions[bot]" | |
| git config user.email "github-actions[bot]@users.noreply.github.com" | |
| release_tag="memos-local-plugin-v${RELEASE_VERSION}" | |
| release_branch="release/${release_tag}" | |
| release_title="release: @memtensor/memos-local-plugin v${RELEASE_VERSION}" | |
| remote_branch_sha() { | |
| local branch="$1" | |
| local out="${RUNNER_TEMP}/memos-local-plugin-remote-branch.txt" | |
| for attempt in 1 2 3; do | |
| set +e | |
| git ls-remote --heads origin "${branch}" >"${out}" 2>&1 | |
| status=$? | |
| set -e | |
| if [ "${status}" = 0 ]; then | |
| awk '{print $1}' "${out}" | |
| return 0 | |
| fi | |
| sed -n '1,120p' "${out}" | |
| if [ "${attempt}" = 3 ]; then | |
| echo "::error::Failed to check remote branch ${branch} after three attempts." | |
| exit "${status}" | |
| fi | |
| sleep "$((attempt * 5))" | |
| done | |
| } | |
| remote_tag_exists() { | |
| local tag="$1" | |
| for attempt in 1 2 3; do | |
| set +e | |
| git ls-remote --exit-code --tags origin "refs/tags/${tag}" >/dev/null 2>&1 | |
| status=$? | |
| set -e | |
| if [ "${status}" = 0 ]; then | |
| return 0 | |
| fi | |
| if [ "${status}" = 2 ]; then | |
| return 1 | |
| fi | |
| if [ "${attempt}" = 3 ]; then | |
| echo "::error::Failed to check remote tag ${tag} after three attempts." | |
| exit "${status}" | |
| fi | |
| sleep "$((attempt * 5))" | |
| done | |
| } | |
| create_release_if_missing() { | |
| release_flags=() | |
| if [[ "${RELEASE_VERSION}" == *-* || "${NPM_DIST_TAG}" != "latest" ]]; then | |
| release_flags+=(--prerelease) | |
| echo "Marking GitHub Release ${release_tag} as prerelease because version=${RELEASE_VERSION}, npm_dist_tag=${NPM_DIST_TAG}." | |
| fi | |
| for attempt in 1 2 3; do | |
| if gh release view "${release_tag}" --repo "${GITHUB_REPOSITORY}" >/dev/null 2>&1; then | |
| echo "GitHub Release ${release_tag} already exists; leaving it unchanged." | |
| return 0 | |
| fi | |
| set +e | |
| gh release create "${release_tag}" \ | |
| --repo "${GITHUB_REPOSITORY}" \ | |
| --target "$(git rev-parse HEAD)" \ | |
| --title "MemOS Local Plugin v${RELEASE_VERSION}" \ | |
| --notes-file "${RELEASE_NOTES_FILE}" \ | |
| "${release_flags[@]}" | |
| status=$? | |
| set -e | |
| if [ "${status}" = 0 ]; then | |
| return 0 | |
| fi | |
| if [ "${attempt}" = 3 ]; then | |
| if gh release view "${release_tag}" --repo "${GITHUB_REPOSITORY}" >/dev/null 2>&1; then | |
| echo "GitHub Release ${release_tag} exists after a failed create response; treating as success." | |
| return 0 | |
| fi | |
| echo "::error::Failed to create GitHub Release ${release_tag} after three attempts." | |
| exit "${status}" | |
| fi | |
| sleep "$((attempt * 5))" | |
| done | |
| } | |
| create_pr_if_missing() { | |
| if gh pr view "${release_branch}" --repo "${GITHUB_REPOSITORY}" >/dev/null 2>&1; then | |
| echo "Release PR already exists for ${release_branch}." | |
| return 0 | |
| fi | |
| for attempt in 1 2 3; do | |
| set +e | |
| gh pr create \ | |
| --repo "${GITHUB_REPOSITORY}" \ | |
| --base "${DEFAULT_BRANCH}" \ | |
| --head "${release_branch}" \ | |
| --title "${release_title}" \ | |
| --body "Synchronizes package and Hermes metadata for the published npm release ${RELEASE_VERSION}." | |
| status=$? | |
| set -e | |
| if [ "${status}" = 0 ]; then | |
| return 0 | |
| fi | |
| if gh pr view "${release_branch}" --repo "${GITHUB_REPOSITORY}" >/dev/null 2>&1; then | |
| echo "Release PR exists after a failed create response; treating as success." | |
| return 0 | |
| fi | |
| if [ "${attempt}" = 3 ]; then | |
| echo "::warning::Failed to create release PR automatically after three attempts. Open a PR from ${release_branch} to ${DEFAULT_BRANCH}." | |
| return 0 | |
| fi | |
| sleep "$((attempt * 5))" | |
| done | |
| } | |
| git add \ | |
| apps/memos-local-plugin/package.json \ | |
| apps/memos-local-plugin/package-lock.json \ | |
| apps/memos-local-plugin/adapters/hermes/plugin.yaml | |
| created_release_commit=false | |
| if ! git diff --staged --quiet; then | |
| git commit -m "${release_title}" | |
| created_release_commit=true | |
| fi | |
| if [ "${created_release_commit}" = "true" ]; then | |
| remote_branch_sha="$(remote_branch_sha "${release_branch}")" | |
| if [ -n "${remote_branch_sha}" ]; then | |
| if [ "${remote_branch_sha}" != "$(git rev-parse HEAD)" ]; then | |
| echo "::error::Release branch ${release_branch} already exists at a different commit; refusing to overwrite it." | |
| exit 1 | |
| fi | |
| echo "Release branch ${release_branch} already points at this commit." | |
| else | |
| bash .github/scripts/retry.sh --label "push release branch" -- git push origin "HEAD:refs/heads/${release_branch}" | |
| fi | |
| fi | |
| if remote_tag_exists "${release_tag}"; then | |
| echo "Tag ${release_tag} already exists on origin; leaving it unchanged." | |
| else | |
| git tag "${release_tag}" | |
| bash .github/scripts/retry.sh --label "push release tag" -- git push origin "refs/tags/${release_tag}" | |
| fi | |
| create_release_if_missing | |
| if [ "${created_release_commit}" = "true" ]; then | |
| create_pr_if_missing | |
| else | |
| echo "No version bump changes to open as a PR." | |
| fi |