diff --git a/.env.1password b/.env.1password new file mode 100644 index 0000000..8f4e655 --- /dev/null +++ b/.env.1password @@ -0,0 +1,13 @@ +# 1Password-backed development environment. Contains REFERENCES, not secrets, +# so this file is committed. Run the app through `op run` to expand them: +# +# npm run dev:op # = op run --env-file=.env.1password -- next dev +# +# Requires 1Password CLI 2.x, unlocked (desktop-app integration or `op signin`). +# Values here are injected into the shell environment, and Next.js does NOT let +# .env.local override an already-set shell variable (see @next/env processEnv), +# so this reference wins over any stale key sitting in .env.local. +TYPESAFE_API_KEY="op://Development/Jev API Key/password" + +# Optional: override the Jev model id (defaults to jev-latest). +# JEV_MODEL=jev-latest diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..5484168 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,83 @@ +# Runs the checks listed in CONTRIBUTING.md on every pull request and on main. +# +# Node: the manifest pins neither Node nor a package manager, so CI uses the +# version this project is developed against. Next 16 requires >= 20.9. +name: CI + +on: + push: + branches: [main] + pull_request: + +permissions: + contents: read + +# A new push to the same branch supersedes an in-flight run. +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +env: + NODE_VERSION: "24" + +jobs: + checks: + name: unit, types, lint, build + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v7 + + - uses: actions/setup-node@v7 + with: + node-version: ${{ env.NODE_VERSION }} + cache: npm + + - run: npm ci + + - name: Unit tests + run: npm test + + - name: Types + run: npm run typecheck + + - name: Lint + run: npm run lint + + - name: Production build + run: npm run build + + e2e: + name: end-to-end (Playwright) + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v7 + + - uses: actions/setup-node@v7 + with: + node-version: ${{ env.NODE_VERSION }} + cache: npm + + - run: npm ci + + # Keyed on the lockfile, so the cache is rebuilt when Playwright's version moves. + - name: Cache Playwright browsers + uses: actions/cache@v6 + with: + path: ~/.cache/ms-playwright + key: playwright-${{ runner.os }}-${{ hashFiles('package-lock.json') }} + + - name: Install Chromium + run: npx playwright install --with-deps chromium + + # playwright.config.ts builds the app and serves it with the key unset, so + # the suite runs in demo mode and needs no credentials. + - name: End-to-end tests + run: npm run e2e + + - name: Upload report + if: failure() + uses: actions/upload-artifact@v7 + with: + name: playwright-report + path: playwright-report/ + retention-days: 7 diff --git a/.gitignore b/.gitignore index e1e3cbc..0c7e312 100644 --- a/.gitignore +++ b/.gitignore @@ -70,6 +70,7 @@ web_modules/ .env.* !.env.example !.env.local.example +!.env.1password # parcel-bundler cache (https://parceljs.org/) .cache diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 772d089..5d1886f 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -20,6 +20,8 @@ npm run build npm run e2e # Playwright, builds and serves the app in demo mode ``` +GitHub Actions runs all of these on every pull request and on `main` (`.github/workflows/ci.yml`): one job for the unit tests, types, lint and build, and one for the end-to-end suite. The end-to-end job needs no credentials, since the suite serves the app with the key unset and routes through the simulator. A failing run uploads its Playwright report as an artifact. + Automated tests should not need a real API key. For UI changes, exercise model/tool modes, option validation, threshold changes, fallback visibility, key removal, storage/export behavior, keyboard controls, themes, and narrow screens. Open a pull request explaining the intended behavior, why it preserves the execution boundary, which tests ran, and any unverified behavior. Include a before/after example or screenshot when useful. A proposed GitHub topic change in `repository-metadata.json` still needs a separate authorized repository-settings update. diff --git a/README.md b/README.md index 1c57bb4..7cbb76d 100644 --- a/README.md +++ b/README.md @@ -38,6 +38,31 @@ cp .env.local.example .env.local # Edit .env.local and set TYPESAFE_API_KEY, then restart the dev server. ``` +### 1Password-backed key (recommended) + +Keep the key in 1Password and run the app through `op run`, so no secret ever +lands in a file: + +```bash +npm run dev:op +``` + +`.env.1password` is committed and holds a **reference**, not a secret: +`TYPESAFE_API_KEY="op://Development/Jev API Key/password"`. `op run` expands it +into the process environment at launch. Requires 1Password CLI 2.x, unlocked +(desktop-app integration or `op signin`). + +Verify it took effect — `live` reports whether the SERVER has a key: + +```bash +curl -s http://localhost:3000/api/route +# {"live":true} ({"live":false} means demo mode) +``` + +A shell-provided variable is not overridden by `.env.local` (`@next/env`'s +`processEnv` only fills keys absent from the initial environment), so this path +wins over any stale key left in `.env.local`. + The browser calls this app's `/api/route` endpoint. The server forwards live evaluations to `POST https://api.typesafe.ai/v1/systemone` using `jev-latest`. Jev answers typed questions; it does not generate the code that executes a route. ### Bring your own key and privacy diff --git a/package.json b/package.json index 9c7bf99..4b8657d 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "typesafe-router", "version": "0.1.0", "private": true, - "description": "Jev Tool & Model Router — Jev decides, your code executes.", + "description": "Jev Tool & Model Router \u2014 Jev decides, your code executes.", "scripts": { "dev": "next dev", "build": "next build", @@ -10,7 +10,9 @@ "lint": "eslint", "test": "vitest run", "e2e": "playwright test", - "typecheck": "next typegen && tsc --noEmit" + "typecheck": "next typegen && tsc --noEmit", + "dev:op": "op run --env-file=.env.1password -- next dev", + "start:op": "op run --env-file=.env.1password -- next start" }, "dependencies": { "next": "16.3.5",