From 8b2cee2c583e0c86cad909e502538fa6c4d91d52 Mon Sep 17 00:00:00 2001 From: sudacode Date: Mon, 13 Jul 2026 23:14:19 -0700 Subject: [PATCH] ci: reuse quality gate across CI and release workflows (#167) --- .github/workflows/ci.yml | 96 +-------------------- .github/workflows/prerelease.yml | 83 +----------------- .github/workflows/quality-gate.yml | 117 ++++++++++++++++++++++++++ .github/workflows/release.yml | 73 +--------------- bun.lock | 94 ++++++++++++--------- changes/reusable-ci-quality-gate.md | 4 + docs/RELEASING.md | 1 + docs/workflow/verification.md | 14 ++- package.json | 20 +++-- patches/@discordjs%2Frest@2.6.1.patch | 13 +++ src/ci-workflow.test.ts | 26 ++---- src/prerelease-workflow.test.ts | 15 ++-- src/quality-gate-workflow.test.ts | 66 +++++++++++++++ src/release-workflow.test.ts | 15 ++-- 14 files changed, 315 insertions(+), 322 deletions(-) create mode 100644 .github/workflows/quality-gate.yml create mode 100644 changes/reusable-ci-quality-gate.md create mode 100644 patches/@discordjs%2Frest@2.6.1.patch create mode 100644 src/quality-gate-workflow.test.ts diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 1769c405..30aa1f68 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -8,98 +8,4 @@ on: jobs: build-test-audit: - runs-on: ubuntu-latest - steps: - - name: Checkout - uses: actions/checkout@v4 - with: - fetch-depth: 0 - submodules: true - - - name: Setup Bun - uses: oven-sh/setup-bun@v2 - with: - bun-version: 1.3.5 - - - name: Cache dependencies - uses: actions/cache@v4 - with: - path: | - ~/.bun/install/cache - node_modules - stats/node_modules - vendor/subminer-yomitan/node_modules - key: ${{ runner.os }}-bun-${{ hashFiles('bun.lock', 'stats/bun.lock', 'vendor/subminer-yomitan/package-lock.json') }} - restore-keys: | - ${{ runner.os }}-bun- - - - name: Install dependencies - run: | - bun install --frozen-lockfile - cd stats && bun install --frozen-lockfile - - - name: Lint changelog fragments - run: bun run changelog:lint - - - name: Lint stats (formatting) - run: bun run lint:stats - - - name: Enforce pull request changelog fragments (`skip-changelog` label bypass) - if: github.event_name == 'pull_request' - run: bun run changelog:pr-check --base-ref "origin/${{ github.base_ref }}" --head-ref "HEAD" --labels "${{ join(github.event.pull_request.labels.*.name, ',') }}" - - - name: Build (TypeScript check) - # Keep explicit typecheck for fast fail before full build/bundle. - run: bun run typecheck - - - name: Verify generated config examples - run: bun run verify:config-example - - - name: Test suite (source) - run: bun run test:fast - - - name: Coverage suite (maintained source lane) - run: bun run test:coverage:src - - - name: Upload coverage artifact - uses: actions/upload-artifact@v4 - with: - name: coverage-test-src - path: coverage/test-src/lcov.info - if-no-files-found: error - - - name: Stats UI tests - run: bun run test:stats - - - name: Launcher smoke suite (source) - run: bun run test:launcher:smoke:src - - - name: Upload launcher smoke artifacts (on failure) - if: failure() - uses: actions/upload-artifact@v4 - with: - name: launcher-smoke - path: .tmp/launcher-smoke/** - if-no-files-found: ignore - - - name: Build (bundle) - run: bun run build - - - name: Immersion SQLite verification - run: bun run test:immersion:sqlite:dist - - - name: Dist smoke suite - run: bun run test:smoke:dist - - - name: Security audit - run: bun audit --audit-level high - continue-on-error: true - - - name: Build Bun subminer wrapper - run: make build-launcher - - - name: Verify Bun subminer wrapper - run: dist/launcher/subminer --help >/dev/null - - - name: Enforce generated launcher workflow - run: bash scripts/verify-generated-launcher.sh + uses: ./.github/workflows/quality-gate.yml diff --git a/.github/workflows/prerelease.yml b/.github/workflows/prerelease.yml index e183d6f4..7888110c 100644 --- a/.github/workflows/prerelease.yml +++ b/.github/workflows/prerelease.yml @@ -12,86 +12,9 @@ concurrency: jobs: quality-gate: - runs-on: ubuntu-latest - steps: - - name: Checkout - uses: actions/checkout@v4 - with: - submodules: true - - - name: Setup Bun - uses: oven-sh/setup-bun@v2 - with: - bun-version: 1.3.5 - - - name: Cache dependencies - uses: actions/cache@v4 - with: - path: | - ~/.bun/install/cache - node_modules - stats/node_modules - vendor/subminer-yomitan/node_modules - key: ${{ runner.os }}-${{ runner.arch }}-bun-${{ hashFiles('bun.lock', 'stats/bun.lock', 'vendor/subminer-yomitan/package-lock.json') }} - restore-keys: | - ${{ runner.os }}-${{ runner.arch }}-bun- - - - name: Install dependencies - run: | - bun install --frozen-lockfile - cd stats && bun install --frozen-lockfile - - - name: Lint stats (formatting) - run: bun run lint:stats - - - name: Build (TypeScript check) - run: bun run typecheck - - - name: Install Lua - run: | - sudo apt-get update - sudo apt-get install -y lua5.4 - sudo ln -sf /usr/bin/lua5.4 /usr/local/bin/lua - lua -v - - - name: Test suite (source) - run: bun run test:fast - - - name: Environment suite - run: bun run test:env - - - name: Coverage suite (maintained source lane) - run: bun run test:coverage:src - - - name: Upload coverage artifact - uses: actions/upload-artifact@v4 - with: - name: coverage-test-src - path: coverage/test-src/lcov.info - if-no-files-found: error - - - name: Stats UI tests - run: bun run test:stats - - - name: Launcher smoke suite (source) - run: bun run test:launcher:smoke:src - - - name: Upload launcher smoke artifacts (on failure) - if: failure() - uses: actions/upload-artifact@v4 - with: - name: launcher-smoke - path: .tmp/launcher-smoke/** - if-no-files-found: ignore - - - name: Build (bundle) - run: bun run build - - - name: Immersion SQLite verification - run: bun run test:immersion:sqlite:dist - - - name: Dist smoke suite - run: bun run test:smoke:dist + permissions: + contents: read + uses: ./.github/workflows/quality-gate.yml build-linux: needs: [quality-gate] diff --git a/.github/workflows/quality-gate.yml b/.github/workflows/quality-gate.yml new file mode 100644 index 00000000..bb5c0d1d --- /dev/null +++ b/.github/workflows/quality-gate.yml @@ -0,0 +1,117 @@ +name: Quality Gate + +on: + workflow_call: + +permissions: + contents: read + +jobs: + quality-gate: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + with: + fetch-depth: 0 + submodules: true + persist-credentials: false + + - name: Setup Bun + uses: oven-sh/setup-bun@v2 + with: + bun-version: 1.3.5 + + - name: Cache dependencies + uses: actions/cache@v4 + with: + path: | + ~/.bun/install/cache + node_modules + stats/node_modules + vendor/subminer-yomitan/node_modules + key: ${{ runner.os }}-${{ runner.arch }}-bun-${{ hashFiles('bun.lock', 'stats/bun.lock', 'vendor/subminer-yomitan/package-lock.json') }} + restore-keys: | + ${{ runner.os }}-${{ runner.arch }}-bun- + + - name: Install dependencies + run: | + bun install --frozen-lockfile + cd stats && bun install --frozen-lockfile + + - name: Lint changelog fragments + run: bun run changelog:lint + + - name: Lint stats (formatting) + run: bun run lint:stats + + - name: Enforce pull request changelog fragments (`skip-changelog` label bypass) + if: github.event_name == 'pull_request' + env: + BASE_REF: ${{ github.base_ref }} + PR_LABELS: ${{ join(github.event.pull_request.labels.*.name, ',') }} + run: bun run changelog:pr-check --base-ref "origin/$BASE_REF" --head-ref "HEAD" --labels "$PR_LABELS" + + - name: Build (TypeScript check) + run: bun run typecheck + + - name: Verify generated config examples + run: bun run verify:config-example + + - name: Install Lua + run: | + sudo apt-get update + sudo apt-get install -y lua5.4 + sudo ln -sf /usr/bin/lua5.4 /usr/local/bin/lua + lua -v + + - name: Test suite (source) + run: bun run test:fast + + - name: Environment suite + run: bun run test:env + + - name: Coverage suite (maintained source lane) + run: bun run test:coverage:src + + - name: Upload coverage artifact + uses: actions/upload-artifact@v4 + with: + name: coverage-test-src + path: coverage/test-src/lcov.info + if-no-files-found: error + + - name: Stats UI tests + run: bun run test:stats + + - name: Launcher smoke suite (source) + run: bun run test:launcher:smoke:src + + - name: Upload launcher smoke artifacts (on failure) + if: failure() + uses: actions/upload-artifact@v4 + with: + name: launcher-smoke + path: .tmp/launcher-smoke/** + if-no-files-found: ignore + + - name: Build (bundle) + run: bun run build + + - name: Immersion SQLite verification + run: bun run test:immersion:sqlite:dist + + - name: Dist smoke suite + run: bun run test:smoke:dist + + - name: Security audit + run: bun audit --audit-level high + + - name: Build Bun subminer wrapper + run: make build-launcher + + - name: Verify Bun subminer wrapper + run: dist/launcher/subminer --help >/dev/null + + - name: Enforce generated launcher workflow + run: bash scripts/verify-generated-launcher.sh diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 722bca6f..e3fa97ae 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -13,76 +13,9 @@ concurrency: jobs: quality-gate: - runs-on: ubuntu-latest - steps: - - name: Checkout - uses: actions/checkout@v4 - with: - submodules: true - - - name: Setup Bun - uses: oven-sh/setup-bun@v2 - with: - bun-version: 1.3.5 - - - name: Cache dependencies - uses: actions/cache@v4 - with: - path: | - ~/.bun/install/cache - node_modules - stats/node_modules - vendor/subminer-yomitan/node_modules - key: ${{ runner.os }}-bun-${{ hashFiles('bun.lock', 'stats/bun.lock', 'vendor/subminer-yomitan/package-lock.json') }} - restore-keys: | - ${{ runner.os }}-bun- - - - name: Install dependencies - run: | - bun install --frozen-lockfile - cd stats && bun install --frozen-lockfile - - - name: Lint stats (formatting) - run: bun run lint:stats - - - name: Build (TypeScript check) - run: bun run typecheck - - - name: Test suite (source) - run: bun run test:fast - - - name: Coverage suite (maintained source lane) - run: bun run test:coverage:src - - - name: Upload coverage artifact - uses: actions/upload-artifact@v4 - with: - name: coverage-test-src - path: coverage/test-src/lcov.info - if-no-files-found: error - - - name: Stats UI tests - run: bun run test:stats - - - name: Launcher smoke suite (source) - run: bun run test:launcher:smoke:src - - - name: Upload launcher smoke artifacts (on failure) - if: failure() - uses: actions/upload-artifact@v4 - with: - name: launcher-smoke - path: .tmp/launcher-smoke/** - if-no-files-found: ignore - - - name: Build (bundle) - run: bun run build - - - name: Immersion SQLite verification - run: bun run test:immersion:sqlite:dist - - - name: Dist smoke suite - run: bun run test:smoke:dist + permissions: + contents: read + uses: ./.github/workflows/quality-gate.yml build-linux: needs: [quality-gate] diff --git a/bun.lock b/bun.lock index 5e68c537..18f3fbe7 100644 --- a/bun.lock +++ b/bun.lock @@ -7,36 +7,42 @@ "dependencies": { "@fontsource-variable/geist": "^5.2.8", "@fontsource-variable/geist-mono": "^5.2.7", - "@xhayper/discord-rpc": "^1.3.3", - "axios": "^1.13.5", + "@xhayper/discord-rpc": "^1.3.4", + "axios": "^1.18.1", "commander": "^14.0.3", "electron-updater": "^6.8.3", - "hono": "^4.12.7", + "hono": "^4.12.28", "jsonc-parser": "^3.3.1", "koffi": "^2.15.6", "libsql": "^0.5.22", - "ws": "^8.19.0", + "ws": "^8.21.0", }, "devDependencies": { "@types/node": "^24.10.0", "@types/ws": "^8.18.1", - "electron": "42.2.0", + "electron": "42.6.0", "electron-builder": "26.8.2", "esbuild": "^0.25.12", "eslint": "^10.4.0", "prettier": "^3.8.1", "typescript": "^5.9.3", + "undici": "7.28.0", }, }, }, + "patchedDependencies": { + "@discordjs/rest@2.6.1": "patches/@discordjs%2Frest@2.6.1.patch", + }, "overrides": { - "@xmldom/xmldom": "0.8.12", + "@xmldom/xmldom": "0.8.13", "app-builder-lib": "26.8.2", "electron-builder-squirrel-windows": "26.8.2", + "form-data": "4.0.6", "lodash": "4.18.0", "minimatch": "10.2.3", "picomatch": "4.0.4", - "tar": "7.5.11", + "tar": "7.5.16", + "tmp": "0.2.7", }, "packages": { "7zip-bin": ["7zip-bin@5.2.0", "", {}, "sha512-ukTPVhqG4jNzMro2qA9HSCSSVJN3aN7tlb+hfqYCt3ER0yWroeA2VR38MNrOHLQ/cVj+DaIMad0kFCtWWowh/A=="], @@ -45,10 +51,12 @@ "@discordjs/collection": ["@discordjs/collection@2.1.1", "", {}, "sha512-LiSusze9Tc7qF03sLCujF5iZp7K+vRNEDBZ86FT9aQAv3vxMLihUvKvpsCWiQ2DJq1tVckopKm1rxomgNUc9hg=="], - "@discordjs/rest": ["@discordjs/rest@2.6.1", "", { "dependencies": { "@discordjs/collection": "^2.1.1", "@discordjs/util": "^1.2.0", "@sapphire/async-queue": "^1.5.3", "@sapphire/snowflake": "^3.5.5", "@vladfrangu/async_event_emitter": "^2.4.6", "discord-api-types": "^0.38.40", "magic-bytes.js": "^1.13.0", "tslib": "^2.6.3", "undici": "6.24.1" } }, "sha512-wwQdgjeaoYFiaG+atbqx6aJDpqW7JHAo0HrQkBTbYzM3/PJ3GweQIpgElNcGZ26DCUOXMyawYd0YF7vtr+fZXg=="], + "@discordjs/rest": ["@discordjs/rest@2.6.1", "", { "dependencies": { "@discordjs/collection": "^2.1.1", "@discordjs/util": "^1.2.0", "@sapphire/async-queue": "^1.5.3", "@sapphire/snowflake": "^3.5.5", "@vladfrangu/async_event_emitter": "^2.4.6", "discord-api-types": "^0.38.40", "magic-bytes.js": "^1.13.0", "tslib": "^2.6.3", "undici": "6.27.0" } }, "sha512-wwQdgjeaoYFiaG+atbqx6aJDpqW7JHAo0HrQkBTbYzM3/PJ3GweQIpgElNcGZ26DCUOXMyawYd0YF7vtr+fZXg=="], "@discordjs/util": ["@discordjs/util@1.2.0", "", { "dependencies": { "discord-api-types": "^0.38.33" } }, "sha512-3LKP7F2+atl9vJFhaBjn4nOaSWahZ/yWjOvA4e5pnXkt2qyXRCHLxoBQy81GFtLGCq7K9lPm9R517M1U+/90Qg=="], + "@electron-internal/extract-zip": ["@electron-internal/extract-zip@1.0.4", "", {}, "sha512-Zr1Vs7E9tpCNhZHDAbFVXc2gEVCG9RqPDjrno5+bdgB6LRAuvgyMHJut4NCVyYwtAieapMzc3fiQ3CSTi75ARg=="], + "@electron/asar": ["@electron/asar@3.4.1", "", { "dependencies": { "commander": "^5.0.0", "glob": "^7.1.6", "minimatch": "^3.0.4" }, "bin": { "asar": "bin/asar.js" } }, "sha512-i4/rNPRS84t0vSRa2HorerGRXWyF4vThfHesw0dmcWHp+cspK743UanA0suA5Q5y8kzY2y6YKrvbIUn69BCAiA=="], "@electron/fuses": ["@electron/fuses@1.8.0", "", { "dependencies": { "chalk": "^4.1.1", "fs-extra": "^9.0.1", "minimist": "^1.2.5" }, "bin": { "electron-fuses": "dist/bin.js" } }, "sha512-zx0EIq78WlY/lBb1uXlziZmDZI4ubcCXIMJ4uGjXzZW0nS19TjSPeXPAjzzTmKQlJUZm0SbmZhPKP7tuQ1SsEw=="], @@ -215,13 +223,11 @@ "@types/ws": ["@types/ws@8.18.1", "", { "dependencies": { "@types/node": "*" } }, "sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg=="], - "@types/yauzl": ["@types/yauzl@2.10.3", "", { "dependencies": { "@types/node": "*" } }, "sha512-oJoftv0LSuaDZE3Le4DbKX+KS9G36NzOeSap90UIK0yMA/NhKJhqlSGtNDORNRaIbQfzjXDrQa0ytJ6mNRGz/Q=="], - "@vladfrangu/async_event_emitter": ["@vladfrangu/async_event_emitter@2.4.7", "", {}, "sha512-Xfe6rpCTxSxfbswi/W/Pz7zp1WWSNn4A0eW4mLkQUewCrXXtMj31lCg+iQyTkh/CkusZSq9eDflu7tjEDXUY6g=="], - "@xhayper/discord-rpc": ["@xhayper/discord-rpc@1.3.3", "", { "dependencies": { "@discordjs/rest": "^2.6.1", "@vladfrangu/async_event_emitter": "^2.4.7", "discord-api-types": "^0.38.42", "ws": "^8.20.0" } }, "sha512-Ih48GHiua7TtZgKO+f0uZPhCeQqb84fY2qUys/oMh8UbUfiUkUJLVCmd/v2AK0/pV33euh0aqSXo7+9LiPSwGw=="], + "@xhayper/discord-rpc": ["@xhayper/discord-rpc@1.3.4", "", { "dependencies": { "@discordjs/rest": "^2.6.1", "@vladfrangu/async_event_emitter": "^2.4.7", "discord-api-types": "^0.38.47", "ws": "^8.20.0" } }, "sha512-ff0uEXuibh9wi+l4vOj7xInLUjtlTaQBje/SCyQkeXZ0j2V0y+Zge5PQIQFRHH9TjjGaYJkTofEcQhncM2q7/w=="], - "@xmldom/xmldom": ["@xmldom/xmldom@0.8.12", "", {}, "sha512-9k/gHF6n/pAi/9tqr3m3aqkuiNosYTurLLUtc7xQ9sxB/wm7WPygCv8GYa6mS0fLJEHhqMC1ATYhz++U/lRHqg=="], + "@xmldom/xmldom": ["@xmldom/xmldom@0.8.13", "", {}, "sha512-KRYzxepc14G/CEpEGc3Yn+JKaAeT63smlDr+vjB8jRfgTBBI9wRj/nkQEO+ucV8p8I9bfKLWp37uHgFrbntPvw=="], "abbrev": ["abbrev@3.0.1", "", {}, "sha512-AO2ac6pjRB3SJmGJo+v5/aK6Omggp6fsLrs6wN9bd35ulu4cCwaAU9+7ZhXjeqHVkaHThLuzH0nZr0YpCDhygg=="], @@ -229,7 +235,7 @@ "acorn-jsx": ["acorn-jsx@5.3.2", "", { "peerDependencies": { "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" } }, "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ=="], - "agent-base": ["agent-base@7.1.4", "", {}, "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ=="], + "agent-base": ["agent-base@6.0.2", "", { "dependencies": { "debug": "4" } }, "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ=="], "ajv": ["ajv@6.14.0", "", { "dependencies": { "fast-deep-equal": "^3.1.1", "fast-json-stable-stringify": "^2.0.0", "json-schema-traverse": "^0.4.1", "uri-js": "^4.2.2" } }, "sha512-IWrosm/yrn43eiKqkfkHis7QioDleaXQHdDVPKg0FSwwd/DuvyX79TZnFOnYpB7dcsFAMmtFztZuXPDvSePkFw=="], @@ -257,7 +263,7 @@ "at-least-node": ["at-least-node@1.0.0", "", {}, "sha512-+q/t7Ekv1EDY2l6Gda6LLiX14rU9TV20Wa3ofeQmwPFZbOMo9DXrLbOjFaaclkXKWidIaopwAObQDqwWtGUjqg=="], - "axios": ["axios@1.13.6", "", { "dependencies": { "follow-redirects": "^1.15.11", "form-data": "^4.0.5", "proxy-from-env": "^1.1.0" } }, "sha512-ChTCHMouEe2kn713WHbQGcuYrr6fXTBiu460OTwWrWob16g1bXn4vtz07Ope7ewMozJAnEquLk5lWQWtBig9DQ=="], + "axios": ["axios@1.18.1", "", { "dependencies": { "follow-redirects": "^1.16.0", "form-data": "^4.0.5", "https-proxy-agent": "^5.0.1", "proxy-from-env": "^2.1.0" } }, "sha512-3nTvFlvpn9Zu/RkHUqtc7/+al4UpRW5az71ap5zccp6e8RAYEzhMTecX8Dz1wWDYrPpUoB1HAQEGEAEvUr7S9g=="], "balanced-match": ["balanced-match@4.0.4", "", {}, "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA=="], @@ -271,8 +277,6 @@ "buffer": ["buffer@5.7.1", "", { "dependencies": { "base64-js": "^1.3.1", "ieee754": "^1.1.13" } }, "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ=="], - "buffer-crc32": ["buffer-crc32@0.2.13", "", {}, "sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ=="], - "buffer-from": ["buffer-from@1.1.2", "", {}, "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ=="], "builder-util": ["builder-util@26.8.1", "", { "dependencies": { "7zip-bin": "~5.2.0", "@types/debug": "^4.1.6", "app-builder-bin": "5.0.0-alpha.12", "builder-util-runtime": "9.5.1", "chalk": "^4.1.2", "cross-spawn": "^7.0.6", "debug": "^4.3.4", "fs-extra": "^10.1.0", "http-proxy-agent": "^7.0.0", "https-proxy-agent": "^7.0.0", "js-yaml": "^4.1.0", "sanitize-filename": "^1.6.3", "source-map-support": "^0.5.19", "stat-mode": "^1.0.0", "temp-file": "^3.4.0", "tiny-async-pool": "1.3.0" } }, "sha512-pm1lTYbGyc90DHgCDO7eo8Rl4EqKLciayNbZqGziqnH9jrlKe8ZANGdityLZU+pJh16dfzjAx2xQq9McuIPEtw=="], @@ -347,7 +351,7 @@ "dir-compare": ["dir-compare@4.2.0", "", { "dependencies": { "minimatch": "^3.0.5", "p-limit": "^3.1.0 " } }, "sha512-2xMCmOoMrdQIPHdsTawECdNPwlVFB9zGcz3kuhmBO6U3oU+UQjsue0i8ayLKpgBcm+hcXPMVSGUN9d+pvJ6+VQ=="], - "discord-api-types": ["discord-api-types@0.38.43", "", {}, "sha512-sSoBf/nK6m7BGtw65mi+QBuvEWaHE8MMziFLqWL+gT6ME/BLg34dRSVKS3Husx40uU06bvxUc3/X+D9Y6/zAbw=="], + "discord-api-types": ["discord-api-types@0.38.49", "", {}, "sha512-XnqcWmnFZFAE8ZM8SHAw9DIV8D3Or00rMQ8iQLotrEA2PmXhl+ykaf6L6q4l474hrSUH1JaYcv+iOMRWp2p6Tg=="], "dmg-builder": ["dmg-builder@26.8.2", "", { "dependencies": { "app-builder-lib": "26.8.2", "builder-util": "26.8.1", "fs-extra": "^10.1.0", "iconv-lite": "^0.6.2", "js-yaml": "^4.1.0" }, "optionalDependencies": { "dmg-license": "^1.0.11" } }, "sha512-DaWI+p4DOqiFVZFMovdGYammBOyJAiHHFWUTQ0Z7gNc0twfdIN0LvyJ+vFsgZEDR1fjgbpCj690IVtbYIsZObQ=="], @@ -363,7 +367,7 @@ "ejs": ["ejs@3.1.10", "", { "dependencies": { "jake": "^10.8.5" }, "bin": { "ejs": "bin/cli.js" } }, "sha512-UeJmFfOrAQS8OJWPZ4qtgHyWExa088/MtK5UEyoJGFH67cDEXkZSviOiKRCZ4Xij0zxI3JECgYs3oKx+AizQBA=="], - "electron": ["electron@42.2.0", "", { "dependencies": { "@electron/get": "^5.0.0", "@types/node": "^24.9.0", "extract-zip": "^2.0.1" }, "bin": { "electron": "cli.js", "install-electron": "install.js" } }, "sha512-b2Tc7sIKiZEl0tBVwFM5GJ+FT5KYhmy9QJHjx8BGVZPVW2SctXWEvrE959ElB56qw7H05dBkhlikDA1DmpaAMw=="], + "electron": ["electron@42.6.0", "", { "dependencies": { "@electron-internal/extract-zip": "^1.0.1", "@electron/get": "^5.0.0", "@types/node": "^24.9.0" }, "bin": { "electron": "cli.js", "install-electron": "install.js" } }, "sha512-axGNgd+yCTg+vi1VEGrQqAj9WVWkePKwbICSAvMiT2eTaxhij9a/xhBHD6rXV8wrlW9ZfJzE5+xg752ImxrmTw=="], "electron-builder": ["electron-builder@26.8.2", "", { "dependencies": { "app-builder-lib": "26.8.2", "builder-util": "26.8.1", "builder-util-runtime": "9.5.1", "chalk": "^4.1.2", "ci-info": "^4.2.0", "dmg-builder": "26.8.2", "fs-extra": "^10.1.0", "lazy-val": "^1.0.5", "simple-update-notifier": "2.0.0", "yargs": "^17.6.2" }, "bin": { "electron-builder": "cli.js", "install-app-deps": "install-app-deps.js" } }, "sha512-ieiiXPdgH3qrG6lcvy2mtnI5iEmAopmLuVRMSJ5j40weU0tgpNx0OAk9J5X5nnO0j9+KIkxHzwFZVUDk1U3aGw=="], @@ -419,8 +423,6 @@ "exponential-backoff": ["exponential-backoff@3.1.3", "", {}, "sha512-ZgEeZXj30q+I0EN+CbSSpIyPaJ5HVQD18Z1m+u1FXbAeT94mr1zw50q4q6jiiC447Nl/YTcIYSAftiGqetwXCA=="], - "extract-zip": ["extract-zip@2.0.1", "", { "dependencies": { "debug": "^4.1.1", "get-stream": "^5.1.0", "yauzl": "^2.10.0" }, "optionalDependencies": { "@types/yauzl": "^2.9.1" }, "bin": { "extract-zip": "cli.js" } }, "sha512-GDhU9ntwuKyGXdZBUgTIe+vXnWj0fppUEtMDL0+idd5Sta8TGpHssn/eusA9mrPr9qNDym6SxAYZjNvCn/9RBg=="], - "extsprintf": ["extsprintf@1.4.1", "", {}, "sha512-Wrk35e8ydCKDj/ArClo1VrPVmN8zph5V4AtHwIuHhvMXsKf73UT3BOD+azBIW+3wOJ4FhEH7zyaJCFvChjYvMA=="], "fast-deep-equal": ["fast-deep-equal@3.1.3", "", {}, "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q=="], @@ -429,8 +431,6 @@ "fast-levenshtein": ["fast-levenshtein@2.0.6", "", {}, "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw=="], - "fd-slicer": ["fd-slicer@1.1.0", "", { "dependencies": { "pend": "~1.2.0" } }, "sha512-cE1qsB/VwyQozZ+q1dGxR8LBYNZeofhEdUNGSMbQD3Gw2lAzX9Zb3uIU6Ebc/Fmyjo9AWWfnn0AUCHqtevs/8g=="], - "fdir": ["fdir@6.5.0", "", { "peerDependencies": { "picomatch": "^3 || ^4" }, "optionalPeers": ["picomatch"] }, "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg=="], "file-entry-cache": ["file-entry-cache@8.0.0", "", { "dependencies": { "flat-cache": "^4.0.0" } }, "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ=="], @@ -443,11 +443,11 @@ "flatted": ["flatted@3.4.2", "", {}, "sha512-PjDse7RzhcPkIJwy5t7KPWQSZ9cAbzQXcafsetQoD7sOJRQlGikNbx7yZp2OotDnJyrDcbyRq3Ttb18iYOqkxA=="], - "follow-redirects": ["follow-redirects@1.15.11", "", {}, "sha512-deG2P0JfjrTxl50XGCDyfI97ZGVCxIpfKYmfyrQ54n5FO/0gfIES8C/Psl6kWVDolizcaaxZJnTS0QSMxvnsBQ=="], + "follow-redirects": ["follow-redirects@1.16.0", "", {}, "sha512-y5rN/uOsadFT/JfYwhxRS5R7Qce+g3zG97+JrtFZlC9klX/W5hD7iiLzScI4nZqUS7DNUdhPgw4xI8W2LuXlUw=="], "foreground-child": ["foreground-child@3.3.1", "", { "dependencies": { "cross-spawn": "^7.0.6", "signal-exit": "^4.0.1" } }, "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw=="], - "form-data": ["form-data@4.0.5", "", { "dependencies": { "asynckit": "^0.4.0", "combined-stream": "^1.0.8", "es-set-tostringtag": "^2.1.0", "hasown": "^2.0.2", "mime-types": "^2.1.12" } }, "sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w=="], + "form-data": ["form-data@4.0.6", "", { "dependencies": { "asynckit": "^0.4.0", "combined-stream": "^1.0.8", "es-set-tostringtag": "^2.1.0", "hasown": "^2.0.4", "mime-types": "^2.1.35" } }, "sha512-vKatAh4SlVfgbv+YtmhiRjhEMJsYpsG1Y2rMQtR+SVSbytsSD1YGzDIcrAJmdFec88u/+VoGmxnl+80gL1tRCQ=="], "fs-extra": ["fs-extra@10.1.0", "", { "dependencies": { "graceful-fs": "^4.2.0", "jsonfile": "^6.0.1", "universalify": "^2.0.0" } }, "sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ=="], @@ -487,9 +487,9 @@ "has-tostringtag": ["has-tostringtag@1.0.2", "", { "dependencies": { "has-symbols": "^1.0.3" } }, "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw=="], - "hasown": ["hasown@2.0.2", "", { "dependencies": { "function-bind": "^1.1.2" } }, "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ=="], + "hasown": ["hasown@2.0.4", "", { "dependencies": { "function-bind": "^1.1.2" } }, "sha512-T2UbfbBEF32wiepXIsMlTW9+dDYC6wMh/t/vYA4tuOMKqWz/n3vr1NFSxQiyP+zk2mXsoMA/i/7qV6LKut1t1A=="], - "hono": ["hono@4.12.8", "", {}, "sha512-VJCEvtrezO1IAR+kqEYnxUOoStaQPGrCmX3j4wDTNOcD1uRPFpGlwQUIW8niPuvHXaTUxeOUl5MMDGrl+tmO9A=="], + "hono": ["hono@4.12.28", "", {}, "sha512-YwUvVpSF7m1yOblFPrU3Hbo8XhPheBoiyfGuII6z19LnOr6JpDnyyp7LFNrfV56wS8tpvtBFGRISHN02pDdLOA=="], "hosted-git-info": ["hosted-git-info@4.1.0", "", { "dependencies": { "lru-cache": "^6.0.0" } }, "sha512-kyCuEOWjJqZuDbRHzL8V93NzQhwIB71oFWSyzVo+KPZI+pnQPPxucdkrOZvkLRnrf5URsQM+IJ09Dw29cRALIA=="], @@ -499,7 +499,7 @@ "http2-wrapper": ["http2-wrapper@1.0.3", "", { "dependencies": { "quick-lru": "^5.1.1", "resolve-alpn": "^1.0.0" } }, "sha512-V+23sDMr12Wnz7iTcDeJr3O6AIxlnvT/bmaAAAP/Xda35C90p9599p0F1eHR/N1KILWSoWVAiOMFjBBXaXSMxg=="], - "https-proxy-agent": ["https-proxy-agent@7.0.6", "", { "dependencies": { "agent-base": "^7.1.2", "debug": "4" } }, "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw=="], + "https-proxy-agent": ["https-proxy-agent@5.0.1", "", { "dependencies": { "agent-base": "6", "debug": "4" } }, "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA=="], "iconv-corefoundation": ["iconv-corefoundation@1.1.7", "", { "dependencies": { "cli-truncate": "^2.1.0", "node-addon-api": "^1.6.3" }, "os": "darwin" }, "sha512-T10qvkw0zz4wnm560lOEg0PovVqUXuOFhhHAkixw8/sycy7TJt7v/RrkEKEQnAw2viPSJu6iAkErxnzR0g8PpQ=="], @@ -663,8 +663,6 @@ "pe-library": ["pe-library@0.4.1", "", {}, "sha512-eRWB5LBz7PpDu4PUlwT0PhnQfTQJlDDdPa35urV4Osrm0t0AqQFGn+UIkU3klZvwJ8KPO3VbBFsXquA6p6kqZw=="], - "pend": ["pend@1.2.0", "", {}, "sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg=="], - "picocolors": ["picocolors@1.1.1", "", {}, "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA=="], "picomatch": ["picomatch@4.0.4", "", {}, "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A=="], @@ -685,7 +683,7 @@ "proper-lockfile": ["proper-lockfile@4.1.2", "", { "dependencies": { "graceful-fs": "^4.2.4", "retry": "^0.12.0", "signal-exit": "^3.0.2" } }, "sha512-TjNPblN4BwAWMXU8s9AEz4JmQxnD1NNL7bNOY/AKUzyamc379FWASUhc/K1pL2noVb+XmZKLL68cjzLsiOAMaA=="], - "proxy-from-env": ["proxy-from-env@1.1.0", "", {}, "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg=="], + "proxy-from-env": ["proxy-from-env@2.1.0", "", {}, "sha512-cJ+oHTW1VAEa8cJslgmUZrc+sjRKgAKl3Zyse6+PV38hZe/V6Z14TbCuXcan9F9ghlz4QrFr2c92TNF82UkYHA=="], "pump": ["pump@3.0.4", "", { "dependencies": { "end-of-stream": "^1.1.0", "once": "^1.3.1" } }, "sha512-VS7sjc6KR7e1ukRFhQSY5LM2uBWAUPiOPa/A3mkKmiMwSmRFUITt0xuj+/lesgnCv+dPIEYlkzrcyXgquIHMcA=="], @@ -767,7 +765,7 @@ "supports-color": ["supports-color@7.2.0", "", { "dependencies": { "has-flag": "^4.0.0" } }, "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw=="], - "tar": ["tar@7.5.11", "", { "dependencies": { "@isaacs/fs-minipass": "^4.0.0", "chownr": "^3.0.0", "minipass": "^7.1.2", "minizlib": "^3.1.0", "yallist": "^5.0.0" } }, "sha512-ChjMH33/KetonMTAtpYdgUFr0tbz69Fp2v7zWxQfYZX4g5ZN2nOBXm1R2xyA+lMIKrLKIoKAwFj93jE/avX9cQ=="], + "tar": ["tar@7.5.16", "", { "dependencies": { "@isaacs/fs-minipass": "^4.0.0", "chownr": "^3.0.0", "minipass": "^7.1.2", "minizlib": "^3.1.0", "yallist": "^5.0.0" } }, "sha512-56adEpPMouktRlBLXiaYFFzZ/3+JXa8P9n7WbR+ibIjtviN55mEaOkiysCnPnWm+7kkui1Dn8J9l+g6zV8731w=="], "temp": ["temp@0.9.4", "", { "dependencies": { "mkdirp": "^0.5.1", "rimraf": "~2.6.2" } }, "sha512-yYrrsWnrXMcdsnu/7YMYAofM1ktpL5By7vZhf15CrXijWWrEYZks5AXBudalfSWJLlnen/QUJUB5aoB0kqZUGA=="], @@ -779,7 +777,7 @@ "tinyglobby": ["tinyglobby@0.2.15", "", { "dependencies": { "fdir": "^6.5.0", "picomatch": "^4.0.3" } }, "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ=="], - "tmp": ["tmp@0.2.5", "", {}, "sha512-voyz6MApa1rQGUxT3E+BK7/ROe8itEx7vD8/HEvt4xwXucvQ5G5oeEiHkmHZJuBO21RpOf+YYm9MOivj709jow=="], + "tmp": ["tmp@0.2.7", "", {}, "sha512-e0votIpp4Uo2AJYSzVHV6xCcawuiez3DzqDAbrTc3YxBkplN6e+dM13ZeIcZnDg/QpSuU2zfZ3rzwY8ukEnaXw=="], "tmp-promise": ["tmp-promise@3.0.3", "", { "dependencies": { "tmp": "^0.2.0" } }, "sha512-RwM7MoPojPxsOBYnyd2hy0bxtIlVrihNs9pj5SUvY8Zz1sQcQG2tG1hSr8PDxfgEB8RNKDhqbIlroIarSNDNsQ=="], @@ -793,7 +791,7 @@ "typescript": ["typescript@5.9.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw=="], - "undici": ["undici@7.25.0", "", {}, "sha512-xXnp4kTyor2Zq+J1FfPI6Eq3ew5h6Vl0F/8d9XU5zZQf1tX9s2Su1/3PiMmUANFULpmksxkClamIZcaUqryHsQ=="], + "undici": ["undici@7.28.0", "", {}, "sha512-cRZYrTDwWznlnRiPjggAGxZXanty6M8RV1ff8Wm4LWXBp7/IG8v5DnOm74DtUBp9OONpK75YlPnIjQqX0dBDtA=="], "undici-types": ["undici-types@7.16.0", "", {}, "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw=="], @@ -823,7 +821,7 @@ "wrappy": ["wrappy@1.0.2", "", {}, "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ=="], - "ws": ["ws@8.20.0", "", { "peerDependencies": { "bufferutil": "^4.0.1", "utf-8-validate": ">=5.0.2" }, "optionalPeers": ["bufferutil", "utf-8-validate"] }, "sha512-sAt8BhgNbzCtgGbt2OxmpuryO63ZoDk/sqaB/znQm94T4fCEsy/yV+7CdC1kJhOU9lboAEU7R3kquuycDoibVA=="], + "ws": ["ws@8.21.0", "", { "peerDependencies": { "bufferutil": "^4.0.1", "utf-8-validate": ">=5.0.2" }, "optionalPeers": ["bufferutil", "utf-8-validate"] }, "sha512-Vsp28b7DRcimFQvrqu2Wek3z1iYxDCWqHYB8Qsnk/S4RfaCQzPGPyBNuVjJV3cd6UiKtUtp6sNM77gWvzcCH+g=="], "xmlbuilder": ["xmlbuilder@15.1.1", "", {}, "sha512-yMqGBqtXyeN1e3TGYvgNgDVZ3j84W4cwkOXQswghol6APgZWaff9lnbvN7MHYJOiXsvGPXtjTYJEiC9J2wv9Eg=="], @@ -835,11 +833,13 @@ "yargs-parser": ["yargs-parser@21.1.1", "", {}, "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw=="], - "yauzl": ["yauzl@2.10.0", "", { "dependencies": { "buffer-crc32": "~0.2.3", "fd-slicer": "~1.1.0" } }, "sha512-p4a9I6X6nu6IhoGmBqAcbJy1mlC4j27vEPZX9F4L4/vZT3Lyq1VkFHw/V/PUcB9Buo+DG3iHkT0x3Qya58zc3g=="], - "yocto-queue": ["yocto-queue@0.1.0", "", {}, "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q=="], - "@discordjs/rest/undici": ["undici@6.24.1", "", {}, "sha512-sC+b0tB1whOCzbtlx20fx3WgCXwkW627p4EA9uM+/tNNPkSS+eSEld6pAs9nDv7WbY1UUljBMYPtu9BCOrCWKA=="], + "@discordjs/rest/discord-api-types": ["discord-api-types@0.38.43", "", {}, "sha512-sSoBf/nK6m7BGtw65mi+QBuvEWaHE8MMziFLqWL+gT6ME/BLg34dRSVKS3Husx40uU06bvxUc3/X+D9Y6/zAbw=="], + + "@discordjs/rest/undici": ["undici@6.27.0", "", {}, "sha512-YmfV3YnEDzXRC5lZ2jWtWWHKGUm1zIt8AhesR1tens+HTNv+YZlN/dp6G727LOvMJ8xjP9Be7Y2Sdr96LDm+pg=="], + + "@discordjs/util/discord-api-types": ["discord-api-types@0.38.43", "", {}, "sha512-sSoBf/nK6m7BGtw65mi+QBuvEWaHE8MMziFLqWL+gT6ME/BLg34dRSVKS3Husx40uU06bvxUc3/X+D9Y6/zAbw=="], "@electron/asar/commander": ["commander@5.1.0", "", {}, "sha512-P0CysNDQ7rtVw4QIQtm+MRxV66vKFSvlsQvGYXZWR3qFU0jlMKHZZZgw8e+8DSah4UDKMqnknRDQz+xuQXQ/Zg=="], @@ -863,6 +863,10 @@ "@malept/flatpak-bundler/fs-extra": ["fs-extra@9.1.0", "", { "dependencies": { "at-least-node": "^1.0.0", "graceful-fs": "^4.2.0", "jsonfile": "^6.0.1", "universalify": "^2.0.0" } }, "sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ=="], + "@npmcli/agent/agent-base": ["agent-base@7.1.4", "", {}, "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ=="], + + "@npmcli/agent/https-proxy-agent": ["https-proxy-agent@7.0.6", "", { "dependencies": { "agent-base": "^7.1.2", "debug": "4" } }, "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw=="], + "@npmcli/agent/lru-cache": ["lru-cache@10.4.3", "", {}, "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ=="], "@types/cacheable-request/@types/node": ["@types/node@25.5.0", "", { "dependencies": { "undici-types": "~7.18.0" } }, "sha512-jp2P3tQMSxWugkCUKLRPVUpGaL5MVFwF8RDuSRztfwgN1wmqJeMSbKlnEtQqU8UrhTmzEmZdu2I6v2dpp7XIxw=="], @@ -877,12 +881,12 @@ "@types/ws/@types/node": ["@types/node@25.5.0", "", { "dependencies": { "undici-types": "~7.18.0" } }, "sha512-jp2P3tQMSxWugkCUKLRPVUpGaL5MVFwF8RDuSRztfwgN1wmqJeMSbKlnEtQqU8UrhTmzEmZdu2I6v2dpp7XIxw=="], - "@types/yauzl/@types/node": ["@types/node@25.5.0", "", { "dependencies": { "undici-types": "~7.18.0" } }, "sha512-jp2P3tQMSxWugkCUKLRPVUpGaL5MVFwF8RDuSRztfwgN1wmqJeMSbKlnEtQqU8UrhTmzEmZdu2I6v2dpp7XIxw=="], - "app-builder-lib/@electron/get": ["@electron/get@3.1.0", "", { "dependencies": { "debug": "^4.1.1", "env-paths": "^2.2.0", "fs-extra": "^8.1.0", "got": "^11.8.5", "progress": "^2.0.3", "semver": "^6.2.0", "sumchecker": "^3.0.1" }, "optionalDependencies": { "global-agent": "^3.0.0" } }, "sha512-F+nKc0xW+kVbBRhFzaMgPy3KwmuNTYX1fx6+FxxoSnNgwYX6LD7AKBTWkU0MQ6IBoe7dz069CNkR673sPAgkCQ=="], "app-builder-lib/ci-info": ["ci-info@4.3.1", "", {}, "sha512-Wdy2Igu8OcBpI2pZePZ5oWjPC38tmDVx5WKUXKwlLYkA0ozo85sLsLvkBbBn/sZaSCMFOGZJ14fvW9t5/d7kdA=="], + "builder-util/https-proxy-agent": ["https-proxy-agent@7.0.6", "", { "dependencies": { "agent-base": "^7.1.2", "debug": "4" } }, "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw=="], + "cacache/glob": ["glob@10.5.0", "", { "dependencies": { "foreground-child": "^3.1.0", "jackspeak": "^3.1.2", "minimatch": "^9.0.4", "minipass": "^7.1.2", "package-json-from-dist": "^1.0.0", "path-scurry": "^1.11.1" }, "bin": { "glob": "dist/esm/bin.mjs" } }, "sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg=="], "cacache/lru-cache": ["lru-cache@10.4.3", "", {}, "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ=="], @@ -893,8 +897,14 @@ "electron-winstaller/fs-extra": ["fs-extra@7.0.1", "", { "dependencies": { "graceful-fs": "^4.1.2", "jsonfile": "^4.0.0", "universalify": "^0.1.0" } }, "sha512-YJDaCJZEnBmcbw13fvdAM9AwNOJwOzrE4pqMqBq5nFiEqXUqHwlK4B+3pUw6JNvfSPtX05xFHtYy/1ni01eGCw=="], + "es-set-tostringtag/hasown": ["hasown@2.0.2", "", { "dependencies": { "function-bind": "^1.1.2" } }, "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ=="], + "foreground-child/signal-exit": ["signal-exit@4.1.0", "", {}, "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw=="], + "get-intrinsic/hasown": ["hasown@2.0.2", "", { "dependencies": { "function-bind": "^1.1.2" } }, "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ=="], + + "http-proxy-agent/agent-base": ["agent-base@7.1.4", "", {}, "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ=="], + "lru-cache/yallist": ["yallist@4.0.0", "", {}, "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A=="], "minipass-flush/minipass": ["minipass@3.3.6", "", { "dependencies": { "yallist": "^4.0.0" } }, "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw=="], @@ -909,6 +919,8 @@ "postject/commander": ["commander@9.5.0", "", {}, "sha512-KRs7WVDKg86PWiuAqhDrAQnTXZKraVcCc6vFdL14qrZ/DcWwuRo7VoiYXalXO7S5GKpqYiVEwCbgFDfxNHKJBQ=="], + "socks-proxy-agent/agent-base": ["agent-base@7.1.4", "", {}, "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ=="], + "tiny-async-pool/semver": ["semver@5.7.2", "", { "bin": { "semver": "bin/semver" } }, "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g=="], "@isaacs/cliui/string-width/emoji-regex": ["emoji-regex@9.2.2", "", {}, "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg=="], @@ -929,14 +941,14 @@ "@types/ws/@types/node/undici-types": ["undici-types@7.18.2", "", {}, "sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w=="], - "@types/yauzl/@types/node/undici-types": ["undici-types@7.18.2", "", {}, "sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w=="], - "app-builder-lib/@electron/get/env-paths": ["env-paths@2.2.1", "", {}, "sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A=="], "app-builder-lib/@electron/get/fs-extra": ["fs-extra@8.1.0", "", { "dependencies": { "graceful-fs": "^4.2.0", "jsonfile": "^4.0.0", "universalify": "^0.1.0" } }, "sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g=="], "app-builder-lib/@electron/get/semver": ["semver@6.3.1", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="], + "builder-util/https-proxy-agent/agent-base": ["agent-base@7.1.4", "", {}, "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ=="], + "cross-spawn/which/isexe": ["isexe@2.0.0", "", {}, "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw=="], "electron-winstaller/fs-extra/jsonfile": ["jsonfile@4.0.0", "", { "optionalDependencies": { "graceful-fs": "^4.1.6" } }, "sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg=="], diff --git a/changes/reusable-ci-quality-gate.md b/changes/reusable-ci-quality-gate.md new file mode 100644 index 00000000..661cbaca --- /dev/null +++ b/changes/reusable-ci-quality-gate.md @@ -0,0 +1,4 @@ +type: internal +area: release + +- Consolidated pull request, stable release, and prerelease quality checks in one reusable workflow, with Lua mpv plugin tests and blocking high-severity dependency audits running in every gate. diff --git a/docs/RELEASING.md b/docs/RELEASING.md index e8bf698f..f815bebe 100644 --- a/docs/RELEASING.md +++ b/docs/RELEASING.md @@ -85,6 +85,7 @@ Notes: - Prerelease tags intentionally keep `changes/*.md` fragments in place so multiple prereleases can reuse the same cumulative pending notes until the final stable cut. `make clean` preserves `release/prerelease-notes.md` while deleting generated build artifacts. - If you need to repair a published release body (for example, a prior version’s section was omitted), regenerate notes from `CHANGELOG.md` and re-edit the release with `gh release edit --notes-file`. - Prerelease tags are handled by `.github/workflows/prerelease.yml`, which always publishes a GitHub prerelease with all current release platforms and never runs the AUR sync job. +- CI, stable release, and prerelease workflows call the shared `.github/workflows/quality-gate.yml`; update that reusable workflow when changing common test coverage. Its environment suite includes the Lua mpv plugin tests. - Tagged release workflow now also attempts to update `subminer-bin` on the AUR after GitHub Release publication. - Stable release tags update `https://docs.subminer.moe/` and `https://docs.subminer.moe/v//` through `.github/workflows/docs-pages.yml`; `/main/` continues to show development docs from `main`. - Keep Cloudflare Pages Git auto-deploy disabled for `docs.subminer.moe`. Production docs are direct-uploaded by Wrangler from GitHub Actions with `--branch main`. diff --git a/docs/workflow/verification.md b/docs/workflow/verification.md index 9a861c5b..1d427900 100644 --- a/docs/workflow/verification.md +++ b/docs/workflow/verification.md @@ -18,6 +18,11 @@ Read when: selecting the right verification lane for a change `--single-process` restores the shared-process mode for debugging. - `bun run test:fast` is the full source gate: discovered `src/**`, launcher unit, `scripts/**`, and the compiled runtime-compat slice. +- `.github/workflows/quality-gate.yml` is the reusable `workflow_call` gate for + pull requests, stable tags, and prerelease tags. Keep common quality steps + there instead of copying them into caller workflows. +- The reusable gate installs Lua and runs `bun run test:env`, so the shipped mpv + plugin tests run for every pull request and tagged release. ## Default Handoff Gate @@ -52,7 +57,14 @@ bun run docs:build - `bun run test:coverage:src` runs the maintained `test:src` lane through a sharded coverage runner: one Bun coverage process per test file, then merged LCOV output. - Machine-readable output lands at `coverage/test-src/lcov.info`. -- CI and release quality-gate runs upload that LCOV file as the `coverage-test-src` artifact. +- Every reusable quality-gate run uploads that LCOV file as the + `coverage-test-src` artifact. + +## Dependency Audit Policy + +- `bun audit --audit-level high` blocks the reusable quality gate. +- Keep security overrides and dependency patches at the minimum fixed version. + Remove them after the owning package ships and adopts a compatible fix. ## Rules diff --git a/package.json b/package.json index 6691f468..32e20dd0 100644 --- a/package.json +++ b/package.json @@ -87,13 +87,15 @@ "build:win:unsigned": "bun run build && node scripts/build-win-unsigned.mjs" }, "overrides": { - "@xmldom/xmldom": "0.8.12", + "@xmldom/xmldom": "0.8.13", "app-builder-lib": "26.8.2", "electron-builder-squirrel-windows": "26.8.2", + "form-data": "4.0.6", "lodash": "4.18.0", "minimatch": "10.2.3", "picomatch": "4.0.4", - "tar": "7.5.11" + "tar": "7.5.16", + "tmp": "0.2.7" }, "keywords": [ "anki", @@ -110,21 +112,22 @@ "dependencies": { "@fontsource-variable/geist": "^5.2.8", "@fontsource-variable/geist-mono": "^5.2.7", - "@xhayper/discord-rpc": "^1.3.3", - "axios": "^1.13.5", + "@xhayper/discord-rpc": "^1.3.4", + "axios": "^1.18.1", "commander": "^14.0.3", "electron-updater": "^6.8.3", - "hono": "^4.12.7", + "hono": "^4.12.28", "jsonc-parser": "^3.3.1", "koffi": "^2.15.6", "libsql": "^0.5.22", - "ws": "^8.19.0" + "ws": "^8.21.0" }, "devDependencies": { "@types/node": "^24.10.0", "@types/ws": "^8.18.1", - "electron": "42.2.0", + "electron": "42.6.0", "electron-builder": "26.8.2", + "undici": "7.28.0", "esbuild": "^0.25.12", "eslint": "^10.4.0", "prettier": "^3.8.1", @@ -259,5 +262,8 @@ "to": "launcher/subminer" } ] + }, + "patchedDependencies": { + "@discordjs/rest@2.6.1": "patches/@discordjs%2Frest@2.6.1.patch" } } diff --git a/patches/@discordjs%2Frest@2.6.1.patch b/patches/@discordjs%2Frest@2.6.1.patch new file mode 100644 index 00000000..78bf73d8 --- /dev/null +++ b/patches/@discordjs%2Frest@2.6.1.patch @@ -0,0 +1,13 @@ +diff --git a/package.json b/package.json +index 02d2d8809a98c5d32b889fceba11458660f1fa6a..72cd7004d92809689daaeec1cd8a274e1cabdff5 100644 +--- a/package.json ++++ b/package.json +@@ -77,7 +77,7 @@ + "discord-api-types": "^0.38.40", + "magic-bytes.js": "^1.13.0", + "tslib": "^2.6.3", +- "undici": "6.24.1", ++ "undici": "6.27.0", + "@discordjs/collection": "^2.1.1", + "@discordjs/util": "^1.2.0" + }, diff --git a/src/ci-workflow.test.ts b/src/ci-workflow.test.ts index 4ffc8165..800d53c4 100644 --- a/src/ci-workflow.test.ts +++ b/src/ci-workflow.test.ts @@ -12,19 +12,6 @@ const packageJson = JSON.parse(readFileSync(packageJsonPath, 'utf8')) as { scripts: Record; }; -test('ci workflow lints changelog fragments', () => { - assert.match(ciWorkflow, /bun run changelog:lint/); -}); - -test('ci workflow checks pull requests for required changelog fragments', () => { - assert.match(ciWorkflow, /bun run changelog:pr-check/); - assert.match(ciWorkflow, /skip-changelog/); -}); - -test('ci workflow verifies generated config examples stay in sync', () => { - assert.match(ciWorkflow, /bun run verify:config-example/); -}); - test('package scripts expose a sharded maintained source coverage lane with lcov output', () => { assert.equal( packageJson.scripts['test:coverage:src'], @@ -32,11 +19,14 @@ test('package scripts expose a sharded maintained source coverage lane with lcov ); }); -test('ci workflow runs the maintained source coverage lane and uploads lcov output', () => { - assert.match(ciWorkflow, /name: Coverage suite \(maintained source lane\)/); - assert.match(ciWorkflow, /run: bun run test:coverage:src/); - assert.match(ciWorkflow, /name: Upload coverage artifact/); - assert.match(ciWorkflow, /path: coverage\/test-src\/lcov\.info/); +test('ci delegates its gate instead of duplicating quality steps', () => { + assert.match( + ciWorkflow, + /build-test-audit:\s*\n\s*uses: \.\/\.github\/workflows\/quality-gate\.yml/, + ); + assert.doesNotMatch(ciWorkflow, /oven-sh\/setup-bun/); + assert.doesNotMatch(ciWorkflow, /bun run test:coverage:src/); + assert.doesNotMatch(ciWorkflow, /bun run test:env/); }); test('main docs deploy exists, serializes deploys, and uses Cloudflare credentials', () => { diff --git a/src/prerelease-workflow.test.ts b/src/prerelease-workflow.test.ts index 129966a5..08c68086 100644 --- a/src/prerelease-workflow.test.ts +++ b/src/prerelease-workflow.test.ts @@ -33,11 +33,16 @@ test('prerelease workflow uses committed prerelease notes and never calls claude assert.doesNotMatch(prereleaseWorkflow, /run: bun run changelog:build/); }); -test('prerelease workflow includes the environment suite in the gate sequence', () => { +test('prerelease delegates its quality gate instead of duplicating quality steps', () => { assert.match( prereleaseWorkflow, - /Test suite \(source\)\n\s*run: bun run test:fast\n\s*\n\s*- name: Environment suite(?: \(source\))?\n\s*run: bun run test:env\n\s*\n\s*- name: Coverage suite \(maintained source lane\)/, + /quality-gate:\s*\n\s*permissions:\s*\n\s*contents: read\s*\n\s*uses: \.\/\.github\/workflows\/quality-gate\.yml/, ); + const qualityGateJob = prereleaseWorkflow.match(/quality-gate:[\s\S]*?(?=\n build-linux:)/)?.[0]; + assert.ok(qualityGateJob); + assert.doesNotMatch(qualityGateJob, /oven-sh\/setup-bun/); + assert.doesNotMatch(qualityGateJob, /bun run test:coverage:src/); + assert.doesNotMatch(qualityGateJob, /bun run test:env/); }); test('prerelease workflow publishes GitHub prereleases and keeps them off latest', () => { @@ -46,15 +51,15 @@ test('prerelease workflow publishes GitHub prereleases and keeps them off latest assert.match(prereleaseWorkflow, /gh release create[\s\S]*--latest=false/); }); -test('prerelease workflow scopes dependency caches by runner architecture', () => { +test('prerelease packaging workflows scope dependency caches by runner architecture', () => { const archScopedCacheKeyMatches = prereleaseWorkflow.match( /key:\s*\${{\s*runner\.os\s*}}-\${{\s*runner\.arch\s*}}-bun-/g, ); const archScopedRestoreKeyMatches = prereleaseWorkflow.match( /\${{\s*runner\.os\s*}}-\${{\s*runner\.arch\s*}}-bun-/g, ); - assert.equal(archScopedCacheKeyMatches?.length, 5); - assert.ok((archScopedRestoreKeyMatches?.length ?? 0) >= 10); + assert.equal(archScopedCacheKeyMatches?.length, 4); + assert.ok((archScopedRestoreKeyMatches?.length ?? 0) >= 8); }); test('prerelease workflow builds and uploads all release platforms', () => { diff --git a/src/quality-gate-workflow.test.ts b/src/quality-gate-workflow.test.ts new file mode 100644 index 00000000..2178a0de --- /dev/null +++ b/src/quality-gate-workflow.test.ts @@ -0,0 +1,66 @@ +import test from 'node:test'; +import assert from 'node:assert/strict'; +import { existsSync, readFileSync } from 'node:fs'; +import { resolve } from 'node:path'; + +const qualityGateWorkflowPath = resolve(__dirname, '../.github/workflows/quality-gate.yml'); +const qualityGateWorkflow = existsSync(qualityGateWorkflowPath) + ? readFileSync(qualityGateWorkflowPath, 'utf8').replace(/\r\n/g, '\n') + : ''; +const verificationDocPath = resolve(__dirname, '../docs/workflow/verification.md'); +const verificationDoc = readFileSync(verificationDocPath, 'utf8').replace(/\r\n/g, '\n'); + +test('quality gate is a reusable workflow', () => { + assert.match(qualityGateWorkflow, /on:\s*\n\s*workflow_call:/); + assert.match(qualityGateWorkflow, /permissions:\s*\n\s*contents: read/); +}); + +test('quality gate checkout does not persist GitHub credentials', () => { + assert.match( + qualityGateWorkflow, + /uses: actions\/checkout@v4[\s\S]*?fetch-depth: 0[\s\S]*?submodules: true[\s\S]*?persist-credentials: false/, + ); +}); + +test('quality gate installs Lua and runs the environment suite before coverage', () => { + assert.match(qualityGateWorkflow, /name: Install Lua/); + assert.match(qualityGateWorkflow, /apt-get install -y lua5\.4/); + assert.match( + qualityGateWorkflow, + /Test suite \(source\)\n\s*run: bun run test:fast\n\s*\n\s*- name: Environment suite\n\s*run: bun run test:env\n\s*\n\s*- name: Coverage suite \(maintained source lane\)/, + ); +}); + +test('quality gate uploads maintained source coverage', () => { + assert.match(qualityGateWorkflow, /run: bun run test:coverage:src/); + assert.match(qualityGateWorkflow, /name: Upload coverage artifact/); + assert.match(qualityGateWorkflow, /path: coverage\/test-src\/lcov\.info/); +}); + +test('quality gate keeps pull request changelog enforcement event-aware', () => { + assert.match(qualityGateWorkflow, /bun run changelog:lint/); + assert.match(qualityGateWorkflow, /if: github\.event_name == 'pull_request'/); + assert.match( + qualityGateWorkflow, + /env:\s*\n\s*BASE_REF: \${{ github\.base_ref }}\s*\n\s*PR_LABELS: \${{ join\(github\.event\.pull_request\.labels\.\*\.name, ','\) }}\s*\n\s*run: bun run changelog:pr-check --base-ref "origin\/\$BASE_REF" --head-ref "HEAD" --labels "\$PR_LABELS"/, + ); + assert.match(qualityGateWorkflow, /skip-changelog/); +}); + +test('quality gate verifies generated config examples', () => { + assert.match(qualityGateWorkflow, /bun run verify:config-example/); +}); + +test('quality gate blocks on high-severity audit findings', () => { + const securityAuditStep = + qualityGateWorkflow.match(/- name: Security audit[\s\S]*?(?=\n {6}- name:|\n?$)/)?.[0] ?? ''; + const dependencyAuditPolicySection = + verificationDoc.match(/## Dependency Audit Policy[\s\S]*?(?=\n## |\n?$)/)?.[0] ?? ''; + + assert.match(securityAuditStep, /run: bun audit --audit-level high/); + assert.doesNotMatch(securityAuditStep, /continue-on-error: true/); + assert.match( + dependencyAuditPolicySection, + /`bun audit --audit-level high` blocks the reusable quality gate/, + ); +}); diff --git a/src/release-workflow.test.ts b/src/release-workflow.test.ts index 641ed17c..0cfda411 100644 --- a/src/release-workflow.test.ts +++ b/src/release-workflow.test.ts @@ -81,11 +81,16 @@ test('release workflow verifies generated config examples before packaging artif assert.match(releaseWorkflow, /bun run verify:config-example/); }); -test('release quality gate runs the maintained source coverage lane and uploads lcov output', () => { - assert.match(releaseWorkflow, /name: Coverage suite \(maintained source lane\)/); - assert.match(releaseWorkflow, /run: bun run test:coverage:src/); - assert.match(releaseWorkflow, /name: Upload coverage artifact/); - assert.match(releaseWorkflow, /path: coverage\/test-src\/lcov\.info/); +test('release delegates its quality gate instead of duplicating quality steps', () => { + assert.match( + releaseWorkflow, + /quality-gate:\s*\n\s*permissions:\s*\n\s*contents: read\s*\n\s*uses: \.\/\.github\/workflows\/quality-gate\.yml/, + ); + const qualityGateJob = releaseWorkflow.match(/quality-gate:[\s\S]*?(?=\n build-linux:)/)?.[0]; + assert.ok(qualityGateJob); + assert.doesNotMatch(qualityGateJob, /oven-sh\/setup-bun/); + assert.doesNotMatch(qualityGateJob, /bun run test:coverage:src/); + assert.doesNotMatch(qualityGateJob, /bun run test:env/); }); test('release build jobs install and cache stats dependencies before packaging', () => {