@technicalshree/auto-fix 1.1.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/README.md ADDED
@@ -0,0 +1,408 @@
1
+ # auto-fix
2
+
3
+ `auto-fix` is a TypeScript CLI that detects local development environment issues and applies safe, explainable fixes for Node, Python, and Docker-based projects.
4
+
5
+ ## What it does
6
+
7
+ - Detects project stack (Node, Python, Docker Compose)
8
+ - Diagnoses common local setup problems
9
+ - Builds an explicit fix plan before execution
10
+ - Runs safe fixes by default
11
+ - Supports gated destructive cleanup (`--deep`, `--approve`)
12
+ - Writes run reports and snapshot metadata for rollback
13
+ - Supports best-effort `undo` for snapshotted changes
14
+
15
+ ## Requirements
16
+
17
+ - Node.js 18+
18
+ - npm (or compatible Node package manager for development)
19
+ - Optional tools (used only when relevant to your project):
20
+ - Python + `pip`/`uv`/`poetry`/`pipenv`
21
+ - Docker + Docker Compose
22
+
23
+ ## Installation
24
+
25
+ ### 1) Install from npm (recommended)
26
+
27
+ Run directly without installing globally:
28
+
29
+ ```bash
30
+ npx auto-fix doctor
31
+ ```
32
+
33
+ Install globally:
34
+
35
+ ```bash
36
+ npm install -g @technicalshree/auto-fix
37
+ auto-fix --help
38
+ ```
39
+
40
+ ### 2) Install from source (development)
41
+
42
+ Clone and install dependencies:
43
+
44
+ ```bash
45
+ git clone <your-repo-url>
46
+ cd ts-cli-tool
47
+ npm install
48
+ ```
49
+
50
+ Build the CLI:
51
+
52
+ ```bash
53
+ npm run build
54
+ ```
55
+
56
+ Run locally:
57
+
58
+ ```bash
59
+ npm start
60
+ ```
61
+
62
+ ## Use as a CLI command (`auto-fix`)
63
+
64
+ After build, the executable is exposed via:
65
+
66
+ - Bin name: `auto-fix`
67
+ - Entry point: `dist/cli.js`
68
+
69
+ You can run it in either of these ways:
70
+
71
+ ```bash
72
+ npm start -- doctor
73
+ ```
74
+
75
+ ```bash
76
+ node dist/cli.js doctor
77
+ ```
78
+
79
+ To install globally from this repo:
80
+
81
+ ```bash
82
+ npm run build
83
+ npm link
84
+ ```
85
+
86
+ Then use:
87
+
88
+ ```bash
89
+ auto-fix
90
+ ```
91
+
92
+ To remove global link:
93
+
94
+ ```bash
95
+ npm unlink -g auto-fix
96
+ ```
97
+
98
+ ## Publish to npm
99
+
100
+ 1. Ensure you are logged in to npm:
101
+
102
+ ```bash
103
+ npm login
104
+ ```
105
+
106
+ 2. Bump the version:
107
+
108
+ ```bash
109
+ npm version patch
110
+ ```
111
+
112
+ 3. Build and validate the package contents:
113
+
114
+ ```bash
115
+ npm run build
116
+ npm pack --dry-run --cache /tmp/npm-cache-autofix
117
+ ```
118
+
119
+ 4. Publish:
120
+
121
+ ```bash
122
+ npm publish
123
+ ```
124
+
125
+ ## Quick start
126
+
127
+ Run safe fixes (default behavior):
128
+
129
+ ```bash
130
+ auto-fix
131
+ ```
132
+
133
+ Diagnosis only (no changes):
134
+
135
+ ```bash
136
+ auto-fix doctor
137
+ ```
138
+
139
+ Preview plan without executing:
140
+
141
+ ```bash
142
+ auto-fix plan
143
+ ```
144
+
145
+ Show latest report as JSON:
146
+
147
+ ```bash
148
+ auto-fix report --json
149
+ ```
150
+
151
+ Rollback latest run (best-effort):
152
+
153
+ ```bash
154
+ auto-fix undo
155
+ ```
156
+
157
+ Clear global package manager caches:
158
+
159
+ ```bash
160
+ auto-fix clear-npm-cache
161
+ auto-fix clear-yarn-cache
162
+ auto-fix clear-pnpm-cache
163
+ ```
164
+
165
+ ## Commands
166
+
167
+ `auto-fix [command] [flags]`
168
+
169
+ - Default command (`auto-fix`): detect + execute safe fixes
170
+ - `doctor`: detection and diagnosis only (no modifications)
171
+ - `plan`: prints execution plan (same as dry-run)
172
+ - `report`: reads latest report (`--run` runs fresh first)
173
+ - `undo`: best-effort restore using latest run snapshots
174
+ - `clear-npm-cache`: runs `npm cache clean --force`
175
+ - `clear-yarn-cache`: runs `yarn cache clean`
176
+ - `clear-pnpm-cache`: runs `pnpm store prune`
177
+ - `help`: show CLI help
178
+
179
+ ## Flags (all)
180
+
181
+ ### Safety and execution control
182
+
183
+ - `--dry-run`: show actions, do not execute
184
+ - `--deep`: enable destructive cleanup steps (for example deleting `node_modules`)
185
+ - `--approve`: skip interactive prompts and allow destructive steps
186
+ - `--force-fresh`: allow fresh rebuild actions (requires `--deep` or `--approve`)
187
+ - `--focus <subsystem>`: restrict execution to one subsystem
188
+ - Allowed values: `node`, `python`, `docker`, `all`
189
+ - `--checks <list>`: checks phase selector
190
+ - Comma-separated values from: `lint,test,format`
191
+ - Example: `--checks lint,test`
192
+ - `--kill-ports [ports]`: enable port cleanup
193
+ - Optional comma-separated ports
194
+ - If omitted, defaults are used from config
195
+
196
+ ### Output and reporting
197
+
198
+ - `--verbose`: print commands and command outputs
199
+ - `--quiet`: minimal output, still prints final summary
200
+ - `--no-color`: disable ANSI colors
201
+ - `--json`: print machine-readable JSON report to stdout
202
+ - `--report-path <path>`: override report directory path
203
+ - `--run`: with `report`, run a fresh execution instead of reading latest
204
+
205
+ ## Typical usage patterns
206
+
207
+ Safe default run:
208
+
209
+ ```bash
210
+ auto-fix
211
+ ```
212
+
213
+ Plan before execution:
214
+
215
+ ```bash
216
+ auto-fix plan
217
+ ```
218
+
219
+ Deep cleanup with explicit approval:
220
+
221
+ ```bash
222
+ auto-fix --deep --approve
223
+ ```
224
+
225
+ Only Node subsystem:
226
+
227
+ ```bash
228
+ auto-fix --focus node
229
+ ```
230
+
231
+ Run only lint + test checks:
232
+
233
+ ```bash
234
+ auto-fix --checks lint,test
235
+ ```
236
+
237
+ Kill default configured ports then execute:
238
+
239
+ ```bash
240
+ auto-fix --kill-ports
241
+ ```
242
+
243
+ Kill specific ports:
244
+
245
+ ```bash
246
+ auto-fix --kill-ports 3000,5173,9229
247
+ ```
248
+
249
+ Read latest report:
250
+
251
+ ```bash
252
+ auto-fix report --json
253
+ ```
254
+
255
+ Trigger run and emit JSON in one command:
256
+
257
+ ```bash
258
+ auto-fix report --run --json
259
+ ```
260
+
261
+ Clear npm global cache:
262
+
263
+ ```bash
264
+ auto-fix clear-npm-cache
265
+ ```
266
+
267
+ Clear yarn global cache:
268
+
269
+ ```bash
270
+ auto-fix clear-yarn-cache
271
+ ```
272
+
273
+ Clear pnpm global cache:
274
+
275
+ ```bash
276
+ auto-fix clear-pnpm-cache
277
+ ```
278
+
279
+ ## Configuration
280
+
281
+ `auto-fix` works without config. To customize behavior, create `.autofix.yml` in project root.
282
+
283
+ The loader searches current directory upward until git root.
284
+
285
+ ### Example `.autofix.yml`
286
+
287
+ ```yaml
288
+ version: 1
289
+
290
+ ports:
291
+ default: [3000, 5173, 8000, 8080]
292
+ extra: [9229]
293
+
294
+ node:
295
+ package_manager: auto # auto | npm | pnpm | yarn
296
+ deep_cleanup:
297
+ remove_node_modules: true
298
+ remove_lockfile: false
299
+ caches:
300
+ next: true
301
+ vite: true
302
+ directories: [".turbo", ".cache"]
303
+
304
+ python:
305
+ venv_path: .venv
306
+ install:
307
+ prefer: uv # uv | pip | poetry | pipenv | auto
308
+ tools:
309
+ format: ["ruff format", "black ."]
310
+ lint: ["ruff check ."]
311
+ test: ["pytest -q"]
312
+
313
+ docker:
314
+ compose_file: auto
315
+ safe_down: true
316
+ rebuild: true
317
+ prune: false
318
+
319
+ checks:
320
+ default: [lint, format, test]
321
+
322
+ output:
323
+ report_dir: .autofix/reports
324
+ snapshot_dir: .autofix/snapshots
325
+ verbosity: normal # quiet | normal | verbose
326
+ ```
327
+
328
+ ## Reports and artifacts
329
+
330
+ By default:
331
+
332
+ - Reports: `.autofix/reports/`
333
+ - Latest report: `.autofix/reports/latest.json`
334
+ - Snapshots: `.autofix/snapshots/`
335
+
336
+ `auto-fix` also ensures `.autofix/` is in `.gitignore`.
337
+
338
+ If the configured snapshot directory is not writable, snapshots fall back to a temp directory:
339
+
340
+ - macOS/Linux pattern: `/tmp/autofix/<run_id>`
341
+
342
+ ## Undo behavior (important)
343
+
344
+ `auto-fix undo` is best-effort and limited to steps that:
345
+
346
+ - Are marked undoable
347
+ - Have snapshot paths recorded in the report
348
+ - Still have snapshot files available
349
+
350
+ Undo cannot restore changes that were never snapshotted or are irreversible by nature.
351
+
352
+ ## Exit codes
353
+
354
+ - `0`: successful run or expected non-error output
355
+ - `1`: runtime error, missing latest report for `report`/`undo`, or `doctor` found issues
356
+
357
+ ## Development
358
+
359
+ ### Scripts
360
+
361
+ - `npm run dev`: run CLI from source with `tsx` (`src/cli.ts`)
362
+ - `npm run build`: compile TypeScript to `dist/`
363
+ - `npm start`: run built CLI (`dist/cli.js`)
364
+ - `npm test`: build silently and run Node test runner
365
+ - `npm run lint`: TypeScript type-check only (`tsc --noEmit`)
366
+
367
+ ### Local development workflow
368
+
369
+ ```bash
370
+ npm install
371
+ npm run lint
372
+ npm test
373
+ npm run dev -- doctor
374
+ npm run build
375
+ ```
376
+
377
+ ## Project structure
378
+
379
+ - `src/cli.ts`: CLI entrypoint and argument parsing
380
+ - `src/core/`: detection, planning, execution, safety, undo
381
+ - `src/config/`: defaults and config loading
382
+ - `src/report/`: summary and report writing
383
+ - `src/subsystems/`: subsystem-specific checks/fixes
384
+ - `src/ui/`: terminal renderer and progress hooks
385
+ - `dist/`: compiled JavaScript output
386
+ - `docs/`: product requirement docs and notes
387
+
388
+ ## Troubleshooting
389
+
390
+ `auto-fix` command not found:
391
+
392
+ - Run `npm run build` first
393
+ - Use `node dist/cli.js ...` directly
394
+ - If using global link, run `npm link` in project root
395
+
396
+ No report found for `report` or `undo`:
397
+
398
+ - Run `auto-fix` once first
399
+ - Check custom `--report-path` usage
400
+ - Verify `.autofix/reports/latest.json` exists
401
+
402
+ Non-interactive environments behave conservatively:
403
+
404
+ - In polyglot projects (Node + Python + Docker), non-interactive mode without `--approve` may limit execution to a safe subset
405
+
406
+ ## License
407
+
408
+ ISC
package/dist/cli.js ADDED
@@ -0,0 +1,288 @@
1
+ #!/usr/bin/env node
2
+ import path from "node:path";
3
+ import { randomBytes } from "node:crypto";
4
+ import { loadConfig } from "./config/loadConfig.js";
5
+ import { runAutoFix } from "./core/run.js";
6
+ import { renderSummary, renderQuietSummary } from "./report/renderSummary.js";
7
+ import { writeRunReport } from "./report/writeReport.js";
8
+ import { isInteractive } from "./utils/tty.js";
9
+ import { readJsonFile } from "./utils/fs.js";
10
+ import { undoLatest } from "./core/undo.js";
11
+ import { createRenderer } from "./ui/renderer.js";
12
+ import { runShellCommand } from "./utils/process.js";
13
+ function makeRunId() {
14
+ return `${new Date().toISOString().replace(/[:.]/g, "-")}-${randomBytes(3).toString("hex")}`;
15
+ }
16
+ function parseCsv(value) {
17
+ return value
18
+ .split(",")
19
+ .map((x) => x.trim())
20
+ .filter(Boolean);
21
+ }
22
+ function printHelp() {
23
+ console.log(`
24
+ auto-fix — detect, diagnose, and safely fix common dev environment issues
25
+
26
+ USAGE
27
+ auto-fix [command] [flags]
28
+
29
+ COMMANDS
30
+ (default) Run safe fixes (detect project, execute fix plan)
31
+ doctor Diagnosis only — no changes, collects facts + recommendations
32
+ plan Print the exact plan that would run (no changes)
33
+ report Show the last run's report (use --json for machine-readable)
34
+ undo Best-effort rollback of the last run
35
+ clear-npm-cache Clear global npm package cache
36
+ clear-yarn-cache Clear global yarn package cache
37
+ clear-pnpm-cache Clear global pnpm package cache
38
+ help Show this help message
39
+
40
+ SAFETY & CONTROL
41
+ --dry-run Show actions, run nothing (same as plan)
42
+ --deep Enable destructive cleanup steps (e.g. delete node_modules)
43
+ --approve Skip prompts, allow destructive steps (still logs)
44
+ --force-fresh Allow fresh rebuild actions (requires --deep or --approve)
45
+ --focus <subsystem> Restrict to: node | python | docker | all (default: all)
46
+ --checks <list> Run checks phase: lint,test,format (comma-separated)
47
+ --kill-ports [ports] Enable port cleanup. Optional comma-separated ports
48
+
49
+ OUTPUT & REPORTING
50
+ --verbose Print executed commands + stdout/stderr
51
+ --quiet Minimal output (still prints final summary)
52
+ --no-color Disable ANSI coloring
53
+ --json Print JSON report to stdout
54
+ --report-path <path> Override where to write reports
55
+ --run With 'report' command: run fresh instead of reading latest
56
+
57
+ EXAMPLES
58
+ auto-fix Run safe fixes for detected project
59
+ auto-fix doctor Diagnose without changing anything
60
+ auto-fix plan Preview what would run
61
+ auto-fix --deep Run with destructive cleanup enabled
62
+ auto-fix --kill-ports Kill processes on default ports first
63
+ auto-fix report --json Print last run report as JSON
64
+ auto-fix undo Rollback the last run (best-effort)
65
+ auto-fix clear-npm-cache Clear npm cache
66
+ auto-fix clear-yarn-cache Clear yarn cache
67
+ auto-fix clear-pnpm-cache Clear pnpm cache
68
+
69
+ CONFIG
70
+ Place .autofix.yml in your project root for custom settings.
71
+ auto-fix works out-of-the-box without any config file.
72
+ `.trim());
73
+ }
74
+ function parseArgs(argv) {
75
+ if (argv.length === 0)
76
+ return { command: "run", flags: defaultFlags(), help: false };
77
+ const hasHelp = argv.includes("--help") || argv.includes("-h") || argv[0] === "help";
78
+ if (hasHelp)
79
+ return { command: "run", flags: defaultFlags(), help: true };
80
+ const [first, ...rest] = argv;
81
+ const command = first === "doctor" ||
82
+ first === "plan" ||
83
+ first === "report" ||
84
+ first === "undo" ||
85
+ first === "clear-npm-cache" ||
86
+ first === "clear-yarn-cache" ||
87
+ first === "clear-pnpm-cache"
88
+ ? first
89
+ : "run";
90
+ const args = command === "run" ? argv : rest;
91
+ const flags = defaultFlags();
92
+ for (let i = 0; i < args.length; i += 1) {
93
+ const arg = args[i];
94
+ if (arg === "--dry-run")
95
+ flags.dryRun = true;
96
+ else if (arg === "--deep")
97
+ flags.deep = true;
98
+ else if (arg === "--approve")
99
+ flags.approve = true;
100
+ else if (arg === "--force-fresh")
101
+ flags.forceFresh = true;
102
+ else if (arg === "--verbose")
103
+ flags.verbose = true;
104
+ else if (arg === "--quiet")
105
+ flags.quiet = true;
106
+ else if (arg === "--no-color")
107
+ flags.noColor = true;
108
+ else if (arg === "--json")
109
+ flags.json = true;
110
+ else if (arg === "--run")
111
+ flags.run = true;
112
+ else if (arg === "--focus" && args[i + 1]) {
113
+ const f = args[i + 1];
114
+ if (f === "node" || f === "python" || f === "docker" || f === "all")
115
+ flags.focus = f;
116
+ i += 1;
117
+ }
118
+ else if (arg === "--checks" && args[i + 1]) {
119
+ flags.checks = parseCsv(args[i + 1]).filter((v) => v === "lint" || v === "test" || v === "format");
120
+ i += 1;
121
+ }
122
+ else if (arg === "--kill-ports") {
123
+ if (args[i + 1] && !args[i + 1].startsWith("--")) {
124
+ flags.killPorts = parseCsv(args[i + 1]).map((v) => Number(v)).filter((n) => Number.isInteger(n) && n > 0);
125
+ i += 1;
126
+ }
127
+ else {
128
+ flags.killPorts = [];
129
+ }
130
+ }
131
+ else if (arg === "--report-path" && args[i + 1]) {
132
+ flags.reportPath = args[i + 1];
133
+ i += 1;
134
+ }
135
+ }
136
+ if (command === "plan")
137
+ flags.dryRun = true;
138
+ return { command, flags, help: false };
139
+ }
140
+ async function runCacheCommand(command, cwd) {
141
+ const shellCommand = command === "clear-npm-cache"
142
+ ? "npm cache clean --force"
143
+ : command === "clear-yarn-cache"
144
+ ? "yarn cache clean"
145
+ : "pnpm store prune";
146
+ console.log(`Running: ${shellCommand}`);
147
+ const result = await runShellCommand(shellCommand, cwd);
148
+ if (result.stdout.trim())
149
+ console.log(result.stdout.trim());
150
+ if (result.stderr.trim())
151
+ console.error(result.stderr.trim());
152
+ if (!result.success) {
153
+ console.error(`Command failed with exit code ${result.code}`);
154
+ return false;
155
+ }
156
+ console.log("Cache cleared successfully.");
157
+ return true;
158
+ }
159
+ function defaultFlags() {
160
+ return {
161
+ dryRun: false,
162
+ deep: false,
163
+ approve: false,
164
+ forceFresh: false,
165
+ focus: "all",
166
+ verbose: false,
167
+ quiet: false,
168
+ noColor: false,
169
+ json: false,
170
+ run: false,
171
+ };
172
+ }
173
+ async function main() {
174
+ const { command, flags, help } = parseArgs(process.argv.slice(2));
175
+ if (help) {
176
+ printHelp();
177
+ return;
178
+ }
179
+ const cwd = process.cwd();
180
+ const interactive = isInteractive();
181
+ const ui = createRenderer(flags, interactive);
182
+ const ctx = {
183
+ command,
184
+ cwd,
185
+ runId: makeRunId(),
186
+ flags,
187
+ interactive,
188
+ };
189
+ const { config } = await loadConfig(cwd);
190
+ const reportDir = path.resolve(cwd, flags.reportPath ?? config.output.report_dir);
191
+ // ── cache clear commands ──
192
+ if (command === "clear-npm-cache" || command === "clear-yarn-cache" || command === "clear-pnpm-cache") {
193
+ const ok = await runCacheCommand(command, cwd);
194
+ if (!ok)
195
+ process.exitCode = 1;
196
+ return;
197
+ }
198
+ // ── undo ──
199
+ if (command === "undo") {
200
+ ui.intro("undo");
201
+ ui.startTask("Reading latest report\u2026");
202
+ const { report, entries } = await undoLatest(path.join(reportDir, "latest.json"), cwd);
203
+ if (!report) {
204
+ ui.stopTask("\u2717 No previous report found");
205
+ if (!ui.isRich)
206
+ console.error("No previous report found for undo.");
207
+ process.exitCode = 1;
208
+ return;
209
+ }
210
+ ui.stopTask("\u2714 Report loaded");
211
+ ui.showDetection(report.detection);
212
+ const failed = entries.reduce((acc, e) => acc + e.failed.length, 0);
213
+ const restored = entries.reduce((acc, e) => acc + e.restored.length, 0);
214
+ const missing = entries.reduce((acc, e) => acc + e.missingSnapshot.length, 0);
215
+ const skipped = entries.reduce((acc, e) => acc + e.skipped.length, 0);
216
+ ui.showUndoResults(restored, skipped, missing, failed);
217
+ if (!ui.isRich) {
218
+ console.log(`Restored: ${restored}, Skipped: ${skipped}, Missing snapshot: ${missing}, Failed: ${failed}`);
219
+ }
220
+ const next = entries.find((e) => e.nextBestAction)?.nextBestAction ?? "Run auto-fix doctor to validate";
221
+ ui.outro(next);
222
+ if (!ui.isRich)
223
+ console.log(`Next: ${next}`);
224
+ return;
225
+ }
226
+ // ── report (read-only) ──
227
+ if (command === "report" && !flags.run) {
228
+ ui.intro("report");
229
+ const latest = await readJsonFile(path.join(reportDir, "latest.json"));
230
+ if (!latest) {
231
+ ui.showReportInfo("No report found at latest.json");
232
+ if (!ui.isRich)
233
+ console.error("No report found at latest.json");
234
+ process.exitCode = 1;
235
+ return;
236
+ }
237
+ if (flags.json) {
238
+ console.log(JSON.stringify(latest, null, 2));
239
+ }
240
+ else {
241
+ ui.showReportInfo("Report loaded. Use --json to print machine-readable output.");
242
+ if (!ui.isRich)
243
+ console.log("Use --json to print machine-readable report.");
244
+ }
245
+ ui.outro("auto-fix report --json");
246
+ return;
247
+ }
248
+ // ── run / doctor / plan ──
249
+ const effectiveCommand = command === "report" ? "run" : command;
250
+ ui.intro(effectiveCommand);
251
+ const callbacks = ui.isRich
252
+ ? {
253
+ onDetection: (det) => ui.showDetection(det),
254
+ onPlanReady: (steps) => {
255
+ if (effectiveCommand === "plan" || effectiveCommand === "doctor") {
256
+ ui.showPlan(steps, effectiveCommand === "doctor" ? "Diagnosis" : undefined);
257
+ }
258
+ },
259
+ stepHooks: effectiveCommand === "run" ? ui.createStepHooks() : undefined,
260
+ }
261
+ : undefined;
262
+ const result = await runAutoFix({ ...ctx, command: effectiveCommand }, config, reportDir, callbacks);
263
+ const report = result.report;
264
+ await writeRunReport(report, reportDir);
265
+ if (ui.isRich) {
266
+ ui.showResults(report.summary);
267
+ ui.outro(report.summary.nextBestAction);
268
+ }
269
+ // Fallback text output for non-rich modes
270
+ if (!ui.isRich) {
271
+ if (flags.quiet) {
272
+ console.log(renderQuietSummary(report, !flags.noColor));
273
+ }
274
+ else {
275
+ console.log(renderSummary(report, !flags.noColor));
276
+ }
277
+ }
278
+ if (flags.json || command === "report") {
279
+ console.log(JSON.stringify(report, null, 2));
280
+ }
281
+ if (effectiveCommand === "doctor" && report.detection.issues.length > 0) {
282
+ process.exitCode = 1;
283
+ }
284
+ }
285
+ main().catch((error) => {
286
+ console.error(error instanceof Error ? error.message : String(error));
287
+ process.exitCode = 1;
288
+ });