workflow-agent-cli 2.2.1 → 2.3.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.
@@ -0,0 +1,1425 @@
1
+ // src/utils/check-runner.ts
2
+ import { execa } from "execa";
3
+ import chalk from "chalk";
4
+ var QUALITY_CHECKS = [
5
+ {
6
+ name: "typecheck",
7
+ displayName: "Type Check",
8
+ command: "pnpm",
9
+ args: ["typecheck"],
10
+ canAutoFix: false
11
+ // TypeScript errors need manual/LLM fix
12
+ },
13
+ {
14
+ name: "lint",
15
+ displayName: "Lint",
16
+ command: "pnpm",
17
+ args: ["lint"],
18
+ fixCommand: "pnpm",
19
+ fixArgs: ["lint", "--fix"],
20
+ canAutoFix: true
21
+ },
22
+ {
23
+ name: "format",
24
+ displayName: "Format",
25
+ command: "pnpm",
26
+ args: ["format", "--check"],
27
+ fixCommand: "pnpm",
28
+ fixArgs: ["format"],
29
+ canAutoFix: true
30
+ },
31
+ {
32
+ name: "test",
33
+ displayName: "Tests",
34
+ command: "pnpm",
35
+ args: ["test"],
36
+ canAutoFix: false
37
+ // Tests need manual/LLM fix
38
+ },
39
+ {
40
+ name: "build",
41
+ displayName: "Build",
42
+ command: "pnpm",
43
+ args: ["build"],
44
+ canAutoFix: false
45
+ // Build errors need manual/LLM fix
46
+ }
47
+ ];
48
+ async function runCheck(check, cwd) {
49
+ const startTime = Date.now();
50
+ try {
51
+ const result = await execa(check.command, check.args, {
52
+ cwd,
53
+ reject: false,
54
+ all: true
55
+ });
56
+ const duration = Date.now() - startTime;
57
+ if (result.exitCode === 0) {
58
+ return {
59
+ check,
60
+ success: true,
61
+ output: result.all || "",
62
+ duration
63
+ };
64
+ } else {
65
+ return {
66
+ check,
67
+ success: false,
68
+ output: result.all || "",
69
+ error: result.stderr || result.all || "Check failed",
70
+ duration
71
+ };
72
+ }
73
+ } catch (error) {
74
+ const duration = Date.now() - startTime;
75
+ const execaError = error;
76
+ return {
77
+ check,
78
+ success: false,
79
+ output: execaError.all?.toString() || execaError.message || "",
80
+ error: execaError.message,
81
+ duration
82
+ };
83
+ }
84
+ }
85
+ async function applyFix(check, cwd) {
86
+ if (!check.canAutoFix || !check.fixCommand) {
87
+ return { success: false, output: "Check does not support auto-fix" };
88
+ }
89
+ try {
90
+ const result = await execa(check.fixCommand, check.fixArgs || [], {
91
+ cwd,
92
+ reject: false,
93
+ all: true
94
+ });
95
+ return {
96
+ success: result.exitCode === 0,
97
+ output: result.all || ""
98
+ };
99
+ } catch (error) {
100
+ const execaError = error;
101
+ return {
102
+ success: false,
103
+ output: execaError.message
104
+ };
105
+ }
106
+ }
107
+ function formatFixCommand(check) {
108
+ if (!check.fixCommand) return "";
109
+ return `${check.fixCommand} ${(check.fixArgs || []).join(" ")}`;
110
+ }
111
+ async function runAllChecks(cwd, options = {}) {
112
+ const {
113
+ maxRetries = 10,
114
+ autoFix = true,
115
+ dryRun = false,
116
+ onProgress
117
+ } = options;
118
+ const log = (message, type = "info") => {
119
+ if (onProgress) {
120
+ onProgress(message, type);
121
+ } else {
122
+ switch (type) {
123
+ case "success":
124
+ console.log(chalk.green(message));
125
+ break;
126
+ case "error":
127
+ console.log(chalk.red(message));
128
+ break;
129
+ case "warning":
130
+ console.log(chalk.yellow(message));
131
+ break;
132
+ default:
133
+ console.log(message);
134
+ }
135
+ }
136
+ };
137
+ let attempt = 0;
138
+ let fixesApplied = 0;
139
+ const pendingFixes = [];
140
+ while (attempt < maxRetries) {
141
+ attempt++;
142
+ log(`
143
+ ${"\u2501".repeat(50)}`, "info");
144
+ log(`\u{1F504} Validation Cycle ${attempt}/${maxRetries}`, "info");
145
+ log(`${"\u2501".repeat(50)}
146
+ `, "info");
147
+ const results = [];
148
+ let allPassed = true;
149
+ let fixAppliedThisCycle = false;
150
+ for (let i = 0; i < QUALITY_CHECKS.length; i++) {
151
+ const check = QUALITY_CHECKS[i];
152
+ const stepNum = i + 1;
153
+ const totalSteps = QUALITY_CHECKS.length;
154
+ log(`\u{1F4CB} Step ${stepNum}/${totalSteps}: ${check.displayName}...`, "info");
155
+ const result = await runCheck(check, cwd);
156
+ results.push(result);
157
+ if (result.success) {
158
+ log(`\u2705 ${check.displayName} passed (${result.duration}ms)`, "success");
159
+ } else {
160
+ allPassed = false;
161
+ log(`\u274C ${check.displayName} failed`, "error");
162
+ if (autoFix && check.canAutoFix && check.fixCommand) {
163
+ if (dryRun) {
164
+ log(
165
+ `\u{1F527} [DRY-RUN] Would run: ${formatFixCommand(check)}`,
166
+ "warning"
167
+ );
168
+ pendingFixes.push({ check, command: formatFixCommand(check) });
169
+ continue;
170
+ }
171
+ log(`\u{1F527} Attempting auto-fix for ${check.displayName}...`, "warning");
172
+ const fixResult = await applyFix(check, cwd);
173
+ if (fixResult.success) {
174
+ log(`\u2728 Auto-fix applied for ${check.displayName}`, "success");
175
+ fixesApplied++;
176
+ fixAppliedThisCycle = true;
177
+ log(
178
+ `
179
+ \u{1F504} Fix applied - restarting all checks to verify...`,
180
+ "warning"
181
+ );
182
+ break;
183
+ } else {
184
+ log(`\u26A0\uFE0F Auto-fix failed for ${check.displayName}`, "error");
185
+ log(` Manual intervention required`, "error");
186
+ if (result.error) {
187
+ const errorPreview = result.error.slice(0, 500);
188
+ log(`
189
+ ${chalk.dim(errorPreview)}`, "error");
190
+ if (result.error.length > 500) {
191
+ log(
192
+ chalk.dim(
193
+ `... (${result.error.length - 500} more characters)`
194
+ ),
195
+ "error"
196
+ );
197
+ }
198
+ }
199
+ return {
200
+ success: false,
201
+ results,
202
+ totalAttempts: attempt,
203
+ fixesApplied
204
+ };
205
+ }
206
+ } else {
207
+ if (check.canAutoFix) {
208
+ log(
209
+ `\u26A0\uFE0F ${check.displayName} can be fixed with: ${formatFixCommand(check)}`,
210
+ "warning"
211
+ );
212
+ } else {
213
+ log(`\u26A0\uFE0F ${check.displayName} requires manual fix`, "error");
214
+ }
215
+ if (result.error) {
216
+ const errorPreview = result.error.slice(0, 500);
217
+ log(`
218
+ ${chalk.dim(errorPreview)}`, "error");
219
+ if (result.error.length > 500) {
220
+ log(
221
+ chalk.dim(`... (${result.error.length - 500} more characters)`),
222
+ "error"
223
+ );
224
+ }
225
+ }
226
+ if (!dryRun) {
227
+ return {
228
+ success: false,
229
+ results,
230
+ totalAttempts: attempt,
231
+ fixesApplied
232
+ };
233
+ }
234
+ }
235
+ }
236
+ }
237
+ if (dryRun && pendingFixes.length > 0) {
238
+ log(`
239
+ ${"\u2501".repeat(50)}`, "info");
240
+ log(`\u{1F4CB} DRY-RUN SUMMARY`, "info");
241
+ log(`${"\u2501".repeat(50)}`, "info");
242
+ log(`
243
+ The following fixes would be applied:`, "warning");
244
+ for (const fix of pendingFixes) {
245
+ log(` \u2022 ${fix.check.displayName}: ${fix.command}`, "info");
246
+ }
247
+ log(`
248
+ Run without --dry-run to apply fixes.`, "info");
249
+ return {
250
+ success: false,
251
+ results,
252
+ totalAttempts: attempt,
253
+ fixesApplied: 0,
254
+ pendingFixes
255
+ };
256
+ }
257
+ if (allPassed) {
258
+ return {
259
+ success: true,
260
+ results,
261
+ totalAttempts: attempt,
262
+ fixesApplied
263
+ };
264
+ }
265
+ if (!fixAppliedThisCycle) {
266
+ return {
267
+ success: false,
268
+ results,
269
+ totalAttempts: attempt,
270
+ fixesApplied
271
+ };
272
+ }
273
+ }
274
+ log(`
275
+ \u274C Maximum retries (${maxRetries}) exceeded`, "error");
276
+ return {
277
+ success: false,
278
+ results: [],
279
+ totalAttempts: attempt,
280
+ fixesApplied
281
+ };
282
+ }
283
+ async function hasUncommittedChanges(cwd) {
284
+ try {
285
+ const result = await execa("git", ["status", "--porcelain"], { cwd });
286
+ return result.stdout.trim().length > 0;
287
+ } catch {
288
+ return false;
289
+ }
290
+ }
291
+ async function stageAllChanges(cwd) {
292
+ try {
293
+ await execa("git", ["add", "-A"], { cwd });
294
+ return true;
295
+ } catch {
296
+ return false;
297
+ }
298
+ }
299
+
300
+ // src/utils/auto-setup.ts
301
+ import { existsSync as existsSync2 } from "fs";
302
+ import { readFile as readFile2, writeFile, mkdir } from "fs/promises";
303
+ import { join as join2 } from "path";
304
+ import { execa as execa3 } from "execa";
305
+
306
+ // src/utils/git-repo.ts
307
+ import { execa as execa2 } from "execa";
308
+ import { existsSync } from "fs";
309
+ import { readFile } from "fs/promises";
310
+ import { join } from "path";
311
+ async function detectPackageManager(projectPath = process.cwd()) {
312
+ if (existsSync(join(projectPath, "pnpm-lock.yaml"))) {
313
+ return "pnpm";
314
+ }
315
+ if (existsSync(join(projectPath, "yarn.lock"))) {
316
+ return "yarn";
317
+ }
318
+ if (existsSync(join(projectPath, "bun.lockb"))) {
319
+ return "bun";
320
+ }
321
+ if (existsSync(join(projectPath, "package-lock.json"))) {
322
+ return "npm";
323
+ }
324
+ try {
325
+ const pkgPath = join(projectPath, "package.json");
326
+ if (existsSync(pkgPath)) {
327
+ const pkg = JSON.parse(await readFile(pkgPath, "utf-8"));
328
+ if (pkg.packageManager) {
329
+ if (pkg.packageManager.startsWith("pnpm")) return "pnpm";
330
+ if (pkg.packageManager.startsWith("yarn")) return "yarn";
331
+ if (pkg.packageManager.startsWith("bun")) return "bun";
332
+ }
333
+ }
334
+ } catch {
335
+ }
336
+ return "npm";
337
+ }
338
+ async function isMonorepo(projectPath = process.cwd()) {
339
+ if (existsSync(join(projectPath, "pnpm-workspace.yaml"))) {
340
+ return true;
341
+ }
342
+ try {
343
+ const pkgPath = join(projectPath, "package.json");
344
+ if (existsSync(pkgPath)) {
345
+ const pkg = JSON.parse(await readFile(pkgPath, "utf-8"));
346
+ if (pkg.workspaces) {
347
+ return true;
348
+ }
349
+ }
350
+ } catch {
351
+ }
352
+ if (existsSync(join(projectPath, "lerna.json"))) {
353
+ return true;
354
+ }
355
+ if (existsSync(join(projectPath, "nx.json"))) {
356
+ return true;
357
+ }
358
+ if (existsSync(join(projectPath, "turbo.json"))) {
359
+ return true;
360
+ }
361
+ return false;
362
+ }
363
+ async function getPackageScripts(projectPath = process.cwd()) {
364
+ try {
365
+ const pkgPath = join(projectPath, "package.json");
366
+ if (!existsSync(pkgPath)) {
367
+ return {};
368
+ }
369
+ const pkg = JSON.parse(await readFile(pkgPath, "utf-8"));
370
+ return pkg.scripts || {};
371
+ } catch {
372
+ return {};
373
+ }
374
+ }
375
+
376
+ // src/utils/auto-setup.ts
377
+ async function analyzeProject(projectPath = process.cwd()) {
378
+ const packageManager = await detectPackageManager(projectPath);
379
+ const mono = await isMonorepo(projectPath);
380
+ const scripts = await getPackageScripts(projectPath);
381
+ const isTypeScript = existsSync2(join2(projectPath, "tsconfig.json")) || existsSync2(join2(projectPath, "src/index.ts")) || existsSync2(join2(projectPath, "index.ts"));
382
+ const framework = await detectFramework(projectPath);
383
+ const existing = {
384
+ typescript: existsSync2(join2(projectPath, "tsconfig.json")),
385
+ eslint: existsSync2(join2(projectPath, "eslint.config.js")) || existsSync2(join2(projectPath, "eslint.config.mjs")) || existsSync2(join2(projectPath, ".eslintrc.js")) || existsSync2(join2(projectPath, ".eslintrc.json")) || existsSync2(join2(projectPath, ".eslintrc")),
386
+ eslintFlat: existsSync2(join2(projectPath, "eslint.config.js")) || existsSync2(join2(projectPath, "eslint.config.mjs")),
387
+ prettier: existsSync2(join2(projectPath, ".prettierrc")) || existsSync2(join2(projectPath, ".prettierrc.json")) || existsSync2(join2(projectPath, "prettier.config.js")) || existsSync2(join2(projectPath, "prettier.config.mjs")),
388
+ vitest: existsSync2(join2(projectPath, "vitest.config.ts")) || existsSync2(join2(projectPath, "vitest.config.js")),
389
+ jest: existsSync2(join2(projectPath, "jest.config.js")) || existsSync2(join2(projectPath, "jest.config.ts")),
390
+ husky: existsSync2(join2(projectPath, ".husky")),
391
+ simpleGitHooks: existsSync2(join2(projectPath, ".git/hooks/pre-commit")) || await hasSimpleGitHooksConfig(projectPath),
392
+ githubActions: existsSync2(join2(projectPath, ".github/workflows"))
393
+ };
394
+ const existingScripts = {
395
+ build: !!scripts.build,
396
+ test: !!scripts.test,
397
+ lint: !!scripts.lint,
398
+ format: !!scripts.format,
399
+ typecheck: !!scripts.typecheck,
400
+ verify: !!scripts.verify
401
+ };
402
+ const setupPlans = await generateSetupPlans(
403
+ projectPath,
404
+ packageManager,
405
+ isTypeScript,
406
+ framework,
407
+ existing,
408
+ existingScripts,
409
+ mono
410
+ );
411
+ return {
412
+ packageManager,
413
+ isMonorepo: mono,
414
+ isTypeScript,
415
+ framework,
416
+ existing,
417
+ scripts: existingScripts,
418
+ setupPlans
419
+ };
420
+ }
421
+ async function hasSimpleGitHooksConfig(projectPath) {
422
+ try {
423
+ const pkgPath = join2(projectPath, "package.json");
424
+ if (!existsSync2(pkgPath)) return false;
425
+ const pkg = JSON.parse(await readFile2(pkgPath, "utf-8"));
426
+ return !!pkg["simple-git-hooks"];
427
+ } catch {
428
+ return false;
429
+ }
430
+ }
431
+ async function detectFramework(projectPath) {
432
+ try {
433
+ const pkgPath = join2(projectPath, "package.json");
434
+ if (!existsSync2(pkgPath)) return "unknown";
435
+ const pkg = JSON.parse(await readFile2(pkgPath, "utf-8"));
436
+ const deps = { ...pkg.dependencies, ...pkg.devDependencies };
437
+ if (existsSync2(join2(projectPath, "shopify.theme.toml")) || existsSync2(join2(projectPath, "config/settings_schema.json")) || deps["@shopify/cli"] || deps["@shopify/theme"]) {
438
+ return "shopify";
439
+ }
440
+ if (deps.next) return "nextjs";
441
+ if (deps["@remix-run/react"]) return "remix";
442
+ if (deps.nuxt) return "nuxt";
443
+ if (deps.vue) return "vue";
444
+ if (deps.svelte || deps["@sveltejs/kit"]) return "svelte";
445
+ if (deps.react && !deps.next) return "react";
446
+ if (deps.hono) return "hono";
447
+ if (deps.express) return "express";
448
+ if (deps["@types/node"] || pkg.type === "module") return "node";
449
+ return "unknown";
450
+ } catch {
451
+ return "unknown";
452
+ }
453
+ }
454
+ async function generateSetupPlans(projectPath, packageManager, isTypeScript, framework, existing, scripts, isMonorepo2) {
455
+ const plans = [];
456
+ if (isTypeScript) {
457
+ plans.push(
458
+ await planTypeScriptSetup(
459
+ projectPath,
460
+ framework,
461
+ existing.typescript,
462
+ isMonorepo2
463
+ )
464
+ );
465
+ }
466
+ plans.push(
467
+ await planESLintSetup(projectPath, isTypeScript, framework, existing)
468
+ );
469
+ plans.push(await planPrettierSetup(projectPath, existing.prettier));
470
+ plans.push(
471
+ await planTestingSetup(projectPath, isTypeScript, framework, existing)
472
+ );
473
+ if (isTypeScript && !["nextjs", "remix", "nuxt"].includes(framework)) {
474
+ plans.push(await planBuildSetup(projectPath, scripts.build));
475
+ }
476
+ plans.push(
477
+ await planScriptsSetup(projectPath, isTypeScript, framework, scripts)
478
+ );
479
+ plans.push(await planHooksSetup(projectPath, existing));
480
+ plans.push(
481
+ await planCISetup(
482
+ projectPath,
483
+ packageManager,
484
+ isTypeScript,
485
+ framework,
486
+ existing.githubActions,
487
+ isMonorepo2
488
+ )
489
+ );
490
+ return plans;
491
+ }
492
+ async function planTypeScriptSetup(projectPath, _framework, hasExisting, isMonorepo2) {
493
+ const changes = [];
494
+ const devDeps = [];
495
+ const pkg = await readPackageJson(projectPath);
496
+ const deps = pkg.dependencies || {};
497
+ const devDepsPkg = pkg.devDependencies || {};
498
+ const allDeps = { ...deps, ...devDepsPkg };
499
+ if (!allDeps.typescript) {
500
+ devDeps.push("typescript");
501
+ }
502
+ if (hasExisting) {
503
+ const tsconfig = await readTSConfig(join2(projectPath, "tsconfig.json"));
504
+ const opts = tsconfig.compilerOptions || {};
505
+ if (!opts.strict) {
506
+ changes.push({
507
+ type: "modify",
508
+ file: "tsconfig.json",
509
+ key: "compilerOptions.strict",
510
+ oldValue: opts.strict,
511
+ newValue: true,
512
+ description: "Enable strict type checking"
513
+ });
514
+ }
515
+ if (!opts.skipLibCheck) {
516
+ changes.push({
517
+ type: "modify",
518
+ file: "tsconfig.json",
519
+ key: "compilerOptions.skipLibCheck",
520
+ oldValue: opts.skipLibCheck,
521
+ newValue: true,
522
+ description: "Skip type checking of declaration files for faster builds"
523
+ });
524
+ }
525
+ if (opts.target !== "ES2022" && opts.target !== "ESNext") {
526
+ changes.push({
527
+ type: "modify",
528
+ file: "tsconfig.json",
529
+ key: "compilerOptions.target",
530
+ oldValue: opts.target,
531
+ newValue: "ES2022",
532
+ description: "Use modern JavaScript target"
533
+ });
534
+ }
535
+ } else {
536
+ changes.push({
537
+ type: "add",
538
+ file: isMonorepo2 ? "tsconfig.base.json" : "tsconfig.json",
539
+ description: `Create TypeScript configuration${isMonorepo2 ? " (shared base for monorepo)" : ""}`
540
+ });
541
+ }
542
+ return {
543
+ name: "typescript",
544
+ description: hasExisting ? "Improve TypeScript configuration" : "Set up TypeScript configuration",
545
+ priority: "high",
546
+ changes,
547
+ dependencies: [],
548
+ devDependencies: devDeps
549
+ };
550
+ }
551
+ async function planESLintSetup(projectPath, isTypeScript, framework, existing) {
552
+ const changes = [];
553
+ const devDeps = [];
554
+ const pkg = await readPackageJson(projectPath);
555
+ const deps = pkg.dependencies || {};
556
+ const devDepsPkg = pkg.devDependencies || {};
557
+ const allDeps = { ...deps, ...devDepsPkg };
558
+ if (!allDeps.eslint) {
559
+ devDeps.push("eslint");
560
+ }
561
+ if (isTypeScript) {
562
+ if (!allDeps["@typescript-eslint/eslint-plugin"]) {
563
+ devDeps.push("@typescript-eslint/eslint-plugin");
564
+ }
565
+ if (!allDeps["@typescript-eslint/parser"]) {
566
+ devDeps.push("@typescript-eslint/parser");
567
+ }
568
+ if (!allDeps["typescript-eslint"]) {
569
+ devDeps.push("typescript-eslint");
570
+ }
571
+ }
572
+ if (framework === "react" || framework === "nextjs") {
573
+ if (!allDeps["eslint-plugin-react"]) devDeps.push("eslint-plugin-react");
574
+ if (!allDeps["eslint-plugin-react-hooks"])
575
+ devDeps.push("eslint-plugin-react-hooks");
576
+ }
577
+ if (existing.eslint) {
578
+ if (!existing.eslintFlat) {
579
+ changes.push({
580
+ type: "modify",
581
+ file: "eslint.config.mjs",
582
+ description: "Migrate to ESLint 9 flat config format (recommended)"
583
+ });
584
+ } else {
585
+ changes.push({
586
+ type: "unchanged",
587
+ file: "eslint.config.mjs",
588
+ description: "ESLint flat config already exists"
589
+ });
590
+ }
591
+ } else {
592
+ changes.push({
593
+ type: "add",
594
+ file: "eslint.config.mjs",
595
+ description: "Create ESLint configuration with TypeScript support"
596
+ });
597
+ }
598
+ return {
599
+ name: "eslint",
600
+ description: existing.eslint ? "Audit ESLint configuration and dependencies" : "Set up ESLint for code linting",
601
+ priority: "high",
602
+ changes,
603
+ dependencies: [],
604
+ devDependencies: devDeps
605
+ };
606
+ }
607
+ async function planPrettierSetup(projectPath, hasExisting) {
608
+ const changes = [];
609
+ const devDeps = [];
610
+ const pkg = await readPackageJson(projectPath);
611
+ const deps = pkg.dependencies || {};
612
+ const devDepsPkg = pkg.devDependencies || {};
613
+ const allDeps = { ...deps, ...devDepsPkg };
614
+ if (!allDeps.prettier) {
615
+ devDeps.push("prettier");
616
+ }
617
+ if (hasExisting) {
618
+ const prettierConfig = await readPrettierConfig(projectPath);
619
+ if (prettierConfig.printWidth === void 0) {
620
+ changes.push({
621
+ type: "modify",
622
+ file: ".prettierrc",
623
+ key: "printWidth",
624
+ newValue: 100,
625
+ description: "Add printWidth setting"
626
+ });
627
+ }
628
+ if (prettierConfig.trailingComma === void 0) {
629
+ changes.push({
630
+ type: "modify",
631
+ file: ".prettierrc",
632
+ key: "trailingComma",
633
+ newValue: "es5",
634
+ description: "Add trailing comma setting"
635
+ });
636
+ }
637
+ if (changes.length === 0) {
638
+ changes.push({
639
+ type: "unchanged",
640
+ file: ".prettierrc",
641
+ description: "Prettier configuration is complete"
642
+ });
643
+ }
644
+ } else {
645
+ changes.push({
646
+ type: "add",
647
+ file: ".prettierrc",
648
+ description: "Create Prettier configuration"
649
+ });
650
+ changes.push({
651
+ type: "add",
652
+ file: ".prettierignore",
653
+ description: "Create Prettier ignore file"
654
+ });
655
+ }
656
+ return {
657
+ name: "prettier",
658
+ description: hasExisting ? "Audit Prettier configuration" : "Set up Prettier for code formatting",
659
+ priority: "high",
660
+ changes,
661
+ dependencies: [],
662
+ devDependencies: devDeps
663
+ };
664
+ }
665
+ async function planTestingSetup(projectPath, isTypeScript, framework, existing) {
666
+ const changes = [];
667
+ const devDeps = [];
668
+ const pkg = await readPackageJson(projectPath);
669
+ const deps = pkg.dependencies || {};
670
+ const devDepsPkg = pkg.devDependencies || {};
671
+ const allDeps = { ...deps, ...devDepsPkg };
672
+ if (existing.jest) {
673
+ changes.push({
674
+ type: "unchanged",
675
+ file: "jest.config.*",
676
+ description: "Jest configuration exists (preserving existing setup)"
677
+ });
678
+ return {
679
+ name: "testing",
680
+ description: "Jest testing already configured",
681
+ priority: "high",
682
+ changes,
683
+ dependencies: [],
684
+ devDependencies: []
685
+ };
686
+ }
687
+ if (!allDeps.vitest) {
688
+ devDeps.push("vitest");
689
+ }
690
+ if (!allDeps["@vitest/coverage-v8"]) {
691
+ devDeps.push("@vitest/coverage-v8");
692
+ }
693
+ if (["react", "nextjs", "vue", "nuxt", "svelte"].includes(framework)) {
694
+ if (!allDeps.jsdom) devDeps.push("jsdom");
695
+ if (framework === "react" || framework === "nextjs") {
696
+ if (!allDeps["@testing-library/react"])
697
+ devDeps.push("@testing-library/react");
698
+ }
699
+ }
700
+ if (existing.vitest) {
701
+ changes.push({
702
+ type: "unchanged",
703
+ file: `vitest.config.${isTypeScript ? "ts" : "js"}`,
704
+ description: "Vitest configuration already exists"
705
+ });
706
+ } else {
707
+ changes.push({
708
+ type: "add",
709
+ file: `vitest.config.${isTypeScript ? "ts" : "js"}`,
710
+ description: "Create Vitest configuration"
711
+ });
712
+ }
713
+ return {
714
+ name: "testing",
715
+ description: existing.vitest ? "Audit Vitest dependencies" : "Set up Vitest for testing",
716
+ priority: "high",
717
+ changes,
718
+ dependencies: [],
719
+ devDependencies: devDeps
720
+ };
721
+ }
722
+ async function planBuildSetup(projectPath, hasBuildScript) {
723
+ const changes = [];
724
+ const devDeps = [];
725
+ const pkg = await readPackageJson(projectPath);
726
+ const deps = pkg.dependencies || {};
727
+ const devDepsPkg = pkg.devDependencies || {};
728
+ const allDeps = { ...deps, ...devDepsPkg };
729
+ if (hasBuildScript) {
730
+ changes.push({
731
+ type: "unchanged",
732
+ file: "package.json",
733
+ key: "scripts.build",
734
+ description: "Build script already configured"
735
+ });
736
+ } else {
737
+ if (!allDeps.tsup) {
738
+ devDeps.push("tsup");
739
+ }
740
+ changes.push({
741
+ type: "add",
742
+ file: "tsup.config.ts",
743
+ description: "Create tsup build configuration"
744
+ });
745
+ }
746
+ return {
747
+ name: "build",
748
+ description: hasBuildScript ? "Build configuration exists" : "Set up tsup for TypeScript builds",
749
+ priority: "medium",
750
+ changes,
751
+ dependencies: [],
752
+ devDependencies: devDeps
753
+ };
754
+ }
755
+ async function planScriptsSetup(_projectPath, isTypeScript, _framework, scripts) {
756
+ const changes = [];
757
+ const scriptsToAdd = {};
758
+ if (!scripts.lint) {
759
+ scriptsToAdd.lint = "eslint src";
760
+ changes.push({
761
+ type: "add",
762
+ file: "package.json",
763
+ key: "scripts.lint",
764
+ newValue: "eslint src",
765
+ description: "Add lint script"
766
+ });
767
+ }
768
+ if (!scripts.format) {
769
+ scriptsToAdd.format = 'prettier --write "src/**/*.{ts,tsx,js,jsx,json}"';
770
+ changes.push({
771
+ type: "add",
772
+ file: "package.json",
773
+ key: "scripts.format",
774
+ newValue: scriptsToAdd.format,
775
+ description: "Add format script"
776
+ });
777
+ }
778
+ if (isTypeScript && !scripts.typecheck) {
779
+ scriptsToAdd.typecheck = "tsc --noEmit";
780
+ changes.push({
781
+ type: "add",
782
+ file: "package.json",
783
+ key: "scripts.typecheck",
784
+ newValue: "tsc --noEmit",
785
+ description: "Add typecheck script"
786
+ });
787
+ }
788
+ if (!scripts.test) {
789
+ scriptsToAdd.test = "vitest run";
790
+ changes.push({
791
+ type: "add",
792
+ file: "package.json",
793
+ key: "scripts.test",
794
+ newValue: "vitest run",
795
+ description: "Add test script"
796
+ });
797
+ }
798
+ if (!scripts.verify) {
799
+ scriptsToAdd.verify = "workflow-agent verify";
800
+ changes.push({
801
+ type: "add",
802
+ file: "package.json",
803
+ key: "scripts.verify",
804
+ newValue: "workflow-agent verify",
805
+ description: "Add verify script"
806
+ });
807
+ }
808
+ if (Object.keys(scriptsToAdd).length === 0) {
809
+ changes.push({
810
+ type: "unchanged",
811
+ file: "package.json",
812
+ description: "All standard scripts already configured"
813
+ });
814
+ }
815
+ return {
816
+ name: "scripts",
817
+ description: "Configure npm scripts",
818
+ priority: "medium",
819
+ changes,
820
+ dependencies: [],
821
+ devDependencies: []
822
+ };
823
+ }
824
+ async function planHooksSetup(projectPath, existing) {
825
+ const changes = [];
826
+ const devDeps = [];
827
+ const pkg = await readPackageJson(projectPath);
828
+ const deps = pkg.dependencies || {};
829
+ const devDepsPkg = pkg.devDependencies || {};
830
+ const allDeps = { ...deps, ...devDepsPkg };
831
+ if (!allDeps["simple-git-hooks"]) {
832
+ devDeps.push("simple-git-hooks");
833
+ }
834
+ if (!allDeps["lint-staged"]) {
835
+ devDeps.push("lint-staged");
836
+ }
837
+ if (existing.husky || existing.simpleGitHooks) {
838
+ changes.push({
839
+ type: "modify",
840
+ file: "package.json",
841
+ key: "simple-git-hooks",
842
+ description: "Ensure pre-commit hook configuration"
843
+ });
844
+ } else {
845
+ changes.push({
846
+ type: "add",
847
+ file: "package.json",
848
+ key: "simple-git-hooks",
849
+ newValue: { "pre-commit": "npx lint-staged" },
850
+ description: "Add pre-commit hook configuration"
851
+ });
852
+ changes.push({
853
+ type: "add",
854
+ file: "package.json",
855
+ key: "lint-staged",
856
+ description: "Add lint-staged configuration"
857
+ });
858
+ }
859
+ return {
860
+ name: "hooks",
861
+ description: existing.husky || existing.simpleGitHooks ? "Audit pre-commit hooks" : "Set up pre-commit hooks",
862
+ priority: "medium",
863
+ changes,
864
+ dependencies: [],
865
+ devDependencies: devDeps
866
+ };
867
+ }
868
+ async function planCISetup(projectPath, _packageManager, _isTypeScript, _framework, hasExisting, _isMonorepo) {
869
+ const changes = [];
870
+ if (hasExisting) {
871
+ if (existsSync2(join2(projectPath, ".github/workflows/ci.yml"))) {
872
+ changes.push({
873
+ type: "unchanged",
874
+ file: ".github/workflows/ci.yml",
875
+ description: "CI workflow already exists"
876
+ });
877
+ } else {
878
+ changes.push({
879
+ type: "add",
880
+ file: ".github/workflows/ci.yml",
881
+ description: "Add CI workflow (other workflows exist)"
882
+ });
883
+ }
884
+ } else {
885
+ changes.push({
886
+ type: "add",
887
+ file: ".github/workflows/ci.yml",
888
+ description: "Create GitHub Actions CI workflow"
889
+ });
890
+ }
891
+ return {
892
+ name: "ci",
893
+ description: hasExisting ? "Audit CI configuration" : "Set up GitHub Actions CI",
894
+ priority: "low",
895
+ changes,
896
+ dependencies: [],
897
+ devDependencies: []
898
+ };
899
+ }
900
+ async function generateAuditReport(projectPath = process.cwd()) {
901
+ const analysis = await analyzeProject(projectPath);
902
+ const allDeps = /* @__PURE__ */ new Set();
903
+ const allDevDeps = /* @__PURE__ */ new Set();
904
+ for (const plan of analysis.setupPlans) {
905
+ plan.dependencies.forEach((d) => allDeps.add(d));
906
+ plan.devDependencies.forEach((d) => allDevDeps.add(d));
907
+ }
908
+ const totalChanges = analysis.setupPlans.reduce(
909
+ (sum, plan) => sum + plan.changes.filter((c) => c.type !== "unchanged").length,
910
+ 0
911
+ );
912
+ return {
913
+ analysis,
914
+ totalChanges,
915
+ allDependencies: Array.from(allDeps),
916
+ allDevDependencies: Array.from(allDevDeps),
917
+ plans: analysis.setupPlans
918
+ };
919
+ }
920
+ function formatAuditReport(report) {
921
+ const lines = [];
922
+ lines.push("\u{1F4CB} Auto-Setup Audit Report\n");
923
+ lines.push(`Framework: ${report.analysis.framework}`);
924
+ lines.push(`Package Manager: ${report.analysis.packageManager}`);
925
+ lines.push(`TypeScript: ${report.analysis.isTypeScript ? "Yes" : "No"}`);
926
+ lines.push(`Monorepo: ${report.analysis.isMonorepo ? "Yes" : "No"}
927
+ `);
928
+ for (const plan of report.plans) {
929
+ const hasChanges = plan.changes.some((c) => c.type !== "unchanged");
930
+ const icon = hasChanges ? "\u{1F527}" : "\u2713";
931
+ lines.push(
932
+ `${icon} ${plan.name.charAt(0).toUpperCase() + plan.name.slice(1)}`
933
+ );
934
+ for (const change of plan.changes) {
935
+ const symbol = change.type === "add" ? "+" : change.type === "modify" ? "~" : "=";
936
+ let line = ` ${symbol} ${change.description}`;
937
+ if (change.key && change.oldValue !== void 0 && change.newValue !== void 0) {
938
+ line += ` (${String(change.oldValue)} \u2192 ${String(change.newValue)})`;
939
+ }
940
+ lines.push(line);
941
+ }
942
+ if (plan.devDependencies.length > 0) {
943
+ lines.push(` \u{1F4E6} Install: ${plan.devDependencies.join(", ")}`);
944
+ }
945
+ lines.push("");
946
+ }
947
+ if (report.allDevDependencies.length > 0) {
948
+ lines.push("Dependencies to install (batched):");
949
+ const pm = report.analysis.packageManager;
950
+ const cmd = pm === "npm" ? "npm install" : pm === "yarn" ? "yarn add" : `${pm} add`;
951
+ lines.push(` ${cmd} -D ${report.allDevDependencies.join(" ")}`);
952
+ lines.push("");
953
+ }
954
+ lines.push(`Total changes: ${report.totalChanges}`);
955
+ return lines.join("\n");
956
+ }
957
+ async function runAllSetups(projectPath = process.cwd(), onProgress) {
958
+ const report = await generateAuditReport(projectPath);
959
+ const results = [];
960
+ const filesCreated = [];
961
+ const filesUpdated = [];
962
+ if (report.allDevDependencies.length > 0) {
963
+ onProgress?.("Installing dependencies", "start");
964
+ try {
965
+ await installDependencies(
966
+ projectPath,
967
+ report.analysis.packageManager,
968
+ report.allDevDependencies
969
+ );
970
+ onProgress?.("Installing dependencies", "done");
971
+ } catch (error) {
972
+ onProgress?.("Installing dependencies", "error");
973
+ results.push({
974
+ success: false,
975
+ name: "dependencies",
976
+ message: `Failed to install: ${error instanceof Error ? error.message : String(error)}`,
977
+ filesCreated: [],
978
+ filesUpdated: [],
979
+ packagesInstalled: []
980
+ });
981
+ return results;
982
+ }
983
+ }
984
+ for (const plan of report.plans) {
985
+ const hasChanges = plan.changes.some((c) => c.type !== "unchanged");
986
+ if (!hasChanges && plan.devDependencies.length === 0) continue;
987
+ onProgress?.(`Setting up ${plan.name}`, "start");
988
+ try {
989
+ const result = await applySetupPlan(projectPath, plan, report.analysis);
990
+ results.push(result);
991
+ filesCreated.push(...result.filesCreated);
992
+ filesUpdated.push(...result.filesUpdated);
993
+ onProgress?.(`Setting up ${plan.name}`, "done");
994
+ } catch (error) {
995
+ onProgress?.(`Setting up ${plan.name}`, "error");
996
+ results.push({
997
+ success: false,
998
+ name: plan.name,
999
+ message: `Failed: ${error instanceof Error ? error.message : String(error)}`,
1000
+ filesCreated: [],
1001
+ filesUpdated: [],
1002
+ packagesInstalled: []
1003
+ });
1004
+ }
1005
+ }
1006
+ onProgress?.("Initializing git hooks", "start");
1007
+ try {
1008
+ await execa3("npx", ["simple-git-hooks"], {
1009
+ cwd: projectPath,
1010
+ stdio: "pipe"
1011
+ });
1012
+ onProgress?.("Initializing git hooks", "done");
1013
+ } catch {
1014
+ onProgress?.("Initializing git hooks", "error");
1015
+ }
1016
+ return results;
1017
+ }
1018
+ async function installDependencies(projectPath, packageManager, packages) {
1019
+ if (packages.length === 0) return;
1020
+ const commands = {
1021
+ npm: { cmd: "npm", args: ["install", "--save-dev"] },
1022
+ pnpm: { cmd: "pnpm", args: ["add", "-D"] },
1023
+ yarn: { cmd: "yarn", args: ["add", "-D"] },
1024
+ bun: { cmd: "bun", args: ["add", "-D"] }
1025
+ };
1026
+ const { cmd, args } = commands[packageManager];
1027
+ await execa3(cmd, [...args, ...packages], {
1028
+ cwd: projectPath,
1029
+ stdio: "pipe"
1030
+ });
1031
+ }
1032
+ async function applySetupPlan(projectPath, plan, analysis) {
1033
+ const filesCreated = [];
1034
+ const filesUpdated = [];
1035
+ switch (plan.name) {
1036
+ case "typescript":
1037
+ await applyTypeScriptSetup(
1038
+ projectPath,
1039
+ analysis,
1040
+ filesCreated,
1041
+ filesUpdated
1042
+ );
1043
+ break;
1044
+ case "eslint":
1045
+ await applyESLintSetup(projectPath, analysis, filesCreated, filesUpdated);
1046
+ break;
1047
+ case "prettier":
1048
+ await applyPrettierSetup(projectPath, filesCreated, filesUpdated);
1049
+ break;
1050
+ case "testing":
1051
+ await applyTestingSetup(
1052
+ projectPath,
1053
+ analysis,
1054
+ filesCreated,
1055
+ filesUpdated
1056
+ );
1057
+ break;
1058
+ case "build":
1059
+ await applyBuildSetup(projectPath, filesCreated, filesUpdated);
1060
+ break;
1061
+ case "scripts":
1062
+ await applyScriptsSetup(projectPath, analysis, filesUpdated);
1063
+ break;
1064
+ case "hooks":
1065
+ await applyHooksSetup(projectPath, filesUpdated);
1066
+ break;
1067
+ case "ci":
1068
+ await applyCISetup(projectPath, analysis, filesCreated, filesUpdated);
1069
+ break;
1070
+ }
1071
+ return {
1072
+ success: true,
1073
+ name: plan.name,
1074
+ message: `${plan.name} configured successfully`,
1075
+ filesCreated,
1076
+ filesUpdated,
1077
+ packagesInstalled: plan.devDependencies
1078
+ };
1079
+ }
1080
+ async function applyTypeScriptSetup(projectPath, analysis, filesCreated, filesUpdated) {
1081
+ const configName = analysis.isMonorepo ? "tsconfig.base.json" : "tsconfig.json";
1082
+ const configPath = join2(projectPath, configName);
1083
+ let tsconfig = {};
1084
+ if (existsSync2(configPath)) {
1085
+ tsconfig = await readJsonFile(configPath);
1086
+ filesUpdated.push(configName);
1087
+ } else {
1088
+ filesCreated.push(configName);
1089
+ }
1090
+ if (!tsconfig.compilerOptions) {
1091
+ tsconfig.compilerOptions = {};
1092
+ }
1093
+ const opts = tsconfig.compilerOptions;
1094
+ const improvements = {
1095
+ target: opts.target || "ES2022",
1096
+ module: opts.module || "ESNext",
1097
+ moduleResolution: opts.moduleResolution || "bundler",
1098
+ esModuleInterop: opts.esModuleInterop ?? true,
1099
+ strict: opts.strict ?? true,
1100
+ skipLibCheck: opts.skipLibCheck ?? true,
1101
+ resolveJsonModule: opts.resolveJsonModule ?? true,
1102
+ isolatedModules: opts.isolatedModules ?? true,
1103
+ declaration: opts.declaration ?? true,
1104
+ declarationMap: opts.declarationMap ?? true,
1105
+ sourceMap: opts.sourceMap ?? true
1106
+ };
1107
+ const frameworkOpts = getFrameworkTsOptions(analysis.framework);
1108
+ tsconfig.compilerOptions = { ...opts, ...improvements, ...frameworkOpts };
1109
+ if (!tsconfig.include) {
1110
+ tsconfig.include = ["src/**/*"];
1111
+ }
1112
+ if (!tsconfig.exclude) {
1113
+ tsconfig.exclude = ["node_modules", "dist", "coverage"];
1114
+ }
1115
+ await writeFile(configPath, JSON.stringify(tsconfig, null, 2) + "\n");
1116
+ }
1117
+ function getFrameworkTsOptions(framework) {
1118
+ switch (framework) {
1119
+ case "nextjs":
1120
+ return {
1121
+ lib: ["dom", "dom.iterable", "esnext"],
1122
+ jsx: "preserve",
1123
+ incremental: true,
1124
+ plugins: [{ name: "next" }]
1125
+ };
1126
+ case "react":
1127
+ case "remix":
1128
+ return {
1129
+ lib: ["dom", "dom.iterable", "esnext"],
1130
+ jsx: "react-jsx"
1131
+ };
1132
+ case "vue":
1133
+ case "nuxt":
1134
+ return {
1135
+ lib: ["esnext", "dom"],
1136
+ jsx: "preserve"
1137
+ };
1138
+ default:
1139
+ return {};
1140
+ }
1141
+ }
1142
+ async function applyESLintSetup(projectPath, analysis, filesCreated, _filesUpdated) {
1143
+ if (analysis.existing.eslintFlat) {
1144
+ return;
1145
+ }
1146
+ const configPath = join2(projectPath, "eslint.config.mjs");
1147
+ const config = generateESLintFlatConfig(
1148
+ analysis.isTypeScript,
1149
+ analysis.framework
1150
+ );
1151
+ await writeFile(configPath, config);
1152
+ filesCreated.push("eslint.config.mjs");
1153
+ }
1154
+ function generateESLintFlatConfig(isTypeScript, framework) {
1155
+ const imports = [];
1156
+ const configs = [];
1157
+ if (isTypeScript) {
1158
+ imports.push(`import tseslint from "typescript-eslint";`);
1159
+ }
1160
+ if (framework === "react" || framework === "nextjs") {
1161
+ imports.push(`import react from "eslint-plugin-react";`);
1162
+ imports.push(`import reactHooks from "eslint-plugin-react-hooks";`);
1163
+ }
1164
+ if (isTypeScript) {
1165
+ configs.push(` ...tseslint.configs.recommended`);
1166
+ }
1167
+ configs.push(` {
1168
+ files: ["**/*.${isTypeScript ? "{ts,tsx}" : "{js,jsx}"}"],
1169
+ rules: {
1170
+ ${isTypeScript ? `"@typescript-eslint/no-unused-vars": ["error", { argsIgnorePattern: "^_" }],` : ""}
1171
+ "no-console": "warn",
1172
+ },
1173
+ }`);
1174
+ configs.push(` {
1175
+ ignores: ["dist/", "node_modules/", "coverage/", ".next/", "build/"],
1176
+ }`);
1177
+ return `${imports.join("\n")}
1178
+
1179
+ export default [
1180
+ ${configs.join(",\n")}
1181
+ ];
1182
+ `;
1183
+ }
1184
+ async function applyPrettierSetup(projectPath, filesCreated, filesUpdated) {
1185
+ const prettierPath = join2(projectPath, ".prettierrc");
1186
+ let config = {
1187
+ semi: true,
1188
+ singleQuote: false,
1189
+ tabWidth: 2,
1190
+ trailingComma: "es5",
1191
+ printWidth: 100,
1192
+ bracketSpacing: true
1193
+ };
1194
+ if (existsSync2(prettierPath)) {
1195
+ const existing = await readPrettierConfig(projectPath);
1196
+ config = { ...config, ...existing };
1197
+ filesUpdated.push(".prettierrc");
1198
+ } else {
1199
+ filesCreated.push(".prettierrc");
1200
+ }
1201
+ await writeFile(prettierPath, JSON.stringify(config, null, 2) + "\n");
1202
+ const ignorePath = join2(projectPath, ".prettierignore");
1203
+ if (!existsSync2(ignorePath)) {
1204
+ const ignoreContent = `dist/
1205
+ node_modules/
1206
+ coverage/
1207
+ .next/
1208
+ build/
1209
+ *.min.js
1210
+ pnpm-lock.yaml
1211
+ package-lock.json
1212
+ yarn.lock
1213
+ `;
1214
+ await writeFile(ignorePath, ignoreContent);
1215
+ filesCreated.push(".prettierignore");
1216
+ }
1217
+ }
1218
+ async function applyTestingSetup(projectPath, analysis, filesCreated, _filesUpdated) {
1219
+ if (analysis.existing.jest || analysis.existing.vitest) {
1220
+ return;
1221
+ }
1222
+ const ext = analysis.isTypeScript ? "ts" : "js";
1223
+ const configPath = join2(projectPath, `vitest.config.${ext}`);
1224
+ const environment = ["react", "nextjs", "vue", "nuxt", "svelte"].includes(
1225
+ analysis.framework
1226
+ ) ? "jsdom" : "node";
1227
+ const config = `import { defineConfig } from "vitest/config";
1228
+
1229
+ export default defineConfig({
1230
+ test: {
1231
+ globals: true,
1232
+ environment: "${environment}",
1233
+ coverage: {
1234
+ provider: "v8",
1235
+ reporter: ["text", "json", "html"],
1236
+ exclude: ["node_modules/", "dist/", "**/*.test.${ext}"],
1237
+ },
1238
+ include: ["src/**/*.test.${ext}", "tests/**/*.test.${ext}"],
1239
+ },
1240
+ });
1241
+ `;
1242
+ await writeFile(configPath, config);
1243
+ filesCreated.push(`vitest.config.${ext}`);
1244
+ }
1245
+ async function applyBuildSetup(projectPath, filesCreated, _filesUpdated) {
1246
+ const configPath = join2(projectPath, "tsup.config.ts");
1247
+ if (existsSync2(configPath)) {
1248
+ return;
1249
+ }
1250
+ const config = `import { defineConfig } from "tsup";
1251
+
1252
+ export default defineConfig({
1253
+ entry: ["src/index.ts"],
1254
+ format: ["esm"],
1255
+ dts: true,
1256
+ clean: true,
1257
+ sourcemap: true,
1258
+ });
1259
+ `;
1260
+ await writeFile(configPath, config);
1261
+ filesCreated.push("tsup.config.ts");
1262
+ }
1263
+ async function applyScriptsSetup(projectPath, analysis, filesUpdated) {
1264
+ const pkgPath = join2(projectPath, "package.json");
1265
+ const pkg = await readPackageJson(projectPath);
1266
+ const scripts = pkg.scripts || {};
1267
+ const scriptsToAdd = {
1268
+ lint: "eslint src",
1269
+ "lint:fix": "eslint src --fix",
1270
+ format: 'prettier --write "src/**/*.{ts,tsx,js,jsx,json}"',
1271
+ "format:check": 'prettier --check "src/**/*.{ts,tsx,js,jsx,json}"',
1272
+ test: "vitest run",
1273
+ "test:watch": "vitest",
1274
+ "test:coverage": "vitest run --coverage",
1275
+ verify: "workflow-agent verify",
1276
+ "verify:fix": "workflow-agent verify --fix",
1277
+ "pre-commit": "workflow-agent verify --fix"
1278
+ };
1279
+ if (analysis.isTypeScript) {
1280
+ scriptsToAdd.typecheck = "tsc --noEmit";
1281
+ }
1282
+ let added = false;
1283
+ for (const [name, cmd] of Object.entries(scriptsToAdd)) {
1284
+ if (!scripts[name]) {
1285
+ scripts[name] = cmd;
1286
+ added = true;
1287
+ }
1288
+ }
1289
+ if (added) {
1290
+ pkg.scripts = scripts;
1291
+ await writeFile(pkgPath, JSON.stringify(pkg, null, 2) + "\n");
1292
+ filesUpdated.push("package.json");
1293
+ }
1294
+ }
1295
+ async function applyHooksSetup(projectPath, filesUpdated) {
1296
+ const pkgPath = join2(projectPath, "package.json");
1297
+ const pkg = await readPackageJson(projectPath);
1298
+ if (!pkg["simple-git-hooks"]) {
1299
+ pkg["simple-git-hooks"] = {
1300
+ "pre-commit": "npx lint-staged"
1301
+ };
1302
+ }
1303
+ if (!pkg["lint-staged"]) {
1304
+ pkg["lint-staged"] = {
1305
+ "*.{ts,tsx,js,jsx}": ["eslint --fix", "prettier --write"],
1306
+ "*.{json,md,yml,yaml}": ["prettier --write"]
1307
+ };
1308
+ }
1309
+ await writeFile(pkgPath, JSON.stringify(pkg, null, 2) + "\n");
1310
+ filesUpdated.push("package.json");
1311
+ }
1312
+ async function applyCISetup(projectPath, analysis, filesCreated, _filesUpdated) {
1313
+ const workflowsDir = join2(projectPath, ".github/workflows");
1314
+ await mkdir(workflowsDir, { recursive: true });
1315
+ const ciPath = join2(workflowsDir, "ci.yml");
1316
+ if (existsSync2(ciPath)) {
1317
+ return;
1318
+ }
1319
+ const workflow = generateCIWorkflow(
1320
+ analysis.packageManager,
1321
+ analysis.isTypeScript,
1322
+ analysis.framework,
1323
+ analysis.isMonorepo
1324
+ );
1325
+ await writeFile(ciPath, workflow);
1326
+ filesCreated.push(".github/workflows/ci.yml");
1327
+ }
1328
+ function generateCIWorkflow(packageManager, isTypeScript, framework, _isMonorepo) {
1329
+ const runCmd = packageManager === "npm" ? "npm run" : packageManager === "yarn" ? "yarn" : packageManager;
1330
+ const isPnpm = packageManager === "pnpm";
1331
+ return `name: CI
1332
+
1333
+ on:
1334
+ push:
1335
+ branches: [main, master]
1336
+ pull_request:
1337
+ branches: [main, master]
1338
+
1339
+ jobs:
1340
+ ci:
1341
+ runs-on: ubuntu-latest
1342
+
1343
+ steps:
1344
+ - name: Checkout
1345
+ uses: actions/checkout@v4
1346
+
1347
+ - name: Setup Node.js
1348
+ uses: actions/setup-node@v4
1349
+ with:
1350
+ node-version: "20"
1351
+ ${isPnpm ? `
1352
+ - name: Install pnpm
1353
+ uses: pnpm/action-setup@v4
1354
+ with:
1355
+ version: 9
1356
+ ` : ""}
1357
+ - name: Install dependencies
1358
+ run: ${packageManager} install
1359
+
1360
+ ${isTypeScript ? ` - name: Type check
1361
+ run: ${runCmd} typecheck
1362
+
1363
+ ` : ""} - name: Lint
1364
+ run: ${runCmd} lint
1365
+
1366
+ - name: Format check
1367
+ run: ${runCmd} format:check || true
1368
+
1369
+ - name: Test
1370
+ run: ${runCmd} test
1371
+ ${isTypeScript && !["nextjs", "remix", "nuxt"].includes(framework) ? `
1372
+ - name: Build
1373
+ run: ${runCmd} build
1374
+ ` : ""}`;
1375
+ }
1376
+ async function readPackageJson(projectPath) {
1377
+ const pkgPath = join2(projectPath, "package.json");
1378
+ if (!existsSync2(pkgPath)) {
1379
+ return {};
1380
+ }
1381
+ return JSON.parse(await readFile2(pkgPath, "utf-8"));
1382
+ }
1383
+ async function readTSConfig(filePath) {
1384
+ if (!existsSync2(filePath)) {
1385
+ return {};
1386
+ }
1387
+ return JSON.parse(await readFile2(filePath, "utf-8"));
1388
+ }
1389
+ async function readJsonFile(filePath) {
1390
+ if (!existsSync2(filePath)) {
1391
+ return {};
1392
+ }
1393
+ return JSON.parse(await readFile2(filePath, "utf-8"));
1394
+ }
1395
+ async function readPrettierConfig(projectPath) {
1396
+ const files = [".prettierrc", ".prettierrc.json", "prettier.config.js"];
1397
+ for (const file of files) {
1398
+ const filePath = join2(projectPath, file);
1399
+ if (existsSync2(filePath)) {
1400
+ if (file.endsWith(".js")) {
1401
+ return {};
1402
+ }
1403
+ try {
1404
+ return JSON.parse(await readFile2(filePath, "utf-8"));
1405
+ } catch {
1406
+ return {};
1407
+ }
1408
+ }
1409
+ }
1410
+ return {};
1411
+ }
1412
+
1413
+ export {
1414
+ QUALITY_CHECKS,
1415
+ runCheck,
1416
+ applyFix,
1417
+ runAllChecks,
1418
+ hasUncommittedChanges,
1419
+ stageAllChanges,
1420
+ analyzeProject,
1421
+ generateAuditReport,
1422
+ formatAuditReport,
1423
+ runAllSetups
1424
+ };
1425
+ //# sourceMappingURL=chunk-YI3LBZ7I.js.map