diff --git a/.changeset/changeset-authoring-guidance.md b/.changeset/changeset-authoring-guidance.md new file mode 100644 index 00000000000..a845151cc84 --- /dev/null +++ b/.changeset/changeset-authoring-guidance.md @@ -0,0 +1,2 @@ +--- +--- diff --git a/.claude/skills/clerk-monorepo/SKILL.md b/.claude/skills/clerk-monorepo/SKILL.md index 7b737b7fbb4..6868bb72cbf 100644 --- a/.claude/skills/clerk-monorepo/SKILL.md +++ b/.claude/skills/clerk-monorepo/SKILL.md @@ -86,11 +86,33 @@ pnpm lint pnpm format # workspace packages plus root files, docs/, integration/, scripts/ pnpm prettier --write '.claude/**/*.md' # pnpm format does not cover .claude/; format skill files this way -# Changesets -pnpm changeset # for package-affecting changes -pnpm changeset:empty # for repo/tooling-only changes (see rules) +# Changesets — write the file, don't run the CLI (both scripts are interactive prompts) +pnpm changeset status --since=origin/main # what CI checks; run this to verify ``` +Write `.changeset/.md` directly, in the **repo root** `.changeset/` (never +`packages//.changeset/`). Package change: + +```md +--- +'@clerk/nextjs': patch +--- + +Remove the redundant `https://*.client.protect.clerk.com` source from CSP headers generated by `clerkMiddleware()`. +``` + +Repo/tooling-only or private-package-only change — the file is exactly two delimiters, nothing else: + +```md +--- +--- +``` + +`@clerk/swingset`, `@clerk/msw`, and `@clerk/headless` are private and never appear in a changeset; +every other `packages/*` entry publishes, **including `@clerk/ui` and `@clerk/clerk-js`**. Bump +selection, which packages to list, body-writing rules, and the major-version gate are in +[`references/changesets.md`](references/changesets.md). + Test runner differs by package (`shared`, `clerk-js`, most adapters use vitest; `backend` runs a multi-runtime suite), but the `pnpm --filter test` invocation is uniform. @@ -106,10 +128,11 @@ Each rule below restates `AGENTS.md`; the parenthetical is how it is enforced. - **pnpm only, Node `>=24.15`, pnpm `>=10.33`.** (`preinstall` blocks npm/yarn; `engines` in `package.json`.) -- **Every PR needs a changeset.** Use `pnpm changeset` for anything that affects a published package. - Use `pnpm changeset:empty` for repo/tooling-only changes; an empty changeset is **two `---` - delimiters with no body** (local `CLAUDE.local.md` convention). A changeset is a changelog entry - for users upgrading, not a summary of the diff. (CI fails PRs missing a changeset.) +- **Every PR needs a changeset.** Write the file directly; the `pnpm changeset` / + `pnpm changeset:empty` scripts are interactive prompts an agent cannot drive. A changeset is a + changelog entry for users upgrading, not a summary of the diff. See + [`references/changesets.md`](references/changesets.md). (CI runs + `pnpm changeset status --since=origin/main` and fails PRs missing one.) - **Conventional commit `type(scope):`, scope is mandatory.** Enforced on the **PR title** (`.github/workflows/pr-title-linter.yml`), not on individual commits. There is no local `commit-msg` hook. Valid `scope` = any `packages/*` short name **and** its `clerk-`-stripped form @@ -128,7 +151,9 @@ Each rule below restates `AGENTS.md`; the parenthetical is how it is enforced. 1. Branch off `main`. 2. Make the change in the right package(s); add/update unit tests next to the code. -3. `pnpm changeset` (or `pnpm changeset:empty`). +3. Write `.changeset/.md` (see above, and + [`references/changesets.md`](references/changesets.md)), then `git add` it. An unstaged changeset + fails CI the same as a missing one. 4. Verify locally: `pnpm build`, `pnpm test` (or the filtered forms above), `pnpm lint`, `pnpm format:check`. 5. Open the PR; the title must be a valid conventional commit (it becomes the squash commit). Fill in @@ -162,7 +187,8 @@ Full decision matrix: `@clerk/ui` appearance/theming system. - Bundled: [`setup-and-footguns.md`](references/setup-and-footguns.md), [`package-map.md`](references/package-map.md), - [`breaking-changes.md`](references/breaking-changes.md). + [`breaking-changes.md`](references/breaking-changes.md), + [`changesets.md`](references/changesets.md). Analyzing or coordinating a **release PR** (the "Version packages" PR) is out of scope for this skill; the release process lives in `docs/PUBLISH.md` and `docs/CICD.md`. Clerk employees may also diff --git a/.claude/skills/clerk-monorepo/references/changesets.md b/.claude/skills/clerk-monorepo/references/changesets.md new file mode 100644 index 00000000000..b38a145497b --- /dev/null +++ b/.claude/skills/clerk-monorepo/references/changesets.md @@ -0,0 +1,147 @@ +# Writing a changeset in clerk/javascript + +Reference for the `clerk-monorepo` skill. The file format and the private-package list are inlined +in `SKILL.md`; this covers bump selection, which packages to list, body-writing, and verification. + +Every PR needs a changeset. CI runs `pnpm changeset status --since=origin/main` and fails the PR +when a changed published package has no entry. + +**Write the file directly with the Write tool.** Do not run `pnpm changeset` or `pnpm changeset:empty`. +Both are interactive prompts that cannot be driven non-interactively. Those scripts exist for +humans; the file they produce is the only thing that matters, and it is plain markdown. + +## Where the file goes + +`.changeset/.md`, in the **repo root** `.changeset/` directory. + +Never `packages//.changeset/`. There is one changeset directory for the whole workspace, and a +file placed under a package is invisible to changesets and to CI. + +The generated names from the CLI (`shiny-words-lay.md`) are fine but unhelpful. Prefer a name that +describes the change: `avoid-domain-loading-shift.md`, `drop-expo-sdk-53.md`. + +## The two forms + +### Package changeset + +For any change to a **published** package: + +```md +--- +'@clerk/nextjs': patch +--- + +Remove the redundant `https://*.client.protect.clerk.com` source from CSP headers generated by `clerkMiddleware()`. +``` + +Multiple packages in one change get one entry each, under the same delimiters: + +```md +--- +'@clerk/expo': major +'@clerk/expo-passkeys': major +--- + +Drop support for Expo SDK 53. The minimum supported version is now Expo SDK 54. +``` + +Single quotes around the package name. A blank line after the closing `---`, then the body. + +### Empty changeset + +For changes that touch **no published package**: tooling, CI, repo config, docs, or a private +package. The whole file is exactly two delimiters and nothing else: + +```md +--- +--- +``` + +Write it with the Write tool like any other file. If you must use the shell, +`printf -- '---\n---\n' > .changeset/.md` produces the same bytes, but there is no reason to +verify it with `cat -A` or `od -c` afterwards. The format has no hidden characters. + +## Which packages to list + +List every published package whose **own** source you changed. Do not list dependents: changesets +bumps them automatically (`updateInternalDependencies: patch` in `.changeset/config.json`), so +listing `@clerk/nextjs` because you changed `@clerk/shared` is wrong and inflates its release notes. + +Private packages never appear in a changeset. In this repo the private ones are: + +| Package | Status | +| ----------------- | ---------------------------- | +| `@clerk/swingset` | private, component workbench | +| `@clerk/msw` | private, test mocks | +| `@clerk/headless` | private | + +Everything else under `packages/*` is published, **including `@clerk/ui` and `@clerk/clerk-js`**. +You do not need to check the `private` field; if it is not one of the three above, it publishes. + +If a change touches only private packages, that is an empty changeset. + +## Choosing the bump + +| Bump | When | +| ------- | ------------------------------------------------------------------------------------------------------- | +| `patch` | Bug fix, internal refactor, style/visual change, perf work. No API surface change. | +| `minor` | New export, new option, new component, added behavior. Existing callers keep working untouched. | +| `major` | Removing or renaming a public export, changing a signature, or any behavior existing callers depend on. | + +`major` is not a solo decision. It requires the `!allow-major` label and passes +`.github/workflows/major-version-check.yml`. Before writing one for `clerk-js` or `ui`, run the +breaking-change check in the `clerk-monorepo` skill. A new `clerk-js` runtime loads into apps +pinned to older framework SDKs, so a removal breaks production for users who never upgraded. + +APIs prefixed `__internal_` / `__experimental_`, or exported from an `/experimental` subpath, carry +no SemVer guarantee. Changing them is a `patch`. + +## Writing the body + +The body is a changelog entry read by someone upgrading the package. It is not a summary of your +diff. The reader has no idea which files you touched and does not care. + +Write what changed for a consumer and what, if anything, they must do about it. + +Good: + +> Fix a crash on Android in Expo Go where rendering `` failed with +> `Cannot find native module 'ClerkExpo'`, even for JavaScript-only flows that use no native components. + +Bad: + +> Refactor `ClerkExpoModule` resolution in `native-module.ts` to use optional requires and update +> the corresponding tests. + +Rules that hold across the repo's history: + +- Lead with the user-visible effect, in the present tense: "Fix…", "Add…", "Remove…", "Drop support for…". +- Name public API in backticks. Never name internal file paths. +- For `major`, the body **must** tell the reader how to migrate. Include a short before/after code + block when the migration is not obvious from prose alone. +- One or two sentences is normal. Length is earned by migration steps, not by detail about the work. + +## Verify + +```bash +# Exactly what CI runs. Reports the bumps your changesets produce. +pnpm changeset status --since=origin/main +``` + +Then confirm the file is staged. An unstaged changeset fails CI the same as a missing one: + +```bash +git status --short .changeset/ +``` + +## Amending an existing changeset + +If the branch already has a changeset and you are adding to the same change, edit that file rather +than adding a second one. Two changesets for one package produce two bullet points in the changelog +for what the reader experiences as a single change. + +Check first: + +```bash +git diff --name-only origin/main...HEAD -- .changeset/ +``` diff --git a/.claude/skills/clerk-monorepo/references/package-map.md b/.claude/skills/clerk-monorepo/references/package-map.md index a263c4d74d8..450f895e64c 100644 --- a/.claude/skills/clerk-monorepo/references/package-map.md +++ b/.claude/skills/clerk-monorepo/references/package-map.md @@ -41,7 +41,7 @@ backwards-compatibility contract (see `breaking-changes.md`). | `@clerk/msw` | tooling | | MSW request handlers for mocking the Clerk API in tests. Private (not published). | | `@clerk/swingset` | tooling | | Component explorer for `@clerk/ui`'s Mosaic design system. Private (not published). | | `@clerk/upgrade` | tooling | | CLI codemod tool for upgrading consumers between SDK versions. | -| `@clerk/eslint-plugin` | tooling | | ESLint plugin enforcing Clerk patterns across JavaScript frameworks (lint rules shipped to apps). Published. | +| `@clerk/eslint-plugin` | tooling | | ESLint plugin enforcing Clerk patterns across JavaScript frameworks (lint rules shipped to apps). Published. | Tests: `pnpm turbo test --filter=@clerk/` (or `pnpm --filter @clerk/ test` after a build). Most packages use vitest; `@clerk/backend` runs a multi-runtime suite (node + edge +