@rrlab/cli 1.0.1-git-908d2c0.0 → 1.1.1-git-4903a88.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.
Files changed (110) hide show
  1. package/bin +3 -5
  2. package/dist/cli.usage.kdl +26 -25
  3. package/dist/config.d.mts +1 -1
  4. package/dist/magic-string.es-BgIV5Mu3.mjs +1011 -0
  5. package/dist/plugin/__tests__/bin-probe.test.d.mts +1 -0
  6. package/dist/plugin/__tests__/bin-probe.test.mjs +64 -0
  7. package/dist/plugin/__tests__/decide-scaffold.test.d.mts +1 -0
  8. package/dist/plugin/__tests__/decide-scaffold.test.mjs +103 -0
  9. package/dist/plugin/__tests__/define-plugin.test.d.mts +1 -0
  10. package/dist/plugin/__tests__/define-plugin.test.mjs +130 -0
  11. package/dist/plugin/__tests__/pick-preset.test.d.mts +1 -0
  12. package/dist/plugin/__tests__/pick-preset.test.mjs +72 -0
  13. package/dist/plugin/__tests__/registry.test.d.mts +1 -0
  14. package/dist/plugin/__tests__/registry.test.mjs +104 -0
  15. package/dist/plugin/bin-probe.d.mts +4 -0
  16. package/dist/plugin/bin-probe.mjs +22 -0
  17. package/dist/plugin/decide-scaffold.d.mts +18 -0
  18. package/dist/plugin/decide-scaffold.mjs +36 -0
  19. package/dist/plugin/define-plugin.d.mts +17 -0
  20. package/dist/plugin/define-plugin.mjs +25 -0
  21. package/dist/plugin/directory.d.mts +47 -0
  22. package/dist/plugin/directory.mjs +45 -0
  23. package/dist/plugin/errors.d.mts +11 -0
  24. package/dist/plugin/errors.mjs +15 -0
  25. package/dist/plugin/index.d.mts +7 -0
  26. package/dist/plugin/index.mjs +50 -0
  27. package/dist/plugin/pick-preset.d.mts +13 -0
  28. package/dist/plugin/pick-preset.mjs +17 -0
  29. package/dist/plugin/registry.d.mts +19 -0
  30. package/dist/plugin/registry.mjs +2 -0
  31. package/dist/plugin/tool-service.d.mts +45 -0
  32. package/dist/plugin/tool-service.mjs +64 -0
  33. package/dist/plugin/types.d.mts +3 -0
  34. package/dist/plugin/types.mjs +1 -0
  35. package/dist/registry-BgqfKK5L.mjs +55 -0
  36. package/dist/run.mjs +969 -585
  37. package/dist/test.DNmyFkvJ-09ScyH13.mjs +13617 -0
  38. package/dist/tool-DKL6TauZ.d.mts +43 -0
  39. package/dist/{types-snfbujDH.d.mts → types-Iu4IyWof.d.mts} +11 -75
  40. package/package.json +7 -6
  41. package/src/actions/clean.ts +36 -0
  42. package/src/actions/config.ts +46 -0
  43. package/src/actions/doctor.ts +47 -0
  44. package/src/actions/format.ts +13 -0
  45. package/src/actions/jsc.ts +13 -0
  46. package/src/actions/lint.ts +13 -0
  47. package/src/actions/pack.ts +12 -0
  48. package/src/actions/plugins/add.ts +143 -0
  49. package/src/actions/plugins/list.ts +27 -0
  50. package/src/actions/plugins/remove.ts +110 -0
  51. package/src/actions/plugins/shared.ts +58 -0
  52. package/src/actions/run-tool.ts +23 -0
  53. package/src/actions/tsc.ts +65 -0
  54. package/src/errors/invalid-plugin-module.ts +6 -0
  55. package/src/errors/missing-plugin.ts +17 -0
  56. package/src/errors/plugin-api-version.ts +6 -0
  57. package/src/errors/unknown-plugin.ts +7 -0
  58. package/src/lib/plugin/define-plugin.ts +56 -0
  59. package/src/lib/plugin/directory.ts +30 -0
  60. package/src/lib/plugin/errors.ts +15 -0
  61. package/src/lib/{plugin.ts → plugin/index.ts} +8 -9
  62. package/src/lib/plugin/registry.ts +82 -0
  63. package/src/{plugin → lib/plugin}/tool-service.ts +10 -14
  64. package/src/{plugin → lib/plugin}/types.ts +10 -33
  65. package/src/program/base.ts +75 -0
  66. package/src/program/commands/check.ts +31 -62
  67. package/src/program/commands/clean.ts +12 -43
  68. package/src/program/commands/completion.ts +6 -4
  69. package/src/program/commands/config.ts +6 -11
  70. package/src/program/commands/doctor.ts +5 -54
  71. package/src/program/commands/format.ts +18 -25
  72. package/src/program/commands/jscheck.ts +18 -31
  73. package/src/program/commands/lint.ts +18 -26
  74. package/src/program/commands/pack.ts +18 -22
  75. package/src/program/commands/plugins.ts +17 -364
  76. package/src/program/commands/tscheck.ts +19 -77
  77. package/src/program/index.ts +20 -27
  78. package/src/program/root.ts +62 -0
  79. package/src/render/banner.ts +25 -0
  80. package/src/render/board.ts +41 -0
  81. package/src/render/footer.ts +31 -0
  82. package/src/render/labels.ts +28 -0
  83. package/src/render/lines.ts +100 -0
  84. package/src/render/plugin-view.ts +68 -0
  85. package/src/render/steps.ts +20 -0
  86. package/src/run.ts +2 -8
  87. package/src/services/config.ts +4 -0
  88. package/src/services/context.ts +84 -0
  89. package/src/services/file-ops.ts +79 -0
  90. package/src/services/json-edit.ts +1 -1
  91. package/src/services/plugin-meta.ts +63 -0
  92. package/src/services/plugin-services.ts +41 -0
  93. package/src/services/prompts.ts +1 -1
  94. package/src/services/static-checker.ts +46 -0
  95. package/src/types/config.ts +2 -1
  96. package/src/types/tool.ts +13 -26
  97. package/src/ui/theme.ts +5 -0
  98. package/dist/plugin.d.mts +0 -87
  99. package/dist/plugin.mjs +0 -214
  100. package/src/plugin/define-plugin.ts +0 -54
  101. package/src/plugin/registry.ts +0 -48
  102. package/src/program/board.ts +0 -86
  103. package/src/program/composed-jsc.ts +0 -43
  104. package/src/program/missing-plugin.ts +0 -18
  105. package/src/program/ui.ts +0 -59
  106. package/src/services/ctx.ts +0 -71
  107. package/src/services/plugins-registry.ts +0 -22
  108. /package/src/{plugin → lib/plugin}/bin-probe.ts +0 -0
  109. /package/src/{plugin → lib/plugin}/decide-scaffold.ts +0 -0
  110. /package/src/{plugin → lib/plugin}/pick-preset.ts +0 -0
@@ -1,57 +1,54 @@
1
1
  import { palette } from "@vlandoss/clibuddy";
2
- import { type Command, createCommand } from "commander";
3
- import { runCheckSections } from "#src/program/board.ts";
4
- import { pluginAnnotation } from "#src/program/ui.ts";
5
- import type { Context } from "#src/services/ctx.ts";
2
+ import { jscAction } from "#src/actions/jsc.ts";
3
+ import { tscAction } from "#src/actions/tsc.ts";
4
+ import { runCheckSections } from "#src/render/board.ts";
5
+ import type { ContextValue } from "#src/services/context.ts";
6
6
  import { logger } from "#src/services/logger.ts";
7
+ import { createCommand } from "../base.ts";
7
8
 
8
9
  /**
9
- * `rr check` runs `jsc` then `tsc`. Rather than keep a parallel action
10
- * registry, it reuses commander's command tree: it finds each sibling on
11
- * `this.parent` and runs it via `parseAsync([])`, which applies the sibling's
12
- * own option defaults. (`this` is the running command inside a non-arrow
13
- * action see cli/CLAUDE.md.)
10
+ * `rr check` runs jsc then tsc. Rather than dispatch through commander's
11
+ * command tree, it calls the same `jscAction`/`tscAction` directly each
12
+ * wrapped in its own `runCheckSections` scope so failures are attributed by
13
+ * section name. A blank line separates the sections; `checkVerdict` is the
14
+ * final overall line.
14
15
  */
15
- export function createCheckCommand(ctx: Context) {
16
+ export function createCheckCommand(ctx: ContextValue) {
16
17
  return createCommand("check")
17
- .summary(`run static checks${checkAnnotation(ctx)}`)
18
+ .addCapabilities(["lint", "format", "jscheck", "typecheck"])
19
+ .summary("run static checks")
18
20
  .description(
19
21
  "Runs `rr jsc` then `rr tsc` in-process, each as its own section. Aggregates their exit codes — non-zero when either subcommand fails.",
20
22
  )
21
- .action(async function checkAction(this: Command) {
22
- const program = this.parent;
23
- if (!program) {
24
- // Can only happen if this command is invoked detached from the root
25
- // program current bin only constructs it as a subcommand.
26
- throw new Error("`rr check` requires the parent program to dispatch sibling subcommands.");
27
- }
23
+ .action(async () => {
24
+ const sections: Array<{ name: string; run: () => Promise<void> }> = [
25
+ {
26
+ name: "jsc",
27
+ run: () => jscAction({ ctx, checker: ctx.plugins.getJsChecker(), options: {} }),
28
+ },
29
+ {
30
+ name: "tsc",
31
+ run: () => tscAction({ ctx, tsc: ctx.plugins.getServiceOrThrow("typecheck") }),
32
+ },
33
+ ];
28
34
 
29
35
  // Sequentially, not in parallel: two live boards can't animate the same
30
- // terminal region at once (decision 012). Each section runs in its own
31
- // `runCheckSections` scope, which frames it and returns the boards it
32
- // rendered — so a failure is attributed by section name, not a fragile
33
- // dispatch-vs-render index.
36
+ // terminal region at once (decision 012).
34
37
  const start = Date.now();
35
38
  const failed: string[] = [];
36
39
  let rendered = false;
37
- for (const name of ["jsc", "tsc"]) {
38
- const cmd = findCommand(program, name);
39
- if (!cmd) {
40
- logger.error(`rr check: subcommand "${name}" is not registered.`);
41
- failed.push(name);
42
- continue;
43
- }
40
+ for (const section of sections) {
44
41
  if (rendered) process.stderr.write("\n"); // one blank line between sections
45
42
  let threw = false;
46
43
  const results = await runCheckSections(async () => {
47
44
  try {
48
- await cmd.parseAsync([], { from: "user" });
45
+ await section.run();
49
46
  } catch (reason) {
50
- logger.error(`rr check (${name}): ${reason instanceof Error ? reason.message : String(reason)}`);
47
+ logger.error(`rr check (${section.name}): ${reason instanceof Error ? reason.message : String(reason)}`);
51
48
  threw = true;
52
49
  }
53
50
  });
54
- if (threw || results.some((r) => !r.ok)) failed.push(name);
51
+ if (threw || results.some((r) => !r.ok)) failed.push(section.name);
55
52
  rendered = true;
56
53
  }
57
54
 
@@ -60,7 +57,8 @@ export function createCheckCommand(ctx: Context) {
60
57
  // of a run that failed in the section above it.
61
58
  process.stderr.write(`\n${checkVerdict(failed, Date.now() - start)}\n`);
62
59
  if (failed.length > 0) process.exitCode = 1;
63
- });
60
+ })
61
+ .addHelpTextAfter(ctx);
64
62
  }
65
63
 
66
64
  function checkVerdict(failed: string[], ms: number): string {
@@ -71,32 +69,3 @@ function checkVerdict(failed: string[], ms: number): string {
71
69
  }
72
70
  return `${palette.success("✔")} check passed${sep}${elapsed}`;
73
71
  }
74
-
75
- function findCommand(program: Command, name: string): Command | undefined {
76
- return program.commands.find((c) => c.name() === name || c.aliases().includes(name));
77
- }
78
-
79
- /**
80
- * Flattens the underlying tool labels of `jsc` + `tsc` for the help summary —
81
- * e.g. `(biome, oxlint)`, deduped, not `(biome + biome, oxlint)`. Falls back to
82
- * the standard `(not configured)` when neither sibling has a provider.
83
- */
84
- function checkAnnotation(ctx: Context): string {
85
- const directJsc = ctx.registry.get("jsc");
86
- const linter = ctx.registry.get("lint");
87
- const formatter = ctx.registry.get("format");
88
- const tsc = ctx.registry.get("tsc");
89
-
90
- const labels: string[] = [];
91
- if (directJsc) {
92
- labels.push(directJsc.ui);
93
- } else {
94
- if (linter) labels.push(linter.ui);
95
- if (formatter) labels.push(formatter.ui);
96
- }
97
- if (tsc) labels.push(tsc.ui);
98
-
99
- if (labels.length === 0) return pluginAnnotation(undefined);
100
- const distinct = [...new Set(labels)];
101
- return pluginAnnotation({ ui: distinct.join(", ") });
102
- }
@@ -1,53 +1,22 @@
1
- import { cwd } from "@vlandoss/clibuddy";
2
- import { createCommand } from "commander";
3
- import { type GlobOptions, glob } from "glob";
4
- import { rimraf } from "rimraf";
5
- import { logger } from "#src/services/logger.ts";
6
- import { TOOL_LABELS } from "../ui.ts";
1
+ import { colorize } from "@vlandoss/clibuddy";
2
+ import { cleanAction } from "#src/actions/clean.ts";
3
+ import { createCommand } from "../base.ts";
7
4
 
8
5
  type Options = {
9
- onlyDist: boolean;
10
- dryRun: boolean;
6
+ onlyDist?: boolean;
7
+ dryRun?: boolean;
11
8
  };
12
9
 
10
+ const rimrafColor = colorize("#7C7270");
11
+
13
12
  export function createCleanCommand() {
14
13
  return createCommand("clean")
15
- .summary(`delete dirty files (${TOOL_LABELS.RIMRAF})`)
14
+ .summary("delete dirty files")
16
15
  .description("Deletes generated files and folders such as 'dist', 'node_modules', and lock files to ensure a clean state.")
17
16
  .option("--only-dist", "delete 'dist' folders only")
18
17
  .option("--dry-run", "outputs the paths that would be deleted")
19
- .action(async function cleanCommandAction(options: Options) {
20
- async function run(paths: string[], globOptions: GlobOptions) {
21
- if (options.dryRun) {
22
- const toDelete = await glob(paths, globOptions);
23
-
24
- logger.info("Paths that would be deleted: %O", toDelete);
25
-
26
- return;
27
- }
28
-
29
- logger.start("Clean started");
30
-
31
- await rimraf(paths, {
32
- glob: globOptions,
33
- });
34
-
35
- logger.success("Clean completed");
36
- }
37
-
38
- const BUILD_PATHS = ["**/dist"];
39
- const ALL_DIRTY_PATHS = ["**/.turbo", "**/node_modules", "pnpm-lock.yaml", "bun.lock", ...BUILD_PATHS];
40
-
41
- if (options.onlyDist) {
42
- await run(BUILD_PATHS, {
43
- cwd,
44
- ignore: ["**/node_modules/**"],
45
- });
46
- } else {
47
- await run(ALL_DIRTY_PATHS, {
48
- cwd,
49
- });
50
- }
51
- })
52
- .addHelpText("afterAll", `\nUnder the hood, this command uses ${TOOL_LABELS.RIMRAF} to delete dirty folders or files.`);
18
+ .action((options: Options) =>
19
+ cleanAction({ options: { onlyDist: Boolean(options.onlyDist), dryRun: Boolean(options.dryRun) } }),
20
+ )
21
+ .addHelpText("after", `\nUnder the hood, this command uses ${rimrafColor("rimraf")} to delete dirty folders or files.`);
53
22
  }
@@ -1,7 +1,9 @@
1
- import { Argument, createCommand } from "commander";
2
- import { TOOL_LABELS } from "../ui.ts";
1
+ import { colorize } from "@vlandoss/clibuddy";
2
+ import { Argument } from "commander";
3
+ import { createCommand } from "../base.ts";
3
4
 
4
5
  const SHELLS = ["bash", "zsh", "fish"] as const;
6
+ const usageColor = colorize("#24C55E");
5
7
 
6
8
  // Ghost command: registered with Commander purely for discoverability — it surfaces
7
9
  // in `rr --help` and is baked into dist/cli.usage.kdl so the completion itself can
@@ -9,7 +11,7 @@ const SHELLS = ["bash", "zsh", "fish"] as const;
9
11
  // dispatcher, which intercepts `rr completion <shell>` before reaching Node.
10
12
  export function createCompletionCommand() {
11
13
  return createCommand("completion")
12
- .summary(`print shell completion script (${TOOL_LABELS.USAGE})`)
14
+ .summary(`print shell completion script`)
13
15
  .description(
14
16
  `Prints a shell completion script for rr. Add to your shell rc file:
15
17
 
@@ -20,7 +22,7 @@ export function createCompletionCommand() {
20
22
  .addArgument(new Argument("<shell>", `target shell`).choices(SHELLS))
21
23
  .addHelpText(
22
24
  "afterAll",
23
- `\nUnder the hood, this command uses ${TOOL_LABELS.USAGE} (https://usage.jdx.dev).
25
+ `\nUnder the hood, this command uses ${usageColor("usage")} (https://usage.jdx.dev).
24
26
  Make sure to have it installed and available in your PATH.`,
25
27
  );
26
28
  }
@@ -1,15 +1,10 @@
1
- import { palette } from "@vlandoss/clibuddy";
2
- import { createCommand } from "commander";
3
- import type { Context } from "#src/services/ctx.ts";
1
+ import { configAction } from "#src/actions/config.ts";
2
+ import type { ContextValue } from "#src/services/context.ts";
3
+ import { createCommand } from "../base.ts";
4
4
 
5
- export function createConfigCommand(ctx: Context) {
5
+ export function createConfigCommand(ctx: ContextValue) {
6
6
  return createCommand("config")
7
7
  .summary("display the current config")
8
- .description("Displays the current configuration settings, including their source file path if available.")
9
- .action(async function configAction() {
10
- const { config, meta } = ctx.config;
11
- console.log(palette.muted("Config:"));
12
- console.log(config);
13
- console.log(palette.muted(`Loaded from ${meta.filepath ? palette.link(meta.filepath) : "n/a"}`));
14
- });
8
+ .description("Displays the current configuration settings, including their source file path and the plugins it registers.")
9
+ .action(() => configAction({ ctx }));
15
10
  }
@@ -1,61 +1,12 @@
1
- import type { Pkg } from "@vlandoss/clibuddy";
2
- import { createCommand } from "commander";
3
- import type { Doctor } from "#src/plugin/types.ts";
4
- import { PLUGIN_KINDS } from "#src/plugin/types.ts";
5
- import type { Context } from "#src/services/ctx.ts";
6
- import { logger } from "#src/services/logger.ts";
7
- import { fanoutTitle, reportTask, runBoard, targetLabel } from "../board.ts";
1
+ import { doctorAction } from "#src/actions/doctor.ts";
2
+ import type { ContextValue } from "#src/services/context.ts";
3
+ import { createCommand } from "../base.ts";
8
4
 
9
- /**
10
- * Subcommand factory used by every plugin-backed command (lint, format, jsc,
11
- * tsc, pack) to expose a `doctor` subcommand that verifies the underlying tool
12
- * is wired correctly. Renders the canonical `doctor (<tool>) · <pkg>` row like
13
- * every other single-target command — `doctor()` returns a `RunReport`.
14
- */
15
- export function createDoctorSubcommand(service: Doctor, appPkg: Pkg) {
16
- return createCommand("doctor")
17
- .summary("check if the underlying tool is working correctly")
18
- .action(async function doctorAction() {
19
- const result = await runBoard([reportTask(targetLabel("doctor", service, appPkg), () => service.doctor())]);
20
- if (!result.ok) process.exitCode = 1;
21
- });
22
- }
23
-
24
- /**
25
- * Top-level `rr doctor` — runs the `doctor()` of every distinct capability
26
- * impl registered with the kernel. Distinct because a single plugin (e.g.
27
- * biome) often serves multiple kinds (`lint`, `format`, `jsc`) from the same
28
- * `BiomeService` instance; running its doctor three times is wasteful.
29
- */
30
- export function createDoctorCommand(ctx: Context) {
5
+ export function createDoctorCommand(ctx: ContextValue) {
31
6
  return createCommand("doctor")
32
7
  .summary("run all plugin doctors")
33
8
  .description(
34
9
  "Runs the `doctor()` of every configured plugin capability. Each plugin reports ok / not working. The exit code is non-zero if any reports not working.",
35
10
  )
36
- .action(async () => {
37
- const services = collectDistinctDoctors(ctx);
38
- if (services.length === 0) {
39
- logger.info("No plugins configured. Use `rr plugins add <name>` to install one.");
40
- return;
41
- }
42
-
43
- // Each tool's health check is one parallel board row — a fan-out across
44
- // tools, so the rows carry the tool name and the title omits a single tool.
45
- const tasks = services.map((svc) => reportTask(svc.ui, () => svc.doctor()));
46
- const result = await runBoard(tasks, { title: fanoutTitle("doctor", undefined, services.length, "tools") });
47
- if (!result.ok) process.exitCode = 1;
48
- });
49
- }
50
-
51
- function collectDistinctDoctors(ctx: Context): Doctor[] {
52
- const seen = new Set<Doctor>();
53
- for (const kind of PLUGIN_KINDS) {
54
- for (const { impl } of ctx.registry.providersOf(kind)) {
55
- // Capability impls carry `doctor` via the Doctor intersection; dedup
56
- // by reference so a single service that backs multiple kinds runs once.
57
- seen.add(impl as unknown as Doctor);
58
- }
59
- }
60
- return [...seen];
11
+ .action(() => doctorAction({ ctx }));
61
12
  }
@@ -1,34 +1,27 @@
1
- import { createCommand } from "commander";
2
- import type { Context } from "#src/services/ctx.ts";
3
- import { runToolCommand } from "../board.ts";
4
- import { pluginAnnotation } from "../ui.ts";
5
- import { createDoctorSubcommand } from "./doctor.ts";
1
+ import { doctorOneAction } from "#src/actions/doctor.ts";
2
+ import { formatAction } from "#src/actions/format.ts";
3
+ import type { ContextValue } from "#src/services/context.ts";
4
+ import { createCommand } from "../base.ts";
6
5
 
7
6
  type ActionOptions = {
8
7
  fix?: boolean;
9
8
  };
10
9
 
11
- export function createFormatCommand(ctx: Context) {
12
- const formatter = ctx.registry.get("format");
13
-
14
- const cmd = createCommand("format")
15
- .summary(`check & fix format errors${pluginAnnotation(formatter)}`)
10
+ export function createFormatCommand(ctx: ContextValue) {
11
+ return createCommand("format")
12
+ .addCapabilities(["format"])
13
+ .summary("check & fix format issues")
16
14
  .description(
17
15
  "Checks the code for formatting issues and optionally fixes them, ensuring it adheres to the defined style standards.",
18
16
  )
19
- .option("--fix", "format all the code");
20
-
21
- if (formatter) {
22
- cmd.addCommand(createDoctorSubcommand(formatter, ctx.appPkg));
23
- }
24
-
25
- cmd.action(async (options: ActionOptions = {}) => {
26
- await runToolCommand(ctx, { name: "format", kind: "format", provider: formatter, run: (p) => p.format(options) });
27
- });
28
-
29
- if (formatter) {
30
- cmd.addHelpText("afterAll", `\nUnder the hood, this command uses the ${formatter.ui} CLI to format the code.`);
31
- }
32
-
33
- return cmd;
17
+ .option("--fix", "format all the code")
18
+ .action(async (options: ActionOptions = {}) => {
19
+ const formatter = ctx.plugins.getServiceOrThrow("format");
20
+ await formatAction({ ctx, formatter, options: { fix: options.fix } });
21
+ })
22
+ .addHelpTextAfter(ctx)
23
+ .addDoctorCommand(async () => {
24
+ const formatter = ctx.plugins.getServiceOrThrow("format");
25
+ await doctorOneAction({ ctx, service: formatter });
26
+ });
34
27
  }
@@ -1,43 +1,30 @@
1
- import { createCommand } from "commander";
2
- import type { Context } from "#src/services/ctx.ts";
3
- import { runToolCommand } from "../board.ts";
4
- import { composedJscProvider } from "../composed-jsc.ts";
5
- import { pluginAnnotation } from "../ui.ts";
6
- import { createDoctorSubcommand } from "./doctor.ts";
1
+ import { doctorOneAction } from "#src/actions/doctor.ts";
2
+ import { jscAction } from "#src/actions/jsc.ts";
3
+ import type { ContextValue } from "#src/services/context.ts";
4
+ import { createCommand } from "../base.ts";
7
5
 
8
6
  type ActionOptions = {
9
7
  fix?: boolean;
10
8
  fixStaged?: boolean;
11
9
  };
12
10
 
13
- export function createJsCheckCommand(ctx: Context) {
14
- const direct = ctx.registry.get("jsc");
15
- const linter = ctx.registry.get("lint");
16
- const formatter = ctx.registry.get("format");
17
- // Compose only when no plugin claims `jsc` directly and we have both
18
- // building blocks separately (e.g. oxc, or eslint + prettier).
19
- const checker = direct ?? (linter && formatter ? composedJscProvider(linter, formatter) : undefined);
20
-
21
- const cmd = createCommand("jsc")
11
+ export function createJsCheckCommand(ctx: ContextValue) {
12
+ return createCommand("jsc")
22
13
  .alias("jscheck")
23
- .summary(`check format and lint${pluginAnnotation(checker)}`)
14
+ .addCapabilities(["lint", "format", "jscheck"])
15
+ .summary("check format and lint")
24
16
  .description(
25
17
  "Checks the code for formatting and linting issues, ensuring it adheres to the defined style and quality standards.",
26
18
  )
27
19
  .option("--fix", "try to fix issues automatically")
28
- .option("--fix-staged", "try to fix staged files only");
29
-
30
- if (checker) {
31
- cmd.addCommand(createDoctorSubcommand(checker, ctx.appPkg));
32
- }
33
-
34
- cmd.action(async (options: ActionOptions = {}) => {
35
- await runToolCommand(ctx, { name: "jsc", kind: "jsc", provider: checker, run: (p) => p.check(options) });
36
- });
37
-
38
- if (checker) {
39
- cmd.addHelpText("afterAll", `\nUnder the hood, this command uses the ${checker.ui} CLI to check the code.`);
40
- }
41
-
42
- return cmd;
20
+ .option("--fix-staged", "try to fix staged files only")
21
+ .action(async (options: ActionOptions = {}) => {
22
+ const checker = ctx.plugins.getJsChecker();
23
+ await jscAction({ ctx, checker, options: { fix: options.fix, fixStaged: options.fixStaged } });
24
+ })
25
+ .addHelpTextAfter(ctx)
26
+ .addDoctorCommand(async () => {
27
+ const checker = ctx.plugins.getJsChecker();
28
+ await doctorOneAction({ ctx, service: checker });
29
+ });
43
30
  }
@@ -1,36 +1,28 @@
1
- import { createCommand } from "commander";
2
- import type { Context } from "#src/services/ctx.ts";
3
- import { runToolCommand } from "../board.ts";
4
- import { pluginAnnotation } from "../ui.ts";
5
- import { createDoctorSubcommand } from "./doctor.ts";
1
+ import { doctorOneAction } from "#src/actions/doctor.ts";
2
+ import { lintAction } from "#src/actions/lint.ts";
3
+ import type { ContextValue } from "#src/services/context.ts";
4
+ import { createCommand } from "../base.ts";
6
5
 
7
6
  type ActionOptions = {
8
- check?: boolean;
9
7
  fix?: boolean;
10
8
  };
11
9
 
12
- export function createLintCommand(ctx: Context) {
13
- const linter = ctx.registry.get("lint");
14
-
15
- const cmd = createCommand("lint")
16
- .summary(`check & fix lint errors${pluginAnnotation(linter)}`)
10
+ export function createLintCommand(ctx: ContextValue) {
11
+ return createCommand("lint")
12
+ .addCapabilities(["lint"])
13
+ .summary("check & fix lint issues")
17
14
  .description(
18
15
  "Checks the code for linting issues and optionally fixes them, ensuring it adheres to the defined quality standards.",
19
16
  )
20
17
  .option("-c, --check", "check if the code is valid", true)
21
- .option("--fix", "try to fix all the code");
22
-
23
- if (linter) {
24
- cmd.addCommand(createDoctorSubcommand(linter, ctx.appPkg));
25
- }
26
-
27
- cmd.action(async (options: ActionOptions = {}) => {
28
- await runToolCommand(ctx, { name: "lint", kind: "lint", provider: linter, run: (p) => p.lint(options) });
29
- });
30
-
31
- if (linter) {
32
- cmd.addHelpText("afterAll", `\nUnder the hood, this command uses the ${linter.ui} CLI to lint the code.`);
33
- }
34
-
35
- return cmd;
18
+ .option("--fix", "try to fix all the code")
19
+ .action(async (options: ActionOptions = {}) => {
20
+ const linter = ctx.plugins.getServiceOrThrow("lint");
21
+ await lintAction({ ctx, linter, options: { fix: options.fix } });
22
+ })
23
+ .addHelpTextAfter(ctx)
24
+ .addDoctorCommand(async () => {
25
+ const linter = ctx.plugins.getServiceOrThrow("lint");
26
+ await doctorOneAction({ ctx, service: linter });
27
+ });
36
28
  }
@@ -1,26 +1,22 @@
1
- import { createCommand } from "commander";
2
- import type { Context } from "#src/services/ctx.ts";
3
- import { runToolCommand } from "../board.ts";
4
- import { pluginAnnotation } from "../ui.ts";
5
- import { createDoctorSubcommand } from "./doctor.ts";
1
+ import { doctorOneAction } from "#src/actions/doctor.ts";
2
+ import { packAction } from "#src/actions/pack.ts";
3
+ import type { ContextValue } from "#src/services/context.ts";
4
+ import { createCommand } from "../base.ts";
6
5
 
7
- export function createPackCommand(ctx: Context) {
8
- const packer = ctx.registry.get("pack");
9
-
10
- const cmd = createCommand("pack")
11
- .summary(`pack a ts library${pluginAnnotation(packer)}`)
6
+ export function createPackCommand(ctx: ContextValue) {
7
+ return createCommand("pack")
8
+ .addCapabilities(["pack"])
9
+ .summary("pack a ts library")
12
10
  .description(
13
11
  "Compiles TypeScript code into JavaScript and generates type declaration files, packaging the library for distribution.",
14
- );
15
-
16
- if (packer) {
17
- cmd.addCommand(createDoctorSubcommand(packer, ctx.appPkg));
18
- cmd.addHelpText("afterAll", `\nUnder the hood, this command uses the ${packer.ui} CLI to pack the project.`);
19
- }
20
-
21
- cmd.action(async () => {
22
- await runToolCommand(ctx, { name: "pack", kind: "pack", provider: packer, run: (p) => p.pack() });
23
- });
24
-
25
- return cmd;
12
+ )
13
+ .action(async () => {
14
+ const packer = ctx.plugins.getServiceOrThrow("pack");
15
+ await packAction({ ctx, packer });
16
+ })
17
+ .addHelpTextAfter(ctx)
18
+ .addDoctorCommand(async () => {
19
+ const packer = ctx.plugins.getServiceOrThrow("pack");
20
+ await doctorOneAction({ ctx, service: packer });
21
+ });
26
22
  }