diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 27f54f75..9c8fe40a 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -22,9 +22,14 @@ concurrency: jobs: build-and-test: - name: Build & test + name: Build & test (Node ${{ matrix.node-version }}) runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + node-version: [20, 24] + steps: - name: Harden the runner uses: step-security/harden-runner@bf7454d06d71f1098171f2acdf0cd4708d7b5920 # v2.20.0 @@ -38,15 +43,10 @@ jobs: uses: pnpm/action-setup@fc06bc1257f339d1d5d8b3a19a8cae5388b55320 # v5.0.0 with: version: 10.34.3 - - # Node 20 is the project's supported runtime (package.json engines pins - # >=20 <21, .nvmrc says 20). Running the matrix on 20 also means the fresh - # install below pulls better-sqlite3's prebuilt binary for the correct ABI, - # which is what makes the server suite runnable in CI. - name: Setup Node.js uses: actions/setup-node@a0853c24544627f65ddf259abe73b1d18a591444 # v5.0.0 with: - node-version: 20 + node-version: ${{ matrix.node-version }} cache: pnpm # The desktop postinstall tries to rebuild the native uiohook-napi module. @@ -70,3 +70,23 @@ jobs: # Runs every package's `test` script (server, web, desktop) via vitest. - name: Test run: pnpm -r test + + # Aggregate gate reporting a single, matrix-independent "Build & test" status. + # Branch protection on main requires the "Build & test" context, but the matrix + # job above reports per-version contexts ("Build & test (Node 20/24)"). This job + # keeps the stable required context alive and fails unless every matrix leg + # succeeded (if: always() so a matrix failure still reports a definitive result + # instead of leaving the required check pending forever). + build-and-test-required: + name: Build & test + if: always() + needs: build-and-test + runs-on: ubuntu-latest + steps: + - name: Verify matrix result + run: | + if [ "${{ needs.build-and-test.result }}" != "success" ]; then + echo "Matrix build-and-test did not succeed: ${{ needs.build-and-test.result }}" + exit 1 + fi + echo "All matrix legs passed." diff --git a/README.md b/README.md index 91bccc63..4fa604eb 100644 --- a/README.md +++ b/README.md @@ -465,10 +465,10 @@ briefly restarts the `backspace` container (clients reconnect automatically). ## Development -Requirements: **Node.js 20 (LTS)** and **pnpm 10**, both pinned (`.nvmrc` plus -the `packageManager` field), so `nvm use` and Corepack select the right versions -automatically. Newer Node majors are untested; the Docker image always builds on -Node 20 regardless of your host. +Requirements: **Node.js 20 or newer** and **pnpm 10**. The `.nvmrc` file keeps +Node 20 as the default development and production baseline; CI additionally +exercises Node 24, and newer majors generally work but are not part of the test +matrix. The Docker image continues to build on Node 20 regardless of your host. ```bash pnpm install @@ -476,6 +476,21 @@ cp .env.example .env # set JWT_SECRET (openssl rand -hex 32) pnpm dev # API server on :3005, Vite dev server on :5173 ``` +On Windows PowerShell, confirm Node 20 or newer and use the native copy command: + +```powershell +node --version +pnpm install +Copy-Item .env.example .env +node -e "console.log(require('crypto').randomBytes(32).toString('hex'))" +``` + +Paste the generated value after `JWT_SECRET=` in `.env`, then start both +development servers with `pnpm dev`. Use `node --version` to confirm the active +version if pnpm reports an engine warning. This covers the server and web dev +servers; building the Electron desktop app still expects a POSIX shell (macOS or +Linux). + > **Server/web only?** `pnpm install` also builds the desktop app's native > keyboard-hook module (`uiohook-napi`), which needs a C++ toolchain > (`make`, `g++`, `python3`). If those are missing it now **warns and continues**, @@ -604,7 +619,7 @@ packages/ | Layer | Technology | |--------------|------------| -| Server | Node.js 20 (LTS), Fastify 4, TypeScript (strict) | +| Server | Node.js 20+, Fastify 4, TypeScript (strict) | | Database | SQLite (better-sqlite3) + Drizzle ORM | | Auth | JWT + bcrypt | | Frontend | React 18, Vite 6, Tailwind CSS 3, Zustand 5 | diff --git a/package.json b/package.json index a2e04279..9091a786 100644 --- a/package.json +++ b/package.json @@ -13,7 +13,7 @@ "scripts": { "dev:server": "pnpm --filter @backspace/server dev", "dev:web": "pnpm --filter @backspace/web dev", - "dev": "pnpm --filter @backspace/server dev & pnpm --filter @backspace/web dev", + "dev": "pnpm --parallel --filter @backspace/server --filter @backspace/web dev", "build:shared": "pnpm --filter @backspace/shared build", "build:server": "pnpm --filter @backspace/server build", "build:web": "pnpm --filter @backspace/web build", @@ -42,7 +42,7 @@ }, "packageManager": "pnpm@10.34.3", "engines": { - "node": ">=20.0.0 <21.0.0", + "node": ">=20.0.0", "pnpm": ">=10.0.0" } } diff --git a/packages/server/package.json b/packages/server/package.json index 35a1a2fa..2005c7a7 100644 --- a/packages/server/package.json +++ b/packages/server/package.json @@ -6,7 +6,7 @@ "author": "Jannis Braun", "type": "module", "scripts": { - "dev": "PORT=3005 tsx watch src/index.ts", + "dev": "cross-env PORT=3005 tsx watch src/index.ts", "start": "node --import tsx/esm src/index.ts", "build": "tsc", "typecheck": "tsc --noEmit", @@ -41,6 +41,7 @@ "@types/jsonwebtoken": "^9.0.6", "@types/node": "^20.14.0", "@types/ws": "^8.18.1", + "cross-env": "^7.0.3", "drizzle-kit": "^0.24.0", "typescript": "^5.4.0", "vitest": "^4.0.18", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 51804221..4ad23b4d 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -120,6 +120,9 @@ importers: '@types/ws': specifier: ^8.18.1 version: 8.18.1 + cross-env: + specifier: ^7.0.3 + version: 7.0.3 drizzle-kit: specifier: ^0.24.0 version: 0.24.2 @@ -2656,6 +2659,11 @@ packages: crc@3.8.0: resolution: {integrity: sha512-iX3mfgcTMIq3ZKLIsVFAbv7+Mc10kxabAGQb8HvjA1o3T1PIYprbakQ65d3I+2HGHt6nSKkM9PYjgoJO2KcFBQ==} + cross-env@7.0.3: + resolution: {integrity: sha512-+/HKd6EgcQCJGh2PSjZuUitQBQynKor4wrFbRg4DtAgS1aWO+gU52xpH7M9ScGgXSYmAVS9bIJ8EzuaGw0oNAw==} + engines: {node: '>=10.14', npm: '>=6', yarn: '>=1'} + hasBin: true + cross-spawn@7.0.6: resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==} engines: {node: '>= 8'} @@ -7993,6 +8001,10 @@ snapshots: buffer: 5.7.1 optional: true + cross-env@7.0.3: + dependencies: + cross-spawn: 7.0.6 + cross-spawn@7.0.6: dependencies: path-key: 3.1.1