diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 4d1ce40..b1da2ab 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -8,6 +8,14 @@ on: branches: [main, dev] pull_request: +permissions: + contents: read + +# A newer push to the same branch supersedes the run still going. +concurrency: + group: ci-${{ github.ref }} + cancel-in-progress: true + jobs: check: runs-on: ubuntu-latest diff --git a/.github/workflows/leaderboard-backup.yml b/.github/workflows/leaderboard-backup.yml index edb3b8b..a77ce75 100644 --- a/.github/workflows/leaderboard-backup.yml +++ b/.github/workflows/leaderboard-backup.yml @@ -1,10 +1,14 @@ # Weekly copy of every leaderboard, kept as a workflow artifact for 90 days. # Upstash's free plan has no scheduled backups, and the boards are the only -# player data the game holds. Needs three repository secrets: the Redis pair -# the app reads, KV_REST_API_URL and KV_REST_API_TOKEN (plus KEY_PREFIX if the -# deployment sets one), and BACKUP_PASSPHRASE. The file is encrypted with the -# passphrase before upload: on a public repository any signed-in GitHub user -# can download an artifact, and the boards list every player name. +# player data the game holds. Needs the repository secrets KV_REST_API_URL, +# KV_REST_API_TOKEN and BACKUP_PASSPHRASE, plus KEY_PREFIX if the deployment +# sets one. Only the KV_* names are passed through, not UPSTASH_REDIS_REST_*. +# An export that finds no boards fails the job rather than uploading an empty +# file. GitHub disables scheduled workflows after 60 days without repository +# activity; re-enable it from the Actions tab if it goes quiet. The file is +# encrypted with the passphrase before upload: on a public repository any +# signed-in GitHub user can download an artifact, and the boards list every +# player name. # Restore with: openssl enc -d -aes-256-cbc -pbkdf2 -in .enc -out name: Leaderboard backup @@ -13,6 +17,9 @@ on: - cron: '17 3 * * 1' workflow_dispatch: +permissions: + contents: read + jobs: export: runs-on: ubuntu-latest diff --git a/scripts/export-leaderboards.mjs b/scripts/export-leaderboards.mjs index 5f6c27a..968b842 100644 --- a/scripts/export-leaderboards.mjs +++ b/scripts/export-leaderboards.mjs @@ -19,6 +19,13 @@ const outPath = process.argv[2] ?? `leaderboard-backup-${stamp}.json`; const h = getUpstash(); const keys = [...(await scanKeys(h, 'leaderboard:*')), ...(await scanKeys(h, 'distance:*'))].sort(); +// Nothing found is a misconfiguration (a wrong KEY_PREFIX, the wrong +// database), not an empty game. Failing here keeps the weekly job from +// uploading an empty file and reporting success. +if (keys.length === 0) { + console.error(`No boards found under prefix "${h.prefix}"; check KEY_PREFIX and the Redis credentials.`); + process.exit(1); +} const boards = {}; let members = 0; diff --git a/tests/debug-access.test.js b/tests/debug-access.test.js index 156cc95..560480d 100644 --- a/tests/debug-access.test.js +++ b/tests/debug-access.test.js @@ -33,7 +33,9 @@ describe('debugAccessAllowed', () => { delete process.env.DEBUG_ACCESS_KEY; expect(debugAccessAllowed(request())).toBe(false); } finally { - process.env.NODE_ENV = original; + // Assigning undefined would store the string "undefined". + if (original === undefined) delete process.env.NODE_ENV; + else process.env.NODE_ENV = original; } }); diff --git a/vitest.config.mjs b/vitest.config.mjs index 2adb292..bff1324 100644 --- a/vitest.config.mjs +++ b/vitest.config.mjs @@ -4,6 +4,10 @@ export default defineConfig({ test: { environment: 'node', include: ['tests/**/*.test.js'], + // Seven files start PGlite (WASM Postgres) in beforeAll. Start-up alone + // measured ~7s on an ARM host, so with workers starting several at once + // the 10s default failed whole files at random. + hookTimeout: 60_000, // lib/upstash.js refuses to build a client without these. The tests mock the // SDK itself, so the values are never dialled -- they only have to exist. env: { diff --git a/vitest.integration.config.mjs b/vitest.integration.config.mjs index ae153d5..fa93d64 100644 --- a/vitest.integration.config.mjs +++ b/vitest.integration.config.mjs @@ -11,6 +11,10 @@ export default defineConfig({ // Real network round-trips, and the overflow tests issue a few hundred of // them, so the default timeout is too tight. testTimeout: 30_000, + // Seven files start PGlite (WASM Postgres) in beforeAll. Start-up alone + // measured ~7s on an ARM host, so with workers starting several at once + // the 10s default failed whole files at random. + hookTimeout: 60_000, // One file at a time: they all share a single Redis and each flushes the // keyspace between tests. fileParallelism: false,