Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
46 changes: 42 additions & 4 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -21,22 +21,26 @@ jobs:

- uses: actions/setup-node@v4
with:
node-version: '20'
node-version: "20"
cache: npm

- name: Install dependencies
run: npm ci || npm install

- name: Syntax check
run: node --check bin/rewrite-release-notes.mjs
run: |
node --check bin/rewrite-release-notes.mjs
node --check bin/build-updater-manifest.mjs

- name: Help works
run: node bin/rewrite-release-notes.mjs --help
run: |
node bin/rewrite-release-notes.mjs --help
node bin/build-updater-manifest.mjs --help

# Smoke: dry-run renders without an LLM call. Uses the repo's own tags
# if any exist; otherwise creates synthetic ones so the script has
# something to diff.
- name: Dry-run smoke
- name: Dry-run smoke (release-notes)
run: |
if [ -z "$(git tag --list)" ]; then
git -c user.email=ci@example.com -c user.name=ci tag v0.0.1 HEAD
Expand All @@ -45,3 +49,37 @@ jobs:
git -c user.email=ci@example.com -c user.name=ci tag v0.0.2 HEAD
fi
node bin/rewrite-release-notes.mjs --dry-run

# Smoke: build-updater-manifest produces a valid latest.json from a
# synthetic dist/ tree. Verifies the platform classifier + sig pairing
# without needing a real Tauri build.
- name: Smoke (build-updater-manifest)
run: |
mkdir -p smoke-dist/macos smoke-dist/nsis smoke-dist/appimage
echo "stub-mac" > smoke-dist/macos/MyApp_0.0.1_universal.app.tar.gz
echo "sig-mac" > smoke-dist/macos/MyApp_0.0.1_universal.app.tar.gz.sig
echo "stub-win" > smoke-dist/nsis/MyApp_0.0.1_x64-setup.nsis.zip
echo "sig-win" > smoke-dist/nsis/MyApp_0.0.1_x64-setup.nsis.zip.sig
echo "stub-linux" > smoke-dist/appimage/MyApp_0.0.1_amd64.AppImage.tar.gz
echo "sig-linux" > smoke-dist/appimage/MyApp_0.0.1_amd64.AppImage.tar.gz.sig
node bin/build-updater-manifest.mjs \
--version 0.0.1 \
--dist smoke-dist \
--base-url https://example.test/0.0.1 \
--out smoke-latest.json
# Sanity: manifest must list all four platform keys (universal mac
# expands to two) and embed the sig contents.
node -e '
const m = require("./smoke-latest.json");
const keys = Object.keys(m.platforms).sort();
const expected = ["darwin-aarch64","darwin-x86_64","linux-x86_64","windows-x86_64"];
if (JSON.stringify(keys) !== JSON.stringify(expected)) {
console.error("Bad platform keys:", keys);
process.exit(1);
}
if (m.platforms["darwin-aarch64"].signature !== "sig-mac") {
console.error("Bad sig embed");
process.exit(1);
}
console.log("manifest smoke ok");
'
289 changes: 289 additions & 0 deletions .github/workflows/tauri-release.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,289 @@
name: Tauri Release

# Reusable workflow that builds, signs, notarizes, and publishes a Tauri 2
# desktop app across macOS (universal), Windows, and Linux. Consumers call
# it from their own thin `build-desktop.yml`:
#
# jobs:
# desktop:
# uses: protoLabsAI/release-tools/.github/workflows/tauri-release.yml@v1
# secrets: inherit
# with:
# project-path: apps/desktop
# app-identifier: studio.protolabs.example
# r2-bucket: example-desktop-releases
# r2-public-base-url: https://dl.example.studio
# draft-github-release: true
#
# See the README's "Tauri release workflow" section for the full secret list.

on:
workflow_call:
inputs:
project-path:
description: "Path to the Tauri project (the directory that contains src-tauri/)."
required: true
type: string
app-identifier:
description: "CFBundleIdentifier / Tauri identifier (e.g. studio.protolabs.example)."
required: true
type: string
r2-bucket:
description: "Cloudflare R2 bucket name where binaries + latest.json are uploaded."
required: true
type: string
r2-public-base-url:
description: "Public-read base URL for the bucket (e.g. https://dl.example.studio). Used in the updater manifest."
required: true
type: string
node-version:
description: "Node.js version for the build."
required: false
type: string
default: "22"
pnpm-version:
description: 'pnpm version. Set "" to use npm instead.'
required: false
type: string
default: "9.15.0"
pre-build-command:
description: "Optional shell command run after install, before tauri build (e.g. workspace builds)."
required: false
type: string
default: ""
draft-github-release:
description: "Create a draft GitHub Release with all artifacts attached."
required: false
type: boolean
default: true
release-notes-text:
description: "Notes embedded in latest.json (the GH Release body comes from action-gh-release)."
required: false
type: string
default: "See the GitHub Release for details."

permissions:
contents: write
Comment on lines +65 to +66
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

cat -n .github/workflows/tauri-release.yml

Repository: protoLabsAI/release-tools

Length of output: 13196


Move contents: write down to the publish job.

This workflow-level permissions block gives all build matrix runners (macOS, Windows, Linux) a write-capable GITHUB_TOKEN, but only the publish job needs it—specifically for the softprops/action-gh-release step that creates the GitHub Release. The build job performs no operations requiring contents write access. Since GitHub Actions supports job-scoped permissions, move this to the publish job only to follow the principle of least privilege and reduce the blast radius. (docs.github.com)

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.github/workflows/tauri-release.yml around lines 65 - 66, The workflow-level
permissions block currently grants contents: write to all jobs; move this
permission into the publish job only by removing or changing the top-level
permissions: contents: write and instead add a job-scoped permissions entry
under the publish job (the job that runs softprops/action-gh-release) so only
publish has GITHUB_TOKEN with contents: write while build matrix runners keep
default (read) permissions.


jobs:
build:
strategy:
fail-fast: false
matrix:
include:
- os: macos-14
platform: macos
args: --target universal-apple-darwin
- os: windows-latest
platform: windows
args: ""
- os: ubuntu-22.04
platform: linux
args: ""

runs-on: ${{ matrix.os }}

env:
# Opt cloudflare/wrangler-action and other node20-pinned actions into
# the Node 24 runner ahead of the 2026-06-02 forced upgrade.
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: "true"

steps:
- uses: actions/checkout@v6

- name: Setup pnpm
if: inputs.pnpm-version != ''
uses: pnpm/action-setup@v6
with:
version: ${{ inputs.pnpm-version }}

- uses: actions/setup-node@v6
with:
node-version: ${{ inputs.node-version }}
cache: ${{ inputs.pnpm-version != '' && 'pnpm' || 'npm' }}

- uses: dtolnay/rust-toolchain@stable
Comment on lines +92 to +105
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

cat -n .github/workflows/tauri-release.yml | sed -n '85,110p'

Repository: protoLabsAI/release-tools

Length of output: 1027


🏁 Script executed:

cat -n .github/workflows/tauri-release.yml | sed -n '150,165p'

Repository: protoLabsAI/release-tools

Length of output: 996


🏁 Script executed:

cat -n .github/workflows/tauri-release.yml | sed -n '176,190p'

Repository: protoLabsAI/release-tools

Length of output: 928


🏁 Script executed:

cat -n .github/workflows/tauri-release.yml | sed -n '190,205p'

Repository: protoLabsAI/release-tools

Length of output: 787


🏁 Script executed:

cat -n .github/workflows/tauri-release.yml | sed -n '249,260p'

Repository: protoLabsAI/release-tools

Length of output: 639


🏁 Script executed:

cat -n .github/workflows/tauri-release.yml | sed -n '277,290p'

Repository: protoLabsAI/release-tools

Length of output: 598


🏁 Script executed:

wc -l .github/workflows/tauri-release.yml

Repository: protoLabsAI/release-tools

Length of output: 109


🌐 Web query:

GitHub Actions security hardening pin full commit SHA

💡 Result:

To “pin GitHub Actions to full commit SHAs” you replace tag/branch references in uses: with the action’s full-length commit SHA (typically 40 hex characters). Example: - Before: uses: actions/checkout@v6 - After: uses: actions/checkout@5a4ac90 # v6 Why this is the recommended hardening: - GitHub’s security guidance states that pinning an action to a full-length commit SHA is the only way to use an action “as an immutable release,” helping mitigate the risk of a malicious actor modifying the action associated with a tag/branch. Operational note: - GitHub added policy enforcement (“SHA pinning enforcement”) so workflows that use actions not pinned to a full commit SHA can fail when the policy is enabled.

Citations:


🏁 Script executed:

# Check if there are any commit SHA pinned actions in the workflow to compare
rg -n '@[a-f0-9]{40}' .github/workflows/tauri-release.yml

Repository: protoLabsAI/release-tools

Length of output: 51


Pin all third-party actions to full commit SHAs.

This release workflow references third-party actions using mutable tags (@v6, @v0, @v3, @stable) while handling signing, notarization, R2, and release credentials. GitHub's security hardening guidance recommends pinning actions to full-length commit SHAs—the only immutable way to reference an action and mitigate the risk of malicious modifications to tagged releases.

Update the following actions: actions/checkout@v6, pnpm/action-setup@v6, actions/setup-node@v6, dtolnay/rust-toolchain@stable, tauri-apps/tauri-action@v0 (lines 156, 182, 196), cloudflare/wrangler-action@v3, and softprops/action-gh-release@v3.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.github/workflows/tauri-release.yml around lines 92 - 105, Replace mutable
action tags with immutable full commit SHAs: update uses entries for
actions/checkout@v6, pnpm/action-setup@v6, actions/setup-node@v6,
dtolnay/rust-toolchain@stable, tauri-apps/tauri-action@v0,
cloudflare/wrangler-action@v3, and softprops/action-gh-release@v3 to use the
corresponding full-length commit SHAs (e.g., actions/checkout@<full-sha>) so the
workflow always references an immutable commit; locate the usages by the exact
strings "actions/checkout", "pnpm/action-setup", "actions/setup-node",
"dtolnay/rust-toolchain", "tauri-apps/tauri-action",
"cloudflare/wrangler-action", and "softprops/action-gh-release" in the file and
replace their tag suffixes with the verified commit SHA for the intended
release.


- name: Add macOS Rust targets (universal)
if: matrix.platform == 'macos'
run: |
rustup target add aarch64-apple-darwin
rustup target add x86_64-apple-darwin

- name: Install Linux deps
if: matrix.platform == 'linux'
run: |
sudo apt-get update
sudo apt-get install -y \
libwebkit2gtk-4.1-dev \
libappindicator3-dev \
librsvg2-dev \
patchelf

- name: Install dependencies (pnpm)
if: inputs.pnpm-version != ''
run: pnpm install --frozen-lockfile

- name: Install dependencies (npm)
if: inputs.pnpm-version == ''
run: npm ci

- name: Pre-build command
if: inputs.pre-build-command != ''
run: ${{ inputs.pre-build-command }}
Comment on lines +123 to +133
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Scope install and pre-build steps to the app path.

Lines 123-133 still run dependency install and the optional pre-build from the repository root, while the actual Tauri build is the only place that respects inputs.project-path. A caller with project-path: apps/desktop and a standalone apps/desktop/package.json/lockfile will fail before tauri-action starts. Add a separate package-root/install-root input, or default these steps to the app path.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.github/workflows/tauri-release.yml around lines 123 - 133, The install and
pre-build steps ("Install dependencies (pnpm)", "Install dependencies (npm)",
"Pre-build command") run in the repo root instead of the app folder; update
those steps to run in the app package root by adding working-directory: ${{
inputs.project-path }} to each step (or add a new input like
package-root/install-root and use that variable instead) so dependency installs
and the pre-build command execute inside the target app (reference
inputs.project-path and the three step names to locate the lines to change).

shell: bash

# ─── macOS: write the App Store Connect API key to disk ──────────────
# Tauri's notarization step expects APPLE_API_KEY_PATH to be a
# filesystem path to the .p8 (not the base64 content). GitHub
# secrets are strings, so consumers store the file as
# APPLE_API_KEY_BASE64 and we decode it here.
- name: Prepare App Store Connect API key (.p8)
if: matrix.platform == 'macos' && env.APPLE_API_KEY_BASE64 != ''
env:
APPLE_API_KEY: ${{ secrets.APPLE_API_KEY }}
APPLE_API_KEY_BASE64: ${{ secrets.APPLE_API_KEY_BASE64 }}
run: |
mkdir -p "$RUNNER_TEMP/appstoreconnect/private_keys"
KEY_PATH="$RUNNER_TEMP/appstoreconnect/private_keys/AuthKey_${APPLE_API_KEY}.p8"
echo "$APPLE_API_KEY_BASE64" | base64 --decode > "$KEY_PATH"
chmod 600 "$KEY_PATH"
echo "APPLE_API_KEY_PATH=$KEY_PATH" >> "$GITHUB_ENV"

# ─── macOS: sign + notarize + updater-sign ────────────────────────────
- name: Build (macOS, signed + notarized)
if: matrix.platform == 'macos'
uses: tauri-apps/tauri-action@v0
env:
# Code signing
APPLE_CERTIFICATE: ${{ secrets.APPLE_CERTIFICATE }}
APPLE_CERTIFICATE_PASSWORD: ${{ secrets.APPLE_CERTIFICATE_PASSWORD }}
APPLE_SIGNING_IDENTITY: ${{ secrets.APPLE_SIGNING_IDENTITY }}
APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }}
KEYCHAIN_PASSWORD: ${{ secrets.KEYCHAIN_PASSWORD }}
# Notarization — App Store Connect API key path (preferred).
# APPLE_API_KEY_PATH is set above by the prepare step from the
# base64-encoded secret.
APPLE_API_ISSUER: ${{ secrets.APPLE_API_ISSUER }}
APPLE_API_KEY: ${{ secrets.APPLE_API_KEY }}
# Notarization — app-specific password fallback
APPLE_ID: ${{ secrets.APPLE_ID }}
APPLE_PASSWORD: ${{ secrets.APPLE_PASSWORD }}
# Updater signing
TAURI_SIGNING_PRIVATE_KEY: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY }}
TAURI_SIGNING_PRIVATE_KEY_PASSWORD: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY_PASSWORD }}
with:
projectPath: ${{ inputs.project-path }}
args: ${{ matrix.args }}

# ─── Windows: SSL.com eSigner + updater-sign ──────────────────────────
- name: Build (Windows, signed via eSigner)
if: matrix.platform == 'windows'
uses: tauri-apps/tauri-action@v0
env:
TAURI_SIGNING_PRIVATE_KEY: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY }}
TAURI_SIGNING_PRIVATE_KEY_PASSWORD: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY_PASSWORD }}
ESIGNER_USERNAME: ${{ secrets.ESIGNER_USERNAME }}
ESIGNER_PASSWORD: ${{ secrets.ESIGNER_PASSWORD }}
ESIGNER_CREDENTIAL_ID: ${{ secrets.ESIGNER_CREDENTIAL_ID }}
ESIGNER_TOTP_SECRET: ${{ secrets.ESIGNER_TOTP_SECRET }}
with:
projectPath: ${{ inputs.project-path }}

# ─── Linux: AppImage, no signing ──────────────────────────────────────
- name: Build (Linux, AppImage)
if: matrix.platform == 'linux'
uses: tauri-apps/tauri-action@v0
env:
TAURI_SIGNING_PRIVATE_KEY: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY }}
TAURI_SIGNING_PRIVATE_KEY_PASSWORD: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY_PASSWORD }}
with:
projectPath: ${{ inputs.project-path }}

- name: Upload build artifacts
uses: actions/upload-artifact@v7
with:
name: tauri-${{ matrix.platform }}
path: |
${{ inputs.project-path }}/src-tauri/target/release/bundle/dmg/*.dmg
${{ inputs.project-path }}/src-tauri/target/release/bundle/macos/*.app.tar.gz
${{ inputs.project-path }}/src-tauri/target/release/bundle/macos/*.app.tar.gz.sig
${{ inputs.project-path }}/src-tauri/target/universal-apple-darwin/release/bundle/dmg/*.dmg
${{ inputs.project-path }}/src-tauri/target/universal-apple-darwin/release/bundle/macos/*.app.tar.gz*
${{ inputs.project-path }}/src-tauri/target/release/bundle/nsis/*.exe
${{ inputs.project-path }}/src-tauri/target/release/bundle/nsis/*-setup.nsis.zip
${{ inputs.project-path }}/src-tauri/target/release/bundle/nsis/*-setup.nsis.zip.sig
${{ inputs.project-path }}/src-tauri/target/release/bundle/appimage/*.AppImage
${{ inputs.project-path }}/src-tauri/target/release/bundle/appimage/*.AppImage.tar.gz
${{ inputs.project-path }}/src-tauri/target/release/bundle/appimage/*.AppImage.tar.gz.sig
if-no-files-found: error

publish:
needs: build
runs-on: ubuntu-latest
if: startsWith(github.ref, 'refs/tags/v')

env:
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: "true"

steps:
- uses: actions/checkout@v6

- uses: actions/download-artifact@v8
with:
path: dist
merge-multiple: true

- uses: actions/setup-node@v6
with:
node-version: ${{ inputs.node-version }}

- name: Compute version
id: version
run: echo "version=${GITHUB_REF#refs/tags/v}" >> "$GITHUB_OUTPUT"

- name: Generate updater manifest (latest.json)
run: |
npx --yes -p '@protolabsai/release-tools@latest' build-updater-manifest \
--version "${{ steps.version.outputs.version }}" \
--dist dist \
--base-url "${{ inputs.r2-public-base-url }}/${{ steps.version.outputs.version }}" \
--notes "${{ inputs.release-notes-text }}" \
--out latest.json
Comment on lines +227 to +234
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🌐 Web query:

What are current best practices for pinning npm package versions in CI/CD release workflows, specifically risks of using npx -p @latest?

💡 Result:

Best practices to pin npm versions in CI/CD release workflows 1) Pin the whole dependency tree via committed lockfiles - Commit package-lock.json (or yarn.lock / pnpm-lock.yaml) to version control. - In CI/CD, install using the deterministic command for your ecosystem (npm ci for npm). - npm ci “installs from a package’s lockfile” and “will never write to … package-lock” and “ensures reproducible builds” by bypassing package.json resolution. It also fails if package.json and the lockfile don’t match. 2) Treat “installing new versions” as a deliberate change - Use automated dependency update PRs (e.g., Renovate/Dependabot) rather than allowing builds to float to whatever is currently “latest”. - Only merge after reviewing lockfile diffs. 3) Harden execution during installs (supply-chain risk reduction) - Avoid running lifecycle scripts unless you must. Many supply-chain attacks rely on install-time scripts (preinstall/postinstall). - Use npm ci flags such as --ignore-scripts when appropriate; or in npm config set ignore-scripts=true (with care because some packages require build steps). 4) Add additional gates beyond “known CVEs” - npm audit is useful but not sufficient; it mainly catches known vulnerabilities rather than novel malicious packages. - Consider proactive scanning / analysis tools and provenance verification as layers before or during CI runs. Risks of using npx -p @latest 1) Non-deterministic builds (time-based changes) - @latest resolves to whatever the registry considers latest at execution time. That means two CI runs at different times can execute different package versions/contents even if your repo code is unchanged. - Deterministic CI is the core goal of lockfiles + npm ci; installing a moving target subverts that goal. 2) Increased exposure window for compromised “new releases” - Supply-chain attacks often rely on a short window between malicious publication and detection/removal. Fetching “latest” in CI means you may pull something that was just published. - Security guidance for supply-chain attacks recommends delaying updates / enforcing minimum release age (in pnpm) and, more generally, avoiding blind upgrades to newly published versions. 3) You may execute attacker code directly as part of your pipeline step - npx/npm exec are designed to “run an arbitrary command from an npm package” and may fetch a remote package if it’s not already installed. - That makes the exact code being executed harder to reason about (and harder to tie back to a reviewed lockfile snapshot), which undermines forensic and compliance goals. 4) Lockfile/integrity guarantees don’t cover the runtime execution of npx@latest - Lockfiles (and npm ci) provide cryptographic integrity verification and strict replay of exactly resolved versions. - A one-off npx -p pkg@latest ... fetch is not the same operational model as installing your project from a committed lockfile; the pipeline no longer has a stable, reviewable dependency snapshot for that executed tool. Concrete “safer” alternatives - If you need a CLI tool in CI: install it as a devDependency and run it from node_modules/.bin - Add the tool to package.json with an exact version (or a controlled range), commit lockfile changes, then use npm ci + npm run <script>. - If you must use npx/npm exec: pin an explicit version (not @latest) and preferably still keep it in lockfiles - Example pattern: resolve a specific version in code review (e.g., npx pkg@1.2.3 ...), not a floating tag. - Better: use your project’s installed tool (npm ci + script) so the version is locked and reviewable. - Install dependencies deterministically - npm ci in CI/CD (not npm install) to avoid lockfile drift and unexpected updates during builds. Short checklist - CI/CD uses npm ci. - package-lock.json is committed and matches package.json. - No floating versions like @latest in release-critical steps; avoid npx -p @latest. - Disable/limit install scripts (ignore-scripts) when feasible. - Add layered supply-chain security checks; don’t rely only on npm audit. If you want, paste your current CI job step(s) (redact secrets) and I can suggest a drop-in replacement for the specific npx -p @latest usages.

Citations:


Pin @protolabsai/release-tools to a specific version instead of @latest.

Using @latest makes releases non-reproducible and creates a supply-chain risk vector. If a malicious version is published between your release runs, your CI pipeline will execute it without review. Additionally, this bypasses the integrity guarantees of lockfiles—npm ci cannot protect against fetching whatever is currently latest.

Pin to a concrete version (e.g., 0.2.0), or expose a release-tools-version input with a fixed default.

Suggested change
-          npx --yes -p '@protolabsai/release-tools@latest' build-updater-manifest \
+          npx --yes -p '@protolabsai/release-tools@0.2.0' build-updater-manifest \
             --version "${{ steps.version.outputs.version }}" \
             --dist dist \
             --base-url "${{ inputs.r2-public-base-url }}/${{ steps.version.outputs.version }}" \
             --notes "${{ inputs.release-notes-text }}" \
             --out latest.json
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
- name: Generate updater manifest (latest.json)
run: |
npx --yes -p '@protolabsai/release-tools@latest' build-updater-manifest \
--version "${{ steps.version.outputs.version }}" \
--dist dist \
--base-url "${{ inputs.r2-public-base-url }}/${{ steps.version.outputs.version }}" \
--notes "${{ inputs.release-notes-text }}" \
--out latest.json
- name: Generate updater manifest (latest.json)
run: |
npx --yes -p '@protolabsai/release-tools@0.2.0' build-updater-manifest \
--version "${{ steps.version.outputs.version }}" \
--dist dist \
--base-url "${{ inputs.r2-public-base-url }}/${{ steps.version.outputs.version }}" \
--notes "${{ inputs.release-notes-text }}" \
--out latest.json
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.github/workflows/tauri-release.yml around lines 227 - 234, The workflow
step that runs npx -p '@protolabsai/release-tools@latest' build-updater-manifest
should not use `@latest`; change it to a pinned semver (e.g.,
'@protolabsai/release-tools@0.2.0') or expose a new input like
release-tools-version with a default and use that variable in the npx package
spec, updating the step name "Generate updater manifest (latest.json)" and the
npx invocation (build-updater-manifest) to reference the pinned or input-driven
package spec so CI installs a reproducible, auditable release-tools version.


- name: Upload binaries to R2 (versioned prefix)
uses: cloudflare/wrangler-action@v3
with:
apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }}
accountId: ${{ secrets.R2_ACCOUNT_ID }}
command: |
for file in dist/*; do
name=$(basename "$file")
wrangler r2 object put \
"${{ inputs.r2-bucket }}/${{ steps.version.outputs.version }}/$name" \
--file "$file" \
--remote
done

- name: Upload updater manifest to R2 (latest.json, no-cache)
uses: cloudflare/wrangler-action@v3
with:
apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }}
accountId: ${{ secrets.R2_ACCOUNT_ID }}
command: |
wrangler r2 object put \
"${{ inputs.r2-bucket }}/latest.json" \
--file latest.json \
--content-type 'application/json' \
--cache-control 'no-cache, no-store, must-revalidate' \
--remote

- name: Create draft GitHub Release
if: inputs.draft-github-release
uses: softprops/action-gh-release@v3
with:
files: dist/**/*
draft: true
generate_release_notes: true
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
Loading
Loading