mirror of
https://github.com/got-feedBack/feedBack-desktop.git
synced 2026-08-10 18:59:55 +00:00
Clean release snapshot
This commit is contained in:
@@ -0,0 +1,4 @@
|
||||
# Shows a "Sponsor" button on this repo linking to the pages below.
|
||||
# This does not enrol the project in the GitHub Sponsors program.
|
||||
patreon: Slopsmith
|
||||
ko_fi: slopsmith
|
||||
@@ -0,0 +1,128 @@
|
||||
name: Addon CI
|
||||
|
||||
# Per-OS build + load smoke-test for the native audio addon
|
||||
# (slopsmith_audio.node), on every pull request.
|
||||
#
|
||||
# Why this exists, separate from build.yml:
|
||||
# build.yml runs the FULL electron-builder packaging/signing matrix and is
|
||||
# deliberately gated off the pull_request event (every review commit would
|
||||
# otherwise re-run a ~3x multi-GB release build). That left a gap: a PR that
|
||||
# breaks the macOS/Linux *addon* compile or link was never built on those
|
||||
# OSes until it had already merged to main. That is exactly how the #250
|
||||
# regression shipped — an undefined symbol (typeinfo for
|
||||
# slopsmith::sandbox::SandboxedProcessor) that links fine but fails at
|
||||
# runtime dlopen, leaving AppImage/dmg users with empty audio device lists
|
||||
# (issue #266, regression fixed in #263).
|
||||
#
|
||||
# This job is the cheap counterpart: it builds ONLY the addon
|
||||
# (`npm run build:audio` — no Python, no electron-builder, no
|
||||
# signing) on all three OSes, then loads the resulting .node and asserts the
|
||||
# device-enumeration API is present. A plain build can't catch the #250 class
|
||||
# of bug: Node native modules link with undefined symbols permitted
|
||||
# (macOS uses -undefined dynamic_lookup; Linux has no -Wl,--no-undefined), so
|
||||
# the compile and link succeed even with an unresolved symbol — the failure
|
||||
# only surfaces at load time. The smoke-test load is what makes that fail CI.
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
push:
|
||||
branches: [main]
|
||||
workflow_dispatch:
|
||||
|
||||
# Cancel an in-flight run when new commits land on the same PR/branch — no
|
||||
# point burning three runners on a superseded commit during a review loop.
|
||||
concurrency:
|
||||
group: addon-ci-${{ github.workflow }}-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
# Least privilege: this workflow only checks out code.
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
addon:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
include:
|
||||
- os: ubuntu-22.04
|
||||
platform: linux
|
||||
arch: x64
|
||||
- os: macos-14
|
||||
platform: mac
|
||||
arch: arm64
|
||||
- os: windows-latest
|
||||
platform: win
|
||||
arch: x64
|
||||
|
||||
runs-on: ${{ matrix.os }}
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
submodules: recursive # JUCE (and any vendored audio third_party)
|
||||
# No step after checkout uses GITHUB_TOKEN (public repo, public
|
||||
# submodules, no push), and this runs on pull_request — including
|
||||
# from forks — so don't leave the token persisted in .git/config.
|
||||
persist-credentials: false
|
||||
|
||||
# Match the Node version the app ships with — the addon is built against
|
||||
# the Electron ABI, but node-addon-api headers come from node_modules and
|
||||
# the toolchain is pinned via .build-config.json (single source of truth,
|
||||
# same as build.yml).
|
||||
- name: Read build configuration
|
||||
id: config
|
||||
shell: bash
|
||||
run: |
|
||||
NODE_VERSION=$(node -p "require('./.build-config.json').versions.node")
|
||||
echo "node=$NODE_VERSION" >> "$GITHUB_OUTPUT"
|
||||
echo "Node ${NODE_VERSION}"
|
||||
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: ${{ steps.config.outputs.node }}
|
||||
# Pin the Node binary architecture per runner (arm64 on macos-14,
|
||||
# x64 elsewhere) so the addon's node-addon-api headers and the
|
||||
# toolchain match the OS the build targets.
|
||||
architecture: ${{ matrix.arch }}
|
||||
|
||||
# Linux: the addon links juce_audio_devices (ALSA/JACK) and pulls in
|
||||
# juce_audio_processors' transitive X11/freetype/fontconfig deps at build
|
||||
# time. Install from .packages/apt.txt — the same canonical list
|
||||
# build-linux-ubuntu.sh uses — so this stays in sync with the real build.
|
||||
# xvfb is added on top (not in apt.txt) purely as insurance for the
|
||||
# load-time smoke-test below.
|
||||
- name: Install Linux build deps
|
||||
if: matrix.platform == 'linux'
|
||||
run: |
|
||||
sudo apt-get update
|
||||
PACKAGES=$(grep -v '^[[:space:]]*#' .packages/apt.txt | grep -v '^[[:space:]]*$' | tr '\n' ' ')
|
||||
sudo apt-get install -y --no-install-recommends $PACKAGES xvfb
|
||||
|
||||
# cmake-js + node-addon-api come from node_modules. No package-lock.json
|
||||
# in this repo, so `npm install` (not `npm ci`).
|
||||
- name: Install npm dependencies
|
||||
run: npm install
|
||||
|
||||
- name: Build native audio addon
|
||||
run: npm run build:audio
|
||||
shell: bash
|
||||
|
||||
# Load the freshly built .node and assert the device-enumeration entry
|
||||
# points are present. slopsmith_audio.node is an N-API addon (ABI-stable
|
||||
# across runtimes), so plain `node` can load an Electron-built binary —
|
||||
# this is the exact reproduction Byron confirmed locally for #250, where
|
||||
# the load fails with "undefined symbol: ...SandboxedProcessor..." even
|
||||
# though the build was green. typeof getDeviceTypes === 'function' also
|
||||
# verifies the device API the empty-device-list regression was about.
|
||||
- name: Smoke-test addon load (Linux)
|
||||
if: matrix.platform == 'linux'
|
||||
shell: bash
|
||||
run: |
|
||||
xvfb-run -a node -e "const a=require('./build/Release/slopsmith_audio.node'); if(typeof a.getDeviceTypes!=='function'){console.error('FAIL: getDeviceTypes export missing'); process.exit(1);} console.log('addon loaded; device API present');"
|
||||
|
||||
- name: Smoke-test addon load (macOS / Windows)
|
||||
if: matrix.platform != 'linux'
|
||||
shell: bash
|
||||
run: |
|
||||
node -e "const a=require('./build/Release/slopsmith_audio.node'); if(typeof a.getDeviceTypes!=='function'){console.error('FAIL: getDeviceTypes export missing'); process.exit(1);} console.log('addon loaded; device API present');"
|
||||
@@ -0,0 +1,563 @@
|
||||
name: Build
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [main]
|
||||
tags: ['v*']
|
||||
# PRs do not build automatically — every commit during review would
|
||||
# otherwise re-run the full 3-platform matrix. Trigger the matrix build
|
||||
# manually (Actions tab → Build → "Run workflow", pick the PR branch)
|
||||
# once the review round is finished.
|
||||
# Note: workflow_dispatch only allows selecting branches that exist in
|
||||
# this repository; it cannot target branches from contributor forks.
|
||||
# For fork PRs, the maintainer must merge to a local branch first.
|
||||
#
|
||||
# The slopsmith_ref input selects which slopsmith *core* ref to bundle.
|
||||
# It defaults to `main` (matching the historical behaviour), but for a
|
||||
# coherent release build the desktop release/vX.Y.Z branch should be
|
||||
# built against the matching core branch — e.g. run this workflow on
|
||||
# release/v0.3.0 with slopsmith_ref=release/v0.3.0.
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
slopsmith_ref:
|
||||
description: "Slopsmith core ref (branch or tag) to bundle, e.g. release/v0.3.0. Defaults to main."
|
||||
required: false
|
||||
default: main
|
||||
|
||||
jobs:
|
||||
build:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
include:
|
||||
- os: ubuntu-22.04
|
||||
platform: linux
|
||||
arch: x64
|
||||
- os: macos-14
|
||||
platform: mac
|
||||
arch: arm64
|
||||
- os: windows-latest
|
||||
platform: win
|
||||
arch: x64
|
||||
|
||||
runs-on: ${{ matrix.os }}
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
submodules: recursive
|
||||
|
||||
# Read build configuration to get version numbers for setup tools
|
||||
- name: Read build configuration
|
||||
id: config
|
||||
shell: bash
|
||||
run: |
|
||||
NODE_VERSION=$(node -p "require('./.build-config.json').versions.node")
|
||||
PYTHON_VERSION=$(node -p "require('./.build-config.json').versions.python")
|
||||
DOTNET_VERSION=$(node -p "require('./.build-config.json').versions.dotnet")
|
||||
echo "node=$NODE_VERSION" >> $GITHUB_OUTPUT
|
||||
echo "python=$PYTHON_VERSION" >> $GITHUB_OUTPUT
|
||||
echo "dotnet=$DOTNET_VERSION" >> $GITHUB_OUTPUT
|
||||
echo "Node ${NODE_VERSION}, Python ${PYTHON_VERSION}, .NET ${DOTNET_VERSION}"
|
||||
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: ${{ steps.config.outputs.node }}
|
||||
|
||||
- uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: ${{ steps.config.outputs.python }}
|
||||
|
||||
- uses: actions/setup-dotnet@v4
|
||||
with:
|
||||
dotnet-version: ${{ steps.config.outputs.dotnet }}.x
|
||||
|
||||
# macOS only: import the Developer ID Application certificate into a
|
||||
# temporary keychain so codesign + electron-builder + notarytool can
|
||||
# find it. The keychain is wiped at the end of the job (see "Clean
|
||||
# up signing keychain" below) so a re-run of the same job doesn't
|
||||
# see stale state. The whole step is a no-op when the secret is
|
||||
# absent — supports forks and PRs from contributors who can't
|
||||
# access repository secrets.
|
||||
# Step is unconditional on macOS so we always enter the shell, then
|
||||
# bail out at runtime if the cert secret isn't present (forks /
|
||||
# contributor PRs without secret access, etc.). step-level env: is
|
||||
# NOT available in step-level if: expressions, and `secrets.*` is
|
||||
# also not available there — runtime check is the only robust way
|
||||
# to gate this on secret presence.
|
||||
- name: Import Apple signing certificate
|
||||
if: matrix.platform == 'mac'
|
||||
env:
|
||||
APPLE_CERTIFICATE_P12_BASE64: ${{ secrets.APPLE_CERTIFICATE_P12_BASE64 }}
|
||||
APPLE_CERTIFICATE_PASSWORD: ${{ secrets.APPLE_CERTIFICATE_PASSWORD }}
|
||||
KEYCHAIN_PASSWORD: ${{ secrets.KEYCHAIN_PASSWORD }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
if [[ -z "${APPLE_CERTIFICATE_P12_BASE64:-}" ]]; then
|
||||
echo "APPLE_CERTIFICATE_P12_BASE64 not set — skipping (unsigned build)"
|
||||
exit 0
|
||||
fi
|
||||
# printf '%s' rather than echo — echo would append a trailing
|
||||
# newline that some toolchains accept and some reject when
|
||||
# piped into base64 --decode, producing a subtly corrupted
|
||||
# .p12 that fails security import with the unhelpful error
|
||||
# "SecKeychainItemImport: One or more parameters passed to a
|
||||
# function were not valid."
|
||||
printf '%s' "$APPLE_CERTIFICATE_P12_BASE64" | base64 --decode > "$RUNNER_TEMP/cert.p12"
|
||||
security create-keychain -p "$KEYCHAIN_PASSWORD" build.keychain
|
||||
security default-keychain -s build.keychain
|
||||
security unlock-keychain -p "$KEYCHAIN_PASSWORD" build.keychain
|
||||
security set-keychain-settings -lut 21600 build.keychain
|
||||
# Add build.keychain to the user search list — codesign and
|
||||
# electron-builder look up identities via the search list, not
|
||||
# the default-keychain pointer. Without this, identity-by-name
|
||||
# lookups fall back to the unmodified login keychain and the
|
||||
# imported cert is invisible. Keep login.keychain-db in the
|
||||
# list so trust anchors etc. still resolve.
|
||||
security list-keychains -d user -s build.keychain login.keychain-db
|
||||
security import "$RUNNER_TEMP/cert.p12" -k build.keychain \
|
||||
-P "$APPLE_CERTIFICATE_PASSWORD" -T /usr/bin/codesign
|
||||
security set-key-partition-list \
|
||||
-S apple-tool:,apple:,codesign: \
|
||||
-s -k "$KEYCHAIN_PASSWORD" build.keychain
|
||||
rm -f "$RUNNER_TEMP/cert.p12"
|
||||
# Sanity check — verify the identity is actually present.
|
||||
security find-identity -v -p codesigning build.keychain | grep -q "Developer ID Application"
|
||||
|
||||
# macOS only: inline the team ID into package.json so electron-builder's
|
||||
# `notarize.teamId` field has a real value (it doesn't expand env vars).
|
||||
# Build using the unified platform script
|
||||
# All bundling and dependency installation is handled by the script.
|
||||
# macOS-only env vars below are consumed by:
|
||||
# APPLE_SIGNING_IDENTITY → sign-macos-binaries.sh (codesign --sign)
|
||||
# APPLE_ID,
|
||||
# APPLE_APP_SPECIFIC_PASSWORD,
|
||||
# APPLE_TEAM_ID → electron-builder's notarytool path
|
||||
# build-macos.sh derives CSC_NAME (which electron-builder uses to
|
||||
# pick the signing identity) from APPLE_SIGNING_IDENTITY by
|
||||
# stripping the "Developer ID Application:" prefix electron-
|
||||
# builder rejects. On non-mac platforms these are empty / unused.
|
||||
- name: Build
|
||||
run: ./scripts/build-release.sh
|
||||
shell: bash
|
||||
env:
|
||||
# Which slopsmith core ref to clone + bundle. Empty on push/tag
|
||||
# events (the inputs context only carries values on
|
||||
# workflow_dispatch), so fall back to main — preserving the
|
||||
# historical default-branch behaviour. clone_slopsmith() in
|
||||
# build-common.sh also defaults to main as a second safety net.
|
||||
SLOPSMITH_REF: ${{ inputs.slopsmith_ref || 'main' }}
|
||||
APPLE_SIGNING_IDENTITY: ${{ secrets.APPLE_SIGNING_IDENTITY }}
|
||||
APPLE_ID: ${{ secrets.APPLE_ID }}
|
||||
APPLE_APP_SPECIFIC_PASSWORD: ${{ secrets.APPLE_APP_SPECIFIC_PASSWORD }}
|
||||
APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }}
|
||||
# electron-builder defensively skips code signing when the
|
||||
# triggering event is `pull_request` ("forks usually don't have
|
||||
# signing secrets"). This workflow no longer runs on the
|
||||
# pull_request event — it runs on push and workflow_dispatch —
|
||||
# so this env var is currently a no-op. It is kept here as a
|
||||
# safety net in case a pull_request trigger is ever re-added:
|
||||
# without it the notary service would reject the submission with
|
||||
# "signature of the binary is invalid" + "no secure timestamp"
|
||||
# + "no hardened runtime" on every nested Electron Helper app.
|
||||
CSC_FOR_PULL_REQUEST: "true"
|
||||
|
||||
# Start a debugging session — runs BEFORE keychain cleanup so a
|
||||
# signing-related failure can be inspected with the keychain still
|
||||
# populated.
|
||||
- name: Setup upterm session
|
||||
uses: owenthereal/action-upterm@v1
|
||||
if: ${{ failure() }}
|
||||
with:
|
||||
wait-timeout-minutes: 5
|
||||
|
||||
# Tester builds (non-tag only): macOS is packaged for distribution by the
|
||||
# Velopack steps below, which run ONLY on tag pushes. On a plain push to
|
||||
# main or a manual workflow_dispatch, electron-builder's "dir" target
|
||||
# leaves an unpackaged release/mac-*/Slopsmith.app that the artifact
|
||||
# upload globs (AppImage/deb/exe/velopack) don't match — so testers got
|
||||
# no macOS build. Zip the .app with ditto (preserves the bundle's
|
||||
# internal symlinks + any signature/xattrs) so a tester-ready macOS
|
||||
# archive ships from ordinary CI runs without cutting a release tag.
|
||||
# Skipped on tags so it never races or duplicates the Velopack output.
|
||||
# The archive is unsigned/un-notarized — testers clear Gatekeeper with
|
||||
# xattr -dr com.apple.quarantine Slopsmith.app
|
||||
- name: Zip macOS app for tester distribution (non-tag)
|
||||
if: matrix.platform == 'mac' && !startsWith(github.ref, 'refs/tags/v')
|
||||
shell: bash
|
||||
run: |
|
||||
set -euo pipefail
|
||||
app=$(ls -d release/mac*/Slopsmith.app 2>/dev/null | head -n1 || true)
|
||||
if [[ -z "${app:-}" || ! -d "$app" ]]; then
|
||||
echo "::error::No Slopsmith.app found under release/mac*/ — macOS build produced no bundle."
|
||||
exit 1
|
||||
fi
|
||||
ditto -c -k --sequesterRsrc --keepParent "$app" "release/Slopsmith-macos-arm64.zip"
|
||||
echo "Zipped $app -> release/Slopsmith-macos-arm64.zip"
|
||||
ls -lh release/Slopsmith-macos-arm64.zip
|
||||
|
||||
# Velopack: derive update channel from tag name. Only runs for
|
||||
# tag pushes (refs/tags/v*) on win + mac — Linux still ships via
|
||||
# electron-builder AppImage/deb with no auto-update.
|
||||
#
|
||||
# Channel mapping (plan gotcha #4 — fail on ambiguous tags rather
|
||||
# than silently defaulting to stable, which would publish a
|
||||
# prerelease into the stable feed):
|
||||
# v1.2.3 → stable
|
||||
# v1.2.3-alpha.<n> → alpha
|
||||
# v1.2.3-beta.<n> → beta
|
||||
# v1.2.3-rc.<n> → rc
|
||||
# anything else → job fails
|
||||
- name: Derive Velopack channel
|
||||
id: velopack_channel
|
||||
# Tag builds: derive the release channel for win + mac (linux is
|
||||
# electron-builder AppImage/deb, no Velopack). Manual dispatch: also
|
||||
# pack a Velopack package for WINDOWS only, on a throwaway `dev`
|
||||
# channel. Without this, dispatch (the documented tester-build path)
|
||||
# produced no usable Windows artifact — win-unpacked/ isn't uploaded
|
||||
# and Velopack only ran on tags, so testers got just .pdb symbols.
|
||||
# macOS dispatch stays Velopack-free (it already uploads a tester .zip
|
||||
# from the "Zip macOS app" step), so we don't add an unsigned-update
|
||||
# mac pack here.
|
||||
if: >-
|
||||
(startsWith(github.ref, 'refs/tags/v') && matrix.platform != 'linux')
|
||||
|| (github.event_name == 'workflow_dispatch' && matrix.platform == 'win')
|
||||
shell: bash
|
||||
run: |
|
||||
set -euo pipefail
|
||||
# Read tag / event / run number from runtime env vars rather than
|
||||
# interpolating GitHub Actions workflow expressions into the script
|
||||
# text — a tag containing shell syntax could otherwise execute
|
||||
# (template injection).
|
||||
if [[ "${GITHUB_EVENT_NAME}" == "workflow_dispatch" ]]; then
|
||||
# Strip any prerelease suffix from the app version (0.2.9-beta.1 ->
|
||||
# 0.2.9), then tag a unique dev build so successive dispatch builds
|
||||
# produce distinct Velopack versions. The `dev` channel keeps these
|
||||
# out of the alpha/beta/rc/stable auto-update feeds.
|
||||
base="$(node -p "require('./package.json').version")"
|
||||
base="${base%%-*}"
|
||||
channel=dev
|
||||
version="${base}-dev.${GITHUB_RUN_NUMBER}"
|
||||
echo "channel=$channel" >> "$GITHUB_OUTPUT"
|
||||
echo "version=$version" >> "$GITHUB_OUTPUT"
|
||||
echo "Manual dispatch -> channel=$channel version=$version"
|
||||
exit 0
|
||||
fi
|
||||
tag="${GITHUB_REF_NAME}"
|
||||
if [[ "$tag" =~ ^v([0-9]+\.[0-9]+\.[0-9]+)-alpha\.[0-9]+$ ]]; then
|
||||
channel=alpha
|
||||
version="${BASH_REMATCH[1]}-alpha.${tag##*-alpha.}"
|
||||
elif [[ "$tag" =~ ^v([0-9]+\.[0-9]+\.[0-9]+)-beta\.[0-9]+$ ]]; then
|
||||
channel=beta
|
||||
version="${BASH_REMATCH[1]}-beta.${tag##*-beta.}"
|
||||
elif [[ "$tag" =~ ^v([0-9]+\.[0-9]+\.[0-9]+)-rc\.[0-9]+$ ]]; then
|
||||
channel=rc
|
||||
version="${BASH_REMATCH[1]}-rc.${tag##*-rc.}"
|
||||
elif [[ "$tag" =~ ^v([0-9]+\.[0-9]+\.[0-9]+)$ ]]; then
|
||||
channel=stable
|
||||
version="${BASH_REMATCH[1]}"
|
||||
else
|
||||
echo "::error::Tag '$tag' does not match a known Velopack channel pattern (vX.Y.Z[-{alpha,beta,rc}.N]). Refusing to default to stable."
|
||||
exit 1
|
||||
fi
|
||||
echo "channel=$channel" >> "$GITHUB_OUTPUT"
|
||||
echo "version=$version" >> "$GITHUB_OUTPUT"
|
||||
echo "Derived channel=$channel version=$version from tag $tag"
|
||||
|
||||
- name: Install Velopack CLI (vpk)
|
||||
if: steps.velopack_channel.outputs.channel != ''
|
||||
shell: bash
|
||||
run: |
|
||||
set -euo pipefail
|
||||
# Pin vpk to the exact version of the velopack npm SDK used by the
|
||||
# app (package.json) — Velopack ships the CLI and SDK in lockstep,
|
||||
# and an unpinned install would pull a different latest on each run.
|
||||
dotnet tool install -g vpk --version 0.0.1589-ga2c5a97
|
||||
echo "$HOME/.dotnet/tools" >> "$GITHUB_PATH"
|
||||
|
||||
# Windows: pack the electron-builder unpacked dir into Velopack
|
||||
# release assets (per-machine MSI, *-full.nupkg, *-delta.nupkg,
|
||||
# releases.<ch>.json). Unsigned for now — see plan gotcha #3.
|
||||
#
|
||||
# The channel is rid-scoped (win-x64-<track>): Velopack requires a
|
||||
# unique channel per os/rid so the win + macOS releases.<ch>.json
|
||||
# manifests don't collide when published to one GitHub release.
|
||||
# The client's update-manager.ts builds the same name to match.
|
||||
#
|
||||
# --msi emits a machine-wide MSI bootstrap (validated decision #2 in
|
||||
# the integration plan: install to %ProgramFiles%\Slopsmith). The flag
|
||||
# is a bool — verified against vpk source at our pinned commit
|
||||
# a2c5a97 (src/vpk/Velopack.Vpk/Commands/Packaging/WindowsPackCommand.cs):
|
||||
# AddOption<bool>((v) => BuildMsi = v, "--msi")
|
||||
#
|
||||
# --instLocation defaults to "Either" (a dual-mode MSI where the user
|
||||
# picks at install time). We force PerMachine so the MSI is always
|
||||
# machine-wide, matching the plan.
|
||||
#
|
||||
# vpk also writes a per-user Setup.exe by default — we delete it
|
||||
# right after packing so only ONE install method ships. Reasons:
|
||||
# - Two install methods for the same app split the user base in
|
||||
# half for every support question ("where is my app installed?").
|
||||
# - The MSI is what the in-app NSIS migration banner downloads, so
|
||||
# it has to ship anyway.
|
||||
# The Velopack feed assets (.nupkg, releases.<channel>.json) drive
|
||||
# auto-update for both install methods, so dropping Setup.exe does
|
||||
# NOT break auto-update for users who installed via the MSI.
|
||||
#
|
||||
# Velopack's WindowsVelopackLocator falls back to %LOCALAPPDATA% for
|
||||
# the packages staging dir when Program Files isn't writable, so the
|
||||
# auto-updater works without elevation. Apply-step elevation is
|
||||
# handled by Update.exe's manifest.
|
||||
- name: Velopack pack (Windows)
|
||||
if: matrix.platform == 'win' && steps.velopack_channel.outputs.channel != ''
|
||||
shell: bash
|
||||
run: |
|
||||
set -euo pipefail
|
||||
vpk pack \
|
||||
--packId Slopsmith \
|
||||
--packVersion "${{ steps.velopack_channel.outputs.version }}" \
|
||||
--channel "win-x64-${{ steps.velopack_channel.outputs.channel }}" \
|
||||
--packDir release/win-unpacked \
|
||||
--msi \
|
||||
--instLocation PerMachine \
|
||||
-o release/velopack
|
||||
# Drop the per-user Setup.exe (default vpk output) so only the
|
||||
# per-machine MSI ships. Match permissively — the exact name
|
||||
# varies between Velopack versions (Slopsmith-win-Setup.exe vs
|
||||
# Slopsmith-win-x64-Setup.exe etc.). nocaseglob so a casing
|
||||
# change in vpk's output can't slip an installer past the glob.
|
||||
shopt -s nullglob nocaseglob
|
||||
removed=0
|
||||
for f in release/velopack/*setup.exe; do
|
||||
echo "Removing per-user installer: $f"
|
||||
rm -f "$f"
|
||||
removed=$((removed+1))
|
||||
done
|
||||
# Enforce the MSI-only policy: hard-fail if any setup installer
|
||||
# survived cleanup (e.g. an rm failure) rather than publishing
|
||||
# two installer types.
|
||||
if compgen -G "release/velopack/*setup.exe" > /dev/null; then
|
||||
echo "::error::Setup.exe artifact(s) remain after cleanup; refusing to publish dual installer types."
|
||||
ls -1 release/velopack/*setup.exe
|
||||
exit 1
|
||||
fi
|
||||
if [[ "$removed" -eq 0 ]]; then
|
||||
# MSI-only is the goal — if vpk emits no Setup.exe at all that
|
||||
# is fine, not a failure. Note it in case the naming drifted.
|
||||
echo "::notice::No Setup.exe output from vpk; shipping MSI-only artifacts."
|
||||
fi
|
||||
# Sanity check: the MSI must exist or the release is meaningless.
|
||||
# compgen -G, not `ls *.msi` — with nullglob on, an unmatched glob
|
||||
# expands to nothing and `ls` would list the cwd and pass silently.
|
||||
if ! compgen -G "release/velopack/*.msi" > /dev/null; then
|
||||
echo "::error::vpk pack --msi x64 did not produce a .msi file in release/velopack/"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# macOS: same, but pass Apple signing identity + notarization
|
||||
# creds so Velopack codesigns + notarizes the bundle it generates
|
||||
# (otherwise Gatekeeper blocks auto-applied updates — plan
|
||||
# gotcha #2). vpk codesigns via --signAppIdentity and notarizes via
|
||||
# --notaryProfile, which names a `xcrun notarytool` credential
|
||||
# profile — vpk has no raw --apple-id/--password/--team-id flags,
|
||||
# so the profile must be created with `notarytool store-credentials`
|
||||
# before vpk pack runs.
|
||||
# --packDir points directly at the .app bundle: Velopack uses a
|
||||
# path ending in .app as a prebuilt bundle. A non-.app folder
|
||||
# would instead trigger auto-bundle mode (which also needs
|
||||
# --icon). electron-builder's "dir" target writes the bundle to
|
||||
# release/mac-arm64/Slopsmith.app on the arm64 runner.
|
||||
- name: Velopack pack (macOS)
|
||||
if: matrix.platform == 'mac' && steps.velopack_channel.outputs.channel != ''
|
||||
shell: bash
|
||||
env:
|
||||
APPLE_SIGNING_IDENTITY: ${{ secrets.APPLE_SIGNING_IDENTITY }}
|
||||
APPLE_ID: ${{ secrets.APPLE_ID }}
|
||||
APPLE_APP_SPECIFIC_PASSWORD: ${{ secrets.APPLE_APP_SPECIFIC_PASSWORD }}
|
||||
APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
# The signed/notarized branch needs ALL four Apple secrets — vpk
|
||||
# would fail outright if handed an empty --notary* value. If any
|
||||
# is missing (forks, partial secret configs) fall back to an
|
||||
# unsigned pack, matching the all-four check in build-macos.sh.
|
||||
if [[ -z "${APPLE_SIGNING_IDENTITY:-}" || -z "${APPLE_ID:-}" \
|
||||
|| -z "${APPLE_APP_SPECIFIC_PASSWORD:-}" \
|
||||
|| -z "${APPLE_TEAM_ID:-}" ]]; then
|
||||
echo "::warning::Apple signing/notarization secrets incomplete — packing macOS Velopack release UNSIGNED. Gatekeeper will block auto-updates on user machines."
|
||||
vpk pack \
|
||||
--packId Slopsmith \
|
||||
--packVersion "${{ steps.velopack_channel.outputs.version }}" \
|
||||
--channel "osx-arm64-${{ steps.velopack_channel.outputs.channel }}" \
|
||||
--packDir release/mac-arm64/Slopsmith.app \
|
||||
-o release/velopack
|
||||
else
|
||||
# vpk notarizes through a notarytool *credential profile*, not
|
||||
# raw Apple-ID flags (it shells out to `xcrun notarytool`, which
|
||||
# takes --keychain-profile). Create the profile first, then hand
|
||||
# vpk --notaryProfile. Do NOT pass --keychain: notarytool's
|
||||
# --keychain wants a file *path* (not a name like
|
||||
# "build.keychain"), and notarytool stores/reads credential
|
||||
# profiles in the login keychain by default. vpk's own internal
|
||||
# notarytool call also defaults to the login keychain, so
|
||||
# leaving --keychain off keeps both sides pointing at the same
|
||||
# store. (build.keychain only holds the signing cert, which
|
||||
# codesign finds via the keychain search list.)
|
||||
xcrun notarytool store-credentials "velopack-notary" \
|
||||
--apple-id "$APPLE_ID" \
|
||||
--password "$APPLE_APP_SPECIFIC_PASSWORD" \
|
||||
--team-id "$APPLE_TEAM_ID"
|
||||
vpk pack \
|
||||
--packId Slopsmith \
|
||||
--packVersion "${{ steps.velopack_channel.outputs.version }}" \
|
||||
--channel "osx-arm64-${{ steps.velopack_channel.outputs.channel }}" \
|
||||
--packDir release/mac-arm64/Slopsmith.app \
|
||||
-o release/velopack \
|
||||
--signAppIdentity "$APPLE_SIGNING_IDENTITY" \
|
||||
--notaryProfile "velopack-notary"
|
||||
fi
|
||||
|
||||
# macOS only: drop the temporary keychain so a re-run doesn't see
|
||||
# leftover state. Placed AFTER the Velopack pack steps (which call
|
||||
# codesign + notarytool and need the keychain present on tag builds).
|
||||
# always() ensures cleanup even on Velopack pack failures.
|
||||
- name: Clean up signing keychain
|
||||
if: always() && matrix.platform == 'mac'
|
||||
run: |
|
||||
security delete-keychain build.keychain 2>/dev/null || true
|
||||
|
||||
- name: Upload artifacts
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: slopsmith-${{ matrix.platform }}-${{ matrix.arch }}
|
||||
# release/win-unpacked/ is intentionally NOT uploaded: it is the
|
||||
# uncompressed intermediate build tree (~1 GB on Windows) that
|
||||
# Velopack packs into release/velopack/. Uploading it duplicated
|
||||
# the whole app in the artifact and ballooned the Windows artifact
|
||||
# ~6x (0.27 -> 1.58 GiB). The packed velopack/ output is all that
|
||||
# downstream needs. See issue #185.
|
||||
# build/Release/*.pdb: Windows debug symbols for the native addon and
|
||||
# the sandbox host. Archived as a workflow artifact (it is NOT
|
||||
# attached to the public Release) so a tester's crash minidump can be
|
||||
# symbolised. Empty on Linux/macOS — if-no-files-found: warn covers it.
|
||||
path: |
|
||||
release/*.AppImage
|
||||
release/*.deb
|
||||
release/*.exe
|
||||
release/velopack/**/*
|
||||
build/Release/*.pdb
|
||||
# Tester-only macOS archive from the "Zip macOS app" step above.
|
||||
# Only produced on non-tag runs; the `release` job's globs below
|
||||
# do NOT include *.zip, so it never leaks into a tagged Release.
|
||||
release/*.zip
|
||||
if-no-files-found: warn
|
||||
# Default retention (90 days) snowballed the org Actions storage cap
|
||||
# — a single matrix run uploads ~2.5 GB across the three platforms,
|
||||
# and old builds piled up for months. Tag builds don't need long
|
||||
# retention either: the `release` job downloads and attaches these
|
||||
# to the GitHub Release within the same workflow run.
|
||||
retention-days: 7
|
||||
|
||||
release:
|
||||
needs: build
|
||||
if: startsWith(github.ref, 'refs/tags/v')
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: write
|
||||
|
||||
steps:
|
||||
- uses: actions/download-artifact@v4
|
||||
with:
|
||||
path: artifacts
|
||||
|
||||
# Every release is published with prerelease=false, but only
|
||||
# stable (bare vX.Y.Z) tags get make_latest=true. Reason:
|
||||
#
|
||||
# The Velopack JS SDK at our pinned version 0.0.1589-ga2c5a97
|
||||
# routes any github.com URL through AutoSource → GithubSource,
|
||||
# and AutoSource hardcodes the third constructor arg
|
||||
# (`prerelease: false`). UpdateOptions has no Prerelease field,
|
||||
# so there is no SDK-side override. If we tagged alpha/beta/rc as
|
||||
# prerelease=true on GitHub, the in-app updater on those channels
|
||||
# would silently see zero updates (verified live during the
|
||||
# v0.2.9-alpha.102 validation: toggling the prerelease flag off
|
||||
# made the release visible to the running app).
|
||||
#
|
||||
# Using make_latest=false on alpha/beta/rc tags keeps the GitHub
|
||||
# UI's "Latest release" badge pinned to the most recent stable
|
||||
# tag — humans browsing the repo still see stable as latest, even
|
||||
# though the underlying release rows are all prerelease=false.
|
||||
# Per the action-gh-release@v2 docs:
|
||||
# "Drafts and prereleases cannot be set as latest"
|
||||
# so we can't use prerelease=true + make_latest=true together.
|
||||
#
|
||||
# Channel scoping for the auto-updater is independent of either
|
||||
# flag — Velopack picks the right release by the channel-named
|
||||
# manifest asset (releases.<channel>.json), iterating recent
|
||||
# releases until it finds the matching channel.
|
||||
#
|
||||
# draft is intentionally NOT set — Velopack clients can't see
|
||||
# draft releases either way.
|
||||
- name: Create Release
|
||||
uses: softprops/action-gh-release@v2
|
||||
with:
|
||||
# Explicitly select only real distributables:
|
||||
# - Linux ships electron-builder .AppImage / .deb
|
||||
# - Windows + macOS ship via Velopack (release/velopack/**):
|
||||
# MSI, *-osx.zip, *.nupkg, releases.<channel>.json
|
||||
# win-unpacked/** and a bare *.exe glob are deliberately omitted —
|
||||
# win-unpacked/ is for CI inspection only.
|
||||
# No electron-builder *.dmg / *.zip globs: macOS is Velopack-only,
|
||||
# so they would match nothing or duplicate the Velopack *-osx.zip.
|
||||
#
|
||||
# The velopack glob is `artifacts/**/velopack/**/*`, NOT
|
||||
# `artifacts/**/release/velopack/**/*`. actions/upload-artifact@v4
|
||||
# strips the longest common path prefix from upload inputs before
|
||||
# archiving, so when the build job uploads both
|
||||
# `release/win-unpacked/**` and `release/velopack/**`, the common
|
||||
# prefix `release/` is stripped and files inside the artifact are
|
||||
# rooted at `velopack/...` / `win-unpacked/...`. The old
|
||||
# `**/release/velopack/**` glob never matched anything, which is
|
||||
# why prior tagged releases shipped only Linux assets and the
|
||||
# Windows + macOS Velopack feeds were silently empty (the in-app
|
||||
# updater would 404 on releases.<channel>.json and report "no
|
||||
# updates available").
|
||||
files: |
|
||||
artifacts/**/*.AppImage
|
||||
artifacts/**/*.deb
|
||||
artifacts/**/velopack/**/*
|
||||
generate_release_notes: true
|
||||
# All tags non-prerelease (Velopack SDK limitation, see the
|
||||
# long comment above). Only stable tags become the GitHub
|
||||
# "Latest release" — alpha/beta/rc are still visible to the
|
||||
# in-app updater but won't be promoted in the repo UI.
|
||||
prerelease: false
|
||||
make_latest: ${{ (contains(github.ref_name, '-alpha.') || contains(github.ref_name, '-beta.') || contains(github.ref_name, '-rc.')) && 'false' || 'true' }}
|
||||
|
||||
# Mirror release version to slopsmith core
|
||||
# Requires SLOPSMITH_SYNC_TOKEN secret
|
||||
notify-slopsmith:
|
||||
needs: release
|
||||
if: startsWith(github.ref, 'refs/tags/v')
|
||||
runs-on: ubuntu-latest
|
||||
permissions: {}
|
||||
|
||||
steps:
|
||||
- name: Dispatch VERSION sync to slopsmith
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.SLOPSMITH_SYNC_TOKEN }}
|
||||
REF_NAME: ${{ github.ref_name }}
|
||||
run: |
|
||||
version="${REF_NAME#v}"
|
||||
if ! [[ "$version" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
|
||||
echo "Tag $REF_NAME is not vX.Y.Z — skipping slopsmith VERSION sync."
|
||||
exit 0
|
||||
fi
|
||||
if [ -z "${GH_TOKEN:-}" ]; then
|
||||
echo "::error::SLOPSMITH_SYNC_TOKEN secret is missing; cannot dispatch."
|
||||
exit 1
|
||||
fi
|
||||
gh api -X POST repos/slopsmith/slopsmith/dispatches \
|
||||
-f event_type=desktop-released \
|
||||
-f "client_payload[version]=$version"
|
||||
echo "Dispatched desktop-released event (version=$version) to slopsmith."
|
||||
@@ -0,0 +1,48 @@
|
||||
name: CI
|
||||
|
||||
# Reusable CI checks (typecheck + npm audit). Invoked by ship-ci.yml's `CI`
|
||||
# job for PRs into main and release/** so the check run is named "CI / check",
|
||||
# matching the org rulesets' required context. It deliberately has no
|
||||
# standalone pull_request trigger: a direct run would publish a bare "check"
|
||||
# that the rulesets can't match.
|
||||
on:
|
||||
workflow_call:
|
||||
workflow_dispatch:
|
||||
|
||||
concurrency:
|
||||
group: ci-${{ github.workflow }}-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
check:
|
||||
runs-on: ubuntu-22.04
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
- name: Read build configuration
|
||||
id: config
|
||||
shell: bash
|
||||
run: |
|
||||
NODE_VERSION=$(node -p "require('./.build-config.json').versions.node")
|
||||
echo "node=$NODE_VERSION" >> "$GITHUB_OUTPUT"
|
||||
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: ${{ steps.config.outputs.node }}
|
||||
|
||||
# No package-lock.json in this repo — npm ci is not available.
|
||||
- name: Install npm dependencies
|
||||
run: npm install
|
||||
|
||||
- name: TypeScript type check
|
||||
run: npm run typecheck
|
||||
|
||||
- name: npm audit
|
||||
run: npm audit --audit-level=high
|
||||
continue-on-error: true
|
||||
@@ -0,0 +1,197 @@
|
||||
name: Nightly
|
||||
|
||||
on:
|
||||
schedule:
|
||||
- cron: '0 2 * * *'
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
setup:
|
||||
runs-on: ubuntu-latest
|
||||
outputs:
|
||||
branch: ${{ steps.branch.outputs.branch }}
|
||||
date: ${{ steps.date.outputs.date }}
|
||||
|
||||
steps:
|
||||
# Find the most recently created release/v* branch by sorting version
|
||||
# strings. Falls back to main if no release branch exists (quiet window
|
||||
# between a release shipping and the next branch being cut).
|
||||
- name: Find active release branch
|
||||
id: branch
|
||||
env:
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
run: |
|
||||
branch=$(gh api "repos/${{ github.repository }}/git/matching-refs/heads/release/v" \
|
||||
--jq '[.[].ref | ltrimstr("refs/heads/")] | sort | last // empty' 2>/dev/null || echo "")
|
||||
if [[ -z "$branch" ]]; then
|
||||
branch="main"
|
||||
fi
|
||||
echo "branch=$branch" >> "$GITHUB_OUTPUT"
|
||||
echo "Active branch: $branch"
|
||||
|
||||
- name: Get date
|
||||
id: date
|
||||
run: echo "date=$(date -u +%Y%m%d)" >> "$GITHUB_OUTPUT"
|
||||
|
||||
build-core:
|
||||
needs: setup
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: read
|
||||
packages: write
|
||||
|
||||
steps:
|
||||
- name: Clone core at active branch
|
||||
run: |
|
||||
git clone --depth 1 --branch "${{ needs.setup.outputs.branch }}" \
|
||||
https://github.com/slopsmith/slopsmith.git core
|
||||
echo "Cloned slopsmith @ $(git -C core rev-parse --short HEAD) (${{ needs.setup.outputs.branch }})"
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v3
|
||||
|
||||
- name: Log in to GHCR
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.actor }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Build and push Docker image
|
||||
uses: docker/build-push-action@v6
|
||||
with:
|
||||
context: core
|
||||
platforms: linux/amd64,linux/arm64
|
||||
push: true
|
||||
tags: |
|
||||
ghcr.io/slopsmith/slopsmith:nightly
|
||||
ghcr.io/slopsmith/slopsmith:nightly-${{ needs.setup.outputs.date }}
|
||||
cache-from: type=gha
|
||||
cache-to: type=gha,mode=max
|
||||
|
||||
build:
|
||||
needs: setup
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
include:
|
||||
- os: ubuntu-22.04
|
||||
platform: linux
|
||||
arch: x64
|
||||
- os: macos-14
|
||||
platform: mac
|
||||
arch: arm64
|
||||
- os: windows-latest
|
||||
platform: win
|
||||
arch: x64
|
||||
|
||||
runs-on: ${{ matrix.os }}
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
ref: ${{ needs.setup.outputs.branch }}
|
||||
submodules: recursive
|
||||
|
||||
- name: Read build configuration
|
||||
id: config
|
||||
shell: bash
|
||||
run: |
|
||||
NODE_VERSION=$(node -p "require('./.build-config.json').versions.node")
|
||||
PYTHON_VERSION=$(node -p "require('./.build-config.json').versions.python")
|
||||
echo "node=$NODE_VERSION" >> $GITHUB_OUTPUT
|
||||
echo "python=$PYTHON_VERSION" >> $GITHUB_OUTPUT
|
||||
echo "Node ${NODE_VERSION}, Python ${PYTHON_VERSION}"
|
||||
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: ${{ steps.config.outputs.node }}
|
||||
|
||||
- uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: ${{ steps.config.outputs.python }}
|
||||
|
||||
# macOS: import signing cert so build-macos.sh can codesign the .app.
|
||||
# The nightly .app is signed but not notarized — testers clear Gatekeeper with
|
||||
# xattr -dr com.apple.quarantine Slopsmith.app
|
||||
# Same graceful fallback as release.yml: no-op when secrets are absent.
|
||||
- name: Import Apple signing certificate
|
||||
if: matrix.platform == 'mac'
|
||||
env:
|
||||
APPLE_CERTIFICATE_P12_BASE64: ${{ secrets.APPLE_CERTIFICATE_P12_BASE64 }}
|
||||
APPLE_CERTIFICATE_PASSWORD: ${{ secrets.APPLE_CERTIFICATE_PASSWORD }}
|
||||
KEYCHAIN_PASSWORD: ${{ secrets.KEYCHAIN_PASSWORD }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
if [[ -z "${APPLE_CERTIFICATE_P12_BASE64:-}" ]]; then
|
||||
echo "APPLE_CERTIFICATE_P12_BASE64 not set — skipping (unsigned build)"
|
||||
exit 0
|
||||
fi
|
||||
printf '%s' "$APPLE_CERTIFICATE_P12_BASE64" | base64 --decode > "$RUNNER_TEMP/cert.p12"
|
||||
security create-keychain -p "$KEYCHAIN_PASSWORD" build.keychain
|
||||
security default-keychain -s build.keychain
|
||||
security unlock-keychain -p "$KEYCHAIN_PASSWORD" build.keychain
|
||||
security set-keychain-settings -lut 21600 build.keychain
|
||||
security list-keychains -d user -s build.keychain login.keychain-db
|
||||
security import "$RUNNER_TEMP/cert.p12" -k build.keychain \
|
||||
-P "$APPLE_CERTIFICATE_PASSWORD" -T /usr/bin/codesign
|
||||
security set-key-partition-list \
|
||||
-S apple-tool:,apple:,codesign: \
|
||||
-s -k "$KEYCHAIN_PASSWORD" build.keychain
|
||||
rm -f "$RUNNER_TEMP/cert.p12"
|
||||
security find-identity -v -p codesigning build.keychain | grep -q "Developer ID Application"
|
||||
|
||||
- name: Build
|
||||
run: ./scripts/build-release.sh
|
||||
shell: bash
|
||||
env:
|
||||
APPLE_SIGNING_IDENTITY: ${{ secrets.APPLE_SIGNING_IDENTITY }}
|
||||
# No notarization for nightlies — testers use xattr -dr com.apple.quarantine
|
||||
CSC_FOR_PULL_REQUEST: "true"
|
||||
|
||||
- name: Setup upterm session
|
||||
uses: owenthereal/action-upterm@v1
|
||||
if: ${{ failure() }}
|
||||
with:
|
||||
wait-timeout-minutes: 5
|
||||
|
||||
# macOS: zip the .app for tester distribution (same as release.yml non-tag path).
|
||||
- name: Zip macOS app
|
||||
if: matrix.platform == 'mac'
|
||||
shell: bash
|
||||
run: |
|
||||
set -euo pipefail
|
||||
app=$(ls -d release/mac*/Slopsmith.app 2>/dev/null | head -n1 || true)
|
||||
if [[ -z "${app:-}" || ! -d "$app" ]]; then
|
||||
echo "::error::No Slopsmith.app found under release/mac*/"
|
||||
exit 1
|
||||
fi
|
||||
ditto -c -k --sequesterRsrc --keepParent "$app" "release/Slopsmith-macos-arm64.zip"
|
||||
echo "Zipped $app -> release/Slopsmith-macos-arm64.zip"
|
||||
|
||||
# Windows: electron-builder's `dir` target leaves win-unpacked/ with no
|
||||
# packaged installer (Velopack is skipped for nightlies). Zip the directory
|
||||
# so testers can download and extract, then launch Slopsmith.exe directly.
|
||||
- name: Zip win-unpacked
|
||||
if: matrix.platform == 'win'
|
||||
shell: pwsh
|
||||
run: |
|
||||
Compress-Archive -Path release\win-unpacked\* -DestinationPath release\Slopsmith-windows-x64.zip
|
||||
Write-Host "Zipped win-unpacked -> release\Slopsmith-windows-x64.zip"
|
||||
|
||||
- name: Clean up signing keychain
|
||||
if: always() && matrix.platform == 'mac'
|
||||
run: |
|
||||
security delete-keychain build.keychain 2>/dev/null || true
|
||||
|
||||
- name: Upload nightly artifacts
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: nightly-${{ needs.setup.outputs.date }}-${{ matrix.platform }}-${{ matrix.arch }}
|
||||
path: |
|
||||
release/*.AppImage
|
||||
release/*.deb
|
||||
release/*.zip
|
||||
build/Release/*.pdb
|
||||
if-no-files-found: warn
|
||||
retention-days: 7
|
||||
@@ -0,0 +1,292 @@
|
||||
name: Release
|
||||
|
||||
on:
|
||||
push:
|
||||
tags: ['v*']
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
build:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
include:
|
||||
- os: ubuntu-22.04
|
||||
platform: linux
|
||||
arch: x64
|
||||
- os: macos-14
|
||||
platform: mac
|
||||
arch: arm64
|
||||
- os: windows-latest
|
||||
platform: win
|
||||
arch: x64
|
||||
|
||||
runs-on: ${{ matrix.os }}
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
submodules: recursive
|
||||
|
||||
- name: Read build configuration
|
||||
id: config
|
||||
shell: bash
|
||||
run: |
|
||||
NODE_VERSION=$(node -p "require('./.build-config.json').versions.node")
|
||||
PYTHON_VERSION=$(node -p "require('./.build-config.json').versions.python")
|
||||
DOTNET_VERSION=$(node -p "require('./.build-config.json').versions.dotnet")
|
||||
echo "node=$NODE_VERSION" >> $GITHUB_OUTPUT
|
||||
echo "python=$PYTHON_VERSION" >> $GITHUB_OUTPUT
|
||||
echo "dotnet=$DOTNET_VERSION" >> $GITHUB_OUTPUT
|
||||
echo "Node ${NODE_VERSION}, Python ${PYTHON_VERSION}, .NET ${DOTNET_VERSION}"
|
||||
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: ${{ steps.config.outputs.node }}
|
||||
|
||||
- uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: ${{ steps.config.outputs.python }}
|
||||
|
||||
- uses: actions/setup-dotnet@v4
|
||||
with:
|
||||
dotnet-version: ${{ steps.config.outputs.dotnet }}.x
|
||||
|
||||
- name: Import Apple signing certificate
|
||||
if: matrix.platform == 'mac'
|
||||
env:
|
||||
APPLE_CERTIFICATE_P12_BASE64: ${{ secrets.APPLE_CERTIFICATE_P12_BASE64 }}
|
||||
APPLE_CERTIFICATE_PASSWORD: ${{ secrets.APPLE_CERTIFICATE_PASSWORD }}
|
||||
KEYCHAIN_PASSWORD: ${{ secrets.KEYCHAIN_PASSWORD }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
if [[ -z "${APPLE_CERTIFICATE_P12_BASE64:-}" ]]; then
|
||||
echo "APPLE_CERTIFICATE_P12_BASE64 not set — skipping (unsigned build)"
|
||||
exit 0
|
||||
fi
|
||||
printf '%s' "$APPLE_CERTIFICATE_P12_BASE64" | base64 --decode > "$RUNNER_TEMP/cert.p12"
|
||||
security create-keychain -p "$KEYCHAIN_PASSWORD" build.keychain
|
||||
security default-keychain -s build.keychain
|
||||
security unlock-keychain -p "$KEYCHAIN_PASSWORD" build.keychain
|
||||
security set-keychain-settings -lut 21600 build.keychain
|
||||
security list-keychains -d user -s build.keychain login.keychain-db
|
||||
security import "$RUNNER_TEMP/cert.p12" -k build.keychain \
|
||||
-P "$APPLE_CERTIFICATE_PASSWORD" -T /usr/bin/codesign
|
||||
security set-key-partition-list \
|
||||
-S apple-tool:,apple:,codesign: \
|
||||
-s -k "$KEYCHAIN_PASSWORD" build.keychain
|
||||
rm -f "$RUNNER_TEMP/cert.p12"
|
||||
security find-identity -v -p codesigning build.keychain | grep -q "Developer ID Application"
|
||||
|
||||
- name: Build
|
||||
run: ./scripts/build-release.sh
|
||||
shell: bash
|
||||
env:
|
||||
APPLE_SIGNING_IDENTITY: ${{ secrets.APPLE_SIGNING_IDENTITY }}
|
||||
APPLE_ID: ${{ secrets.APPLE_ID }}
|
||||
APPLE_APP_SPECIFIC_PASSWORD: ${{ secrets.APPLE_APP_SPECIFIC_PASSWORD }}
|
||||
APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }}
|
||||
CSC_FOR_PULL_REQUEST: "true"
|
||||
|
||||
- name: Setup upterm session
|
||||
uses: owenthereal/action-upterm@v1
|
||||
if: ${{ failure() }}
|
||||
with:
|
||||
wait-timeout-minutes: 5
|
||||
|
||||
- name: Zip macOS app for tester distribution (non-tag)
|
||||
if: matrix.platform == 'mac' && !startsWith(github.ref, 'refs/tags/v')
|
||||
shell: bash
|
||||
run: |
|
||||
set -euo pipefail
|
||||
app=$(ls -d release/mac*/Slopsmith.app 2>/dev/null | head -n1 || true)
|
||||
if [[ -z "${app:-}" || ! -d "$app" ]]; then
|
||||
echo "::error::No Slopsmith.app found under release/mac*/ — macOS build produced no bundle."
|
||||
exit 1
|
||||
fi
|
||||
ditto -c -k --sequesterRsrc --keepParent "$app" "release/Slopsmith-macos-arm64.zip"
|
||||
echo "Zipped $app -> release/Slopsmith-macos-arm64.zip"
|
||||
ls -lh release/Slopsmith-macos-arm64.zip
|
||||
|
||||
- name: Derive Velopack channel
|
||||
id: velopack_channel
|
||||
if: >-
|
||||
(startsWith(github.ref, 'refs/tags/v') && matrix.platform != 'linux')
|
||||
|| (github.event_name == 'workflow_dispatch' && matrix.platform == 'win')
|
||||
shell: bash
|
||||
run: |
|
||||
set -euo pipefail
|
||||
if [[ "${GITHUB_EVENT_NAME}" == "workflow_dispatch" ]]; then
|
||||
base="$(node -p "require('./package.json').version")"
|
||||
base="${base%%-*}"
|
||||
channel=dev
|
||||
version="${base}-dev.${GITHUB_RUN_NUMBER}"
|
||||
echo "channel=$channel" >> "$GITHUB_OUTPUT"
|
||||
echo "version=$version" >> "$GITHUB_OUTPUT"
|
||||
echo "Manual dispatch -> channel=$channel version=$version"
|
||||
exit 0
|
||||
fi
|
||||
tag="${GITHUB_REF_NAME}"
|
||||
if [[ "$tag" =~ ^v([0-9]+\.[0-9]+\.[0-9]+)-alpha\.[0-9]+$ ]]; then
|
||||
channel=alpha
|
||||
version="${BASH_REMATCH[1]}-alpha.${tag##*-alpha.}"
|
||||
elif [[ "$tag" =~ ^v([0-9]+\.[0-9]+\.[0-9]+)-beta\.[0-9]+$ ]]; then
|
||||
channel=beta
|
||||
version="${BASH_REMATCH[1]}-beta.${tag##*-beta.}"
|
||||
elif [[ "$tag" =~ ^v([0-9]+\.[0-9]+\.[0-9]+)-rc\.[0-9]+$ ]]; then
|
||||
channel=rc
|
||||
version="${BASH_REMATCH[1]}-rc.${tag##*-rc.}"
|
||||
elif [[ "$tag" =~ ^v([0-9]+\.[0-9]+\.[0-9]+)$ ]]; then
|
||||
channel=stable
|
||||
version="${BASH_REMATCH[1]}"
|
||||
else
|
||||
echo "::error::Tag '$tag' does not match a known Velopack channel pattern (vX.Y.Z[-{alpha,beta,rc}.N]). Refusing to default to stable."
|
||||
exit 1
|
||||
fi
|
||||
echo "channel=$channel" >> "$GITHUB_OUTPUT"
|
||||
echo "version=$version" >> "$GITHUB_OUTPUT"
|
||||
echo "Derived channel=$channel version=$version from tag $tag"
|
||||
|
||||
- name: Install Velopack CLI (vpk)
|
||||
if: steps.velopack_channel.outputs.channel != ''
|
||||
shell: bash
|
||||
run: |
|
||||
set -euo pipefail
|
||||
dotnet tool install -g vpk --version 0.0.1589-ga2c5a97
|
||||
echo "$HOME/.dotnet/tools" >> "$GITHUB_PATH"
|
||||
|
||||
- name: Velopack pack (Windows)
|
||||
if: matrix.platform == 'win' && steps.velopack_channel.outputs.channel != ''
|
||||
shell: bash
|
||||
run: |
|
||||
set -euo pipefail
|
||||
vpk pack \
|
||||
--packId Slopsmith \
|
||||
--packVersion "${{ steps.velopack_channel.outputs.version }}" \
|
||||
--channel "win-x64-${{ steps.velopack_channel.outputs.channel }}" \
|
||||
--packDir release/win-unpacked \
|
||||
--msi \
|
||||
--instLocation PerMachine \
|
||||
-o release/velopack
|
||||
shopt -s nullglob nocaseglob
|
||||
removed=0
|
||||
for f in release/velopack/*setup.exe; do
|
||||
echo "Removing per-user installer: $f"
|
||||
rm -f "$f"
|
||||
removed=$((removed+1))
|
||||
done
|
||||
if compgen -G "release/velopack/*setup.exe" > /dev/null; then
|
||||
echo "::error::Setup.exe artifact(s) remain after cleanup; refusing to publish dual installer types."
|
||||
ls -1 release/velopack/*setup.exe
|
||||
exit 1
|
||||
fi
|
||||
if [[ "$removed" -eq 0 ]]; then
|
||||
echo "::notice::No Setup.exe output from vpk; shipping MSI-only artifacts."
|
||||
fi
|
||||
if ! compgen -G "release/velopack/*.msi" > /dev/null; then
|
||||
echo "::error::vpk pack --msi x64 did not produce a .msi file in release/velopack/"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
- name: Velopack pack (macOS)
|
||||
if: matrix.platform == 'mac' && steps.velopack_channel.outputs.channel != ''
|
||||
shell: bash
|
||||
env:
|
||||
APPLE_SIGNING_IDENTITY: ${{ secrets.APPLE_SIGNING_IDENTITY }}
|
||||
APPLE_ID: ${{ secrets.APPLE_ID }}
|
||||
APPLE_APP_SPECIFIC_PASSWORD: ${{ secrets.APPLE_APP_SPECIFIC_PASSWORD }}
|
||||
APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
if [[ -z "${APPLE_SIGNING_IDENTITY:-}" || -z "${APPLE_ID:-}" \
|
||||
|| -z "${APPLE_APP_SPECIFIC_PASSWORD:-}" \
|
||||
|| -z "${APPLE_TEAM_ID:-}" ]]; then
|
||||
echo "::warning::Apple signing/notarization secrets incomplete — packing macOS Velopack release UNSIGNED. Gatekeeper will block auto-updates on user machines."
|
||||
vpk pack \
|
||||
--packId Slopsmith \
|
||||
--packVersion "${{ steps.velopack_channel.outputs.version }}" \
|
||||
--channel "osx-arm64-${{ steps.velopack_channel.outputs.channel }}" \
|
||||
--packDir release/mac-arm64/Slopsmith.app \
|
||||
-o release/velopack
|
||||
else
|
||||
xcrun notarytool store-credentials "velopack-notary" \
|
||||
--apple-id "$APPLE_ID" \
|
||||
--password "$APPLE_APP_SPECIFIC_PASSWORD" \
|
||||
--team-id "$APPLE_TEAM_ID"
|
||||
vpk pack \
|
||||
--packId Slopsmith \
|
||||
--packVersion "${{ steps.velopack_channel.outputs.version }}" \
|
||||
--channel "osx-arm64-${{ steps.velopack_channel.outputs.channel }}" \
|
||||
--packDir release/mac-arm64/Slopsmith.app \
|
||||
-o release/velopack \
|
||||
--signAppIdentity "$APPLE_SIGNING_IDENTITY" \
|
||||
--notaryProfile "velopack-notary"
|
||||
fi
|
||||
|
||||
- name: Clean up signing keychain
|
||||
if: always() && matrix.platform == 'mac'
|
||||
run: |
|
||||
security delete-keychain build.keychain 2>/dev/null || true
|
||||
|
||||
- name: Upload artifacts
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: slopsmith-${{ matrix.platform }}-${{ matrix.arch }}
|
||||
path: |
|
||||
release/*.AppImage
|
||||
release/*.deb
|
||||
release/*.exe
|
||||
release/velopack/**/*
|
||||
build/Release/*.pdb
|
||||
release/*.zip
|
||||
if-no-files-found: warn
|
||||
retention-days: 7
|
||||
|
||||
release:
|
||||
needs: build
|
||||
if: startsWith(github.ref, 'refs/tags/v')
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: write
|
||||
|
||||
steps:
|
||||
- uses: actions/download-artifact@v4
|
||||
with:
|
||||
path: artifacts
|
||||
|
||||
- name: Create Release
|
||||
uses: softprops/action-gh-release@v2
|
||||
with:
|
||||
files: |
|
||||
artifacts/**/*.AppImage
|
||||
artifacts/**/*.deb
|
||||
artifacts/**/velopack/**/*
|
||||
generate_release_notes: true
|
||||
prerelease: false
|
||||
make_latest: ${{ (contains(github.ref_name, '-alpha.') || contains(github.ref_name, '-beta.') || contains(github.ref_name, '-rc.')) && 'false' || 'true' }}
|
||||
|
||||
notify-slopsmith:
|
||||
needs: release
|
||||
if: startsWith(github.ref, 'refs/tags/v')
|
||||
runs-on: ubuntu-latest
|
||||
permissions: {}
|
||||
|
||||
steps:
|
||||
- name: Dispatch VERSION sync to slopsmith
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.SLOPSMITH_SYNC_TOKEN }}
|
||||
REF_NAME: ${{ github.ref_name }}
|
||||
run: |
|
||||
version="${REF_NAME#v}"
|
||||
if ! [[ "$version" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
|
||||
echo "Tag $REF_NAME is not vX.Y.Z — skipping slopsmith VERSION sync."
|
||||
exit 0
|
||||
fi
|
||||
if [ -z "${GH_TOKEN:-}" ]; then
|
||||
echo "::error::SLOPSMITH_SYNC_TOKEN secret is missing; cannot dispatch."
|
||||
exit 1
|
||||
fi
|
||||
gh api -X POST repos/slopsmith/slopsmith/dispatches \
|
||||
-f event_type=desktop-released \
|
||||
-f "client_payload[version]=$version"
|
||||
echo "Dispatched desktop-released event (version=$version) to slopsmith."
|
||||
@@ -0,0 +1,158 @@
|
||||
name: Sandbox IPC tests
|
||||
|
||||
# Lightweight, fast-iterating job for the out-of-process plugin sandbox IPC
|
||||
# layer (src/audio/Sandbox). Deliberately separate from the heavyweight Build
|
||||
# matrix (build.yml): it needs only JUCE + a C++20 compiler — no cmake-js,
|
||||
# node-addon-api, ONNX, Python, or electron-builder — so it can run on every
|
||||
# push / PR that touches the sandbox without the full release toolchain.
|
||||
#
|
||||
# This is the iteration loop for the macOS sandbox port (issue #264): a
|
||||
# Linux-only developer pushes a branch and gets the macOS compiler + test
|
||||
# results they can't produce locally. The POSIX backend is shared between Linux
|
||||
# and macOS, so Linux gives fast signal and macOS confirms the Darwin-specific
|
||||
# paths (POSIX_SPAWN_CLOEXEC_DEFAULT, SO_NOSIGPIPE, the macOS shm/sem caveats).
|
||||
#
|
||||
# The TSan run on the threaded audio loopback is the highest-value artifact:
|
||||
# it validates the release/acquire memory ordering on arm64 (macos-14) that a
|
||||
# Linux-only dev cannot otherwise exercise.
|
||||
|
||||
on:
|
||||
push:
|
||||
paths:
|
||||
- 'src/audio/Sandbox/**'
|
||||
- 'src/vst-host/**'
|
||||
- 'tests/sandbox/**'
|
||||
- '.github/workflows/sandbox.yml'
|
||||
pull_request:
|
||||
paths:
|
||||
- 'src/audio/Sandbox/**'
|
||||
- 'src/vst-host/**'
|
||||
- 'tests/sandbox/**'
|
||||
- '.github/workflows/sandbox.yml'
|
||||
workflow_dispatch:
|
||||
|
||||
# Least privilege: these jobs only check out code and upload artifacts.
|
||||
# actions/checkout needs contents:read; upload-artifact uses its own runtime
|
||||
# token. Everything else defaults to none.
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
sandbox-tests:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
include:
|
||||
- os: ubuntu-22.04
|
||||
- os: macos-14
|
||||
runs-on: ${{ matrix.os }}
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
submodules: recursive # JUCE
|
||||
|
||||
# JUCE on Linux needs the usual X11/ALSA dev headers even for juce_core's
|
||||
# transitive config; install the minimal set. (No-op on macOS.)
|
||||
- name: Install Linux deps
|
||||
if: runner.os == 'Linux'
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y --no-install-recommends \
|
||||
ninja-build libasound2-dev libx11-dev libxext-dev libxinerama-dev \
|
||||
libxrandr-dev libxcursor-dev libfreetype6-dev libfontconfig1-dev
|
||||
|
||||
- name: Install Ninja (macOS)
|
||||
if: runner.os == 'macOS'
|
||||
run: brew install ninja
|
||||
|
||||
# Build + run three ways: plain (Debug), ASan, TSan. Each is a fresh
|
||||
# configure of the JUCE-only standalone harness, then ctest.
|
||||
- name: Build + test (plain, ASan, TSan)
|
||||
shell: bash
|
||||
run: |
|
||||
set -euo pipefail
|
||||
for variant in plain address thread; do
|
||||
echo "::group::$variant"
|
||||
args=(-S tests/sandbox/standalone -B "build/sbx-$variant" -G Ninja \
|
||||
-DCMAKE_BUILD_TYPE=Debug)
|
||||
if [[ "$variant" != "plain" ]]; then
|
||||
args+=(-DSLOPSMITH_SANITIZE="$variant")
|
||||
fi
|
||||
cmake "${args[@]}"
|
||||
cmake --build "build/sbx-$variant"
|
||||
# TSAN/ASAN: fail the job on any sanitizer diagnostic.
|
||||
TSAN_OPTIONS="halt_on_error=1" ASAN_OPTIONS="halt_on_error=1" \
|
||||
ctest --test-dir "build/sbx-$variant" --output-on-failure
|
||||
echo "::endgroup::"
|
||||
done
|
||||
|
||||
- name: Upload sandbox child log on failure
|
||||
if: failure()
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: sandbox-logs-${{ matrix.os }}
|
||||
path: |
|
||||
build/sbx-*/Testing/**/*.log
|
||||
if-no-files-found: ignore
|
||||
|
||||
# Heavier end-to-end job: builds a passthrough VST3 + the real
|
||||
# slopsmith-vst-host, spawns it from a host-side driver, and round-trips audio
|
||||
# over the shm ring (posix_spawn + fd inheritance + handshake + process +
|
||||
# state + shutdown). Separate from the unit job because it pulls in
|
||||
# juce_audio_processors / juce_gui_basics / the VST3 plugin client.
|
||||
sandbox-e2e:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
include:
|
||||
- os: ubuntu-22.04
|
||||
- os: macos-14
|
||||
runs-on: ${{ matrix.os }}
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
submodules: recursive
|
||||
|
||||
# Full JUCE Linux GUI/audio dep set (gui_basics + audio_processors).
|
||||
- name: Install Linux deps
|
||||
if: runner.os == 'Linux'
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y --no-install-recommends \
|
||||
ninja-build xvfb \
|
||||
libasound2-dev libjack-jackd2-dev libfreetype6-dev libfontconfig1-dev \
|
||||
libx11-dev libxcomposite-dev libxcursor-dev libxext-dev \
|
||||
libxinerama-dev libxrandr-dev libxrender-dev libglu1-mesa-dev \
|
||||
mesa-common-dev libcurl4-openssl-dev libwebkit2gtk-4.1-dev
|
||||
|
||||
- name: Install Ninja (macOS)
|
||||
if: runner.os == 'macOS'
|
||||
run: brew install ninja
|
||||
|
||||
- name: Build e2e harness
|
||||
run: |
|
||||
cmake -S tests/sandbox/e2e -B build/e2e -G Ninja -DCMAKE_BUILD_TYPE=Debug
|
||||
cmake --build build/e2e
|
||||
|
||||
# The vst-host loads a no-editor VST3, so no display is strictly needed;
|
||||
# run under xvfb on Linux anyway as insurance against any X11 touch in
|
||||
# juce_gui_basics init. macOS has a window server.
|
||||
- name: Run e2e (Linux, xvfb)
|
||||
if: runner.os == 'Linux'
|
||||
run: xvfb-run -a ctest --test-dir build/e2e --output-on-failure
|
||||
|
||||
- name: Run e2e (macOS)
|
||||
if: runner.os == 'macOS'
|
||||
run: ctest --test-dir build/e2e --output-on-failure
|
||||
|
||||
- name: Upload vst-host log on failure
|
||||
if: failure()
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: sandbox-e2e-logs-${{ matrix.os }}
|
||||
path: |
|
||||
${{ runner.temp }}/slopsmith-vst-host-*.log
|
||||
/tmp/slopsmith-vst-host-*.log
|
||||
if-no-files-found: ignore
|
||||
retention-days: 7
|
||||
@@ -0,0 +1,22 @@
|
||||
name: Ship CI
|
||||
|
||||
# PRs into both main and release/** run CI through this wrapper so the check
|
||||
# run is named "CI / check" (reusable-workflow caller prefix from the `CI`
|
||||
# job), matching the org rulesets' required context. ci.yml itself only
|
||||
# triggers via workflow_call — it never runs standalone, which would emit an
|
||||
# unprefixed "check" the rulesets can't match.
|
||||
on:
|
||||
pull_request:
|
||||
branches: [main, 'release/**']
|
||||
workflow_dispatch:
|
||||
|
||||
concurrency:
|
||||
group: ship-ci-${{ github.workflow }}-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
CI:
|
||||
uses: ./.github/workflows/ci.yml
|
||||
Reference in New Issue
Block a user