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
1 change: 1 addition & 0 deletions .gitattributes
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
* text=auto eol=lf
148 changes: 113 additions & 35 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -1,50 +1,128 @@
name: CI
on: [push, pull_request]

on:
push:
pull_request:

permissions:
contents: read

concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: ${{ github.ref != 'refs/heads/main' }}

jobs:
build:
name: test deno v2.x ${{ matrix.os }}
test:
name: Test on Node ${{ matrix.node }} (${{ matrix.os }})
runs-on: ${{ matrix.os }}
timeout-minutes: 5
timeout-minutes: 10
strategy:
fail-fast: false
matrix:
os: [ubuntu-latest, windows-latest, macOS-latest]
fail-fast: true
include:
- { os: ubuntu-latest, node: 22 }
- { os: ubuntu-latest, node: 24, coverage: true }
- { os: ubuntu-latest, node: 26 }
- { os: windows-latest, node: 24 }
- { os: macos-latest, node: 24 }
steps:
- name: Clone repository
uses: actions/checkout@v4
- name: Setup deno
uses: denoland/setup-deno@main
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
- name: Setup pnpm
uses: pnpm/action-setup@ea17c68df8912ef543352723c149a84f56e3d413 # v6.1.0
- name: Setup Node.js
uses: actions/setup-node@249970729cb0ef3589644e2896645e5dc5ba9c38 # v6.5.0
with:
deno-version: v2.x
- name: Check formatting
if: matrix.os == 'ubuntu-latest'
run: deno fmt --check
- name: Check linting
if: matrix.os == 'ubuntu-latest'
run: deno lint
node-version: ${{ matrix.node }}
cache: pnpm
- name: Install dependencies
run: pnpm install --frozen-lockfile
- name: Check lint, formatting, and types
run: pnpm check
- name: Run tests
run: deno test -A --coverage
if: ${{ !matrix.coverage }}
run: pnpm test
- name: Run tests with coverage
if: ${{ matrix.coverage }}
run: pnpm test:coverage
- name: Upload coverage
if: |
matrix.os == 'ubuntu-latest'
uses: codecov/codecov-action@v4
if: ${{ matrix.coverage }}
uses: codecov/codecov-action@fb8b3582c8e4def4969c97caa2f19720cb33a72f # v6.0.2
with:
fail_ci_if_error: true
files: coverage/lcov.info
env:
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
- name: Release info
if: |
github.repository == 'udibo/http-error' &&
matrix.os == 'ubuntu-latest' &&
startsWith(github.ref, 'refs/tags/')
shell: bash
fail_ci_if_error: ${{ github.event_name == 'push' }}
token: ${{ secrets.CODECOV_TOKEN }}

package:
name: Build and smoke-test the package on Node ${{ matrix.node }}
runs-on: ubuntu-latest
timeout-minutes: 10
strategy:
fail-fast: false
matrix:
node: [22, 24, 26]
steps:
- name: Clone repository
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
- name: Setup pnpm
uses: pnpm/action-setup@ea17c68df8912ef543352723c149a84f56e3d413 # v6.1.0
- name: Setup Node.js
uses: actions/setup-node@249970729cb0ef3589644e2896645e5dc5ba9c38 # v6.5.0
with:
node-version: ${{ matrix.node }}
cache: pnpm
- name: Install dependencies
run: pnpm install --frozen-lockfile
- name: Build the npm package
run: pnpm build
- name: Install the packed tarball and run it from Node
run: pnpm smoke
- name: Verify the package publishes to JSR (dry run)
if: matrix.node == 24
run: pnpm jsr:dry-run

release:
name: Release to npm and JSR
needs: [test, package]
if: >-
github.event_name == 'push'
&& github.ref == 'refs/heads/main'
&& vars.RELEASE_ENABLED == 'true'
runs-on: ubuntu-latest
timeout-minutes: 15
permissions:
contents: write
id-token: write
issues: write
pull-requests: write
steps:
- name: Clone repository
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
with:
fetch-depth: 0
- name: Setup pnpm
uses: pnpm/action-setup@ea17c68df8912ef543352723c149a84f56e3d413 # v6.1.0
# No `registry-url` on purpose: it makes setup-node write an .npmrc whose
# auth line is a placeholder, which @semantic-release/npm then reads
# instead of exchanging the OIDC token.
- name: Setup Node.js
uses: actions/setup-node@249970729cb0ef3589644e2896645e5dc5ba9c38 # v6.5.0
with:
node-version: 24
cache: pnpm
# npm trusted publishing (OIDC) needs npm 11.5.1 or later.
- name: Ensure npm supports trusted publishing
run: |
echo "RELEASE_VERSION=${GITHUB_REF/refs\/tags\//}" >> $GITHUB_ENV
- name: Release
uses: softprops/action-gh-release@v1
if: env.RELEASE_VERSION != ''
npm install -g npm@latest
npm --version
- name: Install dependencies
run: pnpm install --frozen-lockfile
- name: Build the npm package
run: pnpm build
# NPM_TOKEN exists only for the first publish, before the package has a
# trusted publisher; delete the secret afterwards (see PUBLISHING.md).
- name: Run semantic-release
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
draft: true
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
run: pnpm exec semantic-release
16 changes: 0 additions & 16 deletions .github/workflows/publish.yml

This file was deleted.

3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1 +1,4 @@
node_modules
dist
coverage
*.tgz
1 change: 1 addition & 0 deletions .nvmrc
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
26
5 changes: 5 additions & 0 deletions .oxfmtrc.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"$schema": "./node_modules/oxfmt/configuration_schema.json",
"printWidth": 80,
"ignorePatterns": ["dist", "coverage", "pnpm-lock.yaml", "node_modules"]
}
17 changes: 17 additions & 0 deletions .oxlintrc.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
{
"$schema": "./node_modules/oxlint/configuration_schema.json",
"plugins": ["typescript", "unicorn", "oxc", "import", "vitest"],
"categories": {
"correctness": "error",
"suspicious": "warn",
"perf": "warn"
},
"rules": {
"typescript/consistent-type-imports": "error",
"typescript/no-explicit-any": "error",
"eqeqeq": "error",
"no-console": "off",
"typescript/no-unsafe-type-assertion": "off"
},
"ignorePatterns": ["dist", "coverage", "node_modules"]
}
35 changes: 35 additions & 0 deletions .releaserc.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
{
"branches": ["main"],
"tagFormat": "${version}",
"plugins": [
[
"@semantic-release/commit-analyzer",
{
"preset": "conventionalcommits",
"releaseRules": [
{ "breaking": true, "release": "minor" },
{ "revert": true, "release": "patch" },
{ "type": "feat", "release": "minor" },
{ "type": "fix", "release": "patch" },
{ "type": "perf", "release": "patch" },
{ "type": "build", "scope": "deps", "release": "patch" },
{ "type": "chore", "scope": "deps", "release": "patch" }
]
}
],
[
"@semantic-release/release-notes-generator",
{ "preset": "conventionalcommits" }
],
"@sebbo2002/semantic-release-jsr",
"@semantic-release/npm",
"@semantic-release/github",
[
"@semantic-release/git",
{
"assets": ["package.json", "jsr.json"],
"message": "chore(release): ${nextRelease.version} [skip ci]\n\n${nextRelease.notes}"
}
]
]
}
23 changes: 0 additions & 23 deletions .vscode/launch.json

This file was deleted.

10 changes: 0 additions & 10 deletions .vscode/settings.json

This file was deleted.

75 changes: 75 additions & 0 deletions PUBLISHING.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
# Publishing @udibo/http-error

The package is published to [npm](https://www.npmjs.com/package/@udibo/http-error)
and [JSR](https://jsr.io/@udibo/http-error) from one semantic-release run on
every push to `main` whose [Conventional Commits](https://www.conventionalcommits.org)
call for a release (`feat` is a minor release, `fix`, `perf`, and `chore(deps)`
are patch releases). While the version is 0.x, breaking changes also release a
minor version, so a `BREAKING CHANGE` footer never produces 1.0.0 on its own.

The release job in [`.github/workflows/ci.yml`](.github/workflows/ci.yml) only
runs when the repository variable `RELEASE_ENABLED` is `true`. Until it is set,
merging to `main` runs the checks and publishes nothing. The first release after
the Node migration is **0.12.0** (the migration PR is a `feat`, and `0.11.1` is
the last tag).

## One-time setup

1. **JSR.** The `@udibo/http-error` package already exists in the `@udibo`
scope. In its settings on jsr.io, confirm it is linked to the GitHub
repository `udibo/http-error`. The release job publishes with GitHub OIDC;
no JSR token is stored.
2. **npm, first publish.** The package does not exist on npm yet, and npm cannot
configure a trusted publisher for a package that does not exist. For the
first release only:
1. Create a granular npm access token that can publish new packages in the
`@udibo` scope (read and write, scoped to `@udibo/*`, short expiry).
2. Save it as the repository secret `NPM_TOKEN`.
3. **Enable releases.** Set the repository variable
`RELEASE_ENABLED=true` (Settings, Secrets and variables, Actions, Variables,
or `gh variable set RELEASE_ENABLED --body true --repo udibo/http-error`).
4. **Merge the migration PR** (squash merge with a `feat:` title). The release
job publishes 0.12.0 to npm (with a provenance attestation) and JSR, pushes
the `chore(release)` commit that updates `package.json` and `jsr.json`, tags
`0.12.0`, and creates the GitHub release.
5. **npm trusted publishing.** Once the package exists on npm, open
npmjs.com, then `@udibo/http-error`, then Settings, then Trusted Publisher,
and add a GitHub Actions publisher: organization `udibo`, repository
`http-error`, workflow filename `ci.yml`, environment empty.
6. **Remove the bootstrap token.** Delete the `NPM_TOKEN` repository secret and
revoke the token on npm. A stored token takes precedence over the OIDC
exchange, so leaving it in place keeps the long-lived credential alive.
Later releases authenticate with the job's OIDC token only.

If `main` gains a ruleset that requires pull requests, the release commit needs
a bypass. Generate an SSH deploy key with write access, store the private half
as a secret, and pass it to `actions/checkout` as `ssh-key`.

## Validate locally

```sh
pnpm install --frozen-lockfile
pnpm check
pnpm test:coverage
pnpm build
pnpm smoke
pnpm jsr:dry-run
```

`pnpm smoke` packs the package, installs the tarball into an empty project, type
checks a consumer under NodeNext resolution, and runs it on Node. CI runs the
same steps on Node 22, 24, and 26.

## What the release does

- Computes the next version from the commits since the last tag.
- Updates the version in `package.json` and `jsr.json`.
- Publishes `dist/` to npm (`pnpm build` runs before semantic-release so npm
authentication is verified before anything is pushed) and the TypeScript
sources in `src/` to JSR.
- Commits the version files, tags the release, and creates the GitHub release.

A release is not a transaction across Git, npm, and JSR. If publication fails
partway, inspect all three before re-running. npm and JSR versions are
immutable, so a version that already exists on either registry cannot be
overwritten.
Loading
Loading