Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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 .claude/rules/tests.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,3 +18,9 @@ Full guide: `docs/testing.md`. Global setup (`__tests__/_setup.ts`) auto-mocks `
- Wiki fixtures in `__tests__/fixtures/` use Unicode filename characters (`∕` U+2215, `‒` U+2012) — preserve them
exactly
- Action inputs are driven by `INPUT_*` env vars via `setupTestInputs()`; defaults come from `action.yml` at test time
- Use dedicated matchers — `toHaveLength(n)` not `.length` comparisons, `toBeInstanceOf(X)` / `.not.toBeInstanceOf(X)`
not `instanceof` booleans, `toBeNaN()` not `Number.isNaN` checks — they report clearer failures
- Fold structurally identical tests into one `it.each` table with a descriptive `name`/`reason` field rendered via
`$name` in the title; keep tests with distinct setup or extra assertions separate
- Declare hooks (`beforeEach`, `afterEach`, …) at the top of their `describe` scope, before any tests or nested
describes
4 changes: 2 additions & 2 deletions .github/workflows/check-dist.yml
Original file line number Diff line number Diff line change
Expand Up @@ -60,9 +60,9 @@ jobs:

# If `dist/` was different than expected, upload the expected version as a
# workflow artifact.
- if: ${{ failure() && steps.diff.outcome == 'failure' }}
name: Upload Artifact
- name: Upload Artifact
id: upload
if: ${{ failure() && steps.diff.outcome == 'failure' }}
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: dist
Expand Down
175 changes: 149 additions & 26 deletions .github/workflows/lint.yml
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
name: Lint Codebase
name: Lint

on:
pull_request:
Expand All @@ -18,10 +18,109 @@ env:
GITLEAKS_VERSION: 8.30.1

jobs:
lint:
name: Lint
format:
name: Format
runs-on: ubuntu-latest
steps:
- name: Checkout
id: checkout
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false

- name: Setup Node.js
id: setup-node
uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
with:
node-version-file: .node-version
cache: npm

- name: Install Dependencies
id: npm-ci
run: npm ci --no-fund

- name: Check Formatting
id: format-check
run: npm run format:check

code:
name: Code
runs-on: ubuntu-latest
steps:
- name: Checkout
id: checkout
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false

- name: Setup Node.js
id: setup-node
uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
with:
node-version-file: .node-version
cache: npm

- name: Install Dependencies
id: npm-ci
run: npm ci --no-fund

- name: Lint Code
id: lint-code
run: npm run lint:code

types:
name: Types
runs-on: ubuntu-latest
steps:
- name: Checkout
id: checkout
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false

- name: Setup Node.js
id: setup-node
uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
with:
node-version-file: .node-version
cache: npm

- name: Install Dependencies
id: npm-ci
run: npm ci --no-fund

- name: Lint Types
id: lint-types
run: npm run lint:types

text:
name: Text
runs-on: ubuntu-latest
steps:
- name: Checkout
id: checkout
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false

- name: Setup Node.js
id: setup-node
uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
with:
node-version-file: .node-version
cache: npm

- name: Install Dependencies
id: npm-ci
run: npm ci --no-fund

- name: Lint Text
id: lint-text
run: npm run lint:text

actions:
name: Actions
runs-on: ubuntu-latest
steps:
- name: Checkout
id: checkout
Expand All @@ -40,35 +139,59 @@ jobs:
id: npm-ci
run: npm ci --no-fund

# Installed as binaries rather than via their GitHub Actions. actionlint publishes no action.yml at all.
# gitleaks-action is the paid product — the CLI is not — and it gates on GITLEAKS_LICENSE *before*
# scanning; since secrets are never exposed to workflows triggered from forked pull requests, that gate
# fails every external contribution with an error the contributor cannot fix. The CLI needs no license,
# so forks scan exactly like branches.
- name: Install external linters
id: install-external-linters
# Installed as a binary rather than via a GitHub Action — actionlint publishes no action.yml at all.
- name: Install actionlint
id: install-actionlint
run: |
curl -sSfL "https://github.com/rhysd/actionlint/releases/download/v${ACTIONLINT_VERSION}/actionlint_${ACTIONLINT_VERSION}_linux_amd64.tar.gz" \
| sudo tar -xz -C /usr/local/bin actionlint
actionlint --version

# `external-linter.mjs` turns a missing binary into a hard failure when CI is set, so a broken
# install step above can never look like a passing lint.
- name: Lint Actions
id: lint-actions
run: npm run lint:actions

secrets:
name: Secrets
runs-on: ubuntu-latest
steps:
- name: Checkout
id: checkout
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false

# Installed as a binary rather than via a GitHub Action. gitleaks-action is the paid product — the
# CLI is not — and it gates on GITLEAKS_LICENSE *before* scanning; since secrets are never exposed
# to workflows triggered from forked pull requests, that gate fails every external contribution with
# an error the contributor cannot fix. The CLI needs no license, so forks scan exactly like branches.
- name: Install gitleaks
id: install-gitleaks
run: |
curl -sSfL "https://github.com/gitleaks/gitleaks/releases/download/v${GITLEAKS_VERSION}/gitleaks_${GITLEAKS_VERSION}_linux_x64.tar.gz" \
| sudo tar -xz -C /usr/local/bin gitleaks
actionlint --version
gitleaks version

- name: Check formatting
id: format
run: npm run format:check

# Runs lint:code, lint:types, lint:text and lint:actions. `external-linter.mjs` turns a missing binary
# into a hard failure when CI is set, so a broken install step above can never look like a passing lint.
- name: Lint
id: lint
run: npm run lint

# Secret scanning is CI-only — it is not part of `npm run lint`, so contributors never need the binary.
# `dir` scans the whole working tree on every run, unlike the action's default of scanning only a pull
# request's own commit range, so a secret already sitting on main keeps failing CI until it is removed.
# Exclusions live in .gitleaks.toml.
- name: Scan for secrets
# Secret scanning is CI-only — it is not part of `npm run lint`, so contributors never need the
# binary. `dir` scans the whole working tree on every run, unlike the action's default of scanning
# only a pull request's own commit range, so a secret already sitting on main keeps failing CI until
# it is removed. Exclusions live in .gitleaks.toml.
- name: Scan for Secrets
id: gitleaks
run: gitleaks dir . --no-banner --redact

# Aggregate gate preserving the single "Lint" check name the previous one-job workflow exposed, so any
# branch-protection rule requiring "Lint" keeps working. Fails unless every lint job succeeded (a skipped
# or cancelled dependency is a failure here, closing the skipped-check loophole).
lint:
name: Lint
runs-on: ubuntu-latest
needs: [format, code, types, text, actions, secrets]
if: always()
steps:
- name: Verify All Lint Jobs Passed
id: verify
if: ${{ contains(needs.*.result, 'failure') || contains(needs.*.result, 'cancelled') || contains(needs.*.result, 'skipped') }}
run: exit 1
20 changes: 11 additions & 9 deletions .github/workflows/release-start.yml
Original file line number Diff line number Diff line change
Expand Up @@ -19,12 +19,13 @@ env:

jobs:
preview-release:
name: release
name: Preview Release
runs-on: ubuntu-latest
permissions:
contents: read # Required to read repo contents. Note: We leverage release-preview app for PR + commit generation
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- name: Checkout
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
fetch-depth: 0 # Get all history which is required for parsing commits
persist-credentials: false
Expand All @@ -40,23 +41,23 @@ jobs:
id: npm-ci
run: npm ci --no-fund

- name: Validate version input
- name: Validate Version Input
run: |
# Ensure the provided version strictly matches X.Y.Z where X,Y,Z are numeric to avoid injection.
if ! [[ '${{ env.VERSION }}' =~ ^[0-9]+(\.[0-9]+){2}$ ]]; then
echo "Error: release_version must match X.Y.Z (numeric). Got: ${{ env.VERSION }}" >&2
exit 1
fi

- name: Update package.json version
- name: Update package.json Version
# Quote the value so the shell treats it as a single literal argument (prevents command/word splitting).
# Flag order changed (options first) for clarity but either order works.
run: npm version --no-git-tag-version '${{ env.VERSION }}'

- name: Build the package
- name: Build the Package
run: npm run package

- name: Run Tests Typescript
- name: Run Tests
run: npm run test
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
Expand All @@ -65,7 +66,7 @@ jobs:
run: npm run coverage

# Ensure that the package.json is formatted correctly as result of the npm version command sorting keys
# differently from biome.continue-on-error:
# differently from biome
- name: Format Fix
run: npm run format

Expand Down Expand Up @@ -95,8 +96,9 @@ jobs:
#
# https://github.com/peter-evans/create-pull-request/blob/main/docs/concepts-guidelines.md#triggering-further-workflow-runs

- uses: actions/create-github-app-token@bcd2ba49218906704ab6c1aa796996da409d3eb1 # v3.2.0
- name: Create GitHub App Token
id: app-token
uses: actions/create-github-app-token@bcd2ba49218906704ab6c1aa796996da409d3eb1 # v3.2.0
with:
app-id: ${{ secrets.RELEASE_PREVIEW_APP_ID }}
private-key: ${{ secrets.RELEASE_PREVIEW_APP_PRIVATE_KEY }}
Expand All @@ -116,7 +118,7 @@ jobs:

# Note: We can't change the head branch once a PR is opened. Thus we need to delete any branches
# that exist from any existing open pull requests. (App Perm = Pull Request: Read + Write)
- name: Close existing release pull requests
- name: Close Existing Release Pull Requests
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
with:
github-token: ${{ steps.app-token.outputs.token }}
Expand Down
10 changes: 6 additions & 4 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ on:

jobs:
tag-and-release:
name: Tag and Release
runs-on: ubuntu-latest
permissions:
contents: write # To publish tags and to publish GitHub release
Expand All @@ -18,13 +19,14 @@ jobs:
github.event.pull_request.merged == true

steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- name: Checkout
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
fetch-depth: 0 # Get all history
fetch-tags: true
persist-credentials: true

- name: Extract version from PR body
- name: Extract Version from PR Body
id: extract-version
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
with:
Expand All @@ -37,7 +39,7 @@ jobs:
}
throw new Error('Version not found in PR body');

- name: Extract release notes from PR body
- name: Extract Release Notes from PR Body
id: extract-release-notes
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
with:
Expand All @@ -55,7 +57,7 @@ jobs:
console.log(releaseNotes);
return releaseNotes;

- name: Create and push tag
- name: Create and Push Tag
run: |
git config user.name "github-actions[bot]"
git config user.email "github-actions[bot]@users.noreply.github.com"
Expand Down
28 changes: 17 additions & 11 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
@@ -1,43 +1,49 @@
name: Test

on:
# Trigger analysis when pushing in master or pull requests, and when creating
# a pull request.
pull_request:
branches:
- main
push:
branches:
- main
pull_request:
types: [opened, synchronize, reopened]

permissions:
contents: read

jobs:
Tests:
runs-on: ubuntu-latest
test:
name: TypeScript Tests
permissions:
contents: read
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- name: Checkout
id: checkout
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
# Disabling shallow clone is recommended for improving relevancy of reporting
# Disabling shallow clone is recommended for improving relevancy of SonarQube reporting
fetch-depth: 0
persist-credentials: false

- name: Setup Node.js
id: setup-node
uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
with:
node-version-file: .node-version
cache: npm

- name: Install Dependencies
id: npm-ci
run: npm ci --no-fund

- name: Run Tests Typescript
- name: Run Tests
id: test
run: npm run test
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

# Note: SonarQube requires the results from tests to get the coverage report
- name: SonarQube Scan
id: sonarqube
uses: SonarSource/sonarqube-scan-action@22918119ff8e1ca75a623e15c8296b6ea4fbe28f # v8.2.1
env:
SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
Loading