diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 029f17df..08ecc032 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -88,6 +88,25 @@ jobs: - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 - run: ./scripts/check-loc.sh + # Mirrors lefthook.yml's own workflow-yaml job -- same script, plus + # actionlint for semantic checks the parse can't see. Unconditional + # like file-loc-limit: GitHub only parses a workflow when its trigger + # fires, so a syntax error in a tag-triggered workflow otherwise + # merges through green CI and detonates at release time (v0.2.0's + # first run). + workflow-lint: + runs-on: ubuntu-latest + timeout-minutes: 5 + steps: + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + - run: ./scripts/check-workflow-yaml.sh + - uses: actions/setup-go@b7ad1dad31e06c5925ef5d2fc7ad053ef454303e # v7.0.0 + with: + go-version: '1.25' + cache: false + - run: go install github.com/rhysd/actionlint/cmd/actionlint@v1.7.7 + - run: actionlint + # Mirrors lefthook.yml's own comment-hygiene job -- same script # (.claude/rules/comments.md is the standard it enforces). Grep-only, # so like file-loc-limit it needs no build setup and no changes gate. @@ -421,6 +440,7 @@ jobs: needs: - changes - file-loc-limit + - workflow-lint - comment-hygiene - ui-copy - rules-frontmatter diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 01448aef..5ccbf50d 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -67,9 +67,13 @@ jobs: node-version: '22' cache: 'npm' cache-dependency-path: frontend/package-lock.json - - uses: arduino/setup-task@c0bc642852239c2689f73f4ea6459c29405f3c52 # v3.0.0 - - name: Install wails3 CLI - run: go install github.com/wailsapp/wails/v3/cmd/wails3@v3.0.0-beta.6 + # Task installed via go install, not arduino/setup-task: the tool + # itself is MIT but that ACTION wrapper is GPL-3.0, which trips + # dependency-review's deny-licenses on any PR touching this file. + - name: Install Task and wails3 CLI + run: | + go install github.com/go-task/task/v3/cmd/task@v3.52.0 + go install github.com/wailsapp/wails/v3/cmd/wails3@v3.0.0-beta.6 # task build already chains: go mod tidy -> generate icons -> install # frontend deps -> generate bindings -> frontend build -> go build. # No GoReleaser (see ADR-0002 / wailsapp/wails#747, closed wont-fix) -- @@ -116,15 +120,22 @@ jobs: - name: Create GitHub release env: GH_TOKEN: ${{ github.token }} - # --notes text is prepended to the generated notes; the app is - # ad-hoc signed (no Apple Developer ID), so first launch needs - # the standard right-click -> Open confirmation. + # --notes-file text is prepended to the generated notes; the app + # is ad-hoc signed (no Apple Developer ID), so first launch + # needs the standard right-click -> Open confirmation. Notes are + # built line-by-line into a file: every line of a run:| block + # must stay indented (an unindented continuation terminates the + # literal scalar -- the v0.2.0 first-run failure), and backticks + # must stay escaped inside double quotes (command substitution). run: | + { + echo "## Install" + echo "" + echo "Download the \`.zip\`, unzip, and drag \`mill.app\` to Applications — no build needed. First launch: **right-click the app → Open → Open** (it is ad-hoc signed, not notarized — macOS asks once). Verify the download came from this repo's CI: \`gh attestation verify -R ${GITHUB_REPOSITORY}\`. Prefer building from source? \`git clone\` + the README's few commands work on any Mac." + } > /tmp/release-notes.md gh release create "${GITHUB_REF_NAME}" \ --repo "${GITHUB_REPOSITORY}" \ --title "${GITHUB_REF_NAME}" \ --generate-notes \ - --notes "## Install - -Download the \`.zip\`, unzip, and drag \`mill.app\` to Applications — no build needed. First launch: **right-click the app → Open → Open** (it is ad-hoc signed, not notarized — macOS asks once). Verify the download came from this repo's CI: \`gh attestation verify -R ${GITHUB_REPOSITORY}\`. Prefer building from source? \`git clone\` + the README's few commands work on any Mac." \ + --notes-file /tmp/release-notes.md \ dist/* diff --git a/lefthook.yml b/lefthook.yml index 4cb403ba..b5e81e4f 100644 --- a/lefthook.yml +++ b/lefthook.yml @@ -8,6 +8,9 @@ pre-commit: - name: file-loc-limit glob: "*.{go,ts,tsx}" run: ./scripts/check-loc.sh + - name: workflow-yaml + glob: ".github/workflows/*.yml" + run: ./scripts/check-workflow-yaml.sh - name: comment-hygiene glob: "*.{go,ts,tsx}" run: ./scripts/check-comment-hygiene.sh diff --git a/scripts/check-workflow-yaml.sh b/scripts/check-workflow-yaml.sh new file mode 100755 index 00000000..3918b92a --- /dev/null +++ b/scripts/check-workflow-yaml.sh @@ -0,0 +1,21 @@ +#!/usr/bin/env bash +# Every .github/workflows/*.yml must parse as YAML. GitHub only parses a +# workflow when its trigger fires, so a syntax error in a rarely-fired +# workflow (release.yml runs only on tags) can merge through green CI +# and fail at the worst moment -- exactly what happened to v0.2.0's +# first run: a run:| block's unindented continuation line terminated +# the literal scalar. Parse-only here (no new local tooling; python3 + +# PyYAML ship with macOS dev setups and ubuntu runners); CI's +# workflow-lint job additionally runs actionlint for semantic checks. +set -euo pipefail +cd "$(dirname "$0")/.." + +fail=0 +for f in .github/workflows/*.yml; do + if ! python3 -c "import yaml, sys; yaml.safe_load(open(sys.argv[1]))" "$f" 2>/tmp/workflow-yaml-err; then + echo "INVALID YAML: $f" + cat /tmp/workflow-yaml-err + fail=1 + fi +done +exit $fail