Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
97 commits
Select commit Hold shift + click to select a range
9a8a5cd
docs(project): fix create URL flag example (#42)
JerryNee Jul 2, 2026
7928b5c
fix(help): include request-timeout in global flag guidance (#41)
JerryNee Jul 2, 2026
5f7dba1
fix(auth): honor request timeout during configure (#52)
JerryNee Jul 2, 2026
547b5be
fix(history): reject fractional page sizes (#55)
JerryNee Jul 2, 2026
9102430
fix(target-url): treat trailing-dot hostnames as loopback in SSRF gua…
lxcario Jul 2, 2026
33b7848
fix(auth): preserve typed API error envelope when key verification fa…
lxcario Jul 2, 2026
2fd8f4c
fix(skill-nudge): require complete Codex managed section (#90)
ahndohun Jul 2, 2026
a524702
fix: align documented Node engine floor (#84)
naufalfx805-source Jul 2, 2026
497bf62
fix(pagination): reject fractional pagination flags (#40)
JerryNee Jul 2, 2026
9aa7ba9
fix(project): reject empty/whitespace-only --name in create and updat…
lxcario Jul 2, 2026
21dc760
fix(cli): validate --output uniformly across all command groups (#14)
Davidson3556 Jul 2, 2026
000c196
ci: add Node 20 to test and build matrix (#133)
lxcario Jul 2, 2026
5e0f7c4
fix(test): reject empty code get --out and strip code-file BOM (#34)
SahilRakhaiya05 Jul 2, 2026
dfdee5a
fix(prompt): preserve buffered input between prompts (#118)
cmdr-chara Jul 2, 2026
be9784a
fix(test): emit auto-minted idempotency-key under --output json in re…
ahndohun Jul 2, 2026
03c4f11
fix(agent): apply symlink fail-close guard to own-file --dry-run inst…
ahndohun Jul 2, 2026
03251d7
fix(test): stop run --all --wait from polling queued runs past the sh…
ahndohun Jul 2, 2026
7b7d80d
fix(credentials): strip CR/LF from values to prevent INI injection (#…
lxcario Jul 2, 2026
76fff29
fix(target-url): treat trailing-dot hostnames as loopback in SSRF gua…
lxcario Jul 2, 2026
8da07fb
fix(project): avoid password file reads in dry-run update (#54)
JerryNee Jul 2, 2026
e53257d
fix(cli): validate --request-timeout flag and de-duplicate its parser…
Davidson3556 Jul 2, 2026
cff19ae
fix(rerun): emit partial stdout on TimeoutError in single-FE rerun --…
Jul 3, 2026
6e7f2b3
fix(test): reject whitespace-only --name in test update (parity with …
lxcario Jul 5, 2026
dc4d337
fix(test): guard parseDuration --since against overflow with VALIDATI…
crypticsaiyan Jul 5, 2026
6b90ff4
feat(cli): respect NO_COLOR environment variable per no-color.org (#12)
lxcario Jul 5, 2026
205b1fb
fix(paginate): bound cursor loops without dropping empty pages (#48)
aldorizona10-glitch Jul 5, 2026
06d2c4d
fix(project): validate list flags before client setup (#150)
JerryNee Jul 5, 2026
495db2f
fix(test): validate list status before client setup (#152)
JerryNee Jul 5, 2026
25ed285
fix: reject blank project passwords (#139)
naufalfx805-source Jul 5, 2026
87a6dff
fix: reject directory code output paths (#140)
naufalfx805-source Jul 5, 2026
b5879de
fix: reject malformed api keys (#141)
naufalfx805-source Jul 5, 2026
c0b52f8
fix(setup): validate endpoint before key check (#149)
JerryNee Jul 5, 2026
0de372c
fix(http): map non-JSON 200 responses to a typed error envelope (#166)
Davidson3556 Jul 5, 2026
b675630
feat(test): JUnit XML report export for batch --wait runs (#96)
SahilRakhaiya05 Jul 5, 2026
38e0f19
fix(rerun): preserve exit code and escalate auth errors in batch reru…
crypticsaiyan Jul 5, 2026
e1d00f1
feat(agent): add kiro as an install target (#10)
lxcario Jul 5, 2026
3fd703f
fix(poll): emit partial run to stdout on TimeoutError in run --wait a…
Awad-de Jul 5, 2026
dcbcbee
fix(steps): surface run-scoped per-step error text and stepType (#167)
Andy00L Jul 5, 2026
9457583
feat(test): add "test diff <run-a> <run-b>" to isolate run-to-run reg…
Andy00L Jul 5, 2026
011208c
feat(agent): stamp installed skills with a version/hash marker and ad…
Andy00L Jul 5, 2026
f732fdb
fix(bundle): only sweep bundle-owned files in commit, preserving user…
kshitij-heizen Jul 5, 2026
93c621c
fix(rerun): reject explicit IDs with --all and --status/--skip-termin…
kshitij-heizen Jul 5, 2026
759e85c
docs(usage): add --debug example + exit codes to help text (#171)
mo01115285816-cyber Jul 5, 2026
5ff6399
fix(cli): route interactive prompts and prelude to stderr (keep stdou…
Davidson3556 Jul 5, 2026
17650be
fix: harden failure bundle artifact downloads (#60)
merlinsantiago982-cmd Jul 5, 2026
d72290d
fix(test): guard default artifact run id path (#172)
Lexiie Jul 5, 2026
2ddb03d
feat(cli): add runtime Node.js version check with clear error message…
lxcario Jul 5, 2026
8eb4acf
feat(agent): add windsurf as an install target (#29)
Davidson3556 Jul 5, 2026
768da46
feat(cli): honor HTTPS_PROXY/HTTP_PROXY/NO_PROXY behind corporate and…
Andy00L Jul 5, 2026
c6f946e
feat(cli): non-blocking "new version available" notice (24h-cached np…
Andy00L Jul 5, 2026
4b72461
feat(cli): add 'test flaky' repeat-run flaky-test detector (#132)
lxcario Jul 5, 2026
b9e9601
feat(test): add "test lint" offline plan/steps validator (#176)
Andy00L Jul 5, 2026
f7b10b7
fix(config): treat empty env vars as unset in loadConfig (#8)
SahilRakhaiya05 Jul 6, 2026
c6d37b7
fix(http): clear per-attempt timeout timers (#137)
JerryNee Jul 6, 2026
78ad224
chore(ci): bump marocchino/sticky-pull-request-comment (#187)
dependabot[bot] Jul 6, 2026
108a913
fix(batch): classify RequestTimeoutError as timeout in --all --wait f…
Awad-de Jul 6, 2026
8ac2ff0
feat(test): make "test wait" variadic — attach to several runs in one…
Andy00L Jul 6, 2026
e819e45
feat(agent): add GitHub Copilot as an install target (#194)
Davidson3556 Jul 6, 2026
edc31c8
feat(cli): add "testsprite doctor" environment diagnostic (#183)
Andy00L Jul 6, 2026
946f5b3
feat(cli): graceful termination signals + broken-pipe guard (#185)
Andy00L Jul 6, 2026
3305dfa
feat(test): add "test scaffold" to emit a schema-correct starter plan…
Andy00L Jul 6, 2026
95e3978
Merge branch 'main' into fix/rerun-wait-timeout-stdout-pr
Awad-de Jul 9, 2026
3d65ff5
release: v0.3.0
zeshi-du Jul 9, 2026
60d55e4
Merge pull request #230 from TestSprite/release/v0.3.0
jangjos-128 Jul 9, 2026
e91c5a1
release: v0.4.0
zeshi-du Jul 16, 2026
7b4937b
Merge pull request #259 from TestSprite/release/v0.4.0
fyZhang66 Jul 17, 2026
d2f0357
fix(ci): migrate github-script to v8 — repair the repo-wide broken ga…
zeshi-du Jul 17, 2026
3abcf2d
fix(doctor): validate output mode (#251)
jangjos-128 Jul 17, 2026
3c3a2e0
Recovered: fix(bundle): atomic re-commit via per-entry aside (#196 by…
jangjos-128 Jul 17, 2026
36e5b14
fix(init): debug log whoami fallback (#223)
Lexiie Jul 17, 2026
94f78ad
fix(auth): tighten Windows credentials ACL (#104) (#253)
naufalfx805-source Jul 17, 2026
34f5d43
fix(http): avoid retrying keyless writes (#256)
naufalfx805-source Jul 17, 2026
759a1c7
feat(cli): add "testsprite completion" for bash/zsh/fish (#227)
Andy00L Jul 17, 2026
45dcd08
fix(agent): update verify skill auth commands (#216)
JerryNee Jul 17, 2026
eaf0585
fix(flaky): propagate auth trigger errors (#217)
JerryNee Jul 18, 2026
8db1882
feat(test): add "test open <test-id>" to jump from the terminal to th…
Andy00L Jul 18, 2026
9d4af09
feat(cli): add text list column controls (#165) (#252)
naufalfx805-source Jul 18, 2026
326fa97
test(test): guard status filter validation order (#222)
Lexiie Jul 18, 2026
5fe0b00
fix(config): normalize empty/whitespace TESTSPRITE_PROFILE to unset (…
0xshalah Jul 18, 2026
2708a40
fix(init): skip API key prompt when credentials already exist (#234)
Tasfia-17 Jul 18, 2026
7975f6f
Update test.rerun.spec.ts
Awad-de Jul 18, 2026
51af939
Update test.rerun.spec.ts
Awad-de Jul 18, 2026
7dd2576
Add newline at end of test.rerun.spec.ts
Awad-de Jul 18, 2026
15ee2dc
Add newline at end of test.rerun.spec.ts
Awad-de Jul 18, 2026
770cd47
Refactor runTestRerun call with new parameters
Awad-de Jul 18, 2026
f7120c2
Add newline at end of test.rerun.spec.ts
Awad-de Jul 18, 2026
0b34b1a
Add newline at end of test.rerun.spec.ts
Awad-de Jul 18, 2026
5e2bf88
Update test.rerun.spec.ts
Awad-de Jul 18, 2026
98f9393
Refactor test parameters for runTestRerun function
Awad-de Jul 18, 2026
c4bef00
Fix formatting and comments in test.rerun.spec.ts
Awad-de Jul 18, 2026
0a3bfe3
Update test.rerun.spec.ts
Awad-de Jul 18, 2026
9a6a48d
Update test.rerun.spec.ts
Awad-de Jul 18, 2026
1937605
Update test.rerun.spec.ts
Awad-de Jul 18, 2026
175018a
Merge branch 'main' into fix/rerun-wait-timeout-stdout-pr
Awad-de Jul 18, 2026
f6aea0e
Update test.rerun.spec.ts
Awad-de Jul 18, 2026
ad0dd10
Update test.rerun.spec.ts
Awad-de Jul 18, 2026
8177812
Update test.rerun.spec.ts
Awad-de Jul 18, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions .gitattributes
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
# Check out all text files with LF on every platform. Tests compare bytes
# from checked-out files (skill templates, snapshots); a CRLF working tree
# (core.autocrlf on Windows) broke those comparisons.
* text=auto eol=lf

*.png binary
4 changes: 3 additions & 1 deletion .github/PULL_REQUEST_TEMPLATE.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,9 @@ agree on the approach before you invest time — see CONTRIBUTING.md.

## Related issue

<!-- e.g. "Fixes #123" or "Refs #123". Link an issue for non-trivial changes. -->
<!-- e.g. "Closes #123". Required for features and behavior changes — open an
issue first and get it assigned (comment `/assign` there); see
CONTRIBUTING.md → Contribution model. Docs and small fixes don't need one. -->

## Type of change

Expand Down
83 changes: 83 additions & 0 deletions .github/workflows/backport-dispatch.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
# backport-dispatch.yml — OPTIONAL latency optimization for DEV-352's
# auto-backport bot. Authored in atlas (release/public path), ships to the
# PUBLIC repo via the snapshot per invariant I0 — this workflow only ever
# runs on the public repo, never on atlas (see the repo guard below).
#
# NOT YET ARMED (2026-07-15): the atlas-side auto-backport.yml has a
# scheduled SWEEP (every 30 min) that scans merged public PRs anonymously
# and backports anything unabsorbed — the whole pipeline is fully
# functional with ZERO public-repo credentials via that path alone. This
# workflow instantly notifies atlas the moment a PR merges instead of
# waiting for the next sweep (seconds vs. up to 30 minutes) — a nice-to-have,
# not a requirement.
#
# Arming it means placing a GitHub App credential with contents:write on
# the PRIVATE atlas repo into THIS PUBLIC repo's secrets — a real trust
# decision (a compromised public-repo secret store would let an attacker
# push to atlas) that is deliberately left to the operator, not decided by
# this file. Until TESTSPRITE_HOB_APP_ID/TESTSPRITE_HOB_PRIVATE_KEY (or the
# ALFHEIM_AGENT_* fallback) exist on the PUBLIC repo with contents:write on
# atlas, this workflow no-ops cleanly (see the "not armed" step) — it does
# NOT fail loud, so it stays quiet for every contributor watching the
# Actions tab until the operator decides to enable it.
#
# SECURITY: this workflow reads ONLY event metadata (PR number, merge SHA,
# base ref) — it never checks out the merged PR's code. That is what makes
# `pull_request_target` safe here: the base-repo context (secrets, a
# write-capable token) is required because a fork-originated merged PR gets
# ZERO secrets under a plain `pull_request` trigger, even for the `closed`
# activity type — but nothing in this job ever runs untrusted fork code, so
# the classic pull_request_target RCE/secret-exfiltration risk (checking
# out and executing the fork's own head ref under a privileged context)
# does not apply. Do not add actions/checkout to this workflow.
name: Notify atlas of a merged public PR

on:
pull_request_target:
types: [closed]

permissions:
contents: read

jobs:
notify:
if: github.repository == 'TestSprite/testsprite-cli' && github.event.pull_request.merged == true
runs-on: ubuntu-latest
steps:
- id: app-token
continue-on-error: true
uses: actions/create-github-app-token@bcd2ba49218906704ab6c1aa796996da409d3eb1 # v3.2.0
with:
app-id: ${{ secrets.TESTSPRITE_HOB_APP_ID || secrets.ALFHEIM_AGENT_APP_ID }}
private-key: ${{ secrets.TESTSPRITE_HOB_PRIVATE_KEY || secrets.ALFHEIM_AGENT_PRIVATE_KEY }}
owner: TestSprite
repositories: testsprite-cli-atlas
# Minimum required for POST /repos/{owner}/{repo}/dispatches (verified
# against docs.github.com/en/rest/using-the-rest-api/permissions-required-for-github-apps —
# the endpoint is gated on Contents:write, not Contents:read).
permission-contents: write

- name: Dispatch to atlas
if: steps.app-token.outputs.token != ''
env:
GH_TOKEN: ${{ steps.app-token.outputs.token }}
# These two fields are fork-influenced (merge_commit_sha/base.ref come
# from a merged PR's event payload) — routed through env instead of
# spliced into the run: string to avoid the GHA script-injection class
# (a maliciously-crafted value could otherwise break out of the -F
# argument and inject arbitrary shell). PR_NUMBER is left inline: it is
# a GitHub-assigned integer, not attacker-authorable content.
MERGE_SHA: ${{ github.event.pull_request.merge_commit_sha }}
BASE_REF: ${{ github.event.pull_request.base.ref }}
run: |
set -euo pipefail
gh api repos/TestSprite/testsprite-cli-atlas/dispatches \
-f event_type=public-pr-merged \
-F 'client_payload[pr_number]=${{ github.event.pull_request.number }}' \
-F "client_payload[merge_commit_sha]=$MERGE_SHA" \
-F "client_payload[base_ref]=$BASE_REF"

- name: Not armed yet — the atlas sweep will pick this up
if: steps.app-token.outputs.token == ''
run: |
echo "::notice::No cross-repo credential configured on this repo yet — atlas's scheduled sweep (auto-backport.yml, every 30 min) will pick up this merge instead of an instant dispatch. See DEV-352 / DEV-348 / DEV-350 for the operator decision to arm this."
149 changes: 149 additions & 0 deletions .github/workflows/ci-nudge.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,149 @@
# "Fix this and we merge" nudge (InsForge `agent-zhang-beihai` pattern, part 3).
# When a PR's CI finishes red, posts ONE sticky comment listing exactly which
# jobs failed and the local one-liner that reproduces/fixes each (the automated
# version of the hand-written "run `npm run format` and you're green" review
# comments). The comment flips to a green confirmation once all checks pass.
#
# Runs in base-repo context via workflow_run — no PR code is checked out, so
# fork PRs are safe. State is recomputed from check-runs each time, so the two
# CI workflows (CI + Test Coverage) can complete in any order.
#
# Bot identity: same App-token-first / GITHUB_TOKEN-fallback pattern as
# pr-triage.yml. The App has had the "Pull requests: Read & write" permission
# since 2026-07-02 (corrected 2026-07-15; this comment previously said the
# permission was still pending) — the fallback path stays as defense-in-depth
# for whenever the App/secrets are absent.
name: CI failure nudge

on:
workflow_run:
workflows: ['CI', 'Test Coverage']
types: [completed]

permissions:
checks: read # read the head SHA's check-run state
pull-requests: write
issues: write # PR comments ride the issues API

env:
MARKER: '<!-- hob:ci-nudge -->'

jobs:
nudge:
# Public repo only; PR-triggered runs only (pushes to main have no PR to nudge).
if: >-
github.repository == 'TestSprite/testsprite-cli' &&
github.event.workflow_run.event == 'pull_request'
runs-on: ubuntu-latest
steps:
- id: app-token
continue-on-error: true
uses: actions/create-github-app-token@bcd2ba49218906704ab6c1aa796996da409d3eb1 # v3.2.0
with:
app-id: ${{ secrets.TESTSPRITE_HOB_APP_ID || secrets.ALFHEIM_AGENT_APP_ID }}
private-key: ${{ secrets.TESTSPRITE_HOB_PRIVATE_KEY || secrets.ALFHEIM_AGENT_PRIVATE_KEY }}
# The script below only ever calls the App client (`appClient`) for
# rest.issues.updateComment/createComment — PR comments ride the
# issues API (see the header comment above). All reads (checks,
# pulls, commit->PR lookup) go through the ambient `github` client,
# governed by this workflow's top-level `permissions:` block, not
# this minted token. Scoping to checks:read/pull-requests:write here
# (matching the job's top-level block literally) would risk the mint
# itself failing if the App's installation doesn't hold Checks
# permission — which would silently drop the App-token path
# entirely (continue-on-error swallows the failure) and always fall
# back to github-actions[bot], defeating this bot's own purpose.
permission-issues: write

- uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0
env:
APP_TOKEN: ${{ steps.app-token.outputs.token }}
with:
script: |
const { owner, repo } = context.repo;
const run = context.payload.workflow_run;
const marker = process.env.MARKER;

const appClient = process.env.APP_TOKEN
? new (github.constructor)({ auth: process.env.APP_TOKEN })
: null;
async function write(fn) {
if (appClient) {
try { return await fn(appClient.rest); }
catch (e) { if (e.status !== 403 && e.status !== 404) throw e; }
}
return await fn(github.rest);
}

// Resolve the PR for this run. `workflow_run.pull_requests` is empty
// for fork PRs, so fall back to the commit→PRs lookup.
let pr = (run.pull_requests || [])[0];
if (!pr) {
const { data } = await github.rest.repos.listPullRequestsAssociatedWithCommit(
{ owner, repo, commit_sha: run.head_sha });
pr = data.find(p => p.state === 'open');
} else {
pr = (await github.rest.pulls.get({ owner, repo, pull_number: pr.number })).data;
}
if (!pr || pr.state !== 'open' || pr.user.type === 'Bot') return;

// Job name → the local command that reproduces/fixes it. Also the
// allowlist of CI jobs this nudge watches — keep in sync with ci.yml.
const FIX = {
'Lint & Format': 'run `npm run lint:fix && npm run format`, then commit',
'Typecheck': 'run `npm run typecheck` and fix the reported type errors',
'Unit Tests': 'run `npm test` and fix the failing tests',
'Build': 'run `npm run build` and fix the compile errors',
'Local E2E Tests': 'run `npm run test:e2e` (it builds first)',
'Coverage (>= 80%)': 'run `npm run test:coverage` — new code needs tests until every metric is back at 80%',
'Secret scan (gitleaks)': 'run `gitleaks detect --no-git --redact --source .` locally (config: .gitleaks.toml) and remove the secret — or, for a provable false positive, add a narrowly-anchored allowlist regex',
};
// Matrix jobs publish check runs as "Unit Tests (Node 20)" etc. —
// exact lookup alone would silently skip them (and the nudge would
// say "all green" while they are red). Try exact first, then the
// name with a trailing parenthetical stripped (review round 2).
const fixFor = (name) => FIX[name] ?? FIX[name.replace(/\s*\([^)]*\)$/, '')];

// Recompute full CI state from this SHA's check runs, narrowed to the
// CI jobs above — other workflows (e.g. pr-triage) also attach
// github-actions check runs to the PR head and must not count here.
const checks = await github.paginate(github.rest.checks.listForRef,
{ owner, repo, ref: run.head_sha, per_page: 100 });
const ours = checks.filter(c => c.app && c.app.slug === 'github-actions' && fixFor(c.name));
const failing = ours.filter(c => ['failure', 'timed_out'].includes(c.conclusion));
const pending = ours.filter(c => c.status !== 'completed');

const comments = await github.paginate(github.rest.issues.listComments,
{ owner, repo, issue_number: pr.number, per_page: 100 });
const sticky = comments.find(c => (c.body || '').includes(marker));

if (failing.length === 0) {
// Only speak up on success if we previously flagged a failure, and
// only once everything has actually finished.
if (sticky && pending.length === 0 && !sticky.body.includes('all green')) {
await write(rest => rest.issues.updateComment({ owner, repo, comment_id: sticky.id,
body: `${marker}\n✅ CI is **all green** now — thanks, @${pr.user.login}!` }));
}
return;
}

const lines = failing
.sort((a, b) => a.name.localeCompare(b.name))
.map(c => {
const fix = fixFor(c.name) || 'see the logs for details';
return `- **${c.name}** — ${fix} ([logs](${c.html_url}))`;
});
const body = `${marker}\nThanks, @${pr.user.login}! CI is red on this PR — `
+ `here's what failed and how to reproduce it locally:\n\n${lines.join('\n')}\n\n`
+ `Everything runs on Node 22 after \`npm ci\`. Push a fix and this comment `
+ `flips green automatically once all checks pass.`;

if (sticky) {
if (sticky.body !== body) {
await write(rest => rest.issues.updateComment(
{ owner, repo, comment_id: sticky.id, body }));
}
} else {
await write(rest => rest.issues.createComment(
{ owner, repo, issue_number: pr.number, body }));
}
77 changes: 74 additions & 3 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1
with:
persist-credentials: false

- name: Setup Node.js
uses: actions/setup-node@a0853c24544627f65ddf259abe73b1d18a591444 # v5.0.0
Expand All @@ -37,6 +39,8 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1
with:
persist-credentials: false

- name: Setup Node.js
uses: actions/setup-node@a0853c24544627f65ddf259abe73b1d18a591444 # v5.0.0
Expand All @@ -48,10 +52,35 @@ jobs:
- run: npm run typecheck

test:
name: Unit Tests
name: Unit Tests (Node ${{ matrix.node-version }})
runs-on: ubuntu-latest
strategy:
matrix:
node-version: [20, 22]
steps:
- uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1
with:
persist-credentials: false

- name: Setup Node.js
uses: actions/setup-node@a0853c24544627f65ddf259abe73b1d18a591444 # v5.0.0
with:
node-version: ${{ matrix.node-version }}
cache: 'npm'

- run: npm ci
- run: npm run build
- run: npm test
env:
CI: true

test-windows:
name: Unit Tests (Windows)
runs-on: windows-latest
steps:
- uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1
with:
persist-credentials: false

- name: Setup Node.js
uses: actions/setup-node@a0853c24544627f65ddf259abe73b1d18a591444 # v5.0.0
Expand All @@ -60,20 +89,27 @@ jobs:
cache: 'npm'

- run: npm ci
- run: npm run build
- run: npm run typecheck
- run: npm test
env:
CI: true

build:
name: Build
name: Build (Node ${{ matrix.node-version }})
runs-on: ubuntu-latest
strategy:
matrix:
node-version: [20, 22]
steps:
- uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1
with:
persist-credentials: false

- name: Setup Node.js
uses: actions/setup-node@a0853c24544627f65ddf259abe73b1d18a591444 # v5.0.0
with:
node-version: 22
node-version: ${{ matrix.node-version }}
cache: 'npm'

- run: npm ci
Expand All @@ -89,6 +125,8 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1
with:
persist-credentials: false

- name: Setup Node.js
uses: actions/setup-node@a0853c24544627f65ddf259abe73b1d18a591444 # v5.0.0
Expand All @@ -100,3 +138,36 @@ jobs:
- run: npm run test:e2e
env:
CI: true

gitleaks:
name: Secret scan (gitleaks)
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1
with:
persist-credentials: false

# Same pinned version + checksum-verified install as
# divergence-sentinel.yml (the more recent/secure of the two gitleaks
# invocations already in this repo — release-build.yml pins an older
# 8.21.2 with no checksum check). Continuous, per-PR/per-push gate: a
# working-tree scan (--no-git), not a full-history scan — history
# scanning is too slow to run on every PR, and this mirrors the mode
# scripts/make-public-snapshot.sh already uses for the release-time scan
# (`gitleaks detect --no-git --no-banner --redact --source <tree>`).
- name: Install gitleaks
env:
GITLEAKS_VERSION: '8.28.0'
run: |
set -euo pipefail
BASE="https://github.com/gitleaks/gitleaks/releases/download/v${GITLEAKS_VERSION}"
TARBALL="gitleaks_${GITLEAKS_VERSION}_linux_x64.tar.gz"
curl -sSL -o "$TARBALL" "${BASE}/${TARBALL}"
curl -sSL -o checksums.txt "${BASE}/gitleaks_${GITLEAKS_VERSION}_checksums.txt"
grep " ${TARBALL}\$" checksums.txt | sha256sum -c -
tar -xzf "$TARBALL" gitleaks
sudo mv gitleaks /usr/local/bin/gitleaks
gitleaks version

- name: Scan working tree for secrets
run: gitleaks detect --no-git --no-banner --redact --source .
Loading