From be7fc8903f59a64712e85c19d321c502805f9132 Mon Sep 17 00:00:00 2001 From: Alicia Sykes Date: Fri, 15 May 2026 18:10:30 +0100 Subject: [PATCH 1/5] =?UTF-8?q?=F0=9F=91=B7=20Smoother=20releases,=20with?= =?UTF-8?q?=20built=20app=20and=20checksum?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/build-release-assets.yml | 87 ----------- .github/workflows/draft-release.yml | 103 ------------- .github/workflows/release.yml | 170 +++++++++++++++++++++ 3 files changed, 170 insertions(+), 190 deletions(-) delete mode 100644 .github/workflows/build-release-assets.yml delete mode 100644 .github/workflows/draft-release.yml create mode 100644 .github/workflows/release.yml diff --git a/.github/workflows/build-release-assets.yml b/.github/workflows/build-release-assets.yml deleted file mode 100644 index 1736b741da..0000000000 --- a/.github/workflows/build-release-assets.yml +++ /dev/null @@ -1,87 +0,0 @@ -name: ๐Ÿ“ฆ Build & Upload Release Assets - -# Builds Dashy and uploads a pre-built tarball to the GitHub release. -# This allows non-Docker installs (e.g. Proxmox VE community scripts) to -# download a ready-to-run package without having to build from source. -# -# The tarball contains the compiled frontend (dist/) plus all server-side -# files. Users extract it and run `yarn install --production` + `node server`. -# -# Triggered whenever a new release is created, or when manually dispatched - -on: - release: - types: [created] - workflow_dispatch: - inputs: - tag: - description: 'Tag to build assets for (must already exist as a release)' - required: true - -permissions: - contents: write - -concurrency: - group: ${{ github.workflow }}-${{ github.event.release.tag_name || github.event.inputs.tag }} - cancel-in-progress: true - -jobs: - build-release-assets: - name: Build app & upload tarball - runs-on: ubuntu-latest - env: - TAG: ${{ github.event.release.tag_name || github.event.inputs.tag }} - - steps: - - name: Checkout code ๐Ÿ›Ž๏ธ - uses: actions/checkout@v6 - with: - ref: refs/tags/${{ env.TAG }} - - - name: Setup Node.js โš™๏ธ - uses: actions/setup-node@v4 - with: - node-version: '20' - cache: 'yarn' - - - name: Install dependencies ๐Ÿ“ฅ - run: yarn install --frozen-lockfile --ignore-engines --network-timeout 300000 - - - name: Build app ๐Ÿ—๏ธ - run: NODE_OPTIONS=--openssl-legacy-provider yarn build --mode production - - - name: Package release artifact ๐Ÿ“ฆ - run: | - STAGING="dashy-release-staging" - mkdir -p "$STAGING" - - # Runtime files - cp -r dist "$STAGING/" - cp -r services "$STAGING/" - cp -r public "$STAGING/" - cp -r user-data "$STAGING/" - cp server.js "$STAGING/" - cp yarn.lock "$STAGING/" - - # src/utils/ files referenced directly by the server at runtime - mkdir -p "$STAGING/src/utils/config" - cp src/utils/config/ConfigSchema.json "$STAGING/src/utils/config/" - - # Strip devDependencies so `yarn install --production` stays lean - node -e " - const pkg = JSON.parse(require('fs').readFileSync('package.json', 'utf8')); - delete pkg.devDependencies; - require('fs').writeFileSync('$STAGING/package.json', JSON.stringify(pkg, null, 2)); - " - - TARBALL="dashy-${TAG}.tar.gz" - tar -czf "${TARBALL}" -C "${STAGING}" . - echo "TARBALL=${TARBALL}" >> "$GITHUB_ENV" - echo "Size: $(du -sh ${TARBALL} | cut -f1)" - - - name: Upload tarball to GitHub Release ๐Ÿš€ - uses: softprops/action-gh-release@v3 - with: - tag_name: ${{ env.TAG }} - files: ${{ env.TARBALL }} - token: ${{ secrets.BOT_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/draft-release.yml b/.github/workflows/draft-release.yml deleted file mode 100644 index beaf1c378d..0000000000 --- a/.github/workflows/draft-release.yml +++ /dev/null @@ -1,103 +0,0 @@ -name: ๐Ÿ—๏ธ Draft New Release - -on: - push: - tags: - - '*.*.*' - workflow_dispatch: - inputs: - tag: - description: 'Tag to draft a release for (must already exist)' - required: true - -permissions: - contents: write - -jobs: - create-draft-release: - runs-on: ubuntu-latest - env: - TAG: ${{ github.event.inputs.tag || github.ref_name }} - steps: - - name: Checkout code ๐Ÿ›Ž๏ธ - uses: actions/checkout@v6 - with: - fetch-depth: 0 - - - name: Check if major or minor version changed ๐Ÿ” - id: version_check - env: - CURRENT_TAG: ${{ github.event.inputs.tag || github.ref_name }} - run: | - git fetch --tags --force - CURRENT_MM=$(echo "$CURRENT_TAG" | sed 's/^v//; s/\([0-9]*\.[0-9]*\)\..*/\1/') - - # Find the immediately previous tag (to detect patch-only bumps) - PREVIOUS_TAG=$(git tag --sort=-version:refname \ - | grep -v "^${CURRENT_TAG}$" | head -1) - - if [ -z "$PREVIOUS_TAG" ]; then - echo "No previous tag found, creating release" - echo "should_release=true" >> $GITHUB_OUTPUT - echo "previous_tag=" >> $GITHUB_OUTPUT - exit 0 - fi - - PREVIOUS_MM=$(echo "$PREVIOUS_TAG" | sed 's/^v//; s/\([0-9]*\.[0-9]*\)\..*/\1/') - if [ "$CURRENT_MM" = "$PREVIOUS_MM" ]; then - echo "Patch-only bump ($PREVIOUS_TAG -> $CURRENT_TAG), skipping" - echo "should_release=false" >> $GITHUB_OUTPUT - echo "previous_tag=" >> $GITHUB_OUTPUT - exit 0 - fi - - # Minor/major bump โ€” find the last tag from the previous release - PREV_RELEASE_TAG=$(git tag --sort=-version:refname | while read -r t; do - [ "$t" = "$CURRENT_TAG" ] && continue - t_mm=$(echo "$t" | sed 's/^v//; s/\([0-9]*\.[0-9]*\)\..*/\1/') - if [ "$t_mm" != "$CURRENT_MM" ]; then echo "$t"; break; fi - done) - echo "Minor/major bump, comparing against ${PREV_RELEASE_TAG:-$PREVIOUS_TAG}" - echo "should_release=true" >> $GITHUB_OUTPUT - echo "previous_tag=${PREV_RELEASE_TAG:-$PREVIOUS_TAG}" >> $GITHUB_OUTPUT - - - name: Create draft release ๐Ÿ“ - if: steps.version_check.outputs.should_release == 'true' || github.event_name == 'workflow_dispatch' - id: create_release - uses: softprops/action-gh-release@v3 - with: - tag_name: ${{ env.TAG }} - name: Release ${{ env.TAG }} - draft: true - prerelease: false - generate_release_notes: true - previous_tag: ${{ steps.version_check.outputs.previous_tag }} - token: ${{ secrets.BOT_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} - - - name: Job summary ๐Ÿ“‹ - if: always() - env: - REPO_URL: ${{ github.server_url }}/${{ github.repository }} - SHOULD_RELEASE: ${{ steps.version_check.outputs.should_release }} - RELEASE_URL: ${{ steps.create_release.outputs.url }} - PREV_TAG: ${{ steps.version_check.outputs.previous_tag }} - run: | - { - echo "## ๐Ÿ—๏ธ Draft Release" - echo "" - echo "| Step | Result |" - echo "|------|--------|" - echo "| Tag | [\`${TAG}\`](${REPO_URL}/releases/tag/${TAG}) |" - - if [ -n "$PREV_TAG" ]; then - echo "| Compared against | [\`${PREV_TAG}\`](${REPO_URL}/releases/tag/${PREV_TAG}) |" - fi - - if [ -n "$RELEASE_URL" ]; then - echo "| Draft release | โœ… [Review and publish](${RELEASE_URL}) |" - elif [ "$SHOULD_RELEASE" = "false" ]; then - echo "| Draft release | โญ๏ธ Skipped (patch-only bump) |" - else - echo "| Draft release | โŒ Failed |" - fi - } >> "$GITHUB_STEP_SUMMARY" diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000000..5b1c3f70f9 --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,170 @@ +# Builds Dashy and drafts a GitHub release with the compiled tarball, +# along with SHA256 checksum and SLSA build-provenance attestation +# +# Triggered by: +# - Push of any major/minor (X.Y.0) git tag +# - Manual dispatch with any existing tag (any version) + +name: ๐Ÿš€ Build & Release + +on: + push: + tags: ['*.*.0'] + workflow_dispatch: + inputs: + tag: + description: 'Existing git tag to release (e.g. 4.2.0)' + required: true + +concurrency: + group: ${{ github.workflow }}-${{ inputs.tag || github.ref_name }} + cancel-in-progress: false + +permissions: + contents: read + +jobs: + release: + name: ๐Ÿš€ Build & Draft Release + runs-on: ubuntu-latest + timeout-minutes: 20 + permissions: + contents: write + id-token: write + attestations: write + env: + TAG: ${{ inputs.tag || github.ref_name }} + steps: + - name: ๐Ÿ›Ž๏ธ Checkout tag + uses: actions/checkout@v6 + with: + ref: refs/tags/${{ env.TAG }} + fetch-depth: 0 + + - name: ๐Ÿ”ง Setup Node.js + uses: actions/setup-node@v6 + with: + node-version: '20' + cache: 'yarn' + + - name: ๐Ÿ“ฅ Install dependencies + run: yarn install --frozen-lockfile --ignore-engines --network-timeout 300000 + + - name: ๐Ÿ—๏ธ Build app + run: NODE_OPTIONS=--openssl-legacy-provider yarn build --mode production + + - name: ๐Ÿ“ฆ Package release tarball + id: package + run: | + set -euo pipefail + STAGING="dashy-release-staging" + mkdir -p "$STAGING" + cp -r dist "$STAGING/" + cp -r services "$STAGING/" + cp -r public "$STAGING/" + cp -r user-data "$STAGING/" + cp server.js "$STAGING/" + cp yarn.lock "$STAGING/" + mkdir -p "$STAGING/src/utils/config" + cp src/utils/config/ConfigSchema.json "$STAGING/src/utils/config/" + node -e " + const fs = require('fs'); + const pkg = JSON.parse(fs.readFileSync('package.json', 'utf8')); + delete pkg.devDependencies; + fs.writeFileSync('$STAGING/package.json', JSON.stringify(pkg, null, 2)); + " + TARBALL="dashy-${TAG}.tar.gz" + tar -czf "$TARBALL" -C "$STAGING" . + echo "tarball=$TARBALL" >> "$GITHUB_OUTPUT" + echo "size=$(du -h "$TARBALL" | cut -f1)" >> "$GITHUB_OUTPUT" + + - name: ๐Ÿ”ข Generate SHA256 checksum + id: checksum + env: + TARBALL: ${{ steps.package.outputs.tarball }} + run: | + set -euo pipefail + CHECKSUM="${TARBALL}.sha256" + sha256sum "$TARBALL" > "$CHECKSUM" + echo "file=$CHECKSUM" >> "$GITHUB_OUTPUT" + + - name: ๐Ÿชช Generate build provenance attestation + id: attest + uses: actions/attest-build-provenance@v4 + with: + subject-path: ${{ steps.package.outputs.tarball }} + + - name: ๐Ÿ“ค Rename attestation bundle + id: attest_asset + env: + TARBALL: ${{ steps.package.outputs.tarball }} + BUNDLE: ${{ steps.attest.outputs.bundle-path }} + run: | + set -euo pipefail + OUT="${TARBALL}.intoto.jsonl" + cp "$BUNDLE" "$OUT" + echo "file=$OUT" >> "$GITHUB_OUTPUT" + + - name: ๐Ÿ”Ž Find previous release tag + id: prev + env: + CURRENT_TAG: ${{ env.TAG }} + run: | + set -euo pipefail + git fetch --tags --force + PREV=$({ echo "$CURRENT_TAG"; git tag | grep -E '^[0-9]+\.[0-9]+\.0$'; } \ + | sort -uV \ + | awk -v cur="$CURRENT_TAG" '$0 == cur { print prev; exit } { prev = $0 }') + echo "tag=$PREV" >> "$GITHUB_OUTPUT" + + - name: ๐Ÿ“ Create draft release + id: release + uses: softprops/action-gh-release@v3 + with: + tag_name: ${{ env.TAG }} + name: Release ${{ env.TAG }} + draft: true + prerelease: false + generate_release_notes: true + previous_tag: ${{ steps.prev.outputs.tag }} + fail_on_unmatched_files: true + files: | + ${{ steps.package.outputs.tarball }} + ${{ steps.checksum.outputs.file }} + ${{ steps.attest_asset.outputs.file }} + token: ${{ secrets.BOT_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} + + - name: ๐Ÿ“‹ Job summary + if: always() + env: + REPO_URL: ${{ github.server_url }}/${{ github.repository }} + PREV_TAG: ${{ steps.prev.outputs.tag }} + RELEASE_URL: ${{ steps.release.outputs.url }} + TARBALL: ${{ steps.package.outputs.tarball }} + SIZE: ${{ steps.package.outputs.size }} + ATTEST_URL: ${{ steps.attest.outputs.attestation-url }} + run: | + set -euo pipefail + { + echo "## ๐Ÿš€ Release Draft" + echo "" + echo "| Item | Value |" + echo "|------|-------|" + echo "| Tag | [\`${TAG}\`](${REPO_URL}/releases/tag/${TAG}) |" + if [ -n "$PREV_TAG" ]; then + echo "| Notes since | [\`${PREV_TAG}\`](${REPO_URL}/releases/tag/${PREV_TAG}) |" + fi + if [ -n "$TARBALL" ]; then + echo "| Tarball | \`${TARBALL}\` (${SIZE:-?}) |" + fi + if [ -n "$ATTEST_URL" ]; then + echo "| Attestation | โœ… [View](${ATTEST_URL}) |" + else + echo "| Attestation | โŒ Failed |" + fi + if [ -n "$RELEASE_URL" ]; then + echo "| Draft release | โœ… [Review and publish](${RELEASE_URL}) |" + else + echo "| Draft release | โŒ Failed |" + fi + } >> "$GITHUB_STEP_SUMMARY" From 0cabd9be8f184f086544feb6ede1521b7431b1e4 Mon Sep 17 00:00:00 2001 From: Alicia Sykes Date: Fri, 15 May 2026 18:10:59 +0100 Subject: [PATCH 2/5] =?UTF-8?q?=F0=9F=91=B7=20Streamlined=20auto-tagging?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/manual-tag.yml | 66 -------- .../workflows/{bump-and-tag.yml => tag.yml} | 152 +++++++++++++----- 2 files changed, 114 insertions(+), 104 deletions(-) delete mode 100644 .github/workflows/manual-tag.yml rename .github/workflows/{bump-and-tag.yml => tag.yml} (67%) diff --git a/.github/workflows/manual-tag.yml b/.github/workflows/manual-tag.yml deleted file mode 100644 index 85cf5ff068..0000000000 --- a/.github/workflows/manual-tag.yml +++ /dev/null @@ -1,66 +0,0 @@ -# Manual fallback for creating a tag with optional version bump. -# The automated flow is handled by bump-and-tag.yml on PR merge. -name: ๐Ÿท๏ธ Tag on Version Change - -on: - workflow_dispatch: - inputs: - version: - description: 'Version to tag (e.g. 3.2.0). Leave empty to auto-bump patch.' - required: false - -concurrency: - group: manual-tag-version - cancel-in-progress: false - -jobs: - tag-version: - runs-on: ubuntu-latest - - steps: - - name: Check Out Repository ๐Ÿ›Ž๏ธ - uses: actions/checkout@v6 - with: - token: ${{ secrets.BOT_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} - fetch-depth: 0 - - - name: Configure Git Identity ๐Ÿค– - run: | - git config user.name "Liss-Bot" - git config user.email "liss-bot@d0h.co" - - - name: Determine and Apply Version ๐Ÿ”ข - id: version - env: - INPUT_VERSION: ${{ github.event.inputs.version }} - run: | - CURRENT=$(node -p "require('./package.json').version") - if [ -n "$INPUT_VERSION" ]; then - TARGET="${INPUT_VERSION#v}" - else - npm version patch --no-git-tag-version > /dev/null - TARGET=$(node -p "require('./package.json').version") - fi - if [ "$TARGET" != "$CURRENT" ]; then - npm version "$TARGET" --no-git-tag-version --allow-same-version - git add package.json - git commit -m "๐Ÿ”– Bump version to $TARGET [skip ci]" - git push - echo "Committed version bump to $TARGET" - else - echo "package.json already at $CURRENT, skipping commit" - fi - echo "TARGET=$TARGET" >> $GITHUB_OUTPUT - - - name: Create and Push Tag โคด๏ธ - env: - TAG: ${{ steps.version.outputs.TARGET }} - run: | - git fetch --tags --force - if git rev-parse "refs/tags/$TAG" >/dev/null 2>&1; then - echo "Tag $TAG already exists, skipping" - else - git tag -a "$TAG" -m "Release v$TAG" - git push origin "$TAG" - echo "Created and pushed tag $TAG" - fi diff --git a/.github/workflows/bump-and-tag.yml b/.github/workflows/tag.yml similarity index 67% rename from .github/workflows/bump-and-tag.yml rename to .github/workflows/tag.yml index 4378d6aa48..377b63fb61 100644 --- a/.github/workflows/bump-and-tag.yml +++ b/.github/workflows/tag.yml @@ -1,17 +1,29 @@ -# Creates a new git tag when a PR is merged +# Creates a new git tag when a PR is merged, or on manual dispatch # -# Here's the flow: +# PR trigger flow: # - Triggered whenever a PR is merged, if that PR made code changes # - If version wasn't bumped in PR, increment patch version and update package.json # - Otherwise (if the PR did bump version) we use the new version from package.json # - Creates and pushes a git tag for the new version # - That git tag then triggers Docker publishing and release drafting in other CI -# - Add tags to issues from newly relesaed features/fixes (if applicable) +# - Add labels and release comments to referenced issues (if applicable) # - Trigger fresh deploy of docs site, so changelog remains up-to-date # - Finally, shows summary of actions taken and new tag published +# +# Manual dispatch flow: +# - If a version is provided, sets package.json to that version +# - If no version is provided, increments patch version automatically +# - Creates and pushes a git tag, then triggers docs site rebuild + name: ๐Ÿ”– Auto Version & Tag on: + workflow_dispatch: + inputs: + version: + description: 'Version to tag (e.g. 4.1.0). Leave blank to auto-increment patch.' + required: false + type: string pull_request_target: types: [closed] branches: [master] @@ -25,14 +37,37 @@ permissions: pull-requests: read issues: write +env: + IS_MANUAL: ${{ github.event_name == 'workflow_dispatch' }} + jobs: version-and-tag: - if: github.event.pull_request.merged == true + if: >- + github.event_name == 'workflow_dispatch' + || github.event.pull_request.merged == true runs-on: ubuntu-latest + timeout-minutes: 15 steps: - - name: Check PR for code changes and version bump ๐Ÿ“‚ + - name: ๐Ÿ”ข Validate manual dispatch + if: env.IS_MANUAL == 'true' + env: + INPUT_VERSION: ${{ inputs.version }} + DISPATCH_REF: ${{ github.ref }} + run: | + set -euo pipefail + if [ "$DISPATCH_REF" != "refs/heads/master" ]; then + echo "::error::Manual dispatch only allowed from master (got: $DISPATCH_REF)" + exit 1 + fi + if [ -n "$INPUT_VERSION" ] && ! printf '%s' "$INPUT_VERSION" | grep -qE '^[0-9]+\.[0-9]+\.[0-9]+$'; then + echo "::error::Invalid version '${INPUT_VERSION}'. Must be semver (e.g. 4.1.0)." + exit 1 + fi + + - name: ๐Ÿ“‚ Check PR for code changes and version bump id: check_pr + if: env.IS_MANUAL != 'true' uses: actions/github-script@v8 with: script: | @@ -43,7 +78,7 @@ jobs: github.rest.pulls.listFiles, { owner, repo, pull_number } ); const codePatterns = [ - /^src\//, /^services\//, /^public\//, /^Dockerfile$/, /^[^/]+\.js$/, + /^src\//, /^services\//, /^public\//, /^Dockerfile$/, /^yarn.lock$/, /^[^/]+\.js$/, ]; const codeChanged = files.some(f => codePatterns.some(p => p.test(f.filename)) @@ -83,21 +118,21 @@ jobs: core.setOutput('needs_bump', needsBump.toString()); core.setOutput('needs_tag', needsTag.toString()); - - name: Checkout repository ๐Ÿ›Ž๏ธ - if: steps.check_pr.outputs.needs_tag == 'true' + - name: ๐Ÿ›Ž๏ธ Checkout repository + if: env.IS_MANUAL == 'true' || steps.check_pr.outputs.needs_tag == 'true' uses: actions/checkout@v6 with: token: ${{ secrets.BOT_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} - - name: Configure git identity ๐Ÿ‘ค - if: steps.check_pr.outputs.needs_tag == 'true' + - name: ๐Ÿ‘ค Configure git identity + if: env.IS_MANUAL == 'true' || steps.check_pr.outputs.needs_tag == 'true' run: | git config user.name "Liss-Bot" git config user.email "liss-bot@d0h.co" - - name: Extract referenced issues ๐Ÿ” + - name: ๐Ÿ” Extract referenced issues id: issues - if: steps.check_pr.outputs.needs_tag == 'true' + if: env.IS_MANUAL != 'true' && steps.check_pr.outputs.needs_tag == 'true' uses: actions/github-script@v8 with: script: | @@ -119,47 +154,76 @@ jobs: core.info(`Found issue references: ${unique.join(', ')}`); core.setOutput('numbers', unique.join(',')); - - name: Bump patch version โฌ†๏ธ - if: steps.check_pr.outputs.needs_bump == 'true' + - name: โฌ†๏ธ Bump version + id: bump + if: >- + env.IS_MANUAL == 'true' + || steps.check_pr.outputs.needs_bump == 'true' + env: + INPUT_VERSION: ${{ inputs.version }} run: | - npm version patch --no-git-tag-version + set -euo pipefail + if [ "$IS_MANUAL" = "true" ] && [ -n "$INPUT_VERSION" ]; then + npm version "$INPUT_VERSION" --no-git-tag-version --allow-same-version + else + npm version patch --no-git-tag-version + fi + NEW_VERSION=$(node -p "require('./package.json').version") git add package.json - git commit -m "๐Ÿ”– Bump version to $(node -p "require('./package.json').version")" - git push + if git diff --cached --quiet; then + echo "package.json already at $NEW_VERSION, nothing to commit" + echo "bumped=false" >> "$GITHUB_OUTPUT" + else + git commit -m "๐Ÿ”– Bump version to $NEW_VERSION" + git push + echo "bumped=true" >> "$GITHUB_OUTPUT" + fi - - name: Create and push tag ๐Ÿท๏ธ + - name: ๐Ÿท๏ธ Create and push tag id: tag - if: steps.check_pr.outputs.needs_tag == 'true' + if: env.IS_MANUAL == 'true' || steps.check_pr.outputs.needs_tag == 'true' env: - PR_NUMBER: ${{ github.event.pull_request.number }} - PR_TITLE: ${{ github.event.pull_request.title }} - PR_AUTHOR: ${{ github.event.pull_request.user.login }} - MERGE_SHA: ${{ github.event.pull_request.merge_commit_sha }} + PR_NUMBER: ${{ github.event.pull_request.number || '' }} + PR_TITLE: ${{ github.event.pull_request.title || '' }} + PR_AUTHOR: ${{ github.event.pull_request.user.login || github.actor }} + MERGE_SHA: ${{ github.event.pull_request.merge_commit_sha || github.sha }} ISSUES: ${{ steps.issues.outputs.numbers }} run: | + set -euo pipefail VERSION=$(node -p "require('./package.json').version") git fetch --tags --force if git rev-parse "refs/tags/$VERSION" >/dev/null 2>&1; then echo "Tag $VERSION already exists, skipping" + echo "result=existed" >> "$GITHUB_OUTPUT" + echo "version=$VERSION" >> "$GITHUB_OUTPUT" exit 0 fi { printf 'Dashy v%s ๐Ÿš€\n\n' "$VERSION" - printf 'PR: #%s - %s\n' "$PR_NUMBER" "$PR_TITLE" + if [ -n "$PR_NUMBER" ]; then + printf 'PR: #%s - %s\n' "$PR_NUMBER" "$PR_TITLE" + else + printf 'Manual release by @%s\n' "$PR_AUTHOR" + fi if [ -n "$ISSUES" ]; then printf 'Resolves: %s\n' "$(echo "$ISSUES" | sed 's/,/, #/g; s/^/#/')" fi printf 'Author: @%s\n' "$PR_AUTHOR" - printf 'Merge commit: %s\n' "$MERGE_SHA" + printf 'Commit: %s\n' "$MERGE_SHA" } > tag-message.txt git tag -a "$VERSION" -F tag-message.txt git push origin "$VERSION" + echo "result=created" >> "$GITHUB_OUTPUT" + echo "version=$VERSION" >> "$GITHUB_OUTPUT" - - name: Label referenced issues ๐Ÿ›ฉ๏ธ + - name: ๐Ÿ›ฉ๏ธ Label referenced issues id: label - if: steps.check_pr.outputs.needs_tag == 'true' && steps.issues.outputs.numbers != '' + if: >- + env.IS_MANUAL != 'true' + && steps.check_pr.outputs.needs_tag == 'true' + && steps.issues.outputs.numbers != '' continue-on-error: true uses: actions/github-script@v8 env: @@ -234,53 +298,65 @@ jobs: } } - - name: Trigger docs site rebuild ๐Ÿ“ + - name: ๐Ÿ“ Trigger docs site rebuild id: docs - if: steps.tag.outcome == 'success' + if: steps.tag.outputs.result == 'created' continue-on-error: true env: HOOK_URL: ${{ secrets.DOCS_SITE_REBUILD_HOOK }} + VERSION: ${{ steps.tag.outputs.version }} run: | + set -euo pipefail if [ -z "$HOOK_URL" ]; then echo "::warning::DOCS_SITE_REBUILD_HOOK secret is not set, skipping" exit 1 fi - VERSION=$(node -p "require('./package.json').version" 2>/dev/null || echo "unknown") curl -sf -X POST -d '{}' "${HOOK_URL}?trigger_title=v${VERSION}+released" \ --max-time 15 --retry 2 --retry-max-time 30 echo "Triggered docs rebuild for v${VERSION}" - - name: Job summary ๐Ÿ“‹ + - name: ๐Ÿ“‹ Job summary if: always() env: - PR_NUMBER: ${{ github.event.pull_request.number }} - PR_TITLE: ${{ github.event.pull_request.title }} + PR_NUMBER: ${{ github.event.pull_request.number || '' }} + PR_TITLE: ${{ github.event.pull_request.title || '' }} REPO_URL: ${{ github.server_url }}/${{ github.repository }} NEEDS_BUMP: ${{ steps.check_pr.outputs.needs_bump }} NEEDS_TAG: ${{ steps.check_pr.outputs.needs_tag }} ISSUES: ${{ steps.issues.outputs.numbers }} + BUMPED: ${{ steps.bump.outputs.bumped }} TAG_OUTCOME: ${{ steps.tag.outcome }} + TAG_RESULT: ${{ steps.tag.outputs.result }} + TAG_VERSION: ${{ steps.tag.outputs.version }} LABEL_OUTCOME: ${{ steps.label.outcome }} DOCS_OUTCOME: ${{ steps.docs.outcome }} run: | - VERSION=$(node -p "require('./package.json').version" 2>/dev/null || echo "unknown") + set -euo pipefail + VERSION="${TAG_VERSION:-$(node -p "require('./package.json').version" 2>/dev/null || echo "unknown")}" { echo "## ๐Ÿ”– Auto Version & Tag" echo "" echo "| Step | Result |" echo "|------|--------|" - echo "| PR | [#${PR_NUMBER}](${REPO_URL}/pull/${PR_NUMBER}) โ€” ${PR_TITLE} |" - if [ "$NEEDS_BUMP" = "true" ]; then + if [ "$IS_MANUAL" = "true" ]; then + echo "| Trigger | Manual dispatch |" + elif [ -n "$PR_NUMBER" ]; then + echo "| PR | [#${PR_NUMBER}](${REPO_URL}/pull/${PR_NUMBER}) โ€” ${PR_TITLE} |" + fi + + if [ "$BUMPED" = "true" ]; then echo "| Version bump | โœ… \`${VERSION}\` |" else echo "| Version bump | โญ๏ธ Skipped |" fi - if [ "$NEEDS_TAG" = "true" ] && [ "$TAG_OUTCOME" = "success" ]; then + if [ "$TAG_RESULT" = "created" ]; then echo "| Tag | โœ… [\`${VERSION}\`](${REPO_URL}/releases/tag/${VERSION}) |" - elif [ "$NEEDS_TAG" = "true" ]; then + elif [ "$TAG_RESULT" = "existed" ]; then + echo "| Tag | โญ๏ธ Already exists: \`${VERSION}\` |" + elif [ "$TAG_OUTCOME" = "failure" ]; then echo "| Tag | โŒ Failed |" else echo "| Tag | โญ๏ธ Skipped |" From b3455afb4984f493ac630f8eaf6919e711e5ce2e Mon Sep 17 00:00:00 2001 From: Alicia Sykes Date: Fri, 15 May 2026 18:11:37 +0100 Subject: [PATCH 3/5] =?UTF-8?q?=F0=9F=91=B7=20Faster=20cross-arch=20Docker?= =?UTF-8?q?=20compilation?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/docker-build-publish.yml | 84 ------ .github/workflows/docker.yml | 305 +++++++++++++++++++++ 2 files changed, 305 insertions(+), 84 deletions(-) delete mode 100644 .github/workflows/docker-build-publish.yml create mode 100644 .github/workflows/docker.yml diff --git a/.github/workflows/docker-build-publish.yml b/.github/workflows/docker-build-publish.yml deleted file mode 100644 index 22757c2213..0000000000 --- a/.github/workflows/docker-build-publish.yml +++ /dev/null @@ -1,84 +0,0 @@ -name: ๐Ÿณ Build + Publish Multi-Platform Image - -on: - workflow_dispatch: - push: - tags: ['*.*.*'] - -concurrency: - group: ${{ github.workflow }}-${{ github.ref }} - cancel-in-progress: true - -permissions: - packages: write - -env: - DH_IMAGE: ${{ secrets.DOCKER_REPO }} - GH_IMAGE: ${{ github.repository_owner }}/${{ github.event.repository.name }} - -jobs: - docker: - runs-on: ubuntu-latest - permissions: { contents: read, packages: write } - - steps: - - name: ๐Ÿ›Ž๏ธ Checkout Repo - uses: actions/checkout@v6 - - - name: ๐Ÿ—‚๏ธ Make Docker Meta - id: meta - uses: docker/metadata-action@v6 - with: - images: | - ${{ env.DH_IMAGE }} - ghcr.io/${{ env.GH_IMAGE }} - tags: | - type=ref,event=tag - type=semver,pattern={{major}}.{{minor}} - type=semver,pattern={{major}}.x - type=raw,value=latest - flavor: | - latest=false - - - name: โฑ๏ธ Capture Build Timestamp - id: timestamp - run: echo "iso=$(date -u +'%Y-%m-%dT%H:%M:%SZ')" >> "$GITHUB_OUTPUT" - - - name: ๐Ÿ”ง Set up QEMU - uses: docker/setup-qemu-action@v4 - with: - platforms: linux/amd64,linux/arm64,linux/arm/v7 - - - name: ๐Ÿ”ง Set up Docker Buildx - uses: docker/setup-buildx-action@v4 - - - name: ๐Ÿ”‘ Login to DockerHub - uses: docker/login-action@v4 - with: - username: ${{ secrets.DOCKER_USERNAME }} - password: ${{ secrets.DOCKER_PASSWORD }} - - - name: ๐Ÿ”‘ Login to GitHub Container Registry - uses: docker/login-action@v4 - with: - registry: ghcr.io - username: ${{ github.repository_owner }} - password: ${{ secrets.GITHUB_TOKEN }} - - - name: ๐Ÿšฆ Check Registry Status - uses: crazy-max/ghaction-docker-status@v4 - - - name: โš’๏ธ Build and Push - uses: docker/build-push-action@v7 - with: - context: . - file: ./Dockerfile - platforms: linux/amd64,linux/arm64,linux/arm/v7 - tags: ${{ steps.meta.outputs.tags }} - build-args: | - VERSION=${{ steps.meta.outputs.version }} - REVISION=${{ github.sha }} - CREATED=${{ steps.timestamp.outputs.iso }} - cache-from: type=gha - cache-to: type=gha,mode=max - push: true diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml new file mode 100644 index 0000000000..66d9c0806f --- /dev/null +++ b/.github/workflows/docker.yml @@ -0,0 +1,305 @@ +# Builds and publishes the multi-arch Docker image +# +# Triggered by: +# - On git tag push, publishes to :X.Y.Z, :X.Y, and :latest +# - On manual dispatch from master, rebuilds and updates :latest +# - On weekly cron, rebuilds :latest from master for upstream patches +# +# The workflow will: +# - Builds multi-arch (amd64, arm64, armv7) in parallel on native runners +# - Trivy scans + reports security issues, and fails on CRITICAL CVEs +# - Publishes to GHCR, and to Docker Hub if creds are configured +# - Attests both the build provenance and SBOM and publishes to GHCR +# - Uploads digest, SBOM and outputs as artifact, and shows MD summary + +name: ๐Ÿณ Build + Publish Multi-Platform Image + +on: + workflow_dispatch: + inputs: + tag: + description: 'Existing git tag to build. Empty = build current ref as :latest.' + required: false + default: '' + push: + tags: ['*.*.*'] + schedule: + - cron: '0 4 * * 0' + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }}-${{ inputs.tag }} + cancel-in-progress: false + +permissions: + contents: read + +env: + DH_IMAGE: ${{ secrets.DOCKER_REPO }} + GH_IMAGE: ghcr.io/${{ github.repository }} + +jobs: + build: + name: ๐Ÿ”จ Build (${{ matrix.arch }}) + timeout-minutes: 30 + permissions: + contents: read + packages: write + security-events: write + strategy: + fail-fast: false + matrix: + include: + - platform: linux/amd64 + runner: ubuntu-latest + arch: amd64 + - platform: linux/arm64 + runner: ubuntu-24.04-arm + arch: arm64 + - platform: linux/arm/v7 + runner: ubuntu-latest + arch: armv7 + runs-on: ${{ matrix.runner }} + steps: + - name: ๐Ÿ›Ž๏ธ Checkout + uses: actions/checkout@v6 + with: + ref: ${{ inputs.tag || github.ref }} + + - name: ๐Ÿท๏ธ Resolve build version + id: version + env: + INPUT_TAG: ${{ inputs.tag }} + EVENT_NAME: ${{ github.event_name }} + REF_NAME: ${{ github.ref_name }} + run: | + set -euo pipefail + if [ -n "$INPUT_TAG" ]; then + v="$INPUT_TAG" + elif [ "$EVENT_NAME" = "push" ]; then + v="$REF_NAME" + else + v="latest" + fi + echo "value=$v" >> "$GITHUB_OUTPUT" + + - name: ๐Ÿ”ง Set up QEMU + if: matrix.arch == 'armv7' + uses: docker/setup-qemu-action@v4 + with: + platforms: linux/arm/v7 + + - name: ๐Ÿ”ง Set up Buildx + uses: docker/setup-buildx-action@v4 + + - name: ๐Ÿ”‘ Login to GHCR + uses: docker/login-action@v4 + with: + registry: ghcr.io + username: ${{ github.repository_owner }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: โฑ๏ธ Capture build timestamp + id: timestamp + run: echo "iso=$(date -u +'%Y-%m-%dT%H:%M:%SZ')" >> "$GITHUB_OUTPUT" + + - name: ๐Ÿ”จ Build image (load for scan) + uses: docker/build-push-action@v7 + with: + context: . + file: ./Dockerfile + platforms: ${{ matrix.platform }} + cache-from: type=gha,scope=${{ matrix.arch }} + cache-to: type=gha,scope=${{ matrix.arch }},mode=max + load: true + tags: dashy-scan:${{ matrix.arch }} + provenance: false + build-args: | + VERSION=${{ steps.version.outputs.value }} + REVISION=${{ github.sha }} + CREATED=${{ steps.timestamp.outputs.iso }} + + - name: ๐Ÿ›ก๏ธ Trivy vulnerability scan + uses: aquasecurity/trivy-action@0.36.0 + env: + TRIVY_DB_REPOSITORY: ghcr.io/aquasecurity/trivy-db:2 + TRIVY_JAVA_DB_REPOSITORY: ghcr.io/aquasecurity/trivy-java-db:1 + with: + image-ref: dashy-scan:${{ matrix.arch }} + severity: CRITICAL + ignore-unfixed: true + exit-code: ${{ github.event_name == 'schedule' && '1' || '0' }} + vuln-type: 'os,library' + format: 'sarif' + output: 'trivy-${{ matrix.arch }}.sarif' + timeout: '10m' + + - name: ๐Ÿ“ค Upload Trivy SARIF + if: always() && hashFiles(format('trivy-{0}.sarif', matrix.arch)) != '' + uses: github/codeql-action/upload-sarif@v3 + with: + sarif_file: trivy-${{ matrix.arch }}.sarif + category: trivy-${{ matrix.arch }} + + - name: ๐Ÿš€ Push by digest + id: push + uses: docker/build-push-action@v7 + with: + context: . + file: ./Dockerfile + platforms: ${{ matrix.platform }} + cache-from: type=gha,scope=${{ matrix.arch }} + outputs: type=image,name=${{ env.GH_IMAGE }},push-by-digest=true,name-canonical=true,push=true + provenance: false + build-args: | + VERSION=${{ steps.version.outputs.value }} + REVISION=${{ github.sha }} + CREATED=${{ steps.timestamp.outputs.iso }} + + - name: ๐Ÿงฌ Write digest + run: | + mkdir -p "${{ runner.temp }}/digests" + echo "${{ steps.push.outputs.digest }}" > "${{ runner.temp }}/digests/${{ matrix.arch }}" + + - name: ๐Ÿ“ค Upload digest + uses: actions/upload-artifact@v7 + with: + name: digest-${{ matrix.arch }} + path: ${{ runner.temp }}/digests/${{ matrix.arch }} + if-no-files-found: error + retention-days: 1 + + merge: + name: ๐Ÿงฉ Merge & Push Manifests + needs: build + timeout-minutes: 30 + runs-on: ubuntu-latest + permissions: + contents: read + packages: write + id-token: write + attestations: write + env: + HAS_DH: ${{ secrets.DOCKER_USERNAME != '' && secrets.DOCKER_PASSWORD != '' && secrets.DOCKER_REPO != '' }} + SEMVER_VALUE: ${{ inputs.tag || github.ref_name }} + SEMVER_ENABLE: ${{ github.event_name == 'push' || inputs.tag != '' }} + LATEST_ENABLE: ${{ inputs.tag == '' }} + steps: + - name: ๐Ÿ“ฅ Download digests + uses: actions/download-artifact@v7 + with: + path: ${{ runner.temp }}/digests + pattern: digest-* + merge-multiple: true + + - name: ๐Ÿ”ง Set up Buildx + uses: docker/setup-buildx-action@v4 + + - name: ๐Ÿ”‘ Login to GHCR + uses: docker/login-action@v4 + with: + registry: ghcr.io + username: ${{ github.repository_owner }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: ๐Ÿ”‘ Login to Docker Hub + if: env.HAS_DH == 'true' + uses: docker/login-action@v4 + with: + username: ${{ secrets.DOCKER_USERNAME }} + password: ${{ secrets.DOCKER_PASSWORD }} + + - name: ๐Ÿ—‚๏ธ Generate tags + id: meta + uses: docker/metadata-action@v6 + with: + images: | + ${{ env.GH_IMAGE }} + ${{ env.HAS_DH == 'true' && env.DH_IMAGE || '' }} + tags: | + type=raw,value=latest,enable=${{ env.LATEST_ENABLE }} + type=semver,pattern={{version}},value=${{ env.SEMVER_VALUE }},enable=${{ env.SEMVER_ENABLE }} + type=semver,pattern={{major}}.{{minor}},value=${{ env.SEMVER_VALUE }},enable=${{ env.SEMVER_ENABLE }} + type=semver,pattern={{major}}.x,value=${{ env.SEMVER_VALUE }},enable=${{ env.SEMVER_ENABLE }} + flavor: | + latest=false + + - name: ๐Ÿงฉ Create & push manifest + id: manifest + working-directory: ${{ runner.temp }}/digests + run: | + set -euo pipefail + TAGS=() + while IFS= read -r tag; do TAGS+=(-t "$tag"); done \ + < <(jq -r '.tags[]' <<< "$DOCKER_METADATA_OUTPUT_JSON") + SOURCES=() + for f in *; do SOURCES+=("${GH_IMAGE}@$(cat "$f")"); done + docker buildx imagetools create "${TAGS[@]}" "${SOURCES[@]}" + PRIMARY=$(jq -r --arg img "$GH_IMAGE" \ + '.tags[] | select(startswith($img + ":"))' \ + <<< "$DOCKER_METADATA_OUTPUT_JSON" | head -1) + DIGEST=$(docker buildx imagetools inspect "$PRIMARY" --format '{{.Manifest.Digest}}') + echo "primary_tag=$PRIMARY" >> "$GITHUB_OUTPUT" + echo "digest=$DIGEST" >> "$GITHUB_OUTPUT" + + - name: ๐Ÿ” Generate SBOM (SPDX) + uses: anchore/sbom-action@v0.24.0 + with: + image: ${{ steps.manifest.outputs.primary_tag }} + format: spdx-json + output-file: sbom.spdx.json + upload-artifact: false + + - name: ๐Ÿชช Attest SBOM + id: attest_sbom + uses: actions/attest-sbom@v4 + continue-on-error: true + with: + subject-name: ${{ env.GH_IMAGE }} + subject-digest: ${{ steps.manifest.outputs.digest }} + sbom-path: sbom.spdx.json + push-to-registry: true + + - name: ๐Ÿ›ก๏ธ Attest build provenance + id: attest_provenance + uses: actions/attest-build-provenance@v4 + continue-on-error: true + with: + subject-name: ${{ env.GH_IMAGE }} + subject-digest: ${{ steps.manifest.outputs.digest }} + push-to-registry: true + + - name: ๐Ÿ“‹ Summary + if: always() + env: + SBOM_OUTCOME: ${{ steps.attest_sbom.outcome }} + SBOM_URL: ${{ steps.attest_sbom.outputs.attestation-url }} + PROV_OUTCOME: ${{ steps.attest_provenance.outcome }} + PROV_URL: ${{ steps.attest_provenance.outputs.attestation-url }} + DIGEST: ${{ steps.manifest.outputs.digest }} + TAGS_JSON: ${{ steps.meta.outputs.json }} + run: | + set -euo pipefail + attest() { + case "$2" in + success) + if [ -n "$3" ]; then + echo "- โœ… $1 attested ([view]($3))" + else + echo "- โœ… $1 attested" + fi ;; + failure) echo "- โš ๏ธ $1 attestation failed (image pushed without attest)" ;; + *) echo "- โญ๏ธ $1 attestation \`$2\`" ;; + esac + } + { + if [ -n "$DIGEST" ]; then + echo "## ๐Ÿณ Docker Image" + echo "**Manifest:** \`$DIGEST\`" + echo '```bash' + jq -r '.tags[] | "docker pull \(.)"' <<< "$TAGS_JSON" + echo '```' + fi + echo "## ๐Ÿชช Attestations" + attest "SBOM" "$SBOM_OUTCOME" "$SBOM_URL" + attest "Build provenance" "$PROV_OUTCOME" "$PROV_URL" + } >> "$GITHUB_STEP_SUMMARY" From 159307f1a3ec4aa14032c6f86653b10fe95f27b8 Mon Sep 17 00:00:00 2001 From: Alicia Sykes Date: Fri, 15 May 2026 18:12:06 +0100 Subject: [PATCH 4/5] =?UTF-8?q?=F0=9F=91=B7=20More=20complete=20PR=20CI=20?= =?UTF-8?q?checks?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../{pr-quality-check.yml => ci.yml} | 330 ++++++++++-------- .github/workflows/release.yml | 2 +- 2 files changed, 180 insertions(+), 152 deletions(-) rename .github/workflows/{pr-quality-check.yml => ci.yml} (52%) diff --git a/.github/workflows/pr-quality-check.yml b/.github/workflows/ci.yml similarity index 52% rename from .github/workflows/pr-quality-check.yml rename to .github/workflows/ci.yml index 60fd24907b..ef384b6ea1 100644 --- a/.github/workflows/pr-quality-check.yml +++ b/.github/workflows/ci.yml @@ -1,151 +1,179 @@ -name: ๐Ÿ” PR Quality Check - -on: - pull_request: - branches: ['master', 'develop'] - paths-ignore: - - '**.md' - -concurrency: - group: ${{ github.workflow }}-${{ github.ref }} - cancel-in-progress: true - -jobs: - changes: - name: ๐Ÿ”Ž Detect Changes - runs-on: ubuntu-latest - outputs: - lockfile: ${{ steps.filter.outputs.lockfile }} - steps: - - name: ๐Ÿ›Ž๏ธ Checkout Code - uses: actions/checkout@v6 - - - name: ๐Ÿ”Ž Filter Paths - uses: dorny/paths-filter@v3 - id: filter - with: - filters: | - lockfile: - - 'yarn.lock' - - lint: - name: ๐Ÿ“ Lint Code - runs-on: ubuntu-latest - steps: - - name: ๐Ÿ›Ž๏ธ Checkout Code - uses: actions/checkout@v6 - - - name: ๐Ÿ”ง Setup Node.js - uses: actions/setup-node@v6 - with: - node-version: '20' - cache: 'yarn' - - - name: ๐Ÿ“ฆ Install Dependencies - run: yarn install --frozen-lockfile - - - name: ๐Ÿ” Run ESLint - run: yarn lint - - typecheck: - name: ๐Ÿงท Type Check - runs-on: ubuntu-latest - steps: - - name: ๐Ÿ›Ž๏ธ Checkout Code - uses: actions/checkout@v6 - - - name: ๐Ÿ”ง Setup Node.js - uses: actions/setup-node@v6 - with: - node-version: '20' - cache: 'yarn' - - - name: ๐Ÿ“ฆ Install Dependencies - run: yarn install --frozen-lockfile - - - name: ๐Ÿงท Run vue-tsc - run: yarn typecheck - - test: - name: ๐Ÿงช Run Tests - runs-on: ubuntu-latest - steps: - - name: ๐Ÿ›Ž๏ธ Checkout Code - uses: actions/checkout@v6 - - - name: ๐Ÿ”ง Setup Node.js - uses: actions/setup-node@v6 - with: - node-version: '20' - cache: 'yarn' - - - name: ๐Ÿ“ฆ Install Dependencies - run: yarn install --frozen-lockfile - - - name: ๐Ÿงช Run Tests - run: yarn test - - build: - name: ๐Ÿ—๏ธ Build Application - runs-on: ubuntu-latest - steps: - - name: ๐Ÿ›Ž๏ธ Checkout Code - uses: actions/checkout@v6 - - - name: ๐Ÿ”ง Setup Node.js - uses: actions/setup-node@v6 - with: - node-version: '20' - cache: 'yarn' - - - name: ๐Ÿ“ฆ Install Dependencies - run: yarn install --frozen-lockfile - - - name: ๐Ÿ—๏ธ Build Project - run: yarn build - - - name: โœ… Verify Build Output - run: | - if [ ! -d "dist" ]; then - echo "โŒ Build failed: dist directory not created" - exit 1 - fi - if [ ! -f "dist/index.html" ]; then - echo "โŒ Build failed: index.html not found" - exit 1 - fi - echo "โœ… Build successful" - - docker-smoke: - name: ๐Ÿณ Docker Smoke Test - runs-on: ubuntu-latest - continue-on-error: true - steps: - - name: ๐Ÿ›Ž๏ธ Checkout Code - uses: actions/checkout@v6 - - - name: ๐Ÿณ Build & Test Docker Image - run: sh tests/docker-smoke-test.sh - timeout-minutes: 10 - - security: - name: ๐Ÿ”’ Security Audit - runs-on: ubuntu-latest - needs: changes - if: needs.changes.outputs.lockfile == 'true' - continue-on-error: true - steps: - - name: ๐Ÿ›Ž๏ธ Checkout Code - uses: actions/checkout@v6 - - - name: ๐Ÿ”ง Setup Node.js - uses: actions/setup-node@v6 - with: - node-version: '20' - cache: 'yarn' - - - name: ๐Ÿ“ฆ Install Dependencies - run: yarn install --frozen-lockfile - - - name: ๐Ÿ”’ Run Security Audit - run: yarn audit --level high +# CI checks to run when PR is opened +name: ๐Ÿšฆ PR Check + +on: + pull_request: + branches: ['master', 'develop'] + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +jobs: + changes: + name: ๐Ÿ”Ž Detect Changes + runs-on: ubuntu-latest + outputs: + lockfile: ${{ steps.filter.outputs.lockfile }} + workflows: ${{ steps.filter.outputs.workflows }} + steps: + - name: Checkout Code + uses: actions/checkout@v6 + + - name: Filter Paths + uses: dorny/paths-filter@v4 + id: filter + with: + filters: | + lockfile: + - 'yarn.lock' + workflows: + - '.github/workflows/**' + + lint: + name: ๐Ÿ›ก๏ธ Lint + runs-on: ubuntu-latest + steps: + - name: Checkout Code + uses: actions/checkout@v6 + + - name: Setup Node.js + uses: actions/setup-node@v6 + with: + node-version: '20' + cache: 'yarn' + + - name: Install Dependencies + run: yarn install --frozen-lockfile + + - name: Run ESLint + run: yarn lint + + typecheck: + name: ๐Ÿฆด Typecheck + runs-on: ubuntu-latest + steps: + - name: Checkout Code + uses: actions/checkout@v6 + + - name: Setup Node.js + uses: actions/setup-node@v6 + with: + node-version: '20' + cache: 'yarn' + + - name: Install Dependencies + run: yarn install --frozen-lockfile + + - name: Run vue-tsc + run: yarn typecheck + + test: + name: ๐Ÿงช Test + runs-on: ubuntu-latest + steps: + - name: Checkout Code + uses: actions/checkout@v6 + + - name: Setup Node.js + uses: actions/setup-node@v6 + with: + node-version: '20' + cache: 'yarn' + + - name: Install Dependencies + run: yarn install --frozen-lockfile + + - name: Run Tests + run: yarn test + + build: + name: ๐Ÿ—๏ธ Build Check + runs-on: ubuntu-latest + steps: + - name: Checkout Code + uses: actions/checkout@v6 + + - name: Setup Node.js + uses: actions/setup-node@v6 + with: + node-version: '20' + cache: 'yarn' + + - name: Install Dependencies + run: yarn install --frozen-lockfile + + - name: Build Project + run: yarn build + + - name: Verify Build Output + run: | + if [ ! -d "dist" ]; then + echo "โŒ Build failed: dist directory not created" + exit 1 + fi + if [ ! -f "dist/index.html" ]; then + echo "โŒ Build failed: index.html not found" + exit 1 + fi + echo "โœ… Build successful" + + docker-smoke: + name: ๐Ÿณ Docker Smoke Test + runs-on: ubuntu-latest + continue-on-error: true + steps: + - name: Checkout Code + uses: actions/checkout@v6 + + - name: Build & Test Docker Image + run: sh tests/docker-smoke-test.sh + timeout-minutes: 10 + + dependency-review: + name: ๐Ÿ”’ Dependency Audit + runs-on: ubuntu-latest + needs: changes + if: needs.changes.outputs.lockfile == 'true' + permissions: + contents: read + steps: + - name: Checkout Code + uses: actions/checkout@v6 + + - name: Review Dependencies + uses: actions/dependency-review-action@v4 + with: + fail-on-severity: moderate + + secret-scan: + name: ๐Ÿ”‘ Secret Scanning + runs-on: ubuntu-latest + permissions: + contents: read + steps: + - name: Checkout Code + uses: actions/checkout@v6 + with: + fetch-depth: 0 + + - name: Scan PR Diff for Secrets + uses: trufflesecurity/trufflehog@v3.95.3 + with: + base: ${{ github.event.pull_request.base.sha }} + head: ${{ github.event.pull_request.head.sha }} + extra_args: --only-verified --fail + + actionlint: + name: ๐Ÿ› ๏ธ Lint Actions + runs-on: ubuntu-latest + needs: changes + if: needs.changes.outputs.workflows == 'true' + steps: + - name: Checkout Code + uses: actions/checkout@v6 + + - name: Run Actionlint + uses: raven-actions/actionlint@v2 + with: + fail-on-error: true diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 5b1c3f70f9..2c89f521a2 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -112,7 +112,7 @@ jobs: run: | set -euo pipefail git fetch --tags --force - PREV=$({ echo "$CURRENT_TAG"; git tag | grep -E '^[0-9]+\.[0-9]+\.0$'; } \ + PREV=$({ echo "$CURRENT_TAG"; git tag | grep -E '^[0-9]+\.[0-9]+\.0$' || true; } \ | sort -uV \ | awk -v cur="$CURRENT_TAG" '$0 == cur { print prev; exit } { prev = $0 }') echo "tag=$PREV" >> "$GITHUB_OUTPUT" From 5aa1dc268775c0cd578c047477f7ce9414a3dd6a Mon Sep 17 00:00:00 2001 From: Alicia Sykes Date: Fri, 15 May 2026 18:17:30 +0100 Subject: [PATCH 5/5] =?UTF-8?q?=F0=9F=92=9A=20Fixes=20secret=20scanning=20?= =?UTF-8?q?check?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index ef384b6ea1..f49eb09548 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -162,7 +162,7 @@ jobs: with: base: ${{ github.event.pull_request.base.sha }} head: ${{ github.event.pull_request.head.sha }} - extra_args: --only-verified --fail + extra_args: --only-verified actionlint: name: ๐Ÿ› ๏ธ Lint Actions