diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000..3524bde --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,26 @@ +version: 2 + +updates: + # Action majors go stale silently — nothing fails when actions/checkout@v5 is + # two majors behind, it just quietly runs on an older runner image. + - package-ecosystem: github-actions + directory: / + schedule: + interval: monthly + commit-message: + prefix: "ci" + + - package-ecosystem: npm + directories: + - / + - /example + schedule: + interval: monthly + commit-message: + prefix: "chore" + # Everything here is a devDependency; the published package has no runtime + # dependencies. One grouped PR a month is enough. + groups: + dev-dependencies: + patterns: + - "*" diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..b40271d --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,62 @@ +name: CI + +# Until this existed, nothing was verified before a merge: the checks lived only +# in the git hooks (skippable with --no-verify) and in the release workflow, +# which runs when the version has already been tagged. +on: + pull_request: + push: + branches: + - main + +# Pushing again to a branch makes the run for the previous push pointless. +concurrency: + group: ci-${{ github.ref }} + cancel-in-progress: true + +env: + # The git hooks exist for developer machines; every check they run is a step + # below. Without this, `pnpm install` would set husky up on the runner. + HUSKY: 0 + +jobs: + check: + runs-on: ubuntu-latest + permissions: + contents: read + + strategy: + fail-fast: false + matrix: + # engines allows Node >= 18, but the toolchain itself does not run + # there: tsdown requires ^22.18.0 || >=24.11.0 and jsdom requires + # ^22.22.2 || ^24.15.0 || >=26.0.0. The published bundle is browser + # code with no Node API in it, so what 18 has to support is importing + # it, which this suite would not exercise anyway. + node: [22, 24] + + name: node ${{ matrix.node }} + + steps: + - uses: actions/checkout@v5 + + - uses: pnpm/action-setup@v4 + + - uses: actions/setup-node@v5 + with: + node-version: ${{ matrix.node }} + cache: pnpm + + - run: pnpm install --frozen-lockfile + + - run: pnpm run check-types + + - run: pnpm run test:coverage + + # Also validates the publishable package: the build runs publint and + # are-the-types-wrong. + - run: pnpm run build + + # The example consumes the built package the way a published consumer + # does, so this catches exports and type-resolution breakage. + - run: pnpm --filter example run check-types diff --git a/.gitignore b/.gitignore index b6cdf57..806e079 100644 --- a/.gitignore +++ b/.gitignore @@ -10,6 +10,7 @@ lerna-debug.log* node_modules dist dist-ssr +coverage *.local *.tsbuildinfo diff --git a/.husky/pre-commit b/.husky/pre-commit index 99ef916..93389f6 100644 --- a/.husky/pre-commit +++ b/.husky/pre-commit @@ -1,8 +1,2 @@ pnpm run check-types pnpm test - -# Also validates the publishable package — the build runs publint and -# are-the-types-wrong. -pnpm run build - -pnpm --filter example run check-types diff --git a/.husky/pre-push b/.husky/pre-push new file mode 100644 index 0000000..4cae113 --- /dev/null +++ b/.husky/pre-push @@ -0,0 +1,9 @@ +# The slower half of the checks, moved off pre-commit now that CI runs them on +# every pull request. Committing stays quick; nothing reaches the remote without +# the publishable package being validated. + +# Validates the publishable package — the build runs publint and +# are-the-types-wrong. +pnpm run build + +pnpm --filter example run check-types diff --git a/CHANGELOG.md b/CHANGELOG.md index dde96b0..844298f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,17 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 _Nothing yet._ +## 1.2.1 — 2026-08-16 + +Infrastructure only — the library itself is unchanged from 1.2.0. + +### Build + +- Publishing moved to GitHub Actions on tag push, authenticated with npm trusted + publishing over OIDC instead of a long-lived token, so releases now carry a + provenance statement. `npm version` no longer publishes from a developer + machine, and `prepublishOnly` refuses to. + ## 1.2.0 — 2026-08-14 ### Added diff --git a/package.json b/package.json index b3a9ef7..46686fd 100644 --- a/package.json +++ b/package.json @@ -30,6 +30,7 @@ }, "scripts": { "test": "vitest run", + "test:coverage": "vitest run --coverage", "check-types": "tsc", "build": "tsdown", "release:patch": "npm version patch", diff --git a/vitest.config.ts b/vitest.config.ts new file mode 100644 index 0000000..c017601 --- /dev/null +++ b/vitest.config.ts @@ -0,0 +1,28 @@ +import { defineConfig } from "vitest/config"; + +export default defineConfig({ + test: { + coverage: { + // Only the published source counts. `all` keeps files that no test ever + // imports in the report, so a new module cannot arrive at 0% unnoticed. + all: true, + include: ["src/**/*.ts"], + exclude: ["src/**/*.test.ts"], + // The default `text` reporter prints an empty file table here (vitest + // 4.1.10) even though the data behind it is right — coverage-final.json + // and the HTML report both list the file. `text-summary` shows the + // numbers without the misleading blank table; `html` is where the + // per-line detail is, in coverage/index.html. + reporter: ["text-summary", "html"], + // The suite covers every line and branch today, so the threshold is the + // current state rather than an aspiration: new code arrives with tests, + // or CI goes red. Lower it deliberately if that ever stops being worth it. + thresholds: { + statements: 100, + branches: 100, + functions: 100, + lines: 100, + }, + }, + }, +});