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
23 changes: 21 additions & 2 deletions packages/uncheck/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,15 +24,15 @@
`uncheck` lints, format checks and type checks your project with one command, and keeps a monorepo consistent. It runs the tools your project already has, so you, your git hooks and your coding agents all run the same check.

```sh
npm i -D uncheck oxlint oxfmt typescript # add sherif in a monorepo
npx uncheck init # install the tools, add the scripts and hooks

npx uncheck # check everything
npx uncheck --fix # fix what can be fixed, report the rest
npx uncheck prepare --pre-commit # check every commit
npx uncheck hooks install claude # check every agent turn
```

uncheck needs Node 22.20 or later. Install only the tools you want: a check runs when its tool is installed and is skipped otherwise, except that a `tsconfig.json` without TypeScript installed fails. uncheck always uses the versions you installed.
uncheck needs Node 22.20 or later. Install only the tools you want, with [`init`](#set-up-a-project) or by hand (`npm i -D uncheck oxlint oxfmt typescript`, plus sherif in a monorepo): a check runs when its tool is installed and is skipped otherwise, except that a `tsconfig.json` without TypeScript installed fails. uncheck always uses the versions you installed.

| Check | Checks | Runs when |
| -------- | -------------------- | -------------------------------------------------------------------------------------------------- |
Expand All @@ -41,6 +41,25 @@ uncheck needs Node 22.20 or later. Install only the tools you want: a check runs
| `oxfmt` | formatting | [oxfmt](https://oxc.rs) is installed |
| `tsc` | types | the project has a `tsconfig.json` |

## Set up a project

```sh
npx uncheck init # or pnpm dlx, yarn dlx, bunx
```

`init` sets up the folder it runs in, a new project or an existing one, and installs with the package manager the project uses: the one its `packageManager` field or lockfile names, or else the one that started `init`. It asks:

1. **Which tools to install:** oxlint and oxfmt, plus sherif at a [workspace root](#monorepos). uncheck itself is always installed.
2. **Which tools get a preset config:** oxlint and oxfmt can start from the [presets](#presets) when they have no config yet.
3. **Whether to check every commit:** it adds the [`prepare` script](#run-it-before-every-commit) (`postinstall` with Yarn 2+) and writes the hook.
4. **Which agents run uncheck** [after every turn](#run-it-after-every-agent-turn).

It also adds a `check` script (`uncheck`) and a `fix` script (`uncheck --fix`), unless the project has scripts with those names. Running `init` again only sets up what is missing. A `prepare` script that already runs `uncheck prepare` keeps its hook, and agents that already run uncheck keep their flags: change those in the `prepare` script or with `uncheck hooks install`.

`npx uncheck init --yes` takes the default answers, which it needs without a terminal. It installs the missing tools, checks every commit, and sets up the agents whose folders the project has, such as `.claude`, that do not run uncheck yet. It writes no preset config.

In a monorepo, run `init` at the workspace root to set up every package at once. TypeScript is left to you: tsc runs once the project has a `tsconfig.json` and TypeScript is installed.

## Check your project

```text
Expand Down
21 changes: 4 additions & 17 deletions packages/uncheck/src/checks/sherif.ts
Original file line number Diff line number Diff line change
@@ -1,22 +1,18 @@
import process from 'node:process'

import { Effect, FileSystem, Path, Predicate } from 'effect'
import { Effect, Path, Predicate } from 'effect'

import { CannotCheck, NothingToCheck } from '../errors'
import { readJson } from '../files'
import { isWorkspaceRoot, readJson } from '../files'
import { resolveBin } from '../tool'
import type { Check } from '../types'

const WORKSPACE_FILES = new Set(['package.json', 'pnpm-workspace.yaml'])

// pnpm also keeps its settings in pnpm-workspace.yaml, and sherif fails on one without packages.
const DECLARES_PACKAGES = /^["']?packages["']?\s*:/m

export const sherif: Check = {
name: 'sherif',
fixes: 'workspace',
plan: Effect.fn(function* ({ cwd, fix, files, deleted }) {
const fs = yield* FileSystem.FileSystem
const path = yield* Path.Path

const bin = yield* resolveBin('sherif', cwd)
Expand All @@ -34,22 +30,13 @@ export const sherif: Check = {
)
}

const [manifest, pnpmWorkspace] = yield* Effect.all(
[
readJson(path.join(cwd, 'package.json')),
fs.readFileString(path.join(cwd, 'pnpm-workspace.yaml')).pipe(
Effect.map((text) => DECLARES_PACKAGES.test(text)),
Effect.orElseSucceed(() => false),
),
],
{ concurrency: 'unbounded' },
)
const manifest = yield* readJson(path.join(cwd, 'package.json'))

if (manifest === undefined) {
return yield* Effect.fail(new NothingToCheck({ reason: 'no package.json found' }))
}

if (manifest.workspaces === undefined && !pnpmWorkspace) {
if (!(yield* isWorkspaceRoot(cwd, manifest))) {
return yield* Effect.fail(new NothingToCheck({ reason: 'not a workspace root' }))
}

Expand Down
6 changes: 1 addition & 5 deletions packages/uncheck/src/checks/tsc.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { CannotCheck, NothingToCheck } from '../errors'
import {
ancestors,
fileKind,
firstFile,
isOutside,
listProjectFiles,
readJson,
Expand Down Expand Up @@ -813,11 +814,6 @@ const loadExtendsChain = Effect.fn(function* (
return [...chains.flat(), { file: configPath, dir: path.dirname(configPath), raw }]
})

const firstFile = (candidates: ReadonlyArray<string>) =>
Effect.findFirst(candidates, (candidate) =>
Effect.map(fileKind(candidate), (type) => type === 'File'),
)

const resolveExtends = Effect.fn(function* (spec: string, dir: string) {
const path = yield* Path.Path

Expand Down
3 changes: 2 additions & 1 deletion packages/uncheck/src/cli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { CliConfig, CliError, CliOutput, Command, GlobalFlag } from 'effect/unst

import pkg from '../package.json'
import { hooks } from './commands/hooks'
import { init } from './commands/init'
import { prepare } from './commands/prepare'
import { staged } from './commands/staged'
import { uncheck } from './commands/uncheck'
Expand All @@ -21,7 +22,7 @@ for (const stream of [process.stdout, process.stderr]) {
// (the shell forwards it, then the kernel), so `once` is not enough.
process.on('SIGHUP', () => process.kill(process.pid, 'SIGTERM'))

Command.run(uncheck.pipe(Command.withSubcommands([staged, prepare, hooks])), {
Command.run(uncheck.pipe(Command.withSubcommands([init, staged, prepare, hooks])), {
version: pkg.version,
}).pipe(
Effect.catchTag('CheckFailed', () =>
Expand Down
Loading
Loading