diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md
new file mode 100644
index 0000000..f035beb
--- /dev/null
+++ b/.github/PULL_REQUEST_TEMPLATE.md
@@ -0,0 +1,13 @@
+## Summary
+
+
+
+## Test plan
+
+- [ ] `pnpm run format:check` passes
+- [ ] `pnpm run lint` passes
+- [ ] `pnpm run build` passes
+- [ ] `pnpm test` passes
+- [ ] Docs / `CHANGELOG.md` updated if this is user-facing
+
+Closes #
diff --git a/.github/actions/setup/action.yml b/.github/actions/setup/action.yml
new file mode 100644
index 0000000..8e278c8
--- /dev/null
+++ b/.github/actions/setup/action.yml
@@ -0,0 +1,30 @@
+name: Setup
+description: Install pnpm, Node.js, and dependencies from the lockfile.
+
+inputs:
+ node-version:
+ description: Node.js version to install
+ required: true
+ registry-url:
+ description: Optional npm registry URL (needed for publish)
+ required: false
+ default: ""
+
+runs:
+ using: composite
+ steps:
+ - uses: pnpm/action-setup@0977fd99725f1db4007ccb2928dbb4e90d06cc86 # v6.0.10
+ - uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
+ if: ${{ inputs.registry-url == '' }}
+ with:
+ node-version: ${{ inputs.node-version }}
+ cache: pnpm
+ - uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
+ if: ${{ inputs.registry-url != '' }}
+ with:
+ node-version: ${{ inputs.node-version }}
+ cache: pnpm
+ registry-url: ${{ inputs.registry-url }}
+ - name: Install dependencies
+ shell: bash
+ run: pnpm install --frozen-lockfile
diff --git a/.github/dependabot.yml b/.github/dependabot.yml
new file mode 100644
index 0000000..05db778
--- /dev/null
+++ b/.github/dependabot.yml
@@ -0,0 +1,16 @@
+version: 2
+updates:
+ - package-ecosystem: github-actions
+ directory: /
+ schedule:
+ interval: weekly
+ groups:
+ github-actions:
+ patterns:
+ - "*"
+
+ - package-ecosystem: npm
+ directory: /
+ schedule:
+ interval: weekly
+ open-pull-requests-limit: 5
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
new file mode 100644
index 0000000..08dc31a
--- /dev/null
+++ b/.github/workflows/ci.yml
@@ -0,0 +1,77 @@
+name: CI
+
+on:
+ push:
+ branches: [main]
+ pull_request:
+ branches: [main]
+ workflow_dispatch:
+
+concurrency:
+ group: ci-${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
+ cancel-in-progress: true
+
+permissions:
+ contents: read
+
+# Unit and mocked integration tests live under src/__tests__ and do not call
+# provider APIs. A live Playwright job would need secrets that fork PRs never
+# receive; add one when a real E2E suite exists, gated on push to main.
+
+jobs:
+ quality:
+ name: Lint, format, and typecheck
+ runs-on: ubuntu-24.04
+ timeout-minutes: 10
+ steps:
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
+ with:
+ persist-credentials: false
+ - uses: ./.github/actions/setup
+ with:
+ node-version: "22"
+ - name: Prettier
+ run: pnpm run format:check
+ - name: ESLint
+ run: pnpm run lint
+ - name: Typecheck
+ run: pnpm run build
+
+ test:
+ name: Test (Node ${{ matrix.node-version }})
+ runs-on: ubuntu-24.04
+ timeout-minutes: 15
+ strategy:
+ fail-fast: false
+ matrix:
+ node-version: [18, 20, 22, 24]
+ steps:
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
+ with:
+ persist-credentials: false
+ - uses: ./.github/actions/setup
+ with:
+ node-version: ${{ matrix.node-version }}
+ - name: Test
+ run: pnpm test
+ - name: Coverage
+ if: matrix.node-version == 22
+ run: pnpm run test:coverage
+ - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
+ if: matrix.node-version == 22
+ with:
+ name: coverage
+ path: coverage/
+ if-no-files-found: error
+
+ ci:
+ name: CI
+ needs: [quality, test]
+ if: always()
+ runs-on: ubuntu-24.04
+ timeout-minutes: 2
+ steps:
+ - name: Required jobs passed
+ run: |
+ test "${{ needs.quality.result }}" = "success"
+ test "${{ needs.test.result }}" = "success"
diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml
new file mode 100644
index 0000000..23f3122
--- /dev/null
+++ b/.github/workflows/codeql.yml
@@ -0,0 +1,39 @@
+name: CodeQL
+
+on:
+ push:
+ branches: [main]
+ pull_request:
+ branches: [main]
+ schedule:
+ - cron: "27 3 * * 1"
+ workflow_dispatch:
+
+concurrency:
+ group: codeql-${{ github.event.pull_request.number || github.ref }}
+ cancel-in-progress: true
+
+permissions:
+ contents: read
+
+jobs:
+ analyze:
+ name: Analyze
+ runs-on: ubuntu-24.04
+ timeout-minutes: 20
+ permissions:
+ security-events: write
+ packages: read
+ actions: read
+ contents: read
+ steps:
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
+ with:
+ persist-credentials: false
+ - uses: github/codeql-action/init@cdf488f595d80d6e07e03d4674febd5ab45fa938 # v4.37.9
+ with:
+ languages: javascript-typescript
+ build-mode: none
+ - uses: github/codeql-action/analyze@cdf488f595d80d6e07e03d4674febd5ab45fa938 # v4.37.9
+ with:
+ category: "/language:javascript-typescript"
diff --git a/.github/workflows/pr-title.yml b/.github/workflows/pr-title.yml
new file mode 100644
index 0000000..6f5d746
--- /dev/null
+++ b/.github/workflows/pr-title.yml
@@ -0,0 +1,32 @@
+name: PR title
+
+on:
+ pull_request:
+ types: [opened, edited, synchronize, reopened, ready_for_review]
+
+permissions:
+ pull-requests: read
+
+jobs:
+ lint:
+ name: Conventional title
+ runs-on: ubuntu-24.04
+ timeout-minutes: 5
+ steps:
+ - uses: amannn/action-semantic-pull-request@48f256284bd46cdaab1048c3721360e808335d50 # v6.1.1
+ env:
+ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+ with:
+ types: |
+ feat
+ fix
+ docs
+ style
+ refactor
+ perf
+ test
+ build
+ ci
+ chore
+ revert
+ requireScope: false
diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
new file mode 100644
index 0000000..e674caf
--- /dev/null
+++ b/.github/workflows/release.yml
@@ -0,0 +1,30 @@
+name: Release
+
+on:
+ release:
+ types: [published]
+ workflow_dispatch:
+
+permissions:
+ contents: read
+ id-token: write
+
+jobs:
+ publish:
+ name: Publish to npm
+ runs-on: ubuntu-24.04
+ timeout-minutes: 15
+ steps:
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
+ with:
+ persist-credentials: false
+ - uses: ./.github/actions/setup
+ with:
+ node-version: "22"
+ registry-url: https://registry.npmjs.org
+ - name: Build
+ run: pnpm run build
+ - name: Publish
+ run: pnpm publish --access public --no-git-checks --provenance
+ env:
+ NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
diff --git a/.gitignore b/.gitignore
index 6ec6965..6bbdb09 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,4 +1,6 @@
dist
node_modules
+coverage
+.pnpm-store
.DS_Store
.env
\ No newline at end of file
diff --git a/CHANGELOG.md b/CHANGELOG.md
index ca22584..5eb6daa 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -9,6 +9,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Added
+- GitHub Actions CI/CD: Prettier, ESLint, typecheck, tests on Node 18–24, CodeQL, conventional PR titles, and npm publish (with provenance) on GitHub Release
- **OpenCode Zen gateway support**: set `gateway: "opencodezen"` in `configure()` and provide `OPENCODEZEN_API_KEY` to route all model requests through [OpenCode Zen](https://opencode.ai/docs/ko/zen/) (`https://opencode.ai/zen/v1`), an OpenAI-compatible gateway with 30+ curated models including Claude, Gemini, GPT, Qwen, and more.
- **OpenAI Support**: Direct integration with OpenAI models via `@ai-sdk/openai` and `OPENAI_API_KEY`
- `maxRetries` option to `AssertionOptions` (default: `1`) to control how many times a failed assertion is retried with a fresh page snapshot and screenshot. Setting it to `0` disables retries.
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index c23028c..434fb22 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -6,7 +6,7 @@ Thank you for your interest in contributing to Passmark! This document provides
1. **Fork** the repository
2. **Clone** your fork locally
-3. **Install dependencies**: `npm install`
+3. **Install dependencies**: `pnpm install` (this repo is pinned to `pnpm@10.33.0` via `packageManager`)
4. **Create a branch** for your change: `git checkout -b feature/your-feature`
## Development Setup
@@ -14,17 +14,23 @@ Thank you for your interest in contributing to Passmark! This document provides
### Prerequisites
- Node.js >= 18.0.0
-- Redis instance (local or remote)
-- API keys for Anthropic and Google Generative AI (for running tests)
-- Playwright: `npx playwright install`
+- pnpm 10 (`corepack enable` will use the version in `package.json`)
+- Redis instance (local or remote) — only needed for live cache / `{{global.*}}` flows
+- API keys for Anthropic and Google Generative AI — only needed for live AI runs, not for `pnpm test`
+- Playwright: `npx playwright install` — only needed for running tests against a real browser
-### Building
+### Checks (same as CI)
```bash
-npm run build
+pnpm run format:check
+pnpm run lint
+pnpm run build
+pnpm test
```
-This runs the TypeScript compiler (`tsc`). Fix any type errors before submitting.
+`pnpm run build` runs the TypeScript compiler (`tsc`). Fix any type errors before submitting.
+
+CI runs these on every pull request to `main` (tests on Node 18, 20, 22, and 24). The unit suite is mocked and does not need API keys, Redis, or a browser.
## Code Style
@@ -42,10 +48,11 @@ This runs the TypeScript compiler (`tsc`). Fix any type errors before submitting
- `feature/description` for new features
- `fix/description` for bug fixes
- `docs/description` for documentation changes
+- `ci/description` for CI/CD changes
### Commit Messages
-Use clear, concise commit messages:
+Use conventional commit messages. PR titles are checked the same way:
```
feat: add support for custom model providers
@@ -53,14 +60,18 @@ fix: handle empty email extraction gracefully
docs: update environment variables table
```
+Allowed prefixes: `feat`, `fix`, `docs`, `style`, `refactor`, `perf`, `test`, `build`, `ci`, `chore`, `revert`.
+
### Pull Requests
-1. Ensure `npm run build` passes with no errors
+1. Ensure CI would pass locally (`pnpm run format:check`, `pnpm run lint`, `pnpm run build`, `pnpm test`)
2. Update documentation if your change affects the public API
3. Add entries to `CHANGELOG.md` under an `[Unreleased]` section
4. Keep PRs focused on a single change
5. Provide a clear description of what changed and why
+Publishing to npm is automated: creating a GitHub Release on `main` runs `.github/workflows/release.yml`. That workflow needs an `NPM_TOKEN` repository secret with publish access to the `passmark` package.
+
## Project Structure
```
diff --git a/README.md b/README.md
index f01eaaa..5738781 100644
--- a/README.md
+++ b/README.md
@@ -8,6 +8,7 @@
+