@rtorcato/repo-tooling 2.59.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +113 -0
- package/LICENSE +21 -0
- package/README.md +201 -0
- package/dist/cli/commands/doctor.js +336 -0
- package/dist/cli/commands/fix-targets.js +192 -0
- package/dist/cli/commands/fix.js +440 -0
- package/dist/cli/commands/setup-presets.js +281 -0
- package/dist/cli/commands/setup.js +501 -0
- package/dist/cli/generators/agent-rules.js +103 -0
- package/dist/cli/generators/badges.js +88 -0
- package/dist/cli/generators/build.js +216 -0
- package/dist/cli/generators/bun.js +25 -0
- package/dist/cli/generators/community-health.js +145 -0
- package/dist/cli/generators/docs-site.js +436 -0
- package/dist/cli/generators/git.js +164 -0
- package/dist/cli/generators/github-actions.js +35 -0
- package/dist/cli/generators/github-workflows.js +24 -0
- package/dist/cli/generators/gitlab-ci.js +10 -0
- package/dist/cli/generators/index.js +123 -0
- package/dist/cli/generators/linting.js +54 -0
- package/dist/cli/generators/misc.js +298 -0
- package/dist/cli/generators/nx.js +16 -0
- package/dist/cli/generators/package-json.js +317 -0
- package/dist/cli/generators/pnpm-workspace.js +126 -0
- package/dist/cli/generators/postcss.js +24 -0
- package/dist/cli/generators/readme.js +268 -0
- package/dist/cli/generators/security.js +156 -0
- package/dist/cli/generators/skills-install.js +70 -0
- package/dist/cli/generators/tailwind.js +34 -0
- package/dist/cli/generators/testing.js +88 -0
- package/dist/cli/generators/treeshake.js +148 -0
- package/dist/cli/generators/tsconfig.js +27 -0
- package/dist/cli/generators/turborepo.js +35 -0
- package/dist/cli/generators/typedoc.js +40 -0
- package/dist/cli/index.js +332 -0
- package/dist/cli/utils/copy-preset.js +98 -0
- package/dist/cli/utils/detect-language.js +22 -0
- package/dist/cli/utils/format.js +32 -0
- package/dist/cli/utils/install.js +28 -0
- package/dist/cli/utils/lockfile.js +84 -0
- package/dist/languages/js/checks.js +949 -0
- package/dist/languages/js/ci.js +251 -0
- package/dist/languages/js/fixers.js +735 -0
- package/dist/languages/registry.js +37 -0
- package/dist/languages/swift/checks.js +127 -0
- package/dist/languages/swift/ci.js +165 -0
- package/dist/languages/swift/fixers.js +102 -0
- package/dist/languages/swift/git-hooks.js +63 -0
- package/dist/languages/swift/gitignore.js +43 -0
- package/dist/languages/swift/scaffold.js +244 -0
- package/package.json +461 -0
- package/tooling/biome/README.md +90 -0
- package/tooling/biome/biome.json +63 -0
- package/tooling/bun/bunfig.toml +14 -0
- package/tooling/changesets/README.md +35 -0
- package/tooling/changesets/config.json +11 -0
- package/tooling/claude/repo-tooling.md +87 -0
- package/tooling/commitlint/commitlint.d.mts +4 -0
- package/tooling/commitlint/commitlint.mjs +40 -0
- package/tooling/cypress/cypress.config.d.mts +4 -0
- package/tooling/cypress/cypress.config.mjs +11 -0
- package/tooling/docusaurus/index.d.mts +17 -0
- package/tooling/docusaurus/index.mjs +38 -0
- package/tooling/docusaurus/sync-changelog.mjs +42 -0
- package/tooling/docusaurus/theme-tokens.css +79 -0
- package/tooling/docusaurus/theme.css +378 -0
- package/tooling/esbuild/index.d.mts +6 -0
- package/tooling/esbuild/index.mjs +102 -0
- package/tooling/eslint/base.d.mts +6 -0
- package/tooling/eslint/base.mjs +122 -0
- package/tooling/eslint/nextjs.d.mts +4 -0
- package/tooling/eslint/nextjs.mjs +22 -0
- package/tooling/eslint/types.d.ts +58 -0
- package/tooling/github-actions/workflows/cloudflare-pages.yml +42 -0
- package/tooling/github-actions/workflows/docker-publish.yml +44 -0
- package/tooling/github-actions/workflows/preview-deployments.yml +54 -0
- package/tooling/github-actions/workflows/vercel-deploy.yml +40 -0
- package/tooling/jest-presets/browser/jest-preset.d.mts +4 -0
- package/tooling/jest-presets/browser/jest-preset.mjs +14 -0
- package/tooling/jest-presets/node/jest-preset.d.mts +4 -0
- package/tooling/jest-presets/node/jest-preset.mjs +13 -0
- package/tooling/mcp/mcp.json.example +27 -0
- package/tooling/nx/nx.json +24 -0
- package/tooling/oxlint/README.md +25 -0
- package/tooling/oxlint/oxlintrc.json +28 -0
- package/tooling/playwright/playwright.config.d.mts +4 -0
- package/tooling/playwright/playwright.config.mjs +19 -0
- package/tooling/prettier/index.d.mts +4 -0
- package/tooling/prettier/index.mjs +36 -0
- package/tooling/release-please/.release-please-manifest.json +3 -0
- package/tooling/release-please/release-please-config.json +9 -0
- package/tooling/rolldown/rolldown.config.d.mts +18 -0
- package/tooling/rolldown/rolldown.config.mjs +59 -0
- package/tooling/rollup/rollup.config.d.mts +20 -0
- package/tooling/rollup/rollup.config.mjs +70 -0
- package/tooling/semantic-release/docker.d.mts +4 -0
- package/tooling/semantic-release/docker.mjs +59 -0
- package/tooling/semantic-release/github.d.mts +4 -0
- package/tooling/semantic-release/github.mjs +79 -0
- package/tooling/semantic-release/index.d.mts +4 -0
- package/tooling/semantic-release/index.mjs +80 -0
- package/tooling/swift/periphery.yml +4 -0
- package/tooling/swift/swiftlint.yml +23 -0
- package/tooling/tests/exports-resolution.d.mts +14 -0
- package/tooling/tests/exports-resolution.mjs +68 -0
- package/tooling/tests/ssr-safety.d.mts +14 -0
- package/tooling/tests/ssr-safety.mjs +53 -0
- package/tooling/tsup/index.d.mts +8 -0
- package/tooling/tsup/index.mjs +33 -0
- package/tooling/typedoc/typedoc.json +7 -0
- package/tooling/typescript/README.md +49 -0
- package/tooling/typescript/reset.d.ts +9 -0
- package/tooling/typescript/tsconfig.base.json +84 -0
- package/tooling/typescript/tsconfig.build.json +11 -0
- package/tooling/typescript/tsconfig.bun.json +9 -0
- package/tooling/typescript/tsconfig.express.json +9 -0
- package/tooling/typescript/tsconfig.next.json +20 -0
- package/tooling/typescript/tsconfig.node.json +9 -0
- package/tooling/typescript/tsconfig.react.json +15 -0
- package/tooling/typescript/tsconfig.test.json +8 -0
- package/tooling/typescript/v1/tsconfig.base.json +81 -0
- package/tooling/typescript/v1/tsconfig.express.json +9 -0
- package/tooling/typescript/v1/tsconfig.next.json +19 -0
- package/tooling/typescript/v1/tsconfig.node.json +9 -0
- package/tooling/typescript/v1/tsconfig.react.json +14 -0
- package/tooling/typescript/v1/tsconfig.test.json +8 -0
- package/tooling/vite/vite.config.d.mts +4 -0
- package/tooling/vite/vite.config.mjs +18 -0
- package/tooling/vitest/jsdom-shims.d.mts +1 -0
- package/tooling/vitest/jsdom-shims.mjs +58 -0
- package/tooling/vitest/vitest.config.d.mts +4 -0
- package/tooling/vitest/vitest.config.mjs +25 -0
- package/tooling/vitest/vitest.config.react.d.mts +4 -0
- package/tooling/vitest/vitest.config.react.mjs +27 -0
- package/tooling/vitest/vitest.setup.d.mts +1 -0
- package/tooling/vitest/vitest.setup.mjs +3 -0
package/AGENTS.md
ADDED
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
# AGENTS.md
|
|
2
|
+
|
|
3
|
+
Orientation for coding agents working with `@rtorcato/repo-tooling`. Human-readable docs live at https://rtorcato.github.io/repo-tooling/.
|
|
4
|
+
|
|
5
|
+
## What this is
|
|
6
|
+
|
|
7
|
+
A one-package JavaScript / TypeScript tooling distribution. Ships every preset (TypeScript, Biome, ESLint, Prettier, Vitest, Jest, Commitlint, semantic-release, tsup, esbuild, Vite, Playwright) plus a CLI to scaffold and audit projects. Consumers get one install.
|
|
8
|
+
|
|
9
|
+
**Swift** repos (detected via `Package.swift`) are covered end to end: `setup --preset swift-library` scaffolds a SwiftPM package, and `doctor`/`fix` run the language-agnostic checks plus SwiftLint / Periphery / `.gitignore` / `Package.swift`. Python and Perl are audit-only for now. See `src/languages/` — one directory per language module, `src/base/` for what's shared.
|
|
10
|
+
|
|
11
|
+
## CLI surface (agent-friendly)
|
|
12
|
+
|
|
13
|
+
Every command supports `--json` and a non-interactive mode. Combine with `--yes` for fully autonomous use.
|
|
14
|
+
|
|
15
|
+
| Command | Non-interactive | JSON output | Use case |
|
|
16
|
+
|---|---|---|---|
|
|
17
|
+
| `setup --preset <name>` | ✅ | `--dry-run` only | Scaffold a new project. Presets: `library`, `web-app`, `node-api`, `nextjs-app`, `react-app`, `swift-library`. |
|
|
18
|
+
| `setup --config <path>` | ✅ | `--dry-run` only | Scaffold with a full `ProjectConfig` JSON file. See `setup --config-schema`. |
|
|
19
|
+
| `setup --config-schema` | ✅ | ✅ (JSON Schema) | Print the JSON Schema for `ProjectConfig`. Use to validate configs before scaffolding. |
|
|
20
|
+
| `setup --dry-run` | ✅ | ✅ | Print resolved config + file list without writing. Pair with `--preset` or `--config`. |
|
|
21
|
+
| `doctor --json` | ✅ | ✅ | Audit a project. Returns `{ directory, results: [{ check, status, detail, hint? }] }`. Status: `ok` / `drift` / `missing` / `optional-missing`. |
|
|
22
|
+
| `fix --json --yes` | ✅ | ✅ | Walk every doctor finding, apply fixers. Returns `FixActionRecord[]` with `status: applied | dry-run | skipped | already-ok | unsupported`. |
|
|
23
|
+
| `fix <target> --json --yes` | ✅ | ✅ | Apply one fixer. Targets from `list --json`. |
|
|
24
|
+
| `fix --dry-run` | ✅ | ✅ | Print what each fixer would write without writing. Combine with `--json`. |
|
|
25
|
+
| `list --json` | ✅ | ✅ | Enumerate the library's surface area. Each entry has `{ name, description, exports, fixTarget }`. |
|
|
26
|
+
| `copy <name>` | ✅ | text only | Copy a single preset (`biome`, `tsconfig`) into the current directory. |
|
|
27
|
+
|
|
28
|
+
## Recommended workflows
|
|
29
|
+
|
|
30
|
+
### Scaffolding a new project from scratch
|
|
31
|
+
|
|
32
|
+
```bash
|
|
33
|
+
npx @rtorcato/repo-tooling setup --preset library -d ./my-lib --skip-install
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
For full control:
|
|
37
|
+
|
|
38
|
+
```bash
|
|
39
|
+
# 1. Get the schema
|
|
40
|
+
npx @rtorcato/repo-tooling setup --config-schema > project-config.schema.json
|
|
41
|
+
|
|
42
|
+
# 2. Write a config matching it
|
|
43
|
+
cat > project.json <<EOF
|
|
44
|
+
{
|
|
45
|
+
"projectName": "my-lib",
|
|
46
|
+
"projectType": "library",
|
|
47
|
+
"typescript": {"enabled": true, "config": "base"},
|
|
48
|
+
"linting": {"tool": "biome"},
|
|
49
|
+
"formatting": {"tool": "biome"},
|
|
50
|
+
"testing": {"framework": "vitest", "environment": "node"},
|
|
51
|
+
"gitHooks": true,
|
|
52
|
+
"commitLint": true,
|
|
53
|
+
"semanticRelease": true,
|
|
54
|
+
"securityAutomation": true,
|
|
55
|
+
"bundler": "tsup"
|
|
56
|
+
}
|
|
57
|
+
EOF
|
|
58
|
+
|
|
59
|
+
# 3. Preview, then scaffold
|
|
60
|
+
npx @rtorcato/repo-tooling setup --config project.json --dry-run
|
|
61
|
+
npx @rtorcato/repo-tooling setup --config project.json -d ./my-lib --skip-install
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
### Auditing an existing project
|
|
65
|
+
|
|
66
|
+
```bash
|
|
67
|
+
# Get findings
|
|
68
|
+
npx @rtorcato/repo-tooling doctor --json -d ./existing-repo > doctor.json
|
|
69
|
+
|
|
70
|
+
# Apply every fixable finding (no prompts, no surprises)
|
|
71
|
+
npx @rtorcato/repo-tooling fix --yes --json -d ./existing-repo > applied.json
|
|
72
|
+
|
|
73
|
+
# Re-audit to confirm clean
|
|
74
|
+
npx @rtorcato/repo-tooling doctor --json -d ./existing-repo
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
### Targeted fixes
|
|
78
|
+
|
|
79
|
+
```bash
|
|
80
|
+
# Apply one fixer from the list (run `list --json` for every target)
|
|
81
|
+
npx @rtorcato/repo-tooling fix dependabot --yes --json
|
|
82
|
+
npx @rtorcato/repo-tooling fix engines --yes --json
|
|
83
|
+
npx @rtorcato/repo-tooling fix docs-site --yes --json # scaffold a Docusaurus docs site under apps/docs
|
|
84
|
+
npx @rtorcato/repo-tooling fix bun --yes --json # Bun runtime/test config
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
## Drift policy (important)
|
|
88
|
+
|
|
89
|
+
`fix` defaults the confirm prompt to **No** for drift cases (existing file that doesn't extend our preset). The `--yes` flag is required to overwrite drift. Safe-merge fixers (`engines`, `husky`, `package-json`) never overwrite — they add/merge — and use friendlier prompt wording. `fix --json` implies `--yes` (prompts would corrupt JSON output).
|
|
90
|
+
|
|
91
|
+
## Source-of-truth files in the repo
|
|
92
|
+
|
|
93
|
+
- `src/cli/commands/setup.ts` — `ProjectConfig` interface and the setup orchestrator
|
|
94
|
+
- `src/cli/commands/setup-presets.ts` — preset definitions, JSON Schema, config validator, `computeFileList`
|
|
95
|
+
- `src/cli/commands/doctor.ts` — all checks and the public `runDoctor(dir)` / `evaluateNodeVersion(version)` / `nextStepSuggestions(results)`
|
|
96
|
+
- `src/cli/commands/fix.ts` — `Fixer` interface, fixer registry, `fixCommand`
|
|
97
|
+
- `src/cli/commands/fix-targets.ts` — shared check → fix target map (used by both doctor's footer and fix's lookup)
|
|
98
|
+
- `src/cli/generators/` — one file per concern (linting, testing, build, git, github-actions, security, misc)
|
|
99
|
+
- `tooling/` — every shipped preset, mirrored 1:1 with `package.json` `exports`
|
|
100
|
+
|
|
101
|
+
## Conventions in this repo
|
|
102
|
+
|
|
103
|
+
- Conventional commits enforced via commitlint; header max 72 chars
|
|
104
|
+
- Biome for lint + format (run via `pnpm exec biome check --config-path=tooling/biome/biome.json src scripts`)
|
|
105
|
+
- Tests live alongside source in `tests/`; vitest with no separate config
|
|
106
|
+
- semantic-release runs on push to `main`; `fix:` → patch, `feat:` → minor, `chore:` / `docs:` → no release
|
|
107
|
+
|
|
108
|
+
## Pointers
|
|
109
|
+
|
|
110
|
+
- Site index for LLMs: https://rtorcato.github.io/repo-tooling/llms.txt
|
|
111
|
+
- Full CLI guide: https://rtorcato.github.io/repo-tooling/guides/cli/
|
|
112
|
+
- For AI agents: https://rtorcato.github.io/repo-tooling/guides/for-ai-agents/
|
|
113
|
+
- Source: https://github.com/rtorcato/repo-tooling
|
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 Richard Torcato
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
<picture>
|
|
2
|
+
<source media="(max-width: 640px)" srcset="./banner-mobile.png">
|
|
3
|
+
<img src="./banner.png" alt="repo-tooling banner" width="1600">
|
|
4
|
+
</picture>
|
|
5
|
+
|
|
6
|
+
<br>
|
|
7
|
+
|
|
8
|
+
[](https://github.com/rtorcato/repo-tooling/actions/workflows/ci.yml)
|
|
9
|
+
[](https://badge.fury.io/js/@rtorcato%2Frepo-tooling)
|
|
10
|
+
[](https://www.npmjs.com/package/@rtorcato/repo-tooling)
|
|
11
|
+
[](https://bundlephobia.com/package/@rtorcato/repo-tooling)
|
|
12
|
+
[](https://codecov.io/gh/rtorcato/repo-tooling)
|
|
13
|
+
[](https://opensource.org/licenses/MIT)
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
One CLI to scaffold, audit and fix your repo's whole toolchain — linting, tests, commits, releases & CI.
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
Most tooling libraries give you one piece — just TypeScript configs, or just an ESLint preset. **repo-tooling** covers the entire lifecycle: TypeScript, Biome/ESLint, Vitest/Jest, Commitlint, Husky, Semantic Release, GitHub Actions CI, and supply-chain security (Dependabot + CodeQL) — all wired together. The interactive `setup` wizard scaffolds everything in one shot; `doctor` checks an existing project for drift; `fix` applies the missing pieces incrementally.
|
|
20
|
+
|
|
21
|
+
**[Full documentation →](https://rtorcato.github.io/repo-tooling/)**
|
|
22
|
+
|
|
23
|
+
> **Package manager: pnpm, by design.** Every generator scaffolds pnpm
|
|
24
|
+
> workflows, workspace files, and scripts. repo-tooling targets pnpm only for
|
|
25
|
+
> now — npm, yarn, and Bun aren't generated.
|
|
26
|
+
|
|
27
|
+
## Start a new project
|
|
28
|
+
|
|
29
|
+
Interactive wizard — answers every prompt, scaffolds the whole project:
|
|
30
|
+
|
|
31
|
+
```bash
|
|
32
|
+
npx @rtorcato/repo-tooling setup
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
Non-interactive — scaffold from a named preset in one shot (CI-friendly):
|
|
36
|
+
|
|
37
|
+
```bash
|
|
38
|
+
npx @rtorcato/repo-tooling setup --preset library -d ./my-lib --skip-install
|
|
39
|
+
# presets: library | web-app | node-api | nextjs-app | react-app | swift-library
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
`swift-library` scaffolds a SwiftPM package (manifest, sources, tests, SwiftLint,
|
|
43
|
+
Periphery, macOS CI) instead of an npm one — see the
|
|
44
|
+
[Swift guide](https://rtorcato.github.io/repo-tooling/guides/swift).
|
|
45
|
+
|
|
46
|
+
Just one config file? Use `copy`:
|
|
47
|
+
|
|
48
|
+
```bash
|
|
49
|
+
npx @rtorcato/repo-tooling copy biome # → biome.json
|
|
50
|
+
npx @rtorcato/repo-tooling copy tsconfig # → tsconfig.json
|
|
51
|
+
npx @rtorcato/repo-tooling copy changesets # → .changeset/config.json
|
|
52
|
+
npx @rtorcato/repo-tooling copy oxlint # → .oxlintrc.json
|
|
53
|
+
npx @rtorcato/repo-tooling copy claude-skill # → .claude/skills/repo-tooling.md
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
**Already have a project?** Don't rerun `setup` — use `doctor` + `fix`:
|
|
57
|
+
|
|
58
|
+
```bash
|
|
59
|
+
npx @rtorcato/repo-tooling doctor # find what's missing or drifted
|
|
60
|
+
npx @rtorcato/repo-tooling fix # apply scaffolders, prompting per item
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
See the [Getting Started guide](https://rtorcato.github.io/repo-tooling/guides/getting-started/) for the full walkthrough.
|
|
64
|
+
|
|
65
|
+
## Commands
|
|
66
|
+
|
|
67
|
+
| Command | What it does | Example |
|
|
68
|
+
| --- | --- | --- |
|
|
69
|
+
| `setup` | Interactive wizard that scaffolds a whole new project (add `--preset` to run non-interactively). | `npx @rtorcato/repo-tooling setup` |
|
|
70
|
+
| `list` | List every tooling configuration this package can scaffold (`--json` for machine output). | `npx @rtorcato/repo-tooling list` |
|
|
71
|
+
| `copy <config>` | Copy a single config file into the current project. | `npx @rtorcato/repo-tooling copy biome` |
|
|
72
|
+
| `doctor` | Diagnose an existing project for missing or drifted tooling. | `npx @rtorcato/repo-tooling doctor` |
|
|
73
|
+
| `fix [target]` | Apply scaffolders for what `doctor` flagged (`--yes`, `--dry-run`, `--diff`). | `npx @rtorcato/repo-tooling fix` |
|
|
74
|
+
|
|
75
|
+
Every command takes `-d, --directory <path>`; run any with `--help` for its full flags. Run `list` (or `list --json`) for the full set of `fix` targets — it's the source of truth. Notable ones include `fix docs-site` (scaffold a [Docusaurus docs site](https://rtorcato.github.io/repo-tooling/guides/docs-site/)) and `fix bun` (Bun runtime config).
|
|
76
|
+
|
|
77
|
+
## The `.repo-tooling.json` lockfile
|
|
78
|
+
|
|
79
|
+
An **optional** manifest that records the tooling choices you adopted. Nothing
|
|
80
|
+
reads it at build/lint/test time — it exists only so `doctor` can tell an
|
|
81
|
+
*intentional opt-out* from *drift*. repo-tooling works fine without it, which is
|
|
82
|
+
why `doctor` reports a missing one as `not configured`, not an error.
|
|
83
|
+
|
|
84
|
+
Generate or refresh it from what's currently on disk:
|
|
85
|
+
|
|
86
|
+
```bash
|
|
87
|
+
npx @rtorcato/repo-tooling fix lockfile
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
**It's the only config file you need.** Don't keep a separate hand-authored
|
|
91
|
+
`.repo-tooling.config.json` next to it — the lockfile already embeds the full
|
|
92
|
+
`ProjectConfig` under `config`, and `setup --config` accepts the lockfile
|
|
93
|
+
directly, so you can re-run a non-interactive setup from it:
|
|
94
|
+
|
|
95
|
+
```bash
|
|
96
|
+
npx @rtorcato/repo-tooling setup --config .repo-tooling.json
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
Each `config.*` key mirrors a setup answer — see the
|
|
100
|
+
[schema](https://rtorcato.github.io/repo-tooling/schemas/lockfile.json) for the
|
|
101
|
+
full field reference. The keys `doctor` acts on:
|
|
102
|
+
|
|
103
|
+
- `typescript.enabled` / `typescript.config` — `base` \| `react` \| `next` \| `node` \| `express`
|
|
104
|
+
- `linting.tool` — `biome` \| `eslint` \| `both` \| `none`
|
|
105
|
+
- `formatting.tool` — `biome` \| `prettier` \| `none`
|
|
106
|
+
- `testing.framework` — `vitest` \| `jest` \| `playwright` \| `none`
|
|
107
|
+
- `gitHooks`, `commitLint`, `semanticRelease` — booleans
|
|
108
|
+
- `securityAutomation` — boolean (Dependabot + CodeQL)
|
|
109
|
+
- `bundler` — `tsup` \| `esbuild` \| `vite` \| `none`
|
|
110
|
+
- `aiSetup` — boolean (AGENTS.md, CLAUDE.md, Cursor/Copilot rules)
|
|
111
|
+
|
|
112
|
+
**How opt-outs actually work.** When the lockfile records that you declined an
|
|
113
|
+
*optional* tool (e.g. `securityAutomation: false`), `doctor` demotes that check
|
|
114
|
+
from `not configured` to `ok — intentionally declined` instead of nagging you to
|
|
115
|
+
add it. This applies only to optional checks — it does **not** silence *drift*:
|
|
116
|
+
if a config you adopted diverges from the shared base (say your `tsconfig.json`
|
|
117
|
+
extends a different preset), `doctor` still flags it, because drift is a mismatch
|
|
118
|
+
in a tool you're using, not an opt-out. There's no field to suppress drift today.
|
|
119
|
+
|
|
120
|
+
**Adoption vs. standalone.** `fix lockfile` infers adoption from what's on disk,
|
|
121
|
+
so on a repo that deliberately runs standalone configs (no
|
|
122
|
+
`@rtorcato/repo-tooling` dependency) it can record `typescript.config: "base"` and
|
|
123
|
+
friends — asserting you extend the shared bases when you don't. If you're not
|
|
124
|
+
adopting repo-tooling's configs, skip the lockfile; it won't stop the `extends`
|
|
125
|
+
drift warnings.
|
|
126
|
+
|
|
127
|
+
## AI agent rules
|
|
128
|
+
|
|
129
|
+
The package ships rules that teach AI coding agents to drive the CLI
|
|
130
|
+
(`doctor` / `fix` / `setup`) non-interactively. Install for your agent — all
|
|
131
|
+
generated from one source, so guidance never drifts between them:
|
|
132
|
+
|
|
133
|
+
```bash
|
|
134
|
+
npx @rtorcato/repo-tooling fix claude-skill --yes # → .claude/skills/repo-tooling.md
|
|
135
|
+
npx @rtorcato/repo-tooling fix cursor-rules --yes # → .cursor/rules/repo-tooling.mdc
|
|
136
|
+
npx @rtorcato/repo-tooling fix copilot-instructions --yes # → .github/copilot-instructions.md
|
|
137
|
+
npx @rtorcato/repo-tooling fix agents-md --yes # → AGENTS.md
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
`copilot-instructions` and `agents-md` upsert a delimited block, so your own
|
|
141
|
+
content in those shared files is never clobbered. Re-running updates the block
|
|
142
|
+
in place on upgrade.
|
|
143
|
+
|
|
144
|
+
Prefer a symlink that auto-syncs the Claude skill on every upgrade?
|
|
145
|
+
|
|
146
|
+
```bash
|
|
147
|
+
mkdir -p .claude/skills
|
|
148
|
+
ln -sf ../../node_modules/@rtorcato/repo-tooling/tooling/claude/repo-tooling.md \
|
|
149
|
+
.claude/skills/repo-tooling.md
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
### Use with Claude Code (plugin)
|
|
153
|
+
|
|
154
|
+
This repo is also a self-hosted Claude Code marketplace. Install the plugin to
|
|
155
|
+
get two skills — `repo-tooling` (adopt/audit the presets via the CLI) and
|
|
156
|
+
`npm-publish` (the family's release rules) — in any session:
|
|
157
|
+
|
|
158
|
+
```
|
|
159
|
+
/plugin marketplace add rtorcato/repo-tooling
|
|
160
|
+
/plugin install repo-tooling@repo-tooling
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
### Use with other AI tools (Cursor / Copilot / Codex)
|
|
164
|
+
|
|
165
|
+
[`AGENTS.md`](AGENTS.md) at the repo root carries the same guidance in the
|
|
166
|
+
cross-tool convention many agents read, and ships in the npm tarball so tools
|
|
167
|
+
scanning `node_modules/@rtorcato/repo-tooling` can find it.
|
|
168
|
+
|
|
169
|
+
## What's new
|
|
170
|
+
|
|
171
|
+
See [CHANGELOG.md](CHANGELOG.md) for the full history.
|
|
172
|
+
|
|
173
|
+
## Related packages
|
|
174
|
+
|
|
175
|
+
- [@rtorcato/js-common](https://github.com/rtorcato/js-common) — General TypeScript/JS utilities (strings, dates, numbers, async, errors)
|
|
176
|
+
- [@rtorcato/browser-common](https://github.com/rtorcato/browser-common) — Browser Web API wrappers (clipboard, observers, storage, etc.)
|
|
177
|
+
|
|
178
|
+
## Roadmap
|
|
179
|
+
|
|
180
|
+
Direction and progress are tracked entirely on GitHub — see the
|
|
181
|
+
[milestones](https://github.com/rtorcato/repo-tooling/milestones) and
|
|
182
|
+
[open issues](https://github.com/rtorcato/repo-tooling/issues).
|
|
183
|
+
|
|
184
|
+
## Contributing
|
|
185
|
+
|
|
186
|
+
Contributions welcome — see [CONTRIBUTING.md](CONTRIBUTING.md).
|
|
187
|
+
|
|
188
|
+
## License
|
|
189
|
+
|
|
190
|
+
MIT — see [LICENSE](LICENSE).
|
|
191
|
+
|
|
192
|
+
<!-- js-tooling:skills:start -->
|
|
193
|
+
## Install the skills (`npx skills`)
|
|
194
|
+
|
|
195
|
+
Any agent that supports the [`skills`](https://www.npmjs.com/package/skills) CLI can install this repo's skills straight from GitHub — no clone, no package install:
|
|
196
|
+
|
|
197
|
+
```bash
|
|
198
|
+
npx skills add https://github.com/rtorcato/repo-tooling --skill repo-tooling
|
|
199
|
+
npx skills add https://github.com/rtorcato/repo-tooling --skill npm-publish
|
|
200
|
+
```
|
|
201
|
+
<!-- js-tooling:skills:end -->
|
|
@@ -0,0 +1,336 @@
|
|
|
1
|
+
import path from 'node:path';
|
|
2
|
+
import chalk from 'chalk';
|
|
3
|
+
import fs from 'fs-extra';
|
|
4
|
+
import { resolveLanguageModule } from '../../languages/registry.js';
|
|
5
|
+
import { SWIFT_GIT_HOOKS, runSwiftChecks } from '../../languages/swift/checks.js';
|
|
6
|
+
import { detectLanguage } from '../utils/detect-language.js';
|
|
7
|
+
import { checkGitHubSettings } from '../../base/github-settings.js';
|
|
8
|
+
import { LOCKFILE_VERSION, readLockfile } from '../utils/lockfile.js';
|
|
9
|
+
import { declinedInLock, getFixTargetForCheck } from './fix-targets.js';
|
|
10
|
+
import { checkAiSetup, checkCodeowners, checkCodeQL, checkCommunityHealth, checkCoverageUpload, checkDependabot, checkEditorConfig, checkFile, checkGitHooks, checkGitHubActions, checkGitLabCI, checkPrePushHook, checkReadmeBadges, COMMITLINT_FILE_CHECK, } from '../../base/checks.js';
|
|
11
|
+
import { allDeps, checkAreTheTypesWrong, checkDocsSite, checkEnginesNode, checkKnip, checkLintStaged, checkNodeVersionConsistency, checkNodeVersionPin, checkPackageJson, checkPublint, checkSemanticRelease, checkSizeLimit, checkTailwind, checkPnpmWorkspace, checkTreeshakeSetup, checkTurborepo, checkTypedoc, checkVerifyScript, checkVscodeExtensions, evaluateNodeVersion, FILE_CHECKS, findDocsAppDir, jsBadgeAudience, jsGitHooksProfile, readPackageJson, } from '../../languages/js/checks.js';
|
|
12
|
+
export { evaluateNodeVersion };
|
|
13
|
+
const PACKAGE = '@rtorcato/repo-tooling';
|
|
14
|
+
// Detects the broken-release-on-protected-main footgun: a workflow that runs
|
|
15
|
+
// semantic-release but only hands it GITHUB_TOKEN, which can't push the version
|
|
16
|
+
// commit + tag past branch protection. The fix is an admin PAT (RELEASE_TOKEN)
|
|
17
|
+
// with a GITHUB_TOKEN fallback.
|
|
18
|
+
async function checkReleaseToken(dir) {
|
|
19
|
+
const workflowsDir = path.join(dir, '.github', 'workflows');
|
|
20
|
+
if (!(await fs.pathExists(workflowsDir))) {
|
|
21
|
+
return { check: 'Release token', status: 'optional-missing', detail: 'no .github/workflows/' };
|
|
22
|
+
}
|
|
23
|
+
try {
|
|
24
|
+
const files = await fs.readdir(workflowsDir);
|
|
25
|
+
for (const f of files) {
|
|
26
|
+
if (!(f.endsWith('.yml') || f.endsWith('.yaml')))
|
|
27
|
+
continue;
|
|
28
|
+
const content = await fs.readFile(path.join(workflowsDir, f), 'utf-8');
|
|
29
|
+
if (!/semantic-release/.test(content))
|
|
30
|
+
continue;
|
|
31
|
+
if (/RELEASE_TOKEN/.test(content)) {
|
|
32
|
+
return {
|
|
33
|
+
check: 'Release token',
|
|
34
|
+
status: 'ok',
|
|
35
|
+
detail: `${f} uses RELEASE_TOKEN (with GITHUB_TOKEN fallback)`,
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
return {
|
|
39
|
+
check: 'Release token',
|
|
40
|
+
status: 'drift',
|
|
41
|
+
detail: `${f} runs semantic-release with bare GITHUB_TOKEN`,
|
|
42
|
+
hint: 'GITHUB_TOKEN cannot push to a protected main. Set the checkout `token:` and the semantic-release `GITHUB_TOKEN` env to `${{ secrets.RELEASE_TOKEN || secrets.GITHUB_TOKEN }}` and add a RELEASE_TOKEN admin PAT secret',
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
return {
|
|
46
|
+
check: 'Release token',
|
|
47
|
+
status: 'optional-missing',
|
|
48
|
+
detail: 'no semantic-release workflow found',
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
catch {
|
|
52
|
+
return {
|
|
53
|
+
check: 'Release token',
|
|
54
|
+
status: 'optional-missing',
|
|
55
|
+
detail: 'unable to read .github/workflows/',
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
// Flags a release workflow still authenticating npm publish with a long-lived
|
|
60
|
+
// NPM_TOKEN secret instead of OIDC trusted publishing (#201). npm is deprecating
|
|
61
|
+
// 2FA-bypass tokens; OIDC needs no secret and adds provenance for free. Only
|
|
62
|
+
// relevant for public packages that actually publish to npm.
|
|
63
|
+
async function checkNpmOidcPublish(dir, pkg) {
|
|
64
|
+
const check = 'npm OIDC publish';
|
|
65
|
+
if (!pkg || pkg.private === true) {
|
|
66
|
+
return { check, status: 'optional-missing', detail: 'private package — no npm publish' };
|
|
67
|
+
}
|
|
68
|
+
const workflowsDir = path.join(dir, '.github', 'workflows');
|
|
69
|
+
if (!(await fs.pathExists(workflowsDir))) {
|
|
70
|
+
return { check, status: 'optional-missing', detail: 'no .github/workflows/' };
|
|
71
|
+
}
|
|
72
|
+
try {
|
|
73
|
+
const files = await fs.readdir(workflowsDir);
|
|
74
|
+
for (const f of files) {
|
|
75
|
+
if (!(f.endsWith('.yml') || f.endsWith('.yaml')))
|
|
76
|
+
continue;
|
|
77
|
+
const content = await fs.readFile(path.join(workflowsDir, f), 'utf-8');
|
|
78
|
+
if (!/semantic-release/.test(content))
|
|
79
|
+
continue;
|
|
80
|
+
if (/secrets\.NPM_TOKEN/.test(content)) {
|
|
81
|
+
return {
|
|
82
|
+
check,
|
|
83
|
+
status: 'drift',
|
|
84
|
+
detail: `${f} authenticates npm publish with NPM_TOKEN`,
|
|
85
|
+
hint: 'Migrate to OIDC trusted publishing: add a Trusted Publisher for each published package on npmjs.com (Settings → Trusted Publisher), then run `fix github-actions` to drop NPM_TOKEN (the release job keeps `id-token: write`). npm is deprecating 2FA-bypass tokens.',
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
return { check, status: 'ok', detail: `${f} publishes via OIDC (no NPM_TOKEN)` };
|
|
89
|
+
}
|
|
90
|
+
return { check, status: 'optional-missing', detail: 'no semantic-release workflow found' };
|
|
91
|
+
}
|
|
92
|
+
catch {
|
|
93
|
+
return { check, status: 'optional-missing', detail: 'unable to read .github/workflows/' };
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
function checkLockfile(lock) {
|
|
97
|
+
if (!lock) {
|
|
98
|
+
return {
|
|
99
|
+
check: 'lockfile',
|
|
100
|
+
status: 'optional-missing',
|
|
101
|
+
detail: 'no .repo-tooling.json — doctor cannot tell intentional opt-outs from drift',
|
|
102
|
+
hint: 'Run `npx @rtorcato/repo-tooling fix lockfile` to record current choices',
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
if (lock.version > LOCKFILE_VERSION) {
|
|
106
|
+
return {
|
|
107
|
+
check: 'lockfile',
|
|
108
|
+
status: 'drift',
|
|
109
|
+
detail: `.repo-tooling.json version ${lock.version} is newer than this CLI supports (v${LOCKFILE_VERSION})`,
|
|
110
|
+
hint: 'Upgrade @rtorcato/repo-tooling to a release that supports this lockfile version',
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
return {
|
|
114
|
+
check: 'lockfile',
|
|
115
|
+
status: 'ok',
|
|
116
|
+
detail: `.repo-tooling.json v${lock.version} (written by ${lock.writtenBy})`,
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
// Lockfile-driven demotion: if the lock records an intentional opt-out for a
|
|
120
|
+
// check that's currently optional-missing, demote it to ok with a clear detail.
|
|
121
|
+
function demoteDeclined(results, lock) {
|
|
122
|
+
if (!lock)
|
|
123
|
+
return results;
|
|
124
|
+
return results.map((r) => {
|
|
125
|
+
if (r.status !== 'optional-missing')
|
|
126
|
+
return r;
|
|
127
|
+
if (!declinedInLock(lock, r.check))
|
|
128
|
+
return r;
|
|
129
|
+
return {
|
|
130
|
+
check: r.check,
|
|
131
|
+
status: 'ok',
|
|
132
|
+
detail: 'intentionally declined (.repo-tooling.json)',
|
|
133
|
+
};
|
|
134
|
+
});
|
|
135
|
+
}
|
|
136
|
+
// The language-agnostic checks (src/base): repo hygiene, git hooks, CI,
|
|
137
|
+
// security, and GitHub repo-settings that apply to any repo regardless of
|
|
138
|
+
// language. Declared once and run for every project — a language module layers
|
|
139
|
+
// its own checks on top rather than re-listing these.
|
|
140
|
+
async function runBaseChecks(dir, lock, opts) {
|
|
141
|
+
const results = [];
|
|
142
|
+
results.push(checkLockfile(lock));
|
|
143
|
+
results.push(await checkEditorConfig(dir));
|
|
144
|
+
results.push(await checkFile(dir, COMMITLINT_FILE_CHECK));
|
|
145
|
+
if (opts.hooks) {
|
|
146
|
+
results.push(await checkGitHooks(dir, opts.hooks));
|
|
147
|
+
results.push(await checkPrePushHook(dir, opts.hooks));
|
|
148
|
+
}
|
|
149
|
+
results.push(await checkGitHubActions(dir));
|
|
150
|
+
results.push(await checkDependabot(dir));
|
|
151
|
+
results.push(await checkCodeQL(dir));
|
|
152
|
+
// GitHub repo-settings drift (branch protection, merge settings, workflow
|
|
153
|
+
// permissions). Read-only; self-skips as `ok` outside a live GitHub repo.
|
|
154
|
+
results.push(...(await checkGitHubSettings(dir)));
|
|
155
|
+
results.push(await checkGitLabCI(dir));
|
|
156
|
+
results.push(await checkCodeowners(dir));
|
|
157
|
+
results.push(await checkCommunityHealth(dir));
|
|
158
|
+
results.push(await checkAiSetup(dir));
|
|
159
|
+
results.push(await checkReadmeBadges(dir, opts.badges.audience, opts.badges.fixTarget));
|
|
160
|
+
results.push(await checkCoverageUpload(dir));
|
|
161
|
+
return results;
|
|
162
|
+
}
|
|
163
|
+
export async function runDoctor(dir) {
|
|
164
|
+
const targetDir = path.resolve(dir);
|
|
165
|
+
const lock = await readLockfile(targetDir);
|
|
166
|
+
// Per-module dispatch (#285): the base checks (repo hygiene, CI, security,
|
|
167
|
+
// GitHub settings) apply to any repo and run for every language. A supported
|
|
168
|
+
// module layers its own checks on top; an unsupported one (Swift/Perl/Python
|
|
169
|
+
// until their modules land) still gets the full base suite instead of the old
|
|
170
|
+
// wholesale skip. 'unknown' (bare dir mid-setup) resolves to JS so a fresh
|
|
171
|
+
// repo runs the full suite.
|
|
172
|
+
const language = await detectLanguage(targetDir);
|
|
173
|
+
const languageModule = resolveLanguageModule(language);
|
|
174
|
+
if (!languageModule.supported) {
|
|
175
|
+
const results = [
|
|
176
|
+
{
|
|
177
|
+
check: 'language',
|
|
178
|
+
status: 'ok',
|
|
179
|
+
detail: `detected ${languageModule.label} — running language-agnostic checks; ${languageModule.label}-specific checks land with its module (#139)`,
|
|
180
|
+
},
|
|
181
|
+
// hooks: null — nothing here encodes a Python/Perl hook convention yet,
|
|
182
|
+
// and guessing one would nag every repo with a fix target that doesn't
|
|
183
|
+
// exist. #289/#290 fill it in.
|
|
184
|
+
...(await runBaseChecks(targetDir, lock, {
|
|
185
|
+
hooks: null,
|
|
186
|
+
badges: { audience: 'public', fixTarget: null },
|
|
187
|
+
})),
|
|
188
|
+
];
|
|
189
|
+
return demoteDeclined(results, lock);
|
|
190
|
+
}
|
|
191
|
+
// Swift suite (#286): base checks plus the module's own. Swift repos have no
|
|
192
|
+
// package.json, so nothing JS-shaped runs.
|
|
193
|
+
if (languageModule.id === 'swift') {
|
|
194
|
+
const results = [
|
|
195
|
+
{
|
|
196
|
+
check: 'language',
|
|
197
|
+
status: 'ok',
|
|
198
|
+
detail: 'detected Swift (Package.swift)',
|
|
199
|
+
},
|
|
200
|
+
...(await runBaseChecks(targetDir, lock, {
|
|
201
|
+
hooks: SWIFT_GIT_HOOKS,
|
|
202
|
+
// A SwiftPM package is distributed as public source over git tags, so
|
|
203
|
+
// badges always apply. No fixer: `fix badges` derives the block from
|
|
204
|
+
// package.json name/repository, which a Swift repo hasn't got.
|
|
205
|
+
badges: { audience: 'public', fixTarget: null },
|
|
206
|
+
})),
|
|
207
|
+
...(await runSwiftChecks(targetDir)),
|
|
208
|
+
];
|
|
209
|
+
return demoteDeclined(results, lock);
|
|
210
|
+
}
|
|
211
|
+
// JS suite: the module's own checks, then the shared base ones. Only the
|
|
212
|
+
// JS-shaped checks are listed here — re-listing the base suite is what made
|
|
213
|
+
// any new base check silently skip JS repos (#309).
|
|
214
|
+
const pkg = await readPackageJson(targetDir);
|
|
215
|
+
const results = [];
|
|
216
|
+
results.push(evaluateNodeVersion(process.version));
|
|
217
|
+
results.push(checkPackageJson(pkg));
|
|
218
|
+
results.push(checkEnginesNode(pkg));
|
|
219
|
+
results.push(await checkVscodeExtensions(targetDir));
|
|
220
|
+
results.push(await checkNodeVersionPin(targetDir));
|
|
221
|
+
results.push(await checkNodeVersionConsistency(targetDir, pkg));
|
|
222
|
+
for (const spec of FILE_CHECKS) {
|
|
223
|
+
results.push(await checkFile(targetDir, spec));
|
|
224
|
+
}
|
|
225
|
+
results.push(await checkLintStaged(targetDir, pkg));
|
|
226
|
+
results.push(await checkVerifyScript(targetDir, pkg));
|
|
227
|
+
results.push(await checkSemanticRelease(targetDir, pkg));
|
|
228
|
+
results.push(await checkKnip(targetDir, pkg));
|
|
229
|
+
results.push(await checkSizeLimit(targetDir, pkg));
|
|
230
|
+
results.push(await checkReleaseToken(targetDir));
|
|
231
|
+
results.push(await checkNpmOidcPublish(targetDir, pkg));
|
|
232
|
+
results.push(await checkTypedoc(targetDir, pkg));
|
|
233
|
+
results.push(await checkAreTheTypesWrong(targetDir, pkg));
|
|
234
|
+
results.push(await checkPublint(targetDir, pkg));
|
|
235
|
+
results.push(await checkTreeshakeSetup(targetDir, pkg));
|
|
236
|
+
results.push(await checkPnpmWorkspace(targetDir, pkg));
|
|
237
|
+
// Turborepo is monorepo-only — only surface the check when a workspace exists.
|
|
238
|
+
if (await fs.pathExists(path.join(targetDir, 'pnpm-workspace.yaml'))) {
|
|
239
|
+
results.push(await checkTurborepo(targetDir));
|
|
240
|
+
}
|
|
241
|
+
// Docs site is opt-in — only surface the check when a Docusaurus site exists.
|
|
242
|
+
const docsAppDir = await findDocsAppDir(targetDir);
|
|
243
|
+
if (docsAppDir) {
|
|
244
|
+
results.push(await checkDocsSite(targetDir, docsAppDir));
|
|
245
|
+
}
|
|
246
|
+
// Tailwind is opt-in — only surface the check when the repo actually depends on it.
|
|
247
|
+
if ('tailwindcss' in allDeps(pkg)) {
|
|
248
|
+
results.push(await checkTailwind(targetDir, pkg));
|
|
249
|
+
}
|
|
250
|
+
results.push(...(await runBaseChecks(targetDir, lock, {
|
|
251
|
+
hooks: jsGitHooksProfile(pkg),
|
|
252
|
+
badges: { audience: jsBadgeAudience(pkg), fixTarget: 'badges' },
|
|
253
|
+
})));
|
|
254
|
+
return demoteDeclined(results, lock);
|
|
255
|
+
}
|
|
256
|
+
const STATUS_ICONS = {
|
|
257
|
+
ok: chalk.green('✅'),
|
|
258
|
+
drift: chalk.yellow('⚠️ '),
|
|
259
|
+
missing: chalk.red('❌'),
|
|
260
|
+
'optional-missing': chalk.gray('➖'),
|
|
261
|
+
};
|
|
262
|
+
function statusLabel(status) {
|
|
263
|
+
switch (status) {
|
|
264
|
+
case 'ok':
|
|
265
|
+
return chalk.green('ok');
|
|
266
|
+
case 'drift':
|
|
267
|
+
return chalk.yellow('drift');
|
|
268
|
+
case 'missing':
|
|
269
|
+
return chalk.red('missing');
|
|
270
|
+
case 'optional-missing':
|
|
271
|
+
return chalk.gray('not configured');
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
const MAX_NEXT_STEP_SUGGESTIONS = 8;
|
|
275
|
+
export function nextStepSuggestions(results, language) {
|
|
276
|
+
const fixable = results.filter((r) => r.status === 'drift' || r.status === 'missing' || r.status === 'optional-missing');
|
|
277
|
+
const lines = [];
|
|
278
|
+
let overflow = 0;
|
|
279
|
+
for (const r of fixable) {
|
|
280
|
+
const target = getFixTargetForCheck(r.check, language);
|
|
281
|
+
if (!target)
|
|
282
|
+
continue;
|
|
283
|
+
if (lines.length >= MAX_NEXT_STEP_SUGGESTIONS) {
|
|
284
|
+
overflow++;
|
|
285
|
+
continue;
|
|
286
|
+
}
|
|
287
|
+
const verb = r.status === 'drift' ? 'align' : 'scaffold';
|
|
288
|
+
lines.push(`Run \`npx @rtorcato/repo-tooling fix ${target}\` to ${verb} ${r.check}`);
|
|
289
|
+
}
|
|
290
|
+
if (overflow > 0) {
|
|
291
|
+
lines.push(`...and ${overflow} more — run \`npx @rtorcato/repo-tooling fix\` to walk all findings`);
|
|
292
|
+
}
|
|
293
|
+
else if (lines.length > 0) {
|
|
294
|
+
lines.push('Run `npx @rtorcato/repo-tooling fix` to walk all findings interactively');
|
|
295
|
+
}
|
|
296
|
+
return lines;
|
|
297
|
+
}
|
|
298
|
+
export function summarize(results) {
|
|
299
|
+
return {
|
|
300
|
+
ok: results.filter((r) => r.status === 'ok').length,
|
|
301
|
+
drift: results.filter((r) => r.status === 'drift').length,
|
|
302
|
+
missing: results.filter((r) => r.status === 'missing').length,
|
|
303
|
+
optionalMissing: results.filter((r) => r.status === 'optional-missing').length,
|
|
304
|
+
};
|
|
305
|
+
}
|
|
306
|
+
export async function doctorCommand(options = {}) {
|
|
307
|
+
const dir = options.directory ?? process.cwd();
|
|
308
|
+
const results = await runDoctor(dir);
|
|
309
|
+
if (options.json) {
|
|
310
|
+
console.log(JSON.stringify({ directory: path.resolve(dir), results }, null, 2));
|
|
311
|
+
}
|
|
312
|
+
else {
|
|
313
|
+
console.log(chalk.cyan(`\n🩺 Diagnosing ${path.resolve(dir)} against ${PACKAGE} presets...\n`));
|
|
314
|
+
for (const r of results) {
|
|
315
|
+
console.log(` ${STATUS_ICONS[r.status]} ${chalk.bold(r.check)} — ${statusLabel(r.status)}`);
|
|
316
|
+
console.log(` ${chalk.gray(r.detail)}`);
|
|
317
|
+
if (r.hint && r.status !== 'ok') {
|
|
318
|
+
console.log(` ${chalk.dim('hint:')} ${chalk.dim(r.hint)}`);
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
const summary = summarize(results);
|
|
322
|
+
console.log();
|
|
323
|
+
console.log(` Summary: ${chalk.green(`${summary.ok} ok`)}, ${chalk.yellow(`${summary.drift} drift`)}, ${chalk.red(`${summary.missing} missing`)}, ${chalk.gray(`${summary.optionalMissing} not configured`)}\n`);
|
|
324
|
+
const suggestions = nextStepSuggestions(results, await detectLanguage(dir));
|
|
325
|
+
if (suggestions.length > 0) {
|
|
326
|
+
console.log(chalk.bold(' Next steps:'));
|
|
327
|
+
for (const s of suggestions) {
|
|
328
|
+
console.log(` ${chalk.gray('-')} ${s}`);
|
|
329
|
+
}
|
|
330
|
+
console.log();
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
const summary = summarize(results);
|
|
334
|
+
const exitCode = summary.drift > 0 || summary.missing > 0 ? 1 : 0;
|
|
335
|
+
process.exitCode = exitCode;
|
|
336
|
+
}
|