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
8 changes: 8 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,16 @@ name: ci

on:
pull_request:
paths-ignore:
- "docs/**"
- "**.md"
- ".gitignore"
push:
branches: [main]
paths-ignore:
- "docs/**"
- "**.md"
- ".gitignore"

permissions:
contents: read
Expand Down
39 changes: 21 additions & 18 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -22,10 +22,9 @@ name: release
on:
push:
tags:
# Semver with a mandatory pre-release suffix. A stable `v1.0.0` would not
# match, which is deliberate: this repository has never shipped, and the
# first tag that claims stability should have to change this file.
- "v[0-9]+.[0-9]+.[0-9]+-*"
# Semver matching either pre-releases (e.g. `v0.1.0-alpha.1`) or stable
# releases (e.g. `v1.0.0`).
- "v[0-9]+.[0-9]+.[0-9]+*"
# A dry run that does everything except publish. Useful for proving the build
# works on a clean runner without spending a tag on it.
workflow_dispatch:
Expand Down Expand Up @@ -166,8 +165,8 @@ jobs:
# type. The shape is asserted rather than trusted: a tag that is not
# `v` + semver-with-prerelease would stamp a version no tool accepts.
case "$tag" in
v[0-9]*.[0-9]*.[0-9]*-*) version="${tag#v}" ;;
*) echo "::error::$tag is not v<semver with a pre-release suffix>" >&2; exit 1 ;;
v[0-9]*.[0-9]*.[0-9]*) version="${tag#v}" ;;
*) echo "::error::$tag is not v<semver>" >&2; exit 1 ;;
esac

# The package version, written with the tool that parses JSON properly.
Expand Down Expand Up @@ -417,7 +416,7 @@ jobs:
# not test, and does not run on `workflow_dispatch` — so a dry run exercises
# the whole pipeline with nothing published at the end.
publish:
name: publish pre-release
name: publish release
needs: [build-linux, build-windows]
if: startsWith(github.ref, 'refs/tags/v')
runs-on: ubuntu-24.04
Expand All @@ -433,26 +432,24 @@ jobs:
with:
path: artifacts

# The tag says what this is. A tag matching the trigger pattern always has
# a `-` suffix, so `--prerelease` is not a guess — but it is asserted here
# rather than assumed, because a stable tag reaching this step by some
# future edit should fail loudly instead of being quietly published as one.
- name: Verify the tag is a pre-release
# Inspect whether the tag carries a pre-release suffix (e.g. `v0.1.0-alpha.1` vs `v1.0.0`).
- name: Inspect release type
run: |
set -euo pipefail
tag="${GITHUB_REF_NAME}"
case "$tag" in
*-*) echo "pre-release tag: $tag" ;;
*) echo "::error::$tag has no pre-release suffix" >&2; exit 1 ;;
esac
if [[ "$tag" == *-* ]]; then
echo "Publishing pre-release for $tag"
else
echo "Publishing stable release for $tag"
fi

# Idempotent, and the reason the first alpha publish failed: `gh release
# create` hard-fails when the tag already has a release, which is exactly
# what a retried run after a partial publish walks into. Create only when
# the tag has no release; otherwise reuse it. Uploading is a separate
# step with `--clobber`, so a retried asset replaces its stale
# counterpart instead of failing on the name.
- name: Create or reuse the pre-release
- name: Create or reuse the release
env:
GH_TOKEN: ${{ github.token }}
TAG: ${{ github.ref_name }}
Expand All @@ -470,10 +467,16 @@ jobs:
if gh release view "$TAG" --repo "$GITHUB_REPOSITORY" > /dev/null 2>&1; then
echo "release for $TAG already exists; uploading into it"
else
EXTRA_ARGS=()
if [[ "$TAG" == *-* ]]; then
EXTRA_ARGS+=("--prerelease")
else
EXTRA_ARGS+=("--latest")
fi
gh release create "$TAG" \
--repo "$GITHUB_REPOSITORY" \
--title "$TAG" \
--prerelease \
"${EXTRA_ARGS[@]}" \
--generate-notes
fi

Expand Down
24 changes: 21 additions & 3 deletions src/ui-smoke.ts
Original file line number Diff line number Diff line change
Expand Up @@ -267,13 +267,31 @@ async function main(): Promise<void> {
// host's path dialect, which is exactly why every entry point must use it here
// instead of shaving the slash itself.
const root = resolve(dirname(fileURLToPath(new URL(import.meta.url))), "..");
const defaultBinary = join(root, "ui", "target", "release", process.platform === "win32" ? "squid-ui.exe" : "squid-ui");
const binary = process.argv[2] ?? defaultBinary;
const binaryName = process.platform === "win32" ? "squid-ui.exe" : "squid-ui";
const releaseBinary = join(root, "ui", "target", "release", binaryName);
const debugBinary = join(root, "ui", "target", "debug", binaryName);

let binary = process.argv[2];
if (binary === undefined) {
try {
await access(releaseBinary);
binary = releaseBinary;
} catch {
try {
await access(debugBinary);
binary = debugBinary;
log(`ui:smoke using debug binary at ${binary}`);
} catch {
binary = releaseBinary;
}
}
}

try {
await access(binary);
} catch {
smokeFailure(
`compiled panel missing at ${binary}; run \`cargo build --release --manifest-path ui/Cargo.toml\`, or pass its path after \`bun run ui:smoke --\`.`,
`compiled panel missing at ${binary}; run \`cargo build --manifest-path ui/Cargo.toml\` (or with \`--release\`), or pass its path after \`bun run ui:smoke --\`.`,
);
}

Expand Down