@kidd-cli/cli 0.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.
@@ -0,0 +1,680 @@
1
+ import { command } from "@kidd-cli/core";
2
+ import { dirname, join, relative } from "node:path";
3
+ import { readManifest } from "@kidd-cli/utils/manifest";
4
+ import { loadConfig } from "@kidd-cli/config/loader";
5
+ import { z } from "zod";
6
+ import pc from "picocolors";
7
+ import { access, mkdir, readFile, writeFile } from "node:fs/promises";
8
+ import { attemptAsync, err, ok } from "@kidd-cli/utils/fp";
9
+ import { jsonParse, jsonStringify } from "@kidd-cli/utils/json";
10
+
11
+ //#region src/lib/checks.ts
12
+ /**
13
+ * Default entry point for the CLI source.
14
+ */
15
+ const DEFAULT_ENTRY = "./src/index.ts";
16
+ /**
17
+ * Default directory for CLI commands.
18
+ */
19
+ const DEFAULT_COMMANDS = "./commands";
20
+ /**
21
+ * Create a CheckContext from the gathered project data.
22
+ *
23
+ * @param params - The context parameters.
24
+ * @returns A frozen CheckContext.
25
+ */
26
+ function createCheckContext(params) {
27
+ return {
28
+ configError: params.configError,
29
+ configResult: params.configResult,
30
+ cwd: params.cwd,
31
+ manifest: params.manifest,
32
+ rawPackageJson: params.rawPackageJson
33
+ };
34
+ }
35
+ /**
36
+ * Create a CheckResult with the given status.
37
+ *
38
+ * @private
39
+ * @param params - The check result parameters.
40
+ * @returns A CheckResult.
41
+ */
42
+ function checkResult(params) {
43
+ return {
44
+ hint: params.hint ?? null,
45
+ message: params.message,
46
+ name: params.name,
47
+ status: params.status
48
+ };
49
+ }
50
+ /**
51
+ * Create a FixResult.
52
+ *
53
+ * @private
54
+ * @param params - The fix result parameters.
55
+ * @returns A FixResult.
56
+ */
57
+ function fixResult(params) {
58
+ return {
59
+ fixed: params.fixed,
60
+ message: params.message,
61
+ name: params.name
62
+ };
63
+ }
64
+ /**
65
+ * Check whether a kidd config file exists.
66
+ *
67
+ * @private
68
+ * @param context - The diagnostic check context.
69
+ * @returns A CheckResult indicating pass or fail.
70
+ */
71
+ async function checkKiddConfig(context) {
72
+ if (context.configResult && context.configResult.configFile) return checkResult({
73
+ message: `Config file found at ./${relative(context.cwd, context.configResult.configFile)}`,
74
+ name: "kidd.config",
75
+ status: "pass"
76
+ });
77
+ return checkResult({
78
+ hint: "Run \"kidd init\" to scaffold a config file",
79
+ message: "No config file found",
80
+ name: "kidd.config",
81
+ status: "fail"
82
+ });
83
+ }
84
+ /**
85
+ * Check whether the loaded config schema is valid.
86
+ *
87
+ * @private
88
+ * @param context - The diagnostic check context.
89
+ * @returns A CheckResult indicating pass, warn, or fail.
90
+ */
91
+ async function checkConfigSchema(context) {
92
+ if (context.configResult) return checkResult({
93
+ message: "Config is valid",
94
+ name: "config schema",
95
+ status: "pass"
96
+ });
97
+ if (context.configError) return checkResult({
98
+ hint: "Check your kidd.config.ts for syntax or schema errors",
99
+ message: `Config validation failed: ${context.configError.message}`,
100
+ name: "config schema",
101
+ status: "fail"
102
+ });
103
+ return checkResult({
104
+ message: "No config file, using defaults",
105
+ name: "config schema",
106
+ status: "warn"
107
+ });
108
+ }
109
+ /**
110
+ * Check whether package.json exists in the project.
111
+ *
112
+ * @private
113
+ * @param context - The diagnostic check context.
114
+ * @returns A CheckResult indicating pass or fail.
115
+ */
116
+ async function checkPackageJson(context) {
117
+ if (context.rawPackageJson) return checkResult({
118
+ message: "Found",
119
+ name: "package.json",
120
+ status: "pass"
121
+ });
122
+ return checkResult({
123
+ hint: "Run \"pnpm init\" to create a package.json",
124
+ message: "Not found",
125
+ name: "package.json",
126
+ status: "fail"
127
+ });
128
+ }
129
+ /**
130
+ * Check whether the package.json has a version field.
131
+ *
132
+ * @private
133
+ * @param context - The diagnostic check context.
134
+ * @returns A CheckResult indicating pass or warn.
135
+ */
136
+ async function checkPackageVersion(context) {
137
+ if (context.manifest && context.manifest.version) return checkResult({
138
+ message: context.manifest.version,
139
+ name: "package version",
140
+ status: "pass"
141
+ });
142
+ return checkResult({
143
+ hint: "Add a \"version\" field to package.json",
144
+ message: "No version field",
145
+ name: "package version",
146
+ status: "warn"
147
+ });
148
+ }
149
+ /**
150
+ * Check whether the package.json declares ESM via `"type": "module"`.
151
+ *
152
+ * @private
153
+ * @param context - The diagnostic check context.
154
+ * @returns A CheckResult indicating pass or fail.
155
+ */
156
+ async function checkModuleType(context) {
157
+ if (context.rawPackageJson && context.rawPackageJson.type === "module") return checkResult({
158
+ message: "ESM (\"type\": \"module\")",
159
+ name: "module type",
160
+ status: "pass"
161
+ });
162
+ return checkResult({
163
+ hint: "Add \"type\": \"module\" to package.json (fixable with --fix)",
164
+ message: "Missing or wrong \"type\" field (expected \"module\")",
165
+ name: "module type",
166
+ status: "fail"
167
+ });
168
+ }
169
+ /**
170
+ * Check whether kidd is listed as a dependency or devDependency.
171
+ *
172
+ * @private
173
+ * @param context - The diagnostic check context.
174
+ * @returns A CheckResult indicating pass or fail.
175
+ */
176
+ async function checkKiddDependency(context) {
177
+ if (!context.rawPackageJson) return checkResult({
178
+ hint: "Run \"pnpm init\" to create a package.json first",
179
+ message: "No package.json found",
180
+ name: "@kidd-cli/core dependency",
181
+ status: "fail"
182
+ });
183
+ const deps = context.rawPackageJson.dependencies ?? {};
184
+ const devDeps = context.rawPackageJson.devDependencies ?? {};
185
+ if ("@kidd-cli/core" in deps) return checkResult({
186
+ message: "Found in dependencies",
187
+ name: "@kidd-cli/core dependency",
188
+ status: "pass"
189
+ });
190
+ if ("@kidd-cli/core" in devDeps) return checkResult({
191
+ message: "Found in devDependencies",
192
+ name: "@kidd-cli/core dependency",
193
+ status: "pass"
194
+ });
195
+ return checkResult({
196
+ hint: "Run \"pnpm add kidd\" or use --fix to add it (fixable with --fix)",
197
+ message: "Not found in dependencies or devDependencies",
198
+ name: "@kidd-cli/core dependency",
199
+ status: "fail"
200
+ });
201
+ }
202
+ /**
203
+ * Check whether the CLI entry point file exists on disk.
204
+ *
205
+ * @private
206
+ * @param context - The diagnostic check context.
207
+ * @returns A CheckResult indicating pass, warn, or fail.
208
+ */
209
+ async function checkEntryPoint(context) {
210
+ const config = extractConfig(context.configResult);
211
+ const entryPath = resolveEntryPath(config);
212
+ if (await fileExists(join(context.cwd, entryPath))) return checkResult({
213
+ message: `Found: ${entryPath}`,
214
+ name: "entry point",
215
+ status: "pass"
216
+ });
217
+ if (!config) return checkResult({
218
+ hint: "Create the entry file or update \"entry\" in kidd.config.ts (fixable with --fix)",
219
+ message: `No config, default not found: ${entryPath}`,
220
+ name: "entry point",
221
+ status: "warn"
222
+ });
223
+ return checkResult({
224
+ hint: "Create the entry file or update \"entry\" in kidd.config.ts (fixable with --fix)",
225
+ message: `Not found: ${entryPath}`,
226
+ name: "entry point",
227
+ status: "fail"
228
+ });
229
+ }
230
+ /**
231
+ * Check whether the commands directory exists on disk.
232
+ *
233
+ * @private
234
+ * @param context - The diagnostic check context.
235
+ * @returns A CheckResult indicating pass, warn, or fail.
236
+ */
237
+ async function checkCommandsDirectory(context) {
238
+ const config = extractConfig(context.configResult);
239
+ const commandsPath = resolveCommandsPath(config);
240
+ if (await fileExists(join(context.cwd, commandsPath))) return checkResult({
241
+ message: `Found: ${commandsPath}`,
242
+ name: "commands directory",
243
+ status: "pass"
244
+ });
245
+ if (!config) return checkResult({
246
+ hint: "Create the directory or update \"commands\" in kidd.config.ts (fixable with --fix)",
247
+ message: `No config, default not found: ${commandsPath}`,
248
+ name: "commands directory",
249
+ status: "warn"
250
+ });
251
+ return checkResult({
252
+ hint: "Create the directory or update \"commands\" in kidd.config.ts (fixable with --fix)",
253
+ message: `Not found: ${commandsPath}`,
254
+ name: "commands directory",
255
+ status: "fail"
256
+ });
257
+ }
258
+ /**
259
+ * Check whether a tsconfig.json exists in the project root.
260
+ *
261
+ * @private
262
+ * @param context - The diagnostic check context.
263
+ * @returns A CheckResult indicating pass or warn.
264
+ */
265
+ async function checkTsconfig(context) {
266
+ if (await fileExists(join(context.cwd, "tsconfig.json"))) return checkResult({
267
+ message: "Found",
268
+ name: "tsconfig.json",
269
+ status: "pass"
270
+ });
271
+ return checkResult({
272
+ hint: "Run \"tsc --init\" to create a tsconfig.json",
273
+ message: "Not found (recommended)",
274
+ name: "tsconfig.json",
275
+ status: "warn"
276
+ });
277
+ }
278
+ /**
279
+ * Fix the module type by adding "type": "module" to package.json.
280
+ *
281
+ * @private
282
+ * @param context - The diagnostic check context.
283
+ * @returns A FixResult indicating whether the fix was applied.
284
+ */
285
+ async function fixModuleType(context) {
286
+ const [updateError] = await updatePackageJson(context.cwd, (pkg) => ({
287
+ ...pkg,
288
+ type: "module"
289
+ }));
290
+ if (updateError) return fixResult({
291
+ fixed: false,
292
+ message: updateError.message,
293
+ name: "module type"
294
+ });
295
+ return fixResult({
296
+ fixed: true,
297
+ message: "Added \"type\": \"module\" to package.json",
298
+ name: "module type"
299
+ });
300
+ }
301
+ /**
302
+ * Fix the kidd dependency by adding it to package.json dependencies.
303
+ *
304
+ * @private
305
+ * @param context - The diagnostic check context.
306
+ * @returns A FixResult indicating whether the fix was applied.
307
+ */
308
+ async function fixKiddDependency(context) {
309
+ const [updateError] = await updatePackageJson(context.cwd, (pkg) => {
310
+ const deps = pkg.dependencies;
311
+ return {
312
+ ...pkg,
313
+ dependencies: {
314
+ ...deps,
315
+ "@kidd-cli/core": "latest"
316
+ }
317
+ };
318
+ });
319
+ if (updateError) return fixResult({
320
+ fixed: false,
321
+ message: updateError.message,
322
+ name: "@kidd-cli/core dependency"
323
+ });
324
+ return fixResult({
325
+ fixed: true,
326
+ message: "Added \"@kidd-cli/core\": \"latest\" to dependencies",
327
+ name: "@kidd-cli/core dependency"
328
+ });
329
+ }
330
+ /**
331
+ * Fix the entry point by creating the entry file with a minimal scaffold.
332
+ *
333
+ * @private
334
+ * @param context - The diagnostic check context.
335
+ * @returns A FixResult indicating whether the fix was applied.
336
+ */
337
+ async function fixEntryPoint(context) {
338
+ const entryPath = resolveEntryPath(extractConfig(context.configResult));
339
+ const absolutePath = join(context.cwd, entryPath);
340
+ const [mkdirError] = await attemptAsync(() => mkdir(dirname(absolutePath), { recursive: true }));
341
+ if (mkdirError) return fixResult({
342
+ fixed: false,
343
+ message: `Failed to create directory: ${mkdirError.message}`,
344
+ name: "entry point"
345
+ });
346
+ const content = `import { create } from '@kidd-cli/core'\n`;
347
+ const [writeError] = await attemptAsync(() => writeFile(absolutePath, content, "utf8"));
348
+ if (writeError) return fixResult({
349
+ fixed: false,
350
+ message: `Failed to create file: ${writeError.message}`,
351
+ name: "entry point"
352
+ });
353
+ return fixResult({
354
+ fixed: true,
355
+ message: `Created ${entryPath}`,
356
+ name: "entry point"
357
+ });
358
+ }
359
+ /**
360
+ * Fix the commands directory by creating it.
361
+ *
362
+ * @private
363
+ * @param context - The diagnostic check context.
364
+ * @returns A FixResult indicating whether the fix was applied.
365
+ */
366
+ async function fixCommandsDirectory(context) {
367
+ const commandsPath = resolveCommandsPath(extractConfig(context.configResult));
368
+ const absolutePath = join(context.cwd, commandsPath);
369
+ const [mkdirError] = await attemptAsync(() => mkdir(absolutePath, { recursive: true }));
370
+ if (mkdirError) return fixResult({
371
+ fixed: false,
372
+ message: `Failed to create directory: ${mkdirError.message}`,
373
+ name: "commands directory"
374
+ });
375
+ return fixResult({
376
+ fixed: true,
377
+ message: `Created ${commandsPath}`,
378
+ name: "commands directory"
379
+ });
380
+ }
381
+ /**
382
+ * All diagnostic checks to run in order.
383
+ */
384
+ const CHECKS = [
385
+ {
386
+ name: "kidd.config",
387
+ run: checkKiddConfig
388
+ },
389
+ {
390
+ name: "config schema",
391
+ run: checkConfigSchema
392
+ },
393
+ {
394
+ name: "package.json",
395
+ run: checkPackageJson
396
+ },
397
+ {
398
+ name: "package version",
399
+ run: checkPackageVersion
400
+ },
401
+ {
402
+ fix: fixModuleType,
403
+ name: "module type",
404
+ run: checkModuleType
405
+ },
406
+ {
407
+ fix: fixKiddDependency,
408
+ name: "@kidd-cli/core dependency",
409
+ run: checkKiddDependency
410
+ },
411
+ {
412
+ fix: fixEntryPoint,
413
+ name: "entry point",
414
+ run: checkEntryPoint
415
+ },
416
+ {
417
+ fix: fixCommandsDirectory,
418
+ name: "commands directory",
419
+ run: checkCommandsDirectory
420
+ },
421
+ {
422
+ name: "tsconfig.json",
423
+ run: checkTsconfig
424
+ }
425
+ ];
426
+ /**
427
+ * Read and parse the raw package.json from a directory.
428
+ *
429
+ * Only extracts the fields needed by diagnostic checks that Manifest does not expose.
430
+ *
431
+ * @param cwd - The directory to read from.
432
+ * @returns A Result tuple with the raw package.json data or an error message.
433
+ */
434
+ async function readRawPackageJson(cwd) {
435
+ const filePath = join(cwd, "package.json");
436
+ const [readError, content] = await attemptAsync(() => readFile(filePath, "utf8"));
437
+ if (readError) return err(`Failed to read package.json: ${readError.message}`);
438
+ const [parseError, data] = jsonParse(content);
439
+ if (parseError) return [parseError, null];
440
+ return ok(data);
441
+ }
442
+ /**
443
+ * Check whether a path exists on disk.
444
+ *
445
+ * @private
446
+ * @param filePath - The path to check.
447
+ * @returns True when the path is accessible, false otherwise.
448
+ */
449
+ async function fileExists(filePath) {
450
+ const [accessError] = await attemptAsync(() => access(filePath));
451
+ return accessError === null;
452
+ }
453
+ /**
454
+ * Extract a KiddConfig from a load result, returning null when absent.
455
+ *
456
+ * @private
457
+ * @param result - The config load result, or null.
458
+ * @returns The config object or null.
459
+ */
460
+ function extractConfig(result) {
461
+ if (result) return result.config;
462
+ return null;
463
+ }
464
+ /**
465
+ * Resolve the entry path from config, falling back to the default.
466
+ *
467
+ * @private
468
+ * @param config - The loaded config or null.
469
+ * @returns The entry path string.
470
+ */
471
+ function resolveEntryPath(config) {
472
+ if (config && config.entry) return config.entry;
473
+ return DEFAULT_ENTRY;
474
+ }
475
+ /**
476
+ * Resolve the commands directory path from config, falling back to the default.
477
+ *
478
+ * @private
479
+ * @param config - The loaded config or null.
480
+ * @returns The commands directory path string.
481
+ */
482
+ function resolveCommandsPath(config) {
483
+ if (config && config.commands) return config.commands;
484
+ return DEFAULT_COMMANDS;
485
+ }
486
+ /**
487
+ * Read, transform, and write back a package.json file.
488
+ *
489
+ * @private
490
+ * @param cwd - The directory containing the package.json.
491
+ * @param transform - A pure function that returns the updated package data.
492
+ * @returns A Result tuple indicating success or failure.
493
+ */
494
+ async function updatePackageJson(cwd, transform) {
495
+ const filePath = join(cwd, "package.json");
496
+ const [readError, content] = await attemptAsync(() => readFile(filePath, "utf8"));
497
+ if (readError) return err(`Failed to read package.json: ${readError.message}`);
498
+ const [parseError, data] = jsonParse(content);
499
+ if (parseError) return [parseError, null];
500
+ const [stringifyError, json] = jsonStringify(transform(data), { pretty: true });
501
+ if (stringifyError) return [stringifyError, null];
502
+ const [writeError] = await attemptAsync(() => writeFile(filePath, `${json}\n`, "utf8"));
503
+ if (writeError) return err(`Failed to write package.json: ${writeError.message}`);
504
+ return ok();
505
+ }
506
+
507
+ //#endregion
508
+ //#region src/commands/doctor.ts
509
+ /**
510
+ * Diagnose common kidd project issues.
511
+ *
512
+ * Validates config, checks package.json setup, verifies entry points exist,
513
+ * and catches anything that could cause build or runtime failures.
514
+ */
515
+ const doctorCommand = command({
516
+ args: z.object({ fix: z.boolean().describe("Auto-fix issues where possible").optional() }),
517
+ description: "Diagnose common kidd project issues",
518
+ handler: async (ctx) => {
519
+ const cwd = process.cwd();
520
+ const shouldFix = ctx.args.fix === true;
521
+ const [configError, configResult] = await loadConfig({ cwd });
522
+ const [, manifest] = await readManifest(cwd);
523
+ const [, rawPackageJson] = await readRawPackageJson(cwd);
524
+ const context = createCheckContext({
525
+ configError,
526
+ configResult,
527
+ cwd,
528
+ manifest,
529
+ rawPackageJson
530
+ });
531
+ ctx.spinner.start("Running diagnostics...");
532
+ const initialResults = await Promise.all(CHECKS.map((check) => check.run(context)));
533
+ const fixResults = await resolveFixResults(shouldFix, initialResults, context);
534
+ const fixed = fixResults.filter((r) => r.fixed).length;
535
+ const results = await resolveResults({
536
+ cwd,
537
+ fixed,
538
+ initialResults,
539
+ shouldFix
540
+ });
541
+ ctx.spinner.stop("Diagnostics complete");
542
+ displayResults(ctx, results, fixResults);
543
+ const passed = results.filter((r) => r.status === "pass").length;
544
+ const warnings = results.filter((r) => r.status === "warn").length;
545
+ const failed = results.filter((r) => r.status === "fail").length;
546
+ const summary = formatSummary({
547
+ failed,
548
+ fixed,
549
+ passed,
550
+ total: results.length,
551
+ warnings
552
+ });
553
+ ctx.output.raw(summary);
554
+ if (failed > 0) ctx.fail(`${failed} ${pluralizeCheck(failed)} failed`);
555
+ }
556
+ });
557
+ /**
558
+ * Resolve fix results based on whether --fix is enabled.
559
+ *
560
+ * @private
561
+ * @param shouldFix - Whether the --fix flag was set.
562
+ * @param results - The initial check results.
563
+ * @param context - The diagnostic check context.
564
+ * @returns Fix results when --fix is enabled, empty array otherwise.
565
+ */
566
+ async function resolveFixResults(shouldFix, results, context) {
567
+ if (!shouldFix) return [];
568
+ return applyFixes(results, context);
569
+ }
570
+ /**
571
+ * Resolve the final check results, re-running after fixes when needed.
572
+ *
573
+ * Rebuilds the context from disk so that fixes to package.json are reflected.
574
+ *
575
+ * @private
576
+ * @param params - The resolution parameters.
577
+ * @returns Updated check results if fixes were applied, otherwise the initial results.
578
+ */
579
+ async function resolveResults(params) {
580
+ if (!params.shouldFix || params.fixed === 0) return params.initialResults;
581
+ const [configError, configResult] = await loadConfig({ cwd: params.cwd });
582
+ const [, manifest] = await readManifest(params.cwd);
583
+ const [, rawPackageJson] = await readRawPackageJson(params.cwd);
584
+ const freshContext = createCheckContext({
585
+ configError,
586
+ configResult,
587
+ cwd: params.cwd,
588
+ manifest,
589
+ rawPackageJson
590
+ });
591
+ return Promise.all(CHECKS.map((check) => check.run(freshContext)));
592
+ }
593
+ /**
594
+ * Apply fixes for all non-passing checks that have a fix function.
595
+ *
596
+ * @private
597
+ * @param results - The initial check results.
598
+ * @param context - The diagnostic check context.
599
+ * @returns An array of FixResults for checks that were attempted.
600
+ */
601
+ async function applyFixes(results, context) {
602
+ const fixable = CHECKS.filter((check) => {
603
+ if (!check.fix) return false;
604
+ const result = results.find((r) => r.name === check.name);
605
+ return result !== void 0 && result.status !== "pass";
606
+ }).flatMap((check) => {
607
+ if (check.fix) return [check.fix];
608
+ return [];
609
+ });
610
+ return Promise.all(fixable.map((fix) => fix(context)));
611
+ }
612
+ /**
613
+ * Display check results with hints and fix indicators.
614
+ *
615
+ * @private
616
+ * @param ctx - The command context.
617
+ * @param results - The check results to display.
618
+ * @param fixResults - The fix results (empty when --fix was not used).
619
+ */
620
+ function displayResults(ctx, results, fixResults) {
621
+ results.map((result) => formatResultLine(ctx, result, fixResults));
622
+ }
623
+ /**
624
+ * Format and display a single check result line with optional hint.
625
+ *
626
+ * @private
627
+ * @param ctx - The command context.
628
+ * @param result - The check result to display.
629
+ * @param fixResults - The fix results to check for applied fixes.
630
+ */
631
+ function formatResultLine(ctx, result, fixResults) {
632
+ const appliedFix = fixResults.find((f) => f.name === result.name && f.fixed);
633
+ if (appliedFix) {
634
+ ctx.output.raw(` ${formatDisplayStatus("fix")} ${result.name} - ${appliedFix.message}\n`);
635
+ return;
636
+ }
637
+ ctx.output.raw(` ${formatDisplayStatus(result.status)} ${result.name} - ${result.message}\n`);
638
+ if (result.hint && result.status !== "pass") ctx.output.raw(` ${pc.dim(`→ ${result.hint}`)}\n`);
639
+ }
640
+ /**
641
+ * Format a display status with color.
642
+ *
643
+ * Accepts both CheckStatus values and the 'fix' display status.
644
+ *
645
+ * @private
646
+ * @param status - The status to format.
647
+ * @returns A colored string representation of the status.
648
+ */
649
+ function formatDisplayStatus(status) {
650
+ if (status === "pass") return pc.green("pass");
651
+ if (status === "warn") return pc.yellow("warn");
652
+ if (status === "fix") return pc.blue("fix ");
653
+ return pc.red("fail");
654
+ }
655
+ /**
656
+ * Format the summary line with counts.
657
+ *
658
+ * @private
659
+ * @param params - The count parameters.
660
+ * @returns A formatted summary string.
661
+ */
662
+ function formatSummary(params) {
663
+ const base = `\n${params.total} checks, ${params.passed} passed, ${params.warnings} warnings, ${params.failed} failed`;
664
+ if (params.fixed > 0) return `${base}, ${params.fixed} fixed\n`;
665
+ return `${base}\n`;
666
+ }
667
+ /**
668
+ * Pluralize the word "check" based on count.
669
+ *
670
+ * @private
671
+ * @param count - The number of checks.
672
+ * @returns "check" or "checks".
673
+ */
674
+ function pluralizeCheck(count) {
675
+ if (count === 1) return "check";
676
+ return "checks";
677
+ }
678
+
679
+ //#endregion
680
+ export { doctorCommand as default };
@@ -0,0 +1,6 @@
1
+ import { Command } from "@kidd-cli/core";
2
+
3
+ //#region src/commands/init.d.ts
4
+ declare const _default: Command;
5
+ //#endregion
6
+ export { _default as default };