From 90d3a983acdb4c5d52cde9be0f6282ec9741d43d Mon Sep 17 00:00:00 2001 From: Wyatt Johnson Date: Fri, 20 Mar 2026 10:38:13 -0600 Subject: [PATCH 1/2] docs: add release documentation and LICENSE Add docs/releasing.md covering the full release flow, channels, and safeguards. Update README with current help output. Add MIT license. --- LICENSE | 21 ++++ docs/releasing.md | 204 ++++++++++++++++++++++++++++++++++++++ scripts/releaser/index.ts | 6 +- 3 files changed, 230 insertions(+), 1 deletion(-) create mode 100644 LICENSE create mode 100644 docs/releasing.md diff --git a/LICENSE b/LICENSE new file mode 100644 index 000000000..daceccfbc --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2026 Clerk, Inc. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/docs/releasing.md b/docs/releasing.md new file mode 100644 index 000000000..a74c42e6f --- /dev/null +++ b/docs/releasing.md @@ -0,0 +1,204 @@ +# Release Flow + +This document describes how the Clerk CLI is built, versioned, and published. + +## Overview + +``` +push to main + → changesets/action creates/updates "Version Packages" PR + → merge "Version Packages" PR + → check-release.ts detects unpublished version + → build job: cross-compile all 8 targets (~5.5s total) + → smoke-test job: verify binaries on native runners + → publish-npm: generate platform packages + publish wrapper + → upload-github-assets: attach binaries to the GitHub Release + → (if no stable release needed) canary.ts versions packages + → build → smoke-test subset → publish @canary + +PR comment "!snapshot [name]" + → snapshot.ts versions packages from PR branch + → build job: cross-compile binaries + → smoke-test job: verify linux-x64 binary + → publish-npm: publish @snapshot packages + → post installation comment on PR +``` + +## Architecture + +The CLI is distributed as an npm wrapper package (`clerk`) plus one platform-specific package per target (e.g., `@clerk/cli-darwin-arm64`). The full list of platform targets is defined in [`scripts/releaser/targets.ts`](../scripts/releaser/targets.ts). + +When a user runs `npm install -g clerk`, npm installs the wrapper plus the matching platform package via `optionalDependencies`. The wrapper's `bin/clerk` shim resolves the binary from the platform package using `require.resolve()`. + +Target names follow Node.js's `${process.platform}-${process.arch}` convention so the shim can derive package names without a lookup table. + +## Release Channels + +### Stable (`@latest`) + +Published when the "Version Packages" PR (created by `changesets/action`) is merged and `check-release.ts` detects that the version in `packages/cli/package.json` is not yet published on npm. Includes full smoke testing on native runners before publishing. Binaries are also attached to a GitHub Release created by the releaser script. + +Install: `npm install -g clerk` + +### Canary (`@canary`) + +Published automatically on every push to `main` that does **not** trigger a stable release. `scripts/canary.ts` uses Changesets snapshot mode to produce versions in the format `x.y.z-canary.v` (e.g., `0.0.1-canary.v20260313145959`). A subset of smoke tests (darwin-arm64, linux-x64, linux-x64-musl) runs before publishing. + +Install: `npm install -g clerk@canary` + +### Snapshot (`@snapshot`) + +Published on-demand from PR branches by commenting `!snapshot` (or `!snapshot `) on a pull request. The commenter must be a member or owner of the repository's organization. `scripts/snapshot.ts` uses Changesets snapshot mode to produce versions in the format `x.y.z-.v` (e.g., `0.0.1-snapshot.v20260313145959` or `0.0.1-my-feature.v20260313145959`). The datetime format ensures multiple snapshots from the same PR sort monotonically in semver. + +Install: `npm install -g clerk@` (version is posted as a PR comment after publishing) + +## Versioning + +Versioning is managed by [Changesets](https://github.com/changesets/changesets). Contributors add changeset files to their PRs by running: + +```sh +bunx changeset +``` + +This launches an interactive prompt that asks which packages changed and whether it is a patch, minor, or major bump. The resulting markdown file is committed with the PR. + +On every push to `main`, the `changesets/action@v1` GitHub Action either creates a new "Version Packages" PR or updates an existing one. That PR aggregates all pending changesets, bumps versions, and updates changelogs. Merging it triggers a stable release (detected by `scripts/check-release.ts`). + +Configuration: + +- `.changeset/config.json` -- Changesets configuration (access, snapshot template, ignored packages) + +## Build Pipeline + +The release workflow (`.github/workflows/release.yml`) runs on every push to `main`. Binary compilation is handled by a reusable workflow (`.github/workflows/build-binaries.yml`) shared across stable, canary, and snapshot pipelines. Build jobs run on Blacksmith runners (`blacksmith-2vcpu-ubuntu-2404`); smoke tests run on platform-native GitHub-hosted runners. + +### 1. Build Job + +Defined in [`.github/workflows/build-binaries.yml`](../.github/workflows/build-binaries.yml) and called by the release, canary, and snapshot pipelines. Runs as a **single sequential job** on a Blacksmith runner that cross-compiles all 8 targets in ~5.5 seconds total using `scripts/build.ts`. For each target, the script: + +1. Cross-compiles the CLI using `bun build --compile --no-compile-autoload-dotenv --target=` +2. Injects the version via `--define "CLI_VERSION=\"$CLI_VERSION\""` +3. Verifies the binary format using `file` output +4. The workflow then uploads each binary as a separate GitHub Actions artifact + +### 2. Smoke Test Job (matrix) + +Downloads each compiled binary and runs `--version` to verify the binary actually executes. Smoke testing is handled by a reusable workflow (`.github/workflows/smoke-test.yml`) shared across stable, canary, and snapshot pipelines. Each caller passes a preset name (`stable`, `canary`, or `snapshot`); the reusable workflow resolves the preset to a target matrix internally. glibc targets run natively on a platform-matched GitHub-hosted runner; musl targets run inside an Alpine Docker container on a Linux runner. + +Not all targets have a native runner available. `win32-arm64` is published as best-effort -- the build job verifies it is a valid PE32+/Aarch64 binary via `file` output, but no execution-level smoke test runs because there is no GitHub-hosted ARM Windows runner. + +Publishing and GitHub Release upload are gated on all smoke tests passing. + +### 3. Publish npm Job + +Runs the releaser script (`scripts/releaser/index.ts`) via `bun run release` (stable), `bun run release:canary` (canary), or `bun run release:snapshot` (snapshot): + +1. Reads the version from `packages/cli/package.json` (or uses `--version` override for canary/snapshot) +2. For each target, generates a platform package in `dist/platform-packages/`: + - Creates `package.json` with `os`/`cpu` fields for npm platform selection + - Copies the compiled binary from the build artifacts +3. Publishes each platform package with `--access public` (authentication and provenance use npm OIDC trusted publishing -- no `NPM_TOKEN` secret needed, just `id-token: write` permission on a GitHub-hosted runner) +4. Temporarily mutates the wrapper `package.json` to add `optionalDependencies` and remove `private: true`, publishes it, then restores the original file +5. For stable releases, creates a Git tag and GitHub Release via the GitHub API + +The releaser accepts these flags: + +- `--dry-run` -- simulate publishing without actually uploading to npm +- `--tag ` -- publish with a specific npm dist-tag (e.g., `canary`, `snapshot`); defaults to `latest` +- `--version ` -- override the version read from `package.json` + +All publishes are idempotent -- the script checks `npm view` before publishing and skips already-published versions. + +#### Environment Variables + +The releaser script and publish workflow steps use these environment variables: + +- `ARTIFACTS_DIR` -- path to directory containing compiled binaries from the build job (defaults to `./dist/artifacts`) +- `GH_TOKEN` -- GitHub token used by the releaser to create tags and releases (stable only) + +#### npm Authentication + +Publishing uses [npm OIDC trusted publishing](https://docs.npmjs.com/trusted-publishers/) instead of stored secrets. The publish jobs run on GitHub-hosted runners with `id-token: write` permission, and npm >= 11.5.1 automatically authenticates via GitHub's OIDC provider. Each package must have a trusted publisher configured on npmjs.com pointing to the correct workflow file. + +> **First publish**: New packages cannot use trusted publishing until they exist on npm. The very first stable release requires a one-time `NODE_AUTH_TOKEN` with a granular access token. After that, configure trusted publishers for all packages and remove the token. + +### 4. Upload GitHub Assets Job + +Attaches the compiled binaries to the GitHub Release for direct download. Binaries are uploaded with display names following the `clerk-` convention (e.g., `clerk-darwin-arm64`, `clerk-win32-x64.exe`). + +## Key Files + +| File | Purpose | +| -------------------------------------- | ------------------------------------------------------------------------------ | +| `packages/cli/bin/clerk` | CJS shim that resolves and spawns the platform binary | +| `packages/cli/package.json` | Wrapper package (has `prepublishOnly` guard against accidental direct publish) | +| `packages/cli-core/src/cli.ts` | CLI entrypoint (reads `CLI_VERSION` global at runtime) | +| `packages/cli-core/src/globals.d.ts` | TypeScript declaration for the `CLI_VERSION` compile-time define | +| `scripts/releaser/index.ts` | Generates platform packages and publishes everything to npm | +| `scripts/releaser/targets.ts` | Target definitions (used by both releaser and build.ts) | +| `scripts/build.ts` | Cross-compiles CLI binaries for all 8 platform targets | +| `scripts/canary.ts` | Versions packages for canary channel using Changesets snapshots | +| `scripts/snapshot.ts` | Versions packages for snapshot channel using Changesets snapshots | +| `scripts/check-release.ts` | Detects if a stable release is needed (compares version to npm registry) | +| `.changeset/config.json` | Changesets configuration | +| `.github/workflows/build-binaries.yml` | Reusable workflow for cross-compiling binaries (called by release + snapshot) | +| `.github/workflows/smoke-test.yml` | Reusable workflow for smoke-testing binaries (called by release + snapshot) | +| `.github/workflows/release.yml` | GitHub Actions release + canary workflow | +| `.github/workflows/snapshot.yml` | GitHub Actions snapshot workflow (triggered by PR comments) | + +## Keeping Targets in Sync + +The target list exists in these places that must stay in sync: + +1. `scripts/releaser/targets.ts` -- used by the releaser to generate platform packages and by `scripts/build.ts` to cross-compile binaries +2. `.github/workflows/smoke-test.yml` preset definitions -- defines the target matrix for each preset (`stable`, `canary`, `snapshot`) + +If you add or remove a target, update both of these. Note that the smoke-test presets may not cover every target if a native runner isn't available (e.g., `win32-arm64`). + +## Local Development + +```sh +# Run CLI source directly (fastest, no compilation) +bun run dev + +# Compile a native binary and run it +bun run start -- --help + +# Cross-compile all 8 targets to dist/artifacts/ +bun run build:compile:all + +# Cross-compile a single target +bun run scripts/build.ts --target=bun-darwin-arm64 +``` + +The `dev` and `start` commands do not inject a version (falls back to `0.0.0-dev`). The release workflow handles version injection. + +To test the releaser without publishing: + +```sh +bun run scripts/releaser/index.ts --dry-run +``` + +## Contributing + +When submitting a PR that changes user-facing behavior, add a changeset: + +```sh +bunx changeset +``` + +Follow the interactive prompts to select the affected package (`clerk`) and the bump type (patch, minor, or major). Commit the generated `.changeset/*.md` file with your PR. The `changesets/action` bot will incorporate it into the next "Version Packages" PR automatically. + +If your change is internal-only (CI, tests, docs, refactoring), you can skip the changeset -- the bot will note that no packages need version bumps. + +## Safeguards + +- **`prepublishOnly` guard**: The wrapper `package.json` has a `prepublishOnly` script that exits with an error, preventing accidental `npm publish` from the package directory. The releaser bypasses this with `--ignore-scripts`. +- **`private: true`**: Both `packages/cli` and `packages/cli-core` are marked private. The releaser removes this flag from the wrapper before publishing and restores it afterward. +- **Idempotent publishing**: The releaser checks npm before publishing and skips already-published versions, making it safe to re-run. +- **Release detection**: `scripts/check-release.ts` compares the version in `package.json` against the npm registry, ensuring stable releases only trigger when there is genuinely a new version to publish. +- **Binary format verification**: The build script verifies each compiled binary matches its expected architecture before uploading. +- **Native smoke tests**: Each binary is executed on a native runner for its platform before publishing. This catches cross-compilation issues that format checks alone would miss. +- **Org membership check**: Snapshot releases require the commenter to be a `MEMBER` or `OWNER` of the repository's organization, verified via `author_association`. +- **OIDC trusted publishing**: Publish jobs authenticate via GitHub's OIDC provider instead of stored npm tokens. This eliminates secret rotation, prevents token exfiltration, and scopes publish permissions to specific workflow files. +- **CI build check**: Every PR to `main` runs a JS bundle build to catch bundler-specific failures before merge. diff --git a/scripts/releaser/index.ts b/scripts/releaser/index.ts index 45210cda1..be494e1b3 100644 --- a/scripts/releaser/index.ts +++ b/scripts/releaser/index.ts @@ -1,4 +1,4 @@ -import { mkdir, cp, rm, chmod } from "node:fs/promises"; +import { mkdir, cp, rm, chmod, copyFile } from "node:fs/promises"; import { join } from "node:path"; import { parseArgs } from "node:util"; import { type Target, targets, SCOPE, PKG_PREFIX } from "./targets.ts"; @@ -66,6 +66,10 @@ async function generatePlatformPackage(target: Target, version: string): Promise } await Bun.write(join(dir, "package.json"), JSON.stringify(pkg, null, 2) + "\n"); + // Include the LICENSE file in each platform package. + const licensePath = join(import.meta.dir, "../../LICENSE"); + await copyFile(licensePath, join(dir, "LICENSE")); + return dir; } From ff18bb3fe30813c7ff96fbdf0390a3418f232349 Mon Sep 17 00:00:00 2001 From: Wyatt Johnson Date: Mon, 23 Mar 2026 10:52:29 -0600 Subject: [PATCH 2/2] docs: add CONTRIBUTING.md and installation instructions to README --- CLAUDE.md | 1 + CONTRIBUTING.md | 129 ++++++++++++++++++++++++++++++++++++++++++++++++ README.md | 8 +++ 3 files changed, 138 insertions(+) create mode 100644 CONTRIBUTING.md diff --git a/CLAUDE.md b/CLAUDE.md index 5f9f635c4..7d72099de 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -13,6 +13,7 @@ This is a Bun workspace monorepo: - `scripts/releaser/` — release publishing script that generates platform packages and publishes to npm See [docs/releasing.md](docs/releasing.md) for the full release flow, channels, and safeguards. +See [CONTRIBUTING.md](CONTRIBUTING.md) for development setup, pre-release install methods, and PR guidelines. ## Bun diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 000000000..6be5538b1 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,129 @@ +# Contributing + +When contributing to this repository, please first discuss the change you wish to make via issue or any other method with the owners of this repository before making a change. + +## Developing locally + +### Prerequisites + +- [Bun](https://bun.sh/) (latest version) +- [Git](https://git-scm.com/) + +### Setting up your local environment + +1. Clone the repo + +```sh +git clone https://github.com/clerk/cli-new +``` + +2. Install dependencies + +```sh +cd cli-new +bun install +``` + +3. Run the CLI from source (fastest, no compilation) + +```sh +bun run dev +``` + +4. Or compile a native binary and run it + +```sh +bun run start -- --help +``` + +### Installing pre-release versions + +In addition to running from source, you can install pre-release builds published to npm. + +**Canary** — published automatically on every push to `main` that does not trigger a stable release: + +```sh +npm install -g clerk@canary +``` + +**Snapshot** — published on-demand from PR branches by commenting `!snapshot` (or `!snapshot `) on a pull request. The commenter must be a member or owner of the repository's organization. The exact version to install is posted as a PR comment after publishing: + +```sh +npm install -g clerk@ +``` + +See [docs/releasing.md](docs/releasing.md) for full details on release channels and version formats. + +### CI checks + +After modifying files, run these commands to match what CI enforces on pull requests: + +```sh +bun run format # Format with oxfmt (writes changes) +bun run lint # Lint with oxlint +bun test # Run all tests +``` + +### Writing tests + +When changing functionality or adding new code, add or update tests to verify the new behavior. Tests use Bun's built-in test runner: + +```sh +bun test +``` + +Check for existing `*.test.ts` files near the code you're modifying. + +## Opening a pull request + +1. Search for open or closed [pull requests](https://github.com/clerk/cli-new/pulls) that relate to your submission to avoid duplicating effort +2. Create your feature branch (`git checkout -b feat/amazing_feature`) +3. Write tests to verify your change +4. If your change affects user-facing behavior, add a changeset (`bunx changeset`) +5. Commit your changes using [conventional commits](https://www.conventionalcommits.org/) (`git commit -m 'feat: add amazing feature'`) +6. Push to the branch (`git push origin feat/amazing_feature`) +7. [Open a pull request](https://github.com/clerk/cli-new/compare?expand=1) + +### Changesets + +We use [Changesets](https://github.com/changesets/changesets) for versioning and changelog generation. When your PR changes user-facing behavior, add a changeset: + +```sh +bunx changeset +``` + +Follow the interactive prompts to select the affected package (`clerk`) and the bump type (patch, minor, or major). Commit the generated `.changeset/*.md` file with your PR. + +If your change is internal-only (CI, tests, docs, refactoring), you can skip the changeset. + +For more details, see [Adding a Changeset](https://github.com/changesets/changesets/blob/main/docs/adding-a-changeset.md). + +### Commit messages + +All commit messages must follow the [conventional commits](https://www.conventionalcommits.org/) specification: + +``` +[optional scope]: +``` + +Common types: `feat`, `fix`, `chore`, `docs`, `refactor`, `test`, `ci`, `build`, `perf`. + +### Notes on pull requests + +- Prefer multiple small PRs with related changes over large PRs +- Always include a description explaining what the PR does and why +- For bug fixes, include steps to reproduce the issue or a screen recording + +## Issues and feature requests + +Found a bug or want to suggest a feature? [Submit an issue on GitHub](https://github.com/clerk/cli-new/issues). Before creating an issue, search the issue archive to avoid duplicates. + +## Publishing packages + +_Note: Only Clerk employees can publish packages._ + +See [docs/releasing.md](docs/releasing.md) for the full release flow. + +## License + +By contributing to Clerk, you agree that your contributions will be licensed under its [MIT License](LICENSE). diff --git a/README.md b/README.md index 42862cf64..6e393fc4c 100644 --- a/README.md +++ b/README.md @@ -2,6 +2,14 @@ The Clerk command-line interface. +## Installation + +```sh +npm install -g clerk +``` + +## Usage + ``` Usage: clerk [options] [command]