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
45 changes: 45 additions & 0 deletions .changeset/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
# Changesets

Every pull request adds one markdown file here instead of editing package versions —
uniquely named files never conflict between concurrent pull requests. Format:

```md
---
"@mirek/throwscript-core": minor
"@mirek/throwscript-cli": minor
---

One-line summary of the change.
```

The two packages are released in lockstep (`fixed` in `config.json`): naming either one
bumps both to the same version. Pick the bump the change needs:

- `patch` — fixes and documentation;
- `minor` — new features;
- `major` — breaking changes.

A pull request that changes nothing publishable (CI, scripts, repository docs) still needs a
file, so that nothing is ever forgotten: run `pnpm changeset --empty`.

## How a release happens

1. Merged changesets accumulate on `main`. The **Publish** workflow
(`.github/workflows/publish.yml`) opens or updates the `chore(release): version packages`
pull request (branch `changeset-release/main`), which runs `pnpm version-packages`:
`changeset version` bumps the packages, prepends their `CHANGELOG.md` entries, and deletes
the consumed files.
2. Merging that pull request leaves `main` with no pending changesets, so the same workflow
runs `pnpm verify` and then `pnpm release:publish` (`changeset publish`): every package
whose version is not yet on npm is published with provenance and tagged
`<package>@<version>`. Packages already on npm are skipped, so a failed run is simply
re-run.

npm authentication is OIDC trusted publishing: each `@mirek/throwscript-*` package lists this
repository and the workflow file name `publish.yml` as its trusted publisher on npmjs.com, so
do not rename the workflow file. A brand-new package cannot use trusted publishing for its
first release — publish it once from a maintainer machine (`npm login`, then
`pnpm build && pnpm release:publish`), add the trusted publisher on npmjs.com, and re-run the
workflow.

Do not run `npm version` or `pnpm publish` in individual packages by hand.
15 changes: 15 additions & 0 deletions .changeset/config.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
{
"$schema": "https://unpkg.com/@changesets/config@3.1.4/schema.json",
"changelog": "@changesets/cli/changelog",
"commit": false,
"fixed": [["@mirek/throwscript-core", "@mirek/throwscript-cli"]],
"linked": [],
"access": "public",
"baseBranch": "main",
"updateInternalDependencies": "patch",
"ignore": [],
"privatePackages": {
"version": false,
"tag": false
}
}
6 changes: 6 additions & 0 deletions .changeset/rename-and-ci.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
"@mirek/throwscript-core": minor
"@mirek/throwscript-cli": minor
---

Rename packages to `@mirek/throwscript-core` and `@mirek/throwscript-cli` (the bin is still `throwscript`); the CLI reports the version from its package manifest.
95 changes: 95 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
name: CI

on:
push:
branches:
- main
pull_request:

concurrency:
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
cancel-in-progress: true

permissions:
contents: read

jobs:
# Every PR must add a changeset (see .changeset/README.md). The automated
# version packages PR is the one exception: it consumes changesets.
changeset:
name: Changeset
if: github.event_name == 'pull_request' && !startsWith(github.head_ref, 'changeset-release/')
runs-on: ubuntu-latest
timeout-minutes: 5
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0

- name: Require a changeset
run: |
added="$(git diff --name-only --diff-filter=A "origin/${{ github.base_ref }}...HEAD" -- '.changeset/*.md' | grep -v '^\.changeset/README\.md$' || true)"
if [ -z "$added" ]; then
echo "::error::This PR adds no changeset. Run \`pnpm changeset\` (or \`pnpm changeset --empty\` for a change that publishes nothing) and commit the file under .changeset/."
exit 1
fi
echo "changesets added:"
echo "$added"

verify:
name: Verify (Node ${{ matrix.node }})
runs-on: ubuntu-latest
timeout-minutes: 15
defaults:
run:
# GitHub's default shell runs `bash -e` without pipefail, so
# `pnpm X | tee X.log` would report success whatever X did.
shell: bash
strategy:
fail-fast: false
matrix:
node:
- 20
- 22
- 24
steps:
- uses: actions/checkout@v4

- uses: pnpm/action-setup@v4

- uses: actions/setup-node@v4
with:
node-version: ${{ matrix.node }}
cache: pnpm

- run: pnpm install --frozen-lockfile
- name: Build
run: pnpm build 2>&1 | tee build.log
- name: Lint
run: pnpm lint 2>&1 | tee lint.log
- name: Test
run: pnpm -r test 2>&1 | tee test.log
- name: Validate tarballs
run: pnpm pack:check 2>&1 | tee pack-check.log

- name: Upload failure diagnostics
if: failure()
uses: actions/upload-artifact@v4
with:
name: diagnostics-node-${{ matrix.node }}
path: '*.log'
retention-days: 1

ci:
name: CI
if: always()
needs:
- changeset
- verify
runs-on: ubuntu-latest
steps:
- name: Require all verification jobs
# The changeset job is skipped on pushes to main and on the version packages PR.
run: |
test "${{ needs.verify.result }}" = success
case "${{ needs.changeset.result }}" in success|skipped) ;; *) exit 1 ;; esac
79 changes: 79 additions & 0 deletions .github/workflows/publish.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
name: Publish

# Changesets release automation. On every push to main:
# - pending .changeset/*.md files → opens/updates the
# `chore(release): version packages` PR (branch changeset-release/main);
# - no pending changesets → runs the full verification gate, then
# `changeset publish`, which publishes every package whose version is not
# on npm yet (pnpm publish, so `workspace:*` ranges are rewritten) and
# tags it <package>@<version>. A no-op when everything is published.
#
# npm auth is OIDC trusted publishing (id-token: write) — no npm token.
# Each @mirek/throwscript-* package must have this repo + THIS WORKFLOW
# FILENAME (publish.yml) configured as a trusted publisher on npmjs.com, so do
# not rename this file. A brand-new package cannot use trusted publishing for
# its first release — see .changeset/README.md.

on:
push:
branches:
- main

permissions:
contents: write # push the changeset-release branch and the <package>@<version> tags
pull-requests: write # open/update the version packages PR
id-token: write # npm OIDC trusted publishing

concurrency:
group: publish-main
cancel-in-progress: false # never cancel a run that may be mid-publish

jobs:
release:
runs-on: ubuntu-latest
timeout-minutes: 30
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0

- uses: pnpm/action-setup@v4

- uses: actions/setup-node@v4
with:
node-version: 24
registry-url: https://registry.npmjs.org
package-manager-cache: false # never use caching in release builds

- run: pnpm install --frozen-lockfile

- name: Detect pending changesets
id: pending
run: |
if git ls-files '.changeset/*.md' | grep -v '^\.changeset/README\.md$' | grep -q .; then
echo "changesets=true" >> "$GITHUB_OUTPUT"
else
echo "changesets=false" >> "$GITHUB_OUTPUT"
fi

# Publishing ships the exact checked-out commit, so it runs the complete
# verification and installed-tarball gate first, whatever CI said elsewhere.
- name: Verify before publishing
if: steps.pending.outputs.changesets == 'false'
run: pnpm verify

# Note: the version packages PR is opened with GITHUB_TOKEN, so CI does
# not run on it automatically (GitHub suppresses workflows for events
# created by the default token). Its diff is versions and changelogs
# only; CI runs on main after merge, and the publish path above verifies
# before anything ships.
- uses: changesets/action@v1
with:
version: pnpm version-packages
publish: pnpm release:publish
commit: 'chore(release): version packages'
title: 'chore(release): version packages'
createGithubReleases: false
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
NPM_CONFIG_PROVENANCE: 'true'
11 changes: 8 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,11 @@ export async function fetchUser(id: string): Promise<User> {

## Usage

```sh
pnpm add -D @mirek/throwscript-cli # installs the `throwscript` bin
# or run without installing: npx @mirek/throwscript-cli
```

```sh
# check every file in the tsconfig.json project in the current directory
throwscript
Expand Down Expand Up @@ -200,8 +205,8 @@ subclasses.

| Package | Description |
| --- | --- |
| [`@throwscript/core`](packages/core) | The analyzer: walks a `ts.Program` and returns structured diagnostics |
| [`throwscript`](packages/cli) | The CLI wrapper |
| [`@mirek/throwscript-core`](packages/core) | The analyzer: walks a `ts.Program` and returns structured diagnostics |
| [`@mirek/throwscript-cli`](packages/cli) | The `throwscript` command-line tool |

## Development

Expand All @@ -221,7 +226,7 @@ import {
analyzeProject,
formatDiagnostic,
formatReport,
} from "@throwscript/core";
} from "@mirek/throwscript-core";

const diagnostics = analyzeProject("tsconfig.json", {
ignoreExternal: true,
Expand Down
8 changes: 7 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,12 @@
"build": "tsc -b packages/core packages/cli",
"lint": "oxlint .",
"test": "pnpm run build && pnpm -r run test",
"check": "pnpm run build && pnpm run lint && pnpm run test"
"pack:check": "node scripts/pack-check.mjs",
"check": "pnpm run build && pnpm run lint && pnpm run test",
"verify": "pnpm run check && pnpm run pack:check",
"changeset": "changeset",
"version-packages": "changeset version",
"release:publish": "changeset publish"
},
"packageManager": "pnpm@10.33.0",
"pnpm": {
Expand All @@ -18,6 +23,7 @@
"node": ">=20"
},
"devDependencies": {
"@changesets/cli": "^2.29.0",
"oxlint": "^1.72.0",
"typescript": "^5.9.3"
}
Expand Down
33 changes: 28 additions & 5 deletions packages/cli/package.json
Original file line number Diff line number Diff line change
@@ -1,9 +1,19 @@
{
"name": "throwscript",
"name": "@mirek/throwscript-cli",
"version": "0.4.0",
"description": "Linter-like checker: every function that can throw must document it with JSDoc @throws",
"type": "module",
"description": "Linter-like CLI: every function that can throw must document it with JSDoc @throws.",
"keywords": [
"throwscript",
"throws",
"jsdoc",
"typescript",
"linter",
"exceptions",
"errors"
],
"author": "Mirek Rusin (https://github.com/mirek)",
"license": "MIT",
"type": "module",
"bin": {
"throwscript": "./dist/main.js"
},
Expand All @@ -16,7 +26,7 @@
"test": "node --import tsx --test test/*.test.ts"
},
"dependencies": {
"@throwscript/core": "workspace:*"
"@mirek/throwscript-core": "workspace:*"
},
"peerDependencies": {
"typescript": ">=5.5.0 <7"
Expand All @@ -25,5 +35,18 @@
"@types/node": "^22.0.0",
"tsx": "^4.19.0",
"typescript": "^5.9.3"
}
},
"engines": {
"node": ">=20"
},
"publishConfig": {
"access": "public"
},
"repository": {
"type": "git",
"url": "git+https://github.com/mirek/throwscript.git",
"directory": "packages/cli"
},
"homepage": "https://github.com/mirek/throwscript/tree/main/packages/cli#readme",
"bugs": "https://github.com/mirek/throwscript/issues"
}
5 changes: 3 additions & 2 deletions packages/cli/src/main.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
#!/usr/bin/env node
import { existsSync, writeFileSync } from "node:fs";
import { createRequire } from "node:module";
import path from "node:path";
import process from "node:process";
import {
Expand All @@ -10,9 +11,9 @@ import {
formatReport,
type AnalyzeOptions,
type ThrowsDiagnostic,
} from "@throwscript/core";
} from "@mirek/throwscript-core";

const VERSION = "0.4.0";
const VERSION = (createRequire(import.meta.url)("../package.json") as { version: string }).version;

const HELP = `throwscript — assert every function that can throw has a JSDoc @throws tag

Expand Down
Loading
Loading