Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
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
5 changes: 5 additions & 0 deletions .changeset/soft-turtles-knip.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@ankhorage/devtools': patch
---

Add a shared Knip config export and CI validation support.
8 changes: 8 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,14 @@ jobs:
echo "No format:check script found; skipping."
fi

- name: Run Knip
run: |
if node -e "const p=require('./package.json'); process.exit(p.scripts?.knip ? 0 : 1)"; then
bun run knip
else
echo "No knip script found; skipping."
fi

- name: Run tests
run: |
if node -e "const p=require('./package.json'); process.exit(p.scripts?.test ? 0 : 1)"; then
Expand Down
69 changes: 67 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
# devtools

Shared ESLint and Prettier configuration for modern TypeScript projects.
Shared ESLint, Prettier, and Knip configuration for modern TypeScript projects.

## What you get

- Consistent linting across repos
- Zero-config Prettier setup
- Shared Knip static-analysis defaults
- Strict TypeScript rules without compromise
- One source of truth for tooling

Expand All @@ -14,6 +15,7 @@ Shared ESLint and Prettier configuration for modern TypeScript projects.
- Flat ESLint config (latest standard)
- Preconfigured plugin ecosystem
- Prettier integration
- Shared Knip config factory
- Monorepo-friendly

## Installation
Expand All @@ -22,6 +24,12 @@ Shared ESLint and Prettier configuration for modern TypeScript projects.
bun add -D @ankhorage/devtools
```

Install the tools used by your local scripts as development dependencies:

```bash
bun add -D eslint prettier knip
```

## Usage

### ESLint
Expand All @@ -40,15 +48,71 @@ export default config();
}
```

### Knip

Create a repo-local `knip.config.ts` file:

```ts
import { createKnipConfig } from '@ankhorage/devtools/knip';

export default createKnipConfig();
```

Add a package script:

```json
{
"scripts": {
"knip": "knip"
}
}
```

Repos can add narrow repo-specific patterns when needed:

```ts
import { createKnipConfig } from '@ankhorage/devtools/knip';

export default createKnipConfig({
entry: ['scripts/release.ts'],
project: ['scripts/**/*.ts'],
ignore: ['fixtures/**'],
});
```

The shared config intentionally keeps defaults narrow so Knip does not fail on unmatched optional paths. Prefer explicit `entry`, `project`, or Knip plugin configuration over broad ignores when tool config files are reported as unused.

### CI

Run Knip in CI after dependencies are installed:

```yaml
- name: Run Knip
run: bun run knip
```

For workflows that support optional scripts, use the same guard style as the other devtools checks:

```yaml
- name: Run Knip
run: |
if node -e "const p=require('./package.json'); process.exit(p.scripts?.knip ? 0 : 1)"; then
bun run knip
else
echo "No knip script found; skipping."
fi
```

## Use Cases

- Monorepos with shared standards
- Teams that want strict, predictable linting
- Projects avoiding duplicated config
- Repos that need consistent unused-file, unused-export, and dependency checks

## Why this exists

Maintaining ESLint + Prettier configs across multiple repositories leads to:
Maintaining ESLint, Prettier, and Knip configs across multiple repositories leads to:

- duplication
- inconsistency
Expand All @@ -62,6 +126,7 @@ Includes:

- ESLint configuration
- Prettier configuration
- Knip configuration

Excludes:

Expand Down
133 changes: 128 additions & 5 deletions bun.lock

Large diffs are not rendered by default.

9 changes: 9 additions & 0 deletions knip.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import { createKnipConfig } from './src/knip.js';

export default createKnipConfig({
ignoreFiles: [
'examples/monorepo/eslint.config.mjs',
'examples/package/eslint.config.mjs',
'examples/package/prettier.config.cjs',
],
});
8 changes: 8 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,15 +18,21 @@
"typescript",
"eslint",
"prettier",
"knip",
"linting",
"formatting",
"static-analysis",
"developer-tools"
],
"exports": {
"./eslint": {
"types": "./dist/eslint.d.ts",
"import": "./dist/eslint.js"
},
"./knip": {
"types": "./dist/knip.d.ts",
"import": "./dist/knip.js"
},
"./prettier": {
"require": "./dist/prettier.cjs"
}
Expand All @@ -39,6 +45,7 @@
],
"scripts": {
"build": "tsc && cp src/prettier.cjs dist/prettier.cjs",
"knip": "knip",
"lint": "eslint .",
"format": "prettier --write .",
"format:check": "prettier --check .",
Expand All @@ -60,6 +67,7 @@
"@changesets/cli": "^2.30.0",
"@types/bun": "^1.3.13",
"@types/node": "^25.2.3",
"knip": "^6.12.2",
"typescript": "^5.9.3"
},
"packageManager": "bun@1.3.13"
Expand Down
27 changes: 27 additions & 0 deletions src/knip.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import { describe, expect, it } from 'bun:test';

import { createKnipConfig } from './knip.js';

describe('createKnipConfig', () => {
it('returns an empty config by default so Knip can use zero-config discovery', () => {
const config = createKnipConfig();

expect(config).toEqual({});
});

it('uses explicit repo-specific config when provided', () => {
const config = createKnipConfig({
entry: ['scripts/release.ts'],
project: ['scripts/**/*.ts'],
ignore: ['fixtures/**'],
ignoreDependencies: ['optional-package'],
ignoreFiles: ['examples/package/prettier.config.cjs'],
});

expect(config.entry).toEqual(['scripts/release.ts']);
expect(config.project).toEqual(['scripts/**/*.ts']);
expect(config.ignore).toEqual(['fixtures/**']);
expect(config.ignoreDependencies).toEqual(['optional-package']);
expect(config.ignoreFiles).toEqual(['examples/package/prettier.config.cjs']);
});
});
26 changes: 26 additions & 0 deletions src/knip.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import type { KnipConfig } from 'knip';

export interface DevtoolsKnipWorkspaceConfigOptions {
entry?: string[];
project?: string[];
ignore?: string[];
ignoreDependencies?: (string | RegExp)[];
ignoreFiles?: string[];
}

export interface DevtoolsKnipConfigOptions extends DevtoolsKnipWorkspaceConfigOptions {
workspaces?: Record<string, DevtoolsKnipWorkspaceConfigOptions>;
}

export function createKnipConfig(options: DevtoolsKnipConfigOptions = {}): KnipConfig {
return {
...(options.entry === undefined ? {} : { entry: options.entry }),
...(options.project === undefined ? {} : { project: options.project }),
...(options.ignore === undefined ? {} : { ignore: options.ignore }),
...(options.ignoreDependencies === undefined
? {}
: { ignoreDependencies: options.ignoreDependencies }),
...(options.ignoreFiles === undefined ? {} : { ignoreFiles: options.ignoreFiles }),
...(options.workspaces === undefined ? {} : { workspaces: options.workspaces }),
} satisfies KnipConfig;
}
4 changes: 2 additions & 2 deletions src/types.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import type tseslint from 'typescript-eslint';

export type FlatConfig = ReturnType<typeof tseslint.config>;
type FlatConfig = ReturnType<typeof tseslint.config>;
export type FlatConfigItem = FlatConfig[number];

export interface RestrictedImport {
interface RestrictedImport {
name: string;
message: string;
}
Expand Down
Loading