Fix/release untagged guard (#399)

* fix(release): prevent untagged fallback from tauri publish

Set explicit github-script output for release_id and validate it before build jobs.
This prevents tauri-action from creating untagged releases when release id wiring
breaks and keeps assets attached to the intended app-v tag release.

* fix(release): add source-only promote mode and harden release binding

Add a source_only dispatch option to promote workflows and propagate it into
channel publish so maintainers can skip platform builds and verify-nix when needed.
Also validate release/tag/commit binding to prevent untagged fallback and ensure
source archives stay aligned with the intended app-v tag.
This commit is contained in:
cucadmuh
2026-05-01 21:09:09 +03:00
committed by GitHub
parent 9ad0f8af6d
commit fef13fefd1
5 changed files with 150 additions and 26 deletions
+86 -11
View File
@@ -28,6 +28,11 @@ on:
required: false
default: true
type: boolean
build_platform_artifacts:
description: "Build and upload macOS/Windows/Linux artifacts + latest.json"
required: false
default: true
type: boolean
jobs:
create-release:
@@ -35,7 +40,7 @@ jobs:
contents: write
runs-on: ubuntu-latest
outputs:
release_id: ${{ steps.create-release.outputs.result }}
release_id: ${{ steps.create_release.outputs.release_id }}
package_version: ${{ steps.get-version.outputs.version }}
release_tag: ${{ steps.tag.outputs.value }}
source_commit_sha: ${{ steps.source-sha.outputs.value }}
@@ -121,7 +126,7 @@ jobs:
echo "$BODY" >> "$GITHUB_OUTPUT"
echo "$EOF_MARKER" >> "$GITHUB_OUTPUT"
- name: create or update release
id: create-release
id: create_release
uses: actions/github-script@v7
env:
PACKAGE_VERSION: ${{ steps.get-version.outputs.version }}
@@ -139,6 +144,7 @@ jobs:
const version = process.env.PACKAGE_VERSION;
const targetCommitish = process.env.RELEASE_COMMIT_SHA;
const name = `Psysonic v${version}`;
let releaseId = null;
try {
const { data } = await github.rest.repos.getReleaseByTag({
owner: context.repo.owner,
@@ -154,23 +160,90 @@ jobs:
draft,
prerelease,
});
return data.id;
releaseId = data.id;
core.info(`Updated existing release id=${releaseId} tag=${tag}`);
} catch (e) {
if (e.status !== 404) throw e;
const { data } = await github.rest.repos.createRelease({
owner: context.repo.owner,
repo: context.repo.repo,
tag_name: tag,
target_commitish: targetCommitish,
name,
body,
draft,
prerelease,
});
releaseId = data.id;
core.info(`Created release id=${releaseId} tag=${tag} target_commitish=${targetCommitish}`);
}
const { data } = await github.rest.repos.createRelease({
core.setOutput("release_id", String(releaseId));
- name: validate release id output
env:
RELEASE_ID: ${{ steps.create_release.outputs.release_id }}
TAG: ${{ steps.tag.outputs.value }}
run: |
set -euo pipefail
if [ -z "${RELEASE_ID:-}" ]; then
echo "::error::create_release did not produce release_id for tag '$TAG'"
exit 1
fi
if ! printf '%s' "$RELEASE_ID" | grep -qE '^[0-9]+$'; then
echo "::error::release_id is not numeric: '$RELEASE_ID'"
exit 1
fi
- name: verify release-tag binding
uses: actions/github-script@v7
env:
RELEASE_ID: ${{ steps.create_release.outputs.release_id }}
EXPECTED_TAG: ${{ steps.tag.outputs.value }}
EXPECTED_SHA: ${{ steps.source-sha.outputs.value }}
with:
script: |
const releaseId = Number(process.env.RELEASE_ID);
const expectedTag = process.env.EXPECTED_TAG;
const expectedSha = process.env.EXPECTED_SHA;
const { data: release } = await github.rest.repos.getRelease({
owner: context.repo.owner,
repo: context.repo.repo,
tag_name: tag,
target_commitish: targetCommitish,
name,
body,
draft,
prerelease,
release_id: releaseId,
});
return data.id;
if (!release.tag_name || release.tag_name.startsWith("untagged-")) {
core.setFailed(`release_id=${releaseId} is bound to untagged release '${release.tag_name ?? ""}'`);
return;
}
if (release.tag_name !== expectedTag) {
core.setFailed(`release_id=${releaseId} has tag '${release.tag_name}', expected '${expectedTag}'`);
return;
}
const { data: ref } = await github.rest.git.getRef({
owner: context.repo.owner,
repo: context.repo.repo,
ref: `tags/${expectedTag}`,
});
let resolvedSha = ref.object.sha;
if (ref.object.type === "tag") {
const { data: tagObj } = await github.rest.git.getTag({
owner: context.repo.owner,
repo: context.repo.repo,
tag_sha: ref.object.sha,
});
resolvedSha = tagObj.object.sha;
}
if (resolvedSha !== expectedSha) {
core.setFailed(`tag '${expectedTag}' points to ${resolvedSha}, expected ${expectedSha}`);
return;
}
core.info(`Verified release_id=${releaseId}, tag=${expectedTag}, commit=${resolvedSha}`);
build-macos-windows:
if: ${{ inputs.build_platform_artifacts }}
needs: create-release
permissions:
contents: write
@@ -254,6 +327,7 @@ jobs:
gh release upload "$RELEASE_TAG" "Psysonic_${ARCH}.app.tar.gz.sig" --clobber
generate-manifest:
if: ${{ inputs.build_platform_artifacts }}
needs: [create-release, build-macos-windows]
runs-on: ubuntu-24.04
permissions:
@@ -275,6 +349,7 @@ jobs:
gh release upload "$RELEASE_TAG" latest.json --clobber
build-linux:
if: ${{ inputs.build_platform_artifacts }}
needs: create-release
permissions:
contents: write