mirror of
https://github.com/Psychotoxical/psysonic.git
synced 2026-07-21 23:05:46 +00:00
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:
@@ -1,8 +1,5 @@
|
|||||||
name: Next Channel
|
name: Next Channel
|
||||||
|
|
||||||
# Pushes made with the default GITHUB_TOKEN from another workflow (e.g. Promote main to next)
|
|
||||||
# do not trigger push workflows — GitHub blocks recursive runs. Use workflow_run below.
|
|
||||||
#
|
|
||||||
# workflow_dispatch: set "Use workflow from branch" to `main` so reusable-channel-publish.yml
|
# workflow_dispatch: set "Use workflow from branch" to `main` so reusable-channel-publish.yml
|
||||||
# is the latest (tag guards, etc.). The tree built for artifacts is always ref `next` below.
|
# is the latest (tag guards, etc.). The tree built for artifacts is always ref `next` below.
|
||||||
on:
|
on:
|
||||||
@@ -14,13 +11,15 @@ on:
|
|||||||
- "flake.lock"
|
- "flake.lock"
|
||||||
- "nix/**"
|
- "nix/**"
|
||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
workflow_run:
|
inputs:
|
||||||
workflows: ["Promote main to next"]
|
source_only:
|
||||||
types: [completed]
|
description: "Create/refresh tagged release only (skip platform builds and verify-nix)"
|
||||||
|
required: false
|
||||||
|
default: false
|
||||||
|
type: boolean
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
publish-next:
|
publish-next:
|
||||||
if: ${{ github.event_name != 'workflow_run' || github.event.workflow_run.conclusion == 'success' }}
|
|
||||||
uses: ./.github/workflows/reusable-channel-publish.yml
|
uses: ./.github/workflows/reusable-channel-publish.yml
|
||||||
with:
|
with:
|
||||||
channel: next
|
channel: next
|
||||||
@@ -29,5 +28,6 @@ jobs:
|
|||||||
target_branch: next
|
target_branch: next
|
||||||
prerelease: true
|
prerelease: true
|
||||||
draft_release: true
|
draft_release: true
|
||||||
verify_nix: true
|
verify_nix: ${{ !(github.event_name == 'workflow_dispatch' && github.event.inputs.source_only == 'true') }}
|
||||||
|
build_platform_artifacts: ${{ !(github.event_name == 'workflow_dispatch' && github.event.inputs.source_only == 'true') }}
|
||||||
secrets: inherit
|
secrets: inherit
|
||||||
|
|||||||
@@ -2,6 +2,12 @@ name: Promote main to next
|
|||||||
|
|
||||||
on:
|
on:
|
||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
|
inputs:
|
||||||
|
source_only:
|
||||||
|
description: "Create/refresh tagged release only (skip platform builds and verify-nix)"
|
||||||
|
required: false
|
||||||
|
default: false
|
||||||
|
type: boolean
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
validate-main:
|
validate-main:
|
||||||
@@ -15,6 +21,7 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
permissions:
|
permissions:
|
||||||
contents: write
|
contents: write
|
||||||
|
actions: write
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v5
|
- uses: actions/checkout@v5
|
||||||
with:
|
with:
|
||||||
@@ -64,3 +71,20 @@ jobs:
|
|||||||
git commit -m "chore(release): bump next channel to ${NEW_VERSION}"
|
git commit -m "chore(release): bump next channel to ${NEW_VERSION}"
|
||||||
- name: push next
|
- name: push next
|
||||||
run: git push --force-with-lease origin HEAD:next
|
run: git push --force-with-lease origin HEAD:next
|
||||||
|
- name: dispatch Next Channel workflow
|
||||||
|
uses: actions/github-script@v7
|
||||||
|
env:
|
||||||
|
SOURCE_ONLY: ${{ inputs.source_only && 'true' || 'false' }}
|
||||||
|
with:
|
||||||
|
script: |
|
||||||
|
const sourceOnly = process.env.SOURCE_ONLY === "true";
|
||||||
|
await github.rest.actions.createWorkflowDispatch({
|
||||||
|
owner: context.repo.owner,
|
||||||
|
repo: context.repo.repo,
|
||||||
|
workflow_id: "next.yml",
|
||||||
|
ref: "main",
|
||||||
|
inputs: {
|
||||||
|
source_only: sourceOnly ? "true" : "false",
|
||||||
|
},
|
||||||
|
});
|
||||||
|
core.info(`Dispatched next.yml with source_only=${sourceOnly}`);
|
||||||
|
|||||||
@@ -2,12 +2,19 @@ name: Promote next to release
|
|||||||
|
|
||||||
on:
|
on:
|
||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
|
inputs:
|
||||||
|
source_only:
|
||||||
|
description: "Create/refresh tagged release only (skip platform builds and verify-nix)"
|
||||||
|
required: false
|
||||||
|
default: false
|
||||||
|
type: boolean
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
promote:
|
promote:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
permissions:
|
permissions:
|
||||||
contents: write
|
contents: write
|
||||||
|
actions: write
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v5
|
- uses: actions/checkout@v5
|
||||||
with:
|
with:
|
||||||
@@ -49,3 +56,20 @@ jobs:
|
|||||||
git commit -m "chore(release): finalize release version ${NEW_VERSION}"
|
git commit -m "chore(release): finalize release version ${NEW_VERSION}"
|
||||||
- name: push release
|
- name: push release
|
||||||
run: git push --force-with-lease origin HEAD:release
|
run: git push --force-with-lease origin HEAD:release
|
||||||
|
- name: dispatch Release Channel workflow
|
||||||
|
uses: actions/github-script@v7
|
||||||
|
env:
|
||||||
|
SOURCE_ONLY: ${{ inputs.source_only && 'true' || 'false' }}
|
||||||
|
with:
|
||||||
|
script: |
|
||||||
|
const sourceOnly = process.env.SOURCE_ONLY === "true";
|
||||||
|
await github.rest.actions.createWorkflowDispatch({
|
||||||
|
owner: context.repo.owner,
|
||||||
|
repo: context.repo.repo,
|
||||||
|
workflow_id: "release.yml",
|
||||||
|
ref: "main",
|
||||||
|
inputs: {
|
||||||
|
source_only: sourceOnly ? "true" : "false",
|
||||||
|
},
|
||||||
|
});
|
||||||
|
core.info(`Dispatched release.yml with source_only=${sourceOnly}`);
|
||||||
|
|||||||
@@ -1,7 +1,5 @@
|
|||||||
name: Release Channel
|
name: Release Channel
|
||||||
|
|
||||||
# Same GITHUB_TOKEN push limitation as Next Channel (see next.yml).
|
|
||||||
#
|
|
||||||
# workflow_dispatch: use workflow from `main` for latest publish logic; checkout ref is always `release`.
|
# workflow_dispatch: use workflow from `main` for latest publish logic; checkout ref is always `release`.
|
||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
@@ -11,13 +9,15 @@ on:
|
|||||||
- "flake.lock"
|
- "flake.lock"
|
||||||
- "nix/**"
|
- "nix/**"
|
||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
workflow_run:
|
inputs:
|
||||||
workflows: ["Promote next to release"]
|
source_only:
|
||||||
types: [completed]
|
description: "Create/refresh tagged release only (skip platform builds and verify-nix)"
|
||||||
|
required: false
|
||||||
|
default: false
|
||||||
|
type: boolean
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
publish-release:
|
publish-release:
|
||||||
if: ${{ github.event_name != 'workflow_run' || github.event.workflow_run.conclusion == 'success' }}
|
|
||||||
uses: ./.github/workflows/reusable-channel-publish.yml
|
uses: ./.github/workflows/reusable-channel-publish.yml
|
||||||
with:
|
with:
|
||||||
channel: release
|
channel: release
|
||||||
@@ -25,5 +25,6 @@ jobs:
|
|||||||
target_branch: release
|
target_branch: release
|
||||||
prerelease: false
|
prerelease: false
|
||||||
draft_release: true
|
draft_release: true
|
||||||
verify_nix: true
|
verify_nix: ${{ !(github.event_name == 'workflow_dispatch' && github.event.inputs.source_only == 'true') }}
|
||||||
|
build_platform_artifacts: ${{ !(github.event_name == 'workflow_dispatch' && github.event.inputs.source_only == 'true') }}
|
||||||
secrets: inherit
|
secrets: inherit
|
||||||
|
|||||||
@@ -28,6 +28,11 @@ on:
|
|||||||
required: false
|
required: false
|
||||||
default: true
|
default: true
|
||||||
type: boolean
|
type: boolean
|
||||||
|
build_platform_artifacts:
|
||||||
|
description: "Build and upload macOS/Windows/Linux artifacts + latest.json"
|
||||||
|
required: false
|
||||||
|
default: true
|
||||||
|
type: boolean
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
create-release:
|
create-release:
|
||||||
@@ -35,7 +40,7 @@ jobs:
|
|||||||
contents: write
|
contents: write
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
outputs:
|
outputs:
|
||||||
release_id: ${{ steps.create-release.outputs.result }}
|
release_id: ${{ steps.create_release.outputs.release_id }}
|
||||||
package_version: ${{ steps.get-version.outputs.version }}
|
package_version: ${{ steps.get-version.outputs.version }}
|
||||||
release_tag: ${{ steps.tag.outputs.value }}
|
release_tag: ${{ steps.tag.outputs.value }}
|
||||||
source_commit_sha: ${{ steps.source-sha.outputs.value }}
|
source_commit_sha: ${{ steps.source-sha.outputs.value }}
|
||||||
@@ -121,7 +126,7 @@ jobs:
|
|||||||
echo "$BODY" >> "$GITHUB_OUTPUT"
|
echo "$BODY" >> "$GITHUB_OUTPUT"
|
||||||
echo "$EOF_MARKER" >> "$GITHUB_OUTPUT"
|
echo "$EOF_MARKER" >> "$GITHUB_OUTPUT"
|
||||||
- name: create or update release
|
- name: create or update release
|
||||||
id: create-release
|
id: create_release
|
||||||
uses: actions/github-script@v7
|
uses: actions/github-script@v7
|
||||||
env:
|
env:
|
||||||
PACKAGE_VERSION: ${{ steps.get-version.outputs.version }}
|
PACKAGE_VERSION: ${{ steps.get-version.outputs.version }}
|
||||||
@@ -139,6 +144,7 @@ jobs:
|
|||||||
const version = process.env.PACKAGE_VERSION;
|
const version = process.env.PACKAGE_VERSION;
|
||||||
const targetCommitish = process.env.RELEASE_COMMIT_SHA;
|
const targetCommitish = process.env.RELEASE_COMMIT_SHA;
|
||||||
const name = `Psysonic v${version}`;
|
const name = `Psysonic v${version}`;
|
||||||
|
let releaseId = null;
|
||||||
try {
|
try {
|
||||||
const { data } = await github.rest.repos.getReleaseByTag({
|
const { data } = await github.rest.repos.getReleaseByTag({
|
||||||
owner: context.repo.owner,
|
owner: context.repo.owner,
|
||||||
@@ -154,23 +160,90 @@ jobs:
|
|||||||
draft,
|
draft,
|
||||||
prerelease,
|
prerelease,
|
||||||
});
|
});
|
||||||
return data.id;
|
releaseId = data.id;
|
||||||
|
core.info(`Updated existing release id=${releaseId} tag=${tag}`);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
if (e.status !== 404) throw 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,
|
owner: context.repo.owner,
|
||||||
repo: context.repo.repo,
|
repo: context.repo.repo,
|
||||||
tag_name: tag,
|
release_id: releaseId,
|
||||||
target_commitish: targetCommitish,
|
|
||||||
name,
|
|
||||||
body,
|
|
||||||
draft,
|
|
||||||
prerelease,
|
|
||||||
});
|
});
|
||||||
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:
|
build-macos-windows:
|
||||||
|
if: ${{ inputs.build_platform_artifacts }}
|
||||||
needs: create-release
|
needs: create-release
|
||||||
permissions:
|
permissions:
|
||||||
contents: write
|
contents: write
|
||||||
@@ -254,6 +327,7 @@ jobs:
|
|||||||
gh release upload "$RELEASE_TAG" "Psysonic_${ARCH}.app.tar.gz.sig" --clobber
|
gh release upload "$RELEASE_TAG" "Psysonic_${ARCH}.app.tar.gz.sig" --clobber
|
||||||
|
|
||||||
generate-manifest:
|
generate-manifest:
|
||||||
|
if: ${{ inputs.build_platform_artifacts }}
|
||||||
needs: [create-release, build-macos-windows]
|
needs: [create-release, build-macos-windows]
|
||||||
runs-on: ubuntu-24.04
|
runs-on: ubuntu-24.04
|
||||||
permissions:
|
permissions:
|
||||||
@@ -275,6 +349,7 @@ jobs:
|
|||||||
gh release upload "$RELEASE_TAG" latest.json --clobber
|
gh release upload "$RELEASE_TAG" latest.json --clobber
|
||||||
|
|
||||||
build-linux:
|
build-linux:
|
||||||
|
if: ${{ inputs.build_platform_artifacts }}
|
||||||
needs: create-release
|
needs: create-release
|
||||||
permissions:
|
permissions:
|
||||||
contents: write
|
contents: write
|
||||||
|
|||||||
Reference in New Issue
Block a user