name: Release on: push: tags: ["v*", "!v*-nightly.*"] workflow_dispatch: inputs: tag: description: "Tag to release (e.g. v0.1.0); must already exist" required: true permissions: contents: read concurrency: group: release-${{ github.event.inputs.tag || github.ref_name }} env: CARGO_TERM_COLOR: always CARGO_INCREMENTAL: 0 TAG: ${{ github.event.inputs.tag || github.ref_name }} COMMUNITY_PLUGINS_INDEX: https://github.com/l0ng-ai/openidea-plugins/releases/download/index/index.json jobs: # Creates (or reuses) one draft release that every platform build uploads into. create-release: if: ${{ !contains(github.event.inputs.tag || github.ref_name, '-nightly.') }} permissions: contents: write runs-on: ubuntu-latest outputs: release_id: ${{ steps.release.outputs.id }} steps: - uses: actions/checkout@v5 with: { ref: "${{ env.TAG }}" } - name: Tag matches app version run: | app="v$(jq -r .version src-tauri/tauri.conf.json)" if [ "$app" != "$TAG" ]; then echo "::error::tag $TAG does not match tauri.conf.json version $app" exit 1 fi # A shipped plugin API is frozen: its WIT must be the snapshot api_frozen.rs checks later # versions against (plugins/README.md, "Evolving the plugin API"). - name: Plugin API snapshot matches wit/ run: | api() { sed -n 's/^package openidea:plugin@\(.*\);$/\1/p' "$1/plugin.wit"; } snapshot=sdk/rust/openidea-plugin/tests/api/released if [ "$(api wit)" != "$(api "$snapshot")" ]; then echo "::error::plugin API $(api wit) ships without a snapshot ($snapshot is $(api "$snapshot")): copy wit/*.wit to $snapshot/" exit 1 fi - id: release uses: actions/github-script@v8 with: script: | const tag = process.env.TAG; const { owner, repo } = context.repo; const releases = await github.paginate(github.rest.repos.listReleases, { owner, repo }); const existing = releases.find((r) => r.tag_name === tag); if (existing) { if (!existing.draft) core.setFailed(`release ${tag} is already published`); core.setOutput("id", existing.id); return; } // Nightly builds are prereleases: the notes span stable release to stable release. const previous = releases.find((r) => !r.draft && !r.prerelease); const { data: notes } = await github.rest.repos.generateReleaseNotes({ owner, repo, tag_name: tag, previous_tag_name: previous?.tag_name, }); const { data } = await github.rest.repos.createRelease({ owner, repo, tag_name: tag, name: `OpenIDEA ${tag}`, draft: true, prerelease: tag.includes("-"), body: notes.body, }); core.setOutput("id", data.id); binaries: permissions: contents: write needs: create-release uses: ./.github/workflows/release-build.yml with: tag: ${{ github.event.inputs.tag || github.ref_name }} release_id: ${{ needs.create-release.outputs.release_id }} secrets: inherit # The official plugins, signed, and the marketplace index released apps read # (releases/latest/download/plugins-index.json). They are not in the # installers: users install them from Settings › Plugins. Without the # signing key (secret PLUGIN_SIGNING_KEY: the file `openidea-plugin keygen` # writes) nothing is published and the release goes on. The index also lists # the community plugins of the registry (COMMUNITY_PLUGINS_INDEX; refreshed # between releases by plugins-index.yml), and the release carries the # openidea-plugin tool for Linux x64, which that workflow and the registry's # checks run. plugins: permissions: contents: write needs: create-release name: Official plugins runs-on: ubuntu-22.04 env: SIGNING_KEY: ${{ secrets.PLUGIN_SIGNING_KEY }} steps: - id: key run: | if [ -z "$SIGNING_KEY" ]; then echo "::warning::PLUGIN_SIGNING_KEY is not set: the official plugins are not published" echo "skip=true" >> "$GITHUB_OUTPUT" fi - uses: actions/checkout@v5 if: steps.key.outputs.skip != 'true' with: { ref: "${{ env.TAG }}" } - uses: dtolnay/rust-toolchain@stable if: steps.key.outputs.skip != 'true' with: { targets: wasm32-wasip2 } - uses: Swatinem/rust-cache@v2 if: steps.key.outputs.skip != 'true' with: { key: release-plugins } - uses: pnpm/action-setup@v5 if: steps.key.outputs.skip != 'true' with: { version: 11 } - uses: actions/setup-node@v5 if: steps.key.outputs.skip != 'true' with: { node-version: 22 } # Builds the web pages; fails when a checked-in component isn't built from these sources. - name: Build if: steps.key.outputs.skip != 'true' run: | plugins/build.sh --check cargo build --release --locked -p ide-host --bin openidea-plugin - name: Pack, index and upload if: steps.key.outputs.skip != 'true' shell: bash env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} run: | set -euo pipefail tool="$PWD/target/release/openidea-plugin" key="$RUNNER_TEMP/openidea.key" out="$RUNNER_TEMP/market" (umask 077 && printf '%s' "$SIGNING_KEY" > "$key") trap 'rm -f "$key"' EXIT mkdir -p "$out" for dir in plugins/official/*/; do id="$(sed -n 's/^id *= *"\(.*\)".*/\1/p' "$dir/plugin.toml" | head -1)" version="$(sed -n 's/^version *= *"\(.*\)".*/\1/p' "$dir/plugin.toml" | head -1)" "$tool" pack "$dir" --key "$key" --publisher openidea --out "$out/$id-$version.oiplugin" done # The published index's revocations, earlier keys and community plugins carry over. curl -fsSL "https://github.com/$GITHUB_REPOSITORY/releases/latest/download/plugins-index.json" \ -o "$out/plugins-index.json" || rm -f "$out/plugins-index.json" # Downloads point at this release: older versions kept for older IDEs stay where they were published. "$tool" index "$out" --publisher openidea --name OpenIDEA --key "$key" --out "$out/plugins-index.json" \ --download-base "https://github.com/$GITHUB_REPOSITORY/releases/download/$TAG" "$tool" merge "$out/plugins-index.json" "$COMMUNITY_PLUGINS_INDEX" --keep openidea || echo "::warning::the community plugins were not refreshed" gzip -9 -c "$tool" > "$out/openidea-plugin-linux-x86_64.gz" gh release upload "$TAG" "$out"/*.oiplugin "$out/plugins-index.json" \ "$out/openidea-plugin-linux-x86_64.gz" --clobber publish: permissions: contents: write needs: [create-release, binaries, plugins] runs-on: ubuntu-latest steps: - uses: actions/github-script@v8 env: RELEASE_ID: ${{ needs.create-release.outputs.release_id }} with: script: | await github.rest.repos.updateRelease({ ...context.repo, release_id: Number(process.env.RELEASE_ID), draft: false, }); # The China mirror the updater's CNB source reads; a failure here leaves the GitHub release as is. cnb-mirror: needs: publish uses: ./.github/workflows/cnb-mirror.yml with: { tag: "${{ github.event.inputs.tag || github.ref_name }}" } secrets: inherit