Skip to content

Latest commit

 

History

History
30 lines (19 loc) · 6.23 KB

File metadata and controls

30 lines (19 loc) · 6.23 KB

Contributing to documents.js

Conventional commits, enforced by commitlint through a commit-msg hook; the same hook also rejects a commit message carrying a Claude-Session: trailer, since that line is agent bookkeeping rather than project history. pre-commit additionally rejects a merge commit on main and a squash merge (detected via a lingering SQUASH_MSG) — history stays linear by rebasing, never merging or squashing — before running ESLint over staged files in the package that owns them. pre-push runs typecheck and unit tests across the workspace, both through turbo, so the cost is proportional to what actually changed. Work in whichever package's directory the change belongs to; each package's own README documents its architecture and the format-specific decisions behind it.

See the root README for how the workspace, task pipeline, and release orchestration fit together before making a structural change.

Formatting is Prettier's, enforced through ESLint rather than a separate command: prettier/prettier is an ordinary lint rule, so pnpm lint reports a formatting difference and the pre-commit hook fixes staged files automatically. prettier.config.ts sets one option (singleQuote: false); everything else is Prettier's own default deliberately, so the config states a decision rather than restating defaults.

One local step is worth doing once per clone:

git config blame.ignoreRevsFile .git-blame-ignore-revs

.git-blame-ignore-revs lists the whole-tree reformat commits, so git blame attributes each line to the change that actually wrote it rather than to the reformat. Git does not read the file on its own — the config above is what opts in. GitHub's blame view reads it automatically, so this only affects blame run locally.

Adding a package to the workspace

Bringing a new package into packages/* — whether newly written or absorbed from a former standalone repo — needs more than a directory and a pnpm-workspace.yaml glob match to release cleanly. In order:

  1. Create packages/<name>/ with its own package.json, matching the sibling scripts convention: build/_build, lint/_lint, typecheck/_typecheck (plus _typecheck:node if the package needs a second tsconfig.node.json pass), test/_test, and the rest of turbo.json's task names. pnpm-workspace.yaml's packages: ["packages/*"] picks it up automatically — no workspace-file edit needed just for registration.

  2. Declare sibling dependencies as ordinary semver ranges, never workspace:*linkWorkspacePackages: true links the workspace copy whenever the range is satisfied, falling back to the registry otherwise (see the root README's Dependency ranges between packages).

  3. Add the package's bare name to pnpm-workspace.yaml's minimumReleaseAgeExclude list. Without it, pnpm install --frozen-lockfile (the mode CI runs) can reject the package's own same-day release the moment a sibling bumps to depend on it.

  4. Give it an eslint.config.ts that calls packageLintConfig from the root's eslint.shared.ts, passing tsconfigRootDir: import.meta.dirname and — if the package is Worker-isomorphic, as every foundation and format-codec package is (see the root README's Conventions) — isomorphic: true, plus a test:workers suite proving it at runtime. Don't copy a sibling's config and don't restate the Node-builtin ban: the shared helper derives that ban from node:module's own builtinModules, so a package opts in with one flag rather than a hand-maintained module list that silently rots as Node adds builtins. Anything genuinely specific to the package goes through the helper's own options (additionalRestrictedImportPatterns, additionalIgnores, barrelPolicy, nonNullAssertion, …), not a second config block: flat config replaces a same-key rule rather than merging it, so a second no-restricted-imports of your own would silently switch the isomorphism ban back off while still reporting your rule as working.

  5. If the package publishes and exposes types, add "_typecheck:attw": "attw --pack" to its scripts and include _typecheck:attw in its own typecheck script's turbo run list. Turbo silently skips a package missing the script — no error, no warning — so a package that forgets this step gets no export-resolution check at all, forever, until someone happens to notice.

  6. Configure the package's npm trusted publisher to point at this repository's own .github/workflows/ci.yml, not wherever it published from before. For a package absorbed from a former standalone repo, this means updating whichever trusted-publisher configuration it already had on npmjs.com — OIDC publishing from this workflow fails outright (ENONPMTOKEN No npm token specified) until it's updated, and because packages release in one topological pass that aborts on the first failure, a misconfigured package sitting early in that order blocks every other package's release too, not just its own. Do this before the package's first release attempt here, not after.

    A genuinely new package (never published anywhere before) hits a different version of the same problem: npm has no settings page to configure a trusted publisher on until the package has been published at least once, so its very first release-job publish attempt fails outright with an OIDC token exchange 404, then ENONPMTOKEN on the fallback, since this workspace configures no NPM_TOKEN to fall back to. The npm registration CI job (.github/scripts/check-npm-registration.ts) catches this on the pull request that adds the package, rather than letting it surface as a red post-merge Release job: it fails whenever a package the pull request's own diff touches has never been published. Bootstrap the package with a one-time manual npm publish from an authenticated maintainer account (or the setup-npm-trusted-publish tool), then configure trusted publishing on its new npmjs.com settings page exactly as above, before merging.

  7. List the package in the root README's package table (and its Worker-isomorphic list in Conventions, if applicable), and run pnpm install at the root so the lockfile picks it up.