From bc0340c2e13c1008e2ec276664c1193067bde310 Mon Sep 17 00:00:00 2001 From: David Condrey Date: Tue, 4 Aug 2026 00:49:37 -0700 Subject: [PATCH 1/2] ci: move off broken pnpm 11.12.0 and take the pending action majors --- .github/workflows/ci.yml | 4 +--- .github/workflows/demo.yml | 9 +++++---- .github/workflows/release.yml | 8 +++----- .github/workflows/scorecard.yml | 2 +- package.json | 2 +- 5 files changed, 11 insertions(+), 14 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index b39f60f..7f4e1c3 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -17,9 +17,7 @@ jobs: steps: - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7 - - uses: pnpm/action-setup@b906affcce14559ad1aafd4ab0e942779e9f58b1 # v4 - with: - version: 11.12.0 + - uses: pnpm/action-setup@0977fd99725f1db4007ccb2928dbb4e90d06cc86 # v6.0.10 - uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7 with: diff --git a/.github/workflows/demo.yml b/.github/workflows/demo.yml index dde7152..8d00624 100644 --- a/.github/workflows/demo.yml +++ b/.github/workflows/demo.yml @@ -29,9 +29,7 @@ jobs: steps: - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7 - - uses: pnpm/action-setup@b906affcce14559ad1aafd4ab0e942779e9f58b1 # v4 - with: - version: 11.12.0 + - uses: pnpm/action-setup@0977fd99725f1db4007ccb2928dbb4e90d06cc86 # v6.0.10 - uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7 with: @@ -56,9 +54,12 @@ jobs: cp apps/dashboard/dist/index.html apps/dashboard/dist/404.html touch apps/dashboard/dist/.nojekyll - - uses: actions/upload-pages-artifact@56afc609e74202658d3ffba0e8f6dda462b719fa # v3 + # include-hidden-files: v4 of this action started excluding dotfiles from the tarball, which + # would silently drop the .nojekyll written above. + - uses: actions/upload-pages-artifact@fc324d3547104276b827a68afc52ff2a11cc49c9 # v5.0.0 with: path: apps/dashboard/dist + include-hidden-files: true deploy: needs: build diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 433af83..0cf1057 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -26,9 +26,7 @@ jobs: - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7 - - uses: pnpm/action-setup@b906affcce14559ad1aafd4ab0e942779e9f58b1 # v4 - with: - version: 11.12.0 + - uses: pnpm/action-setup@0977fd99725f1db4007ccb2928dbb4e90d06cc86 # v6.0.10 - uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7 with: @@ -66,13 +64,13 @@ jobs: output-file: facet-sbom.spdx.json - name: Attest SBOM - uses: actions/attest-sbom@bd218ad0dbcb3e146bd073d1d9c6d78e08aa8a0b # v2 + uses: actions/attest-sbom@c604332985a26aa8cf1bdc465b92731239ec6b9e # v4.1.0 with: subject-path: 'dist-tarballs/*.tgz' sbom-path: facet-sbom.spdx.json - name: Upload SBOM artifact - uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4 + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 with: name: sbom-spdx path: facet-sbom.spdx.json diff --git a/.github/workflows/scorecard.yml b/.github/workflows/scorecard.yml index 3775bb3..559d52a 100644 --- a/.github/workflows/scorecard.yml +++ b/.github/workflows/scorecard.yml @@ -32,6 +32,6 @@ jobs: publish_results: true - name: Upload SARIF to code scanning - uses: github/codeql-action/upload-sarif@b7351df727350dca84cb9d725d57dcf5bc82ba26 # v3 + uses: github/codeql-action/upload-sarif@f205ea1c3313d32999d8d6a48b4f6530d4437b38 # v4.37.4 with: sarif_file: results.sarif diff --git a/package.json b/package.json index 556eea8..1a01f41 100644 --- a/package.json +++ b/package.json @@ -11,7 +11,7 @@ "homepage": "https://github.com/writerslogic/facet#readme", "bugs": "https://github.com/writerslogic/facet/issues", "type": "module", - "packageManager": "pnpm@11.12.0", + "packageManager": "pnpm@11.20.0", "engines": { "node": ">=22" }, From eaeb2b31077660bd71e19d3bad76c745c2a7279a Mon Sep 17 00:00:00 2001 From: David Condrey Date: Tue, 4 Aug 2026 01:28:56 -0700 Subject: [PATCH 2/2] test: make the alerts cron test hermetic and unstub fetch for every dashboard test --- apps/dashboard/src/test/settings.test.tsx | 3 -- apps/dashboard/src/test/setup.ts | 8 ++++- apps/server/test/alerts.test.ts | 37 +++++++++++++++-------- 3 files changed, 32 insertions(+), 16 deletions(-) diff --git a/apps/dashboard/src/test/settings.test.tsx b/apps/dashboard/src/test/settings.test.tsx index 1c2082e..af5d24d 100644 --- a/apps/dashboard/src/test/settings.test.tsx +++ b/apps/dashboard/src/test/settings.test.tsx @@ -163,9 +163,6 @@ beforeEach(() => { afterEach(() => { vi.restoreAllMocks(); - // `restoreAllMocks` does not undo `stubGlobal`, so without this the stubbed fetch outlives the - // test that installed it and a late react-query retry lands on the wrong mock. - vi.unstubAllGlobals(); }); async function openSettingsWithToken() { diff --git a/apps/dashboard/src/test/setup.ts b/apps/dashboard/src/test/setup.ts index 6fdc6bf..6689467 100644 --- a/apps/dashboard/src/test/setup.ts +++ b/apps/dashboard/src/test/setup.ts @@ -1,6 +1,6 @@ import '@testing-library/jest-dom/vitest'; import { cleanup } from '@testing-library/react'; -import { afterEach } from 'vitest'; +import { afterEach, vi } from 'vitest'; // jsdom in this config does not expose Storage; provide a minimal in-memory implementation. function makeStorage(): Storage { @@ -61,4 +61,10 @@ if (!('ResizeObserver' in globalThis)) { // React Testing Library needs explicit cleanup because vitest globals are off. afterEach(() => { cleanup(); + // `restoreAllMocks` does not undo `stubGlobal`, so a test that stubs `fetch` and only restores + // mocks leaves the stub installed. An unmounted tree's react-query request can still be in + // flight, and it then resolves against the NEXT test's DOM — a failure that moves between files + // and machines depending on which request loses the race. Unstubbing here covers every file + // rather than relying on each one to remember. + vi.unstubAllGlobals(); }); diff --git a/apps/server/test/alerts.test.ts b/apps/server/test/alerts.test.ts index bf1f3a2..8fb8c1f 100644 --- a/apps/server/test/alerts.test.ts +++ b/apps/server/test/alerts.test.ts @@ -624,20 +624,33 @@ describe('cron alerting', () => { noRetry() {}, } as unknown as ScheduledController; - await expect( - runScheduled(event, env as Env, [ - alertsJob, - { - name: 'after', - run: async () => { - ran = true; + // `alertsJob` calls `runAlerts` with no injectable fetch, so the unreachable-endpoint case has + // to be produced at the global. It used to be produced by letting the real `fetch` try to dial + // hooks.example.com and fail, which made the assertion depend on the runner's DNS: a resolver + // that answers NXDOMAIN with a landing page (common on ISP and VPN networks) fails it outright, + // and workerd surfaced the abandoned connection as an unhandled `internal error` rejection on + // every run of this suite. + vi.stubGlobal( + 'fetch', + vi.fn(() => Promise.reject(new Error('unreachable'))), + ); + try { + await expect( + runScheduled(event, env as Env, [ + alertsJob, + { + name: 'after', + run: async () => { + ran = true; + }, }, - }, - ]), - ).resolves.toBeUndefined(); + ]), + ).resolves.toBeUndefined(); + } finally { + vi.unstubAllGlobals(); + } expect(ran).toBe(true); - // The job used the real `fetch`, which cannot reach hooks.example.com from the test runtime — - // exactly the "endpoint is unreachable" case. It must be recorded, not thrown. + // A dead endpoint must be recorded, not thrown. const rows = await deliveryRows(); expect(rows).toHaveLength(1); expect(rows[0]?.status).toBe('failed');