Skip to content
Draft
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
308 changes: 147 additions & 161 deletions DEVELOPMENT.md
Original file line number Diff line number Diff line change
@@ -1,19 +1,42 @@
# Development Documentation

## Quick reference

| Task | Command |
| ----------------------------- | ---------------------------- |
| Run tests | `npm test` or `npm run test` |
| Tests + coverage | `npm run test-coverage` |
| Lint | `npm run lint` |
| Lint and fix | `npm run lint-fix` |
| Build | `npm run build` |
| Run from source | `npm run dev` |
| Link package | `npm link` (see Build) |
| Run knip (for unused imports) | `npm run knip` |

## Testing and Code Coverage

### Running Tests
### Run our test suite

The project uses Vitest for testing. To run the test suite you can use:
The project uses Vitest to write and run tests. To run the test suite use the
command:

```bash
npm test
```

This runs all tests with code coverage analysis enabled.
That runs all tests without a coverage report. If you want coverage (handy for
seeing what's covered and what's now), use `npm run test-coverage`—see Coverage
reports below.

If you like running tests from VS Code, install the Vitest extension or use the
built-in test runner so your tests are discovered and you can debug in the IDE.

When you run the test suite it will default to running all tests and we continue
to run in "watch" mode. This means that if you make changes to the code, the
tests will automatically re-run.

Note: if you want to run your tests locally in VS Code using the interactive
IDE, be sure to install the Jest (orta) extension so your tests are discovered.
To exit the watch mode, you can press `Ctrl+C` or `Command+C` depending on your
operating system. Or hit `q` which prompt vitest to exit.

### Test Configuration

Expand All @@ -33,226 +56,189 @@ configuration file:

Tests will fail if coverage drops below these thresholds.

TODO: We likely want much higher coverage than this given the use of the project
but for now we are slowly refactoring and improving coverage as we go.

### Coverage Reports

Run `test-coverage` to generate coverage reports in the `coverage/` directory:
Run `npm run test-coverage` to generate coverage reports in the `coverage/`
directory:

- **`coverage/lcov-report/index.html`** — Interactive HTML report (open in
browser)
- **`coverage/lcov-report/index.html`** — Interactive HTML report (open in a
browser to explore)
- **`coverage/lcov.info`** — LCOV format (used by Codecov)

The HTML report provides a visual breakdown of which files are covered by tests.
The HTML report gives you a visual breakdown of which files are covered by
tests.

### Codecov Integration

The project uses Codecov to track code coverage over time and on pull requests.

#### CI Integration
#### CI integration

- The GitHub Actions workflow (`.github/workflows/test-deploy.yml`)
automatically uploads coverage to Codecov after running tests
- The GitHub Actions workflow (`.github/workflows/test-deploy.yml`) uploads
coverage to Codecov after running tests
- Coverage reports appear as comments on pull requests (if enabled)
- The workflow uses `codecov/codecov-action@vxxx` (whatever version is most
recent) to upload the `lcov.info` file

#### Configuration

Codecov behavior is configured in `.codecov.yml`:
Codecov is configured in `.codecov.yml`:

- Patch coverage is tracked (checks coverage of changed code in PRs)
- Project-level status checks are disabled
- PR comments are disabled
- Codecov now requires a token for all uploads so we have one generated in the
repo as a secret `secrets.CODECOV_TOKEN`.
repo as a secret `secrets.CODECOV_TOKEN`. This token was generated by
`@lwasser` but it can be easily regenerated if a maintianer needs to do that.

#### Local Usage
#### Local usage

You don't need a Codecov account to view coverage locally—just run `pnpm test`
and open the HTML report. Codecov integration is primarily for tracking coverage
trends and to simplify PR reviews in the CI/CD pipeline.
You don't need a Codecov account to view coverage locally. Just run
`npm run test-coverage` and open the HTML report. The Codecov integration is
mainly for tracking coverage over time and for PR reviews in CI.

## Linting

2026 update: The project is now using ESLint 9 and the flat config format. As we
update things from the old config we will update this document to reflect the
changes. Once the code base is stable and things work, we can delete some of the
historical documentation. But for now this document can help us track design
decisions and changes made.

### Overview

The project uses a native ESLint configuration that was migrated from
kcd-scripts. The setup supports both CommonJS source files and ES module test
files, using ESLint 9's flat config format.

### What changed

Previously, the project used `kcd-scripts` for linting configuration. Because
that project is no longer actively maintained, we migrated to a native ESLint
setup that replicates the same behavior.

**Migration details:**

- Replaced `kcd-scripts lint` with native ESLint
- Created `eslint.config.mjs` using ESLint 9 flat config format
- Extracted rules from `eslint-config-kentcdodds` to create a simplified,
maintainable config. This included removing a lot of the custom rules and
leaning more on the recommended rules.
- Preserved some custom rule overrides from the original setup
- Supports both CommonJS (source files) and ES modules (test files)

Note: We should consider refacting the project to use ES modules instead of
CommonJS (if everyone supports that) in the future for consistency.
The project uses ESLint 9 with a native configuration (we migrated from
kcd-scripts). The setup supports both CommonJS source files and ES module test
files.

### How to run the linter

To check for linting errors you can run:
To check for linting errors:

```bash
npm run lint
```

ESlint can automatically fix some errors. to fix errors automatically run:
To fix many issues automatically (recommended before committing):

```bash
npm run lint -- --fix
npm run lint-fix
```

The `--fix` flag will automatically fix many common issues like:
The fix command handles a lot of common issues, for example:

- Vitest method aliases (e.g., `toThrowError()` → `toThrow()`)
- Vitest method aliases (e.g. `toThrowError()` → `toThrow()`)
- Unused eslint-disable directives
- Some formatting issues
- Many formatting issues

**Note:** Some errors require manual fixes (e.g., unused variables, tests
without assertions).
Some things still need manual fixes (e.g. unused variables, tests without
assertions), but lint-fix will get you most of the way there.

### Pre-commit hooks

The project uses Husky to run pre-commit hooks that automatically lint and fix
staged files before each commit.
We use Husky so that staged files are linted and fixed before each commit. That
way the codebase stays consistent without you having to remember to run lint
every time.

#### How it works:
1. When you run `git commit`, Husky runs `lint-staged`.
2. `lint-staged` runs `eslint --fix` and Prettier on staged JS/TS files.
3. If everything passes, the commit goes through; if not, the commit is blocked
so you can fix the issues.

1. When you run `git commit`, Husky intercepts the commit
2. The pre-commit hook runs `lint-staged`
3. `lint-staged` runs `eslint --fix` on all staged JavaScript/TypeScript files
4. If linting passes, the commit proceeds; if it fails, the commit is blocked
If the hook fails, run `npm run lint-fix` (see above) and address any remaining
errors. If you're in a pinch, you can skip the hook once with
`git commit --no-verify`—but if you do that, please mention it in your PR so a
maintainer can help you get things passing.

What this means... this workflow ensures that code is automatically linted and
fixed before it's committed to version control.
Husky and `lint-staged` are configured in `package.json`; the ESLint config
lives in `eslint.config.mjs`.

If you are encountering issues with the pre-commit hook, you can run the
following command to manually lint and fix the files:
### Dependencies

These are the main packages we use for linting:

- **eslint** — ESLint core
- **@eslint/js** — Recommended rules
- **eslint-plugin-import** — Import/export rules and module resolution
- **@vitest/eslint-plugin** — Vitest-specific rules for test files
- **globals** — Global variables for Node and Vitest environments

### Configuration file

The ESLint config is in `eslint.config.mjs` at the project root. It uses ESLint
9's flat config format and includes:

- Base rules from `eslint:recommended`
- Import plugin rules
- Vitest plugin rules (for test files only)
- Custom overrides for project-specific needs
- Support for both CommonJS and ES module syntax

## Build and local testing

The build has been migrated from `kcd-scripts build` to native Babel. After a
build, the CLI runs from the `dist/` directory.

### How to build

From the repo root:

```bash
npm run lint -- --fix
npm run build
```

Or if it's really problematic, you can skip verification and commit anyway with
the `--no-verify` flag.
That compiles `src/` into `dist/` using Babel (see `babel.config.js`). The entry
point is `dist/cli.js`; you can also run it with `npm start`.

### Running from source (no build)

When you're developing, you often don't need to build. You can run the CLI from
source instead:

```bash
git commit --no-verify
npm run dev
```

If you do this please ping one of the maintainers in the PR that you open so
they can help you fix the issues!
That runs `./src/cli.js` directly. Use it when you're iterating; use the built
package when you want to test the same code path that users get after they
install from npm.

**Configuration:**
### Linking the package locally

- Husky configuration is in `package.json` under the `husky.hooks.pre-commit`
field
- `lint-staged` configuration is in `package.json` under the `lint-staged` field
- The setup uses the native ESLint configuration (`eslint.config.mjs`)
If you want to install the cli as if you'd installed it from npm you can do so
by linking the package locally.

**Note:** Previously, the project used `kcd-scripts pre-commit` which handled
both hook management and linting. We migrated to using `lint-staged` directly
with our native ESLint config to avoid version conflicts.
1. In the CLI repo root, build and link the project:
```bash
npm run build
npm link
```
2. In another directory (any repo that has a `.all-contributorsrc`, or wherever
you want to run the binary), the global `all-contributors` command will now
use your built package. run:

### Dependencies
```bash
all-contributors add <username> code
all-contributors generate
```

The following packages are typical starters for linting a JavaScript project:
3. When you're done, you can unlink with `npm unlink -g all-contributors-cli` in
the CLI repo, and reinstall the real package elsewhere if you need to.

- **eslint** — ESLint core
- **@eslint/js** — ESLint recommended rules configuration
- **eslint-plugin-import** — Import/export rules for module resolution and best
practices
- **@vitest/eslint-plugin** — Vitest-specific rules for test files
- **globals** — Provides properly formatted global variables for Node.js and
Vitest environments
### Testing the linked package

### Configuration file
Once you've run `npm link`, switch to another directory and run
`all-contributors` commands as you normally would. Confirm that behavior matches
what you expect.

The ESLint configuration is in `eslint.config.mjs` at the project root. It uses
ESLint 9's flat config format and includes:
Remember: after you change code in the CLI repo, run `npm run build` again
before testing from the other directory. The link points at the built `dist/`
output, so your latest changes won't show up until you rebuild.

- Base rules from `eslint:recommended`
- Import plugin rules
- Vitest plugin rules (for test files only)
- Custom overrides for project-specific needs
- Support for both CommonJS and ES module syntax
This workflow is a great way to double-check the built artifact and the exact
code path users get when they install the package.

### Build dependencies (migration notes)

We removed most of the build dependencies that kcd-scripts used; they're
redundant with current Node.

Removed: `@babel/plugin-transform-class-properties`, `babel-plugin-macros`,
`semver`, `@babel/runtime`.

## Build system and process migration

Our build system has been migrated from `kcd-scripts build` to native Babel.
Below are the dependencies we add for the new native build and a description of
what each does.

For now we are keeping _some_ of the dependencies from the kcd-scripts build
system. We might be able to remove others as we refine the build process and
better get to know the project.

### Build dependencies (from kcd-scripts → native Babel)

- **@babel/core**
- **Status:** Required
- **Purpose:** The Babel compiler. Performs the actual transpilation (parse →
transform → generate).

- **@babel/cli**
- **Status:** Required
- **Purpose:** Command-line interface. Runs `babel src --out-dir dist` and
feeds files to @babel/core.

- **@babel/preset-env**
- **Status:** Required
- **Purpose:** Preset that compiles modern JS to match a target environment
(e.g. Node 22). Handles syntax and, optionally, module format.

- **@babel/plugin-transform-runtime**
- **Status:** REMOVED
- **Purpose:** Replaces inlined Babel helpers with
`require('@babel/runtime/...')` so helpers live in one place. Keeps dist
smaller and avoids duplicating helper code in every file.

- **@babel/plugin-transform-modules-commonjs**
- **Status:** REMOVED
- **Purpose:** Converts ES module syntax to CommonJS. Only needed if
preset-env is set with `modules: false`; if preset-env uses
`modules: 'commonjs'`, this plugin is redundant.

- **@babel/plugin-transform-class-properties**
- **Status:** REMOVED
- **Purpose:** Transpiles class properties (including static) in loose mode.

- **babel-plugin-macros**
- **Status:** Removed
- **Purpose:** Enables macro-based transforms (e.g. preval, codegen).
kcd-scripts includes it by default. We don't use macros in this project.

- **semver**
- **Status:** Removed
- **Purpose:** Used in a config helper to read `engines.node` from
package.json and derive the target Node version. We hardcode our target
(e.g. `node: '22.22.0'`) in babel.config.js instead.

- **@babel/runtime**
- **Status:** Required
- **Purpose:** Already a production dependency. Provides the helper functions
that `@babel/plugin-transform-runtime` injects imports for. Required at
runtime when using that plugin.

IMPORTANT: there are still 2 bugs in the cli that i don't want to fix in this PR
but the cli is broken. We can fix these bugs in a future pr and also add tests
that will catch these bugs in the future.
Kept: `@babel/cli`, `@babel/core` (and any others you see in `package.json`).
Loading
Loading