@rrlab/cli 0.0.4-git-9887d65.0 → 1.0.1-git-e008b4d.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.usage.kdl +2 -2
- package/dist/config.d.mts +1 -1
- package/dist/plugin.d.mts +12 -15
- package/dist/plugin.mjs +29 -13
- package/dist/run.mjs +207 -134
- package/dist/{types-DnqiiIxe.d.mts → types-6gZWuLJf.d.mts} +25 -16
- package/package.json +3 -3
- package/src/lib/plugin.ts +1 -2
- package/src/plugin/tool-service.ts +38 -18
- package/src/plugin/types.ts +3 -4
- package/src/program/board.ts +99 -0
- package/src/program/commands/check.ts +46 -25
- package/src/program/commands/doctor.ts +14 -38
- package/src/program/commands/format.ts +3 -4
- package/src/program/commands/jscheck.ts +3 -4
- package/src/program/commands/lint.ts +3 -4
- package/src/program/commands/pack.ts +3 -4
- package/src/program/commands/tscheck.ts +31 -62
- package/src/program/composed-jsc.ts +28 -16
- package/src/types/tool.ts +24 -16
package/dist/cli.usage.kdl
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// @generated by @usage-spec/commander from Commander.js metadata
|
|
2
2
|
name rr
|
|
3
3
|
bin rr
|
|
4
|
-
version "
|
|
4
|
+
version "1.0.1-git-e008b4d.0"
|
|
5
5
|
usage "<command...> [options...]"
|
|
6
6
|
flag --usage help="print KDL spec for this CLI (https://kdl.dev)"
|
|
7
7
|
cmd completion help="print shell completion script (usage)" {
|
|
@@ -38,7 +38,7 @@ cmd format help="check & fix format errors" {
|
|
|
38
38
|
cmd doctor help="check if the underlying tool is working correctly"
|
|
39
39
|
}
|
|
40
40
|
cmd check help="run static checks" {
|
|
41
|
-
long_help "Runs `rr jsc`
|
|
41
|
+
long_help "Runs `rr jsc` then `rr tsc` in-process, each as its own section. Aggregates their exit codes — non-zero when either subcommand fails."
|
|
42
42
|
}
|
|
43
43
|
cmd doctor help="run all plugin doctors" {
|
|
44
44
|
long_help "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."
|
package/dist/config.d.mts
CHANGED
package/dist/plugin.d.mts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { C as
|
|
1
|
+
import { C as StaticChecker, D as ReleaseService, E as TypeChecker, O as ReleaseServiceOptions, S as RunReport, T as TypeCheckOptions, _ as Doctor, a as InstallFlags, b as LintOptions, c as PLUGIN_KINDS, d as PluginCapabilities, f as PluginContext, g as UninstallResult, h as UninstallFlags, i as InstallContext, l as Packer, m as UninstallContext, n as ClackPromptsSelectOption, o as InstallResult, p as PluginKind, r as FileOp, s as JsonEdit, t as ClackPrompts, u as Plugin, v as FormatOptions, w as StaticCheckerOptions, x as Linter, y as Formatter } from "./types-6gZWuLJf.mjs";
|
|
2
2
|
import { ShellService } from "@vlandoss/clibuddy";
|
|
3
3
|
|
|
4
4
|
//#region src/plugin/decide-scaffold.d.ts
|
|
@@ -42,15 +42,6 @@ type PickPresetOptions<K extends string> = {
|
|
|
42
42
|
};
|
|
43
43
|
declare function pickPreset<K extends string>(ctx: InstallContext, opts: PickPresetOptions<K>): Promise<K>;
|
|
44
44
|
//#endregion
|
|
45
|
-
//#region ../../node_modules/.pnpm/tinyexec@1.1.2/node_modules/tinyexec/dist/main.d.mts
|
|
46
|
-
//#endregion
|
|
47
|
-
//#region src/main.d.ts
|
|
48
|
-
interface Output {
|
|
49
|
-
stderr: string;
|
|
50
|
-
stdout: string;
|
|
51
|
-
exitCode: number | undefined;
|
|
52
|
-
}
|
|
53
|
-
//#endregion
|
|
54
45
|
//#region src/plugin/tool-service.d.ts
|
|
55
46
|
type ToolServiceOptions = {
|
|
56
47
|
pkg: string;
|
|
@@ -66,9 +57,8 @@ type ToolServiceOptions = {
|
|
|
66
57
|
*/
|
|
67
58
|
from: string;
|
|
68
59
|
};
|
|
69
|
-
type
|
|
60
|
+
type RunReportOptions = {
|
|
70
61
|
cwd?: string;
|
|
71
|
-
verbose?: boolean;
|
|
72
62
|
};
|
|
73
63
|
declare class ToolService {
|
|
74
64
|
#private;
|
|
@@ -83,8 +73,15 @@ declare class ToolService {
|
|
|
83
73
|
from
|
|
84
74
|
}: ToolServiceOptions);
|
|
85
75
|
getBinDir(): Promise<string>;
|
|
86
|
-
|
|
87
|
-
|
|
76
|
+
/**
|
|
77
|
+
* Runs the tool capturing its output instead of streaming it, and reports the
|
|
78
|
+
* verdict straight from the exit code — never a guess parsed from the output.
|
|
79
|
+
* The board needs the capture to attribute each parallel run's output to its
|
|
80
|
+
* package; the non-zero exit is returned (not thrown) so every task settles
|
|
81
|
+
* and the caller can aggregate. See `decisions/013-check-stream-to-capture-contract.md`.
|
|
82
|
+
*/
|
|
83
|
+
runReport(args?: string[], options?: RunReportOptions): Promise<RunReport>;
|
|
84
|
+
doctor(): Promise<RunReport>;
|
|
88
85
|
}
|
|
89
86
|
//#endregion
|
|
90
|
-
export { type ClackPrompts, type ClackPromptsSelectOption, type DecideScaffoldOptions, type Doctor, type
|
|
87
|
+
export { type ClackPrompts, type ClackPromptsSelectOption, type DecideScaffoldOptions, type Doctor, type FileOp, type FormatOptions, type Formatter, type InstallContext, type InstallFlags, type InstallResult, type JsonEdit, type LintOptions, type Linter, PLUGIN_KINDS, type Packer, type PickPresetOptions, type Plugin, type PluginCapabilities, type PluginContext, type PluginDefinition, type PluginKind, ReleaseService, type ReleaseServiceOptions, type RunReport, type ScaffoldDecision, type StaticChecker, type StaticCheckerOptions, ToolService, type ToolServiceOptions, type TypeCheckOptions, type TypeChecker, type UninstallContext, type UninstallFlags, type UninstallResult, decideScaffold, definePlugin, pickPreset };
|
package/dist/plugin.mjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { resolvePackageBin } from "@vlandoss/clibuddy";
|
|
1
|
+
import { palette, resolvePackageBin } from "@vlandoss/clibuddy";
|
|
2
2
|
//#region src/plugin/decide-scaffold.ts
|
|
3
3
|
async function decideScaffold(ctx, opts) {
|
|
4
4
|
const { label, fileExists, patchHint, unattendedExistingAction = "patch" } = opts;
|
|
@@ -63,25 +63,41 @@ var ToolService = class {
|
|
|
63
63
|
binName: this.#bin
|
|
64
64
|
});
|
|
65
65
|
}
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
66
|
+
/**
|
|
67
|
+
* Runs the tool capturing its output instead of streaming it, and reports the
|
|
68
|
+
* verdict straight from the exit code — never a guess parsed from the output.
|
|
69
|
+
* The board needs the capture to attribute each parallel run's output to its
|
|
70
|
+
* package; the non-zero exit is returned (not thrown) so every task settles
|
|
71
|
+
* and the caller can aggregate. See `decisions/013-check-stream-to-capture-contract.md`.
|
|
72
|
+
*/
|
|
73
|
+
async runReport(args = [], options = {}) {
|
|
74
|
+
const output = await (options.cwd ? this.#shellService.at(options.cwd) : this.#shellService).runCaptured(await this.getBinDir(), args, { throwOnError: false });
|
|
75
|
+
const header = palette.dim(`$ ${[this.#bin, ...args].join(" ")}`);
|
|
76
|
+
const body = combine(output.stdout, output.stderr);
|
|
77
|
+
return {
|
|
78
|
+
ok: output.exitCode === 0,
|
|
79
|
+
output: body ? `${header}\n${body}` : header
|
|
80
|
+
};
|
|
72
81
|
}
|
|
73
82
|
async doctor() {
|
|
74
83
|
const output = await this.#shellService.runCaptured(await this.getBinDir(), ["--help"], { throwOnError: false });
|
|
84
|
+
const ok = output.exitCode === 0;
|
|
85
|
+
const command = palette.dim(`$ ${this.#bin} --help`);
|
|
86
|
+
if (ok) return {
|
|
87
|
+
ok,
|
|
88
|
+
output: command
|
|
89
|
+
};
|
|
90
|
+
const detail = combine(output.stdout, output.stderr);
|
|
75
91
|
return {
|
|
76
|
-
ok
|
|
77
|
-
output: {
|
|
78
|
-
stdout: output.stdout,
|
|
79
|
-
stderr: output.stderr,
|
|
80
|
-
exitCode: output.exitCode
|
|
81
|
-
}
|
|
92
|
+
ok,
|
|
93
|
+
output: detail ? `${command}\n${detail}` : command
|
|
82
94
|
};
|
|
83
95
|
}
|
|
84
96
|
};
|
|
97
|
+
/** Joins the non-empty, trimmed streams of a captured run. */
|
|
98
|
+
function combine(stdout, stderr) {
|
|
99
|
+
return [stdout, stderr].map((stream) => stream?.trim()).filter(Boolean).join("\n");
|
|
100
|
+
}
|
|
85
101
|
//#endregion
|
|
86
102
|
//#region src/plugin/bin-probe.ts
|
|
87
103
|
async function probeBins(services, pluginName) {
|
package/dist/run.mjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import path from "node:path";
|
|
2
|
-
import { colorize, createPkg, createShellService, cwd, dirnameOf, palette, run, text } from "@vlandoss/clibuddy";
|
|
1
|
+
import path, { basename } from "node:path";
|
|
2
|
+
import { colorize, createPkg, createShellService, cwd, dirnameOf, palette, run, runTaskBoard, text } from "@vlandoss/clibuddy";
|
|
3
3
|
import { generateToStdout } from "@usage-spec/commander";
|
|
4
4
|
import { Argument, Option, createCommand } from "commander";
|
|
5
5
|
import fs from "node:fs";
|
|
@@ -137,6 +137,101 @@ async function createContext(binDir) {
|
|
|
137
137
|
};
|
|
138
138
|
}
|
|
139
139
|
//#endregion
|
|
140
|
+
//#region src/program/missing-plugin.ts
|
|
141
|
+
const SUGGESTIONS = {
|
|
142
|
+
lint: [
|
|
143
|
+
"biome",
|
|
144
|
+
"oxc",
|
|
145
|
+
"eslint"
|
|
146
|
+
],
|
|
147
|
+
format: ["biome", "oxc"],
|
|
148
|
+
jsc: ["biome"],
|
|
149
|
+
tsc: ["ts"],
|
|
150
|
+
pack: ["tsdown"]
|
|
151
|
+
};
|
|
152
|
+
function missingPluginError(kind) {
|
|
153
|
+
const aliases = SUGGESTIONS[kind] ?? [];
|
|
154
|
+
const officialList = aliases.map((a) => `@rrlab/${a}-plugin`).join(", ");
|
|
155
|
+
const addList = aliases.map((a) => `rr plugins add ${a}`).join(" | ");
|
|
156
|
+
return /* @__PURE__ */ new Error(`No plugin provides the '${kind}' capability.` + (officialList ? `\n Install one of: ${officialList}.` : "") + (addList ? `\n Try: ${addList}.` : ""));
|
|
157
|
+
}
|
|
158
|
+
//#endregion
|
|
159
|
+
//#region src/program/board.ts
|
|
160
|
+
/** `<command> (<tool>)`, deduped to just `<command>` when the tool's binary is the command itself (e.g. `tsc`). */
|
|
161
|
+
function commandTool(command, provider) {
|
|
162
|
+
return provider.bin === command ? command : `${command} (${provider.ui})`;
|
|
163
|
+
}
|
|
164
|
+
function pkgName(appPkg) {
|
|
165
|
+
return appPkg.packageJson.name ?? basename(appPkg.dirPath);
|
|
166
|
+
}
|
|
167
|
+
/**
|
|
168
|
+
* The one canonical row label for a single-target run: `<command> (<tool>) · <package>`.
|
|
169
|
+
* Every command/subcommand that acts on one target (lint, format, jsc, pack,
|
|
170
|
+
* single-app tsc, a `doctor` subcommand) builds its row through here so they
|
|
171
|
+
* all read identically.
|
|
172
|
+
*/
|
|
173
|
+
function targetLabel(command, provider, appPkg) {
|
|
174
|
+
return `${commandTool(command, provider)} ${palette.dim(`· ${pkgName(appPkg)}`)}`;
|
|
175
|
+
}
|
|
176
|
+
/**
|
|
177
|
+
* The one canonical section title for a fan-out run: `<command> (<tool>) · <n> <unit>`
|
|
178
|
+
* (e.g. `tsc (oxlint) · 8 packages`). The tool is omitted when the fan-out
|
|
179
|
+
* spans several tools (`rr doctor` → `doctor · 3 tools`); the rows then carry
|
|
180
|
+
* the per-unit name.
|
|
181
|
+
*/
|
|
182
|
+
function fanoutTitle(command, provider, count, unit) {
|
|
183
|
+
return `${provider ? commandTool(command, provider) : command} · ${count} ${unit}`;
|
|
184
|
+
}
|
|
185
|
+
/**
|
|
186
|
+
* Bridges a check-family verb (which returns a `RunReport`) to a board row.
|
|
187
|
+
* The row's spinner reflects the in-flight run; the captured `output` becomes
|
|
188
|
+
* the detail the board flushes grouped under the label.
|
|
189
|
+
*/
|
|
190
|
+
function reportTask(label, run) {
|
|
191
|
+
return {
|
|
192
|
+
label,
|
|
193
|
+
async run() {
|
|
194
|
+
const report = await run();
|
|
195
|
+
return {
|
|
196
|
+
ok: report.ok,
|
|
197
|
+
detail: report.output
|
|
198
|
+
};
|
|
199
|
+
}
|
|
200
|
+
};
|
|
201
|
+
}
|
|
202
|
+
let collector = null;
|
|
203
|
+
async function runCheckSections(run) {
|
|
204
|
+
const previous = collector;
|
|
205
|
+
collector = [];
|
|
206
|
+
try {
|
|
207
|
+
await run();
|
|
208
|
+
return collector;
|
|
209
|
+
} finally {
|
|
210
|
+
collector = previous;
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
/** Runs the rows on the board and returns whether every row passed. */
|
|
214
|
+
async function runBoard(tasks, options = {}) {
|
|
215
|
+
const sink = collector;
|
|
216
|
+
const result = await runTaskBoard(tasks, {
|
|
217
|
+
...options,
|
|
218
|
+
frame: options.frame ?? (sink !== null || void 0)
|
|
219
|
+
});
|
|
220
|
+
if (sink) sink.push(result);
|
|
221
|
+
return result;
|
|
222
|
+
}
|
|
223
|
+
/**
|
|
224
|
+
* The shared action body for a single-provider tool command (lint, format, jsc,
|
|
225
|
+
* pack): require the provider, run its verb as one board row labelled
|
|
226
|
+
* `<name> (<tool>) · <pkg>`, and aggregate the exit code. Commands that fan out
|
|
227
|
+
* (tsc) or compose siblings (check) call `runBoard` directly instead.
|
|
228
|
+
*/
|
|
229
|
+
async function runToolCommand(ctx, spec) {
|
|
230
|
+
const { provider } = spec;
|
|
231
|
+
if (!provider) throw missingPluginError(spec.kind);
|
|
232
|
+
if (!(await runBoard([reportTask(targetLabel(spec.name, provider, ctx.appPkg), () => spec.run(provider))])).ok) process.exitCode = 1;
|
|
233
|
+
}
|
|
234
|
+
//#endregion
|
|
140
235
|
//#region src/program/ui.ts
|
|
141
236
|
const CREDITS_TEXT = `\nAcknowledgment:
|
|
142
237
|
- kcd-scripts: for main inspiration
|
|
@@ -201,34 +296,42 @@ ${runRunColor(`
|
|
|
201
296
|
* parent program without any late-binding ceremony.
|
|
202
297
|
*/
|
|
203
298
|
function createCheckCommand(ctx) {
|
|
204
|
-
return createCommand("check").summary(`run static checks${checkAnnotation(ctx)}`).description("Runs `rr jsc`
|
|
299
|
+
return createCommand("check").summary(`run static checks${checkAnnotation(ctx)}`).description("Runs `rr jsc` then `rr tsc` in-process, each as its own section. Aggregates their exit codes — non-zero when either subcommand fails.").action(async function checkAction() {
|
|
205
300
|
const program = this.parent;
|
|
206
301
|
if (!program) throw new Error("`rr check` requires the parent program to dispatch sibling subcommands.");
|
|
207
|
-
const
|
|
208
|
-
name,
|
|
209
|
-
cmd: findCommand(program, name)
|
|
210
|
-
}));
|
|
211
|
-
const missing = cmds.filter(({ cmd }) => !cmd).map(({ name }) => name);
|
|
212
|
-
if (missing.length > 0) {
|
|
213
|
-
for (const name of missing) logger.error(`rr check: subcommand "${name}" is not registered.`);
|
|
214
|
-
process.exitCode = 1;
|
|
215
|
-
return;
|
|
216
|
-
}
|
|
217
|
-
const results = await Promise.allSettled(cmds.map(({ cmd }) => cmd.parseAsync([], { from: "user" })));
|
|
302
|
+
const start = Date.now();
|
|
218
303
|
const failed = [];
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
logger.error(`rr check (${name}): ${msg}`);
|
|
304
|
+
let rendered = false;
|
|
305
|
+
for (const name of ["jsc", "tsc"]) {
|
|
306
|
+
const cmd = findCommand(program, name);
|
|
307
|
+
if (!cmd) {
|
|
308
|
+
logger.error(`rr check: subcommand "${name}" is not registered.`);
|
|
309
|
+
failed.push(name);
|
|
310
|
+
continue;
|
|
227
311
|
}
|
|
228
|
-
process.
|
|
312
|
+
if (rendered) process.stderr.write("\n");
|
|
313
|
+
let threw = false;
|
|
314
|
+
const results = await runCheckSections(async () => {
|
|
315
|
+
try {
|
|
316
|
+
await cmd.parseAsync([], { from: "user" });
|
|
317
|
+
} catch (reason) {
|
|
318
|
+
logger.error(`rr check (${name}): ${reason instanceof Error ? reason.message : String(reason)}`);
|
|
319
|
+
threw = true;
|
|
320
|
+
}
|
|
321
|
+
});
|
|
322
|
+
if (threw || results.some((r) => !r.ok)) failed.push(name);
|
|
323
|
+
rendered = true;
|
|
229
324
|
}
|
|
325
|
+
process.stderr.write(`\n${checkVerdict(failed, Date.now() - start)}\n`);
|
|
326
|
+
if (failed.length > 0) process.exitCode = 1;
|
|
230
327
|
});
|
|
231
328
|
}
|
|
329
|
+
function checkVerdict(failed, ms) {
|
|
330
|
+
const elapsed = palette.dim(ms < 1e3 ? `${Math.round(ms)}ms` : `${(ms / 1e3).toFixed(1)}s`);
|
|
331
|
+
const sep = palette.dim(" · ");
|
|
332
|
+
if (failed.length > 0) return `${palette.error("✖")} check failed${sep}${[...new Set(failed)].join(", ")}${sep}${elapsed}`;
|
|
333
|
+
return `${palette.success("✔")} check passed${sep}${elapsed}`;
|
|
334
|
+
}
|
|
232
335
|
function findCommand(program, name) {
|
|
233
336
|
return program.commands.find((c) => c.name() === name || c.aliases().includes(name));
|
|
234
337
|
}
|
|
@@ -322,21 +425,13 @@ const PLUGIN_KINDS = [
|
|
|
322
425
|
//#region src/program/commands/doctor.ts
|
|
323
426
|
/**
|
|
324
427
|
* Subcommand factory used by every plugin-backed command (lint, format, jsc,
|
|
325
|
-
* tsc, pack) to expose a `doctor` subcommand that verifies the underlying
|
|
326
|
-
*
|
|
428
|
+
* tsc, pack) to expose a `doctor` subcommand that verifies the underlying tool
|
|
429
|
+
* is wired correctly. Renders the canonical `doctor (<tool>) · <pkg>` row like
|
|
430
|
+
* every other single-target command — `doctor()` returns a `RunReport`.
|
|
327
431
|
*/
|
|
328
|
-
function createDoctorSubcommand(service) {
|
|
432
|
+
function createDoctorSubcommand(service, appPkg) {
|
|
329
433
|
return createCommand("doctor").summary("check if the underlying tool is working correctly").action(async function doctorAction() {
|
|
330
|
-
|
|
331
|
-
const { ok, output } = await service.doctor();
|
|
332
|
-
if (ok) {
|
|
333
|
-
logger.success(`${service.ui} ok`);
|
|
334
|
-
debug("%O", output);
|
|
335
|
-
} else {
|
|
336
|
-
logger.error(`${service.ui} not working`);
|
|
337
|
-
debug("%O", output);
|
|
338
|
-
process.exit(output.exitCode ?? 1);
|
|
339
|
-
}
|
|
434
|
+
if (!(await runBoard([reportTask(targetLabel("doctor", service, appPkg), () => service.doctor())])).ok) process.exitCode = 1;
|
|
340
435
|
});
|
|
341
436
|
}
|
|
342
437
|
/**
|
|
@@ -352,23 +447,7 @@ function createDoctorCommand(ctx) {
|
|
|
352
447
|
logger.info("No plugins configured. Use `rr plugins add <name>` to install one.");
|
|
353
448
|
return;
|
|
354
449
|
}
|
|
355
|
-
|
|
356
|
-
const results = await Promise.all(services.map(async (svc) => {
|
|
357
|
-
return {
|
|
358
|
-
svc,
|
|
359
|
-
result: await svc.doctor()
|
|
360
|
-
};
|
|
361
|
-
}));
|
|
362
|
-
let failures = 0;
|
|
363
|
-
for (const { svc, result } of results) if (result.ok) {
|
|
364
|
-
logger.success(`${svc.ui} ok`);
|
|
365
|
-
debug("%s: %O", svc.ui, result.output);
|
|
366
|
-
} else {
|
|
367
|
-
logger.error(`${svc.ui} not working`);
|
|
368
|
-
debug("%s: %O", svc.ui, result.output);
|
|
369
|
-
failures++;
|
|
370
|
-
}
|
|
371
|
-
if (failures > 0) process.exitCode = 1;
|
|
450
|
+
if (!(await runBoard(services.map((svc) => reportTask(svc.ui, () => svc.doctor())), { title: fanoutTitle("doctor", void 0, services.length, "tools") })).ok) process.exitCode = 1;
|
|
372
451
|
});
|
|
373
452
|
}
|
|
374
453
|
function collectDistinctDoctors(ctx) {
|
|
@@ -377,33 +456,18 @@ function collectDistinctDoctors(ctx) {
|
|
|
377
456
|
return [...seen];
|
|
378
457
|
}
|
|
379
458
|
//#endregion
|
|
380
|
-
//#region src/program/missing-plugin.ts
|
|
381
|
-
const SUGGESTIONS = {
|
|
382
|
-
lint: [
|
|
383
|
-
"biome",
|
|
384
|
-
"oxc",
|
|
385
|
-
"eslint"
|
|
386
|
-
],
|
|
387
|
-
format: ["biome", "oxc"],
|
|
388
|
-
jsc: ["biome"],
|
|
389
|
-
tsc: ["ts"],
|
|
390
|
-
pack: ["tsdown"]
|
|
391
|
-
};
|
|
392
|
-
function missingPluginError(kind) {
|
|
393
|
-
const aliases = SUGGESTIONS[kind] ?? [];
|
|
394
|
-
const officialList = aliases.map((a) => `@rrlab/${a}-plugin`).join(", ");
|
|
395
|
-
const addList = aliases.map((a) => `rr plugins add ${a}`).join(" | ");
|
|
396
|
-
return /* @__PURE__ */ new Error(`No plugin provides the '${kind}' capability.` + (officialList ? `\n Install one of: ${officialList}.` : "") + (addList ? `\n Try: ${addList}.` : ""));
|
|
397
|
-
}
|
|
398
|
-
//#endregion
|
|
399
459
|
//#region src/program/commands/format.ts
|
|
400
460
|
function createFormatCommand(ctx) {
|
|
401
461
|
const formatter = ctx.registry.get("format");
|
|
402
462
|
const cmd = createCommand("format").summary(`check & fix format errors${pluginAnnotation(formatter)}`).description("Checks the code for formatting issues and optionally fixes them, ensuring it adheres to the defined style standards.").option("--fix", "format all the code");
|
|
403
|
-
if (formatter) cmd.addCommand(createDoctorSubcommand(formatter));
|
|
463
|
+
if (formatter) cmd.addCommand(createDoctorSubcommand(formatter, ctx.appPkg));
|
|
404
464
|
cmd.action(async (options = {}) => {
|
|
405
|
-
|
|
406
|
-
|
|
465
|
+
await runToolCommand(ctx, {
|
|
466
|
+
name: "format",
|
|
467
|
+
kind: "format",
|
|
468
|
+
provider: formatter,
|
|
469
|
+
run: (p) => p.format(options)
|
|
470
|
+
});
|
|
407
471
|
});
|
|
408
472
|
if (formatter) cmd.addHelpText("afterAll", `\nUnder the hood, this command uses the ${formatter.ui} CLI to format the code.`);
|
|
409
473
|
return cmd;
|
|
@@ -417,7 +481,8 @@ function createFormatCommand(ctx) {
|
|
|
417
481
|
* prettier) but no single plugin claims `jsc`.
|
|
418
482
|
*
|
|
419
483
|
* The check runs lint then format sequentially — interleaved stdout from a
|
|
420
|
-
* parallel run is hard to read for the user
|
|
484
|
+
* parallel run is hard to read for the user — and merges their reports into one
|
|
485
|
+
* so the board renders the pair as a single row. `fixStaged` is dropped because
|
|
421
486
|
* the underlying tools don't have a uniform staged-aware mode.
|
|
422
487
|
*/
|
|
423
488
|
function composedJscProvider(linter, formatter) {
|
|
@@ -425,24 +490,40 @@ function composedJscProvider(linter, formatter) {
|
|
|
425
490
|
bin: `${linter.bin}+${formatter.bin}`,
|
|
426
491
|
ui: `${linter.ui} + ${formatter.ui}`,
|
|
427
492
|
async check({ fix }) {
|
|
428
|
-
await linter.lint({ fix });
|
|
429
|
-
await formatter.format({ fix });
|
|
493
|
+
const lintReport = await linter.lint({ fix });
|
|
494
|
+
const formatReport = await formatter.format({ fix });
|
|
495
|
+
return mergeReports([{
|
|
496
|
+
ui: linter.ui,
|
|
497
|
+
report: lintReport
|
|
498
|
+
}, {
|
|
499
|
+
ui: formatter.ui,
|
|
500
|
+
report: formatReport
|
|
501
|
+
}]);
|
|
430
502
|
},
|
|
431
503
|
async doctor() {
|
|
432
504
|
const [lintRes, fmtRes] = await Promise.all([linter.doctor(), formatter.doctor()]);
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
exitCode: firstFailure?.output.exitCode ?? 0
|
|
441
|
-
}
|
|
442
|
-
};
|
|
505
|
+
return mergeReports([{
|
|
506
|
+
ui: linter.ui,
|
|
507
|
+
report: lintRes
|
|
508
|
+
}, {
|
|
509
|
+
ui: formatter.ui,
|
|
510
|
+
report: fmtRes
|
|
511
|
+
}]);
|
|
443
512
|
}
|
|
444
513
|
};
|
|
445
514
|
}
|
|
515
|
+
/**
|
|
516
|
+
* Folds the lint + format reports into one so the composed `jsc` renders as a
|
|
517
|
+
* single board row: ok only when both passed, with each tool's output kept
|
|
518
|
+
* under its own header so the flushed detail stays attributable.
|
|
519
|
+
*/
|
|
520
|
+
function mergeReports(parts) {
|
|
521
|
+
const sections = parts.filter((part) => part.report.output.trim()).map((part) => `${part.ui}:\n${part.report.output}`).join("\n\n");
|
|
522
|
+
return {
|
|
523
|
+
ok: parts.every((part) => part.report.ok),
|
|
524
|
+
output: sections
|
|
525
|
+
};
|
|
526
|
+
}
|
|
446
527
|
//#endregion
|
|
447
528
|
//#region src/program/commands/jscheck.ts
|
|
448
529
|
function createJsCheckCommand(ctx) {
|
|
@@ -451,10 +532,14 @@ function createJsCheckCommand(ctx) {
|
|
|
451
532
|
const formatter = ctx.registry.get("format");
|
|
452
533
|
const checker = direct ?? (linter && formatter ? composedJscProvider(linter, formatter) : void 0);
|
|
453
534
|
const cmd = createCommand("jsc").alias("jscheck").summary(`check format and lint${pluginAnnotation(checker)}`).description("Checks the code for formatting and linting issues, ensuring it adheres to the defined style and quality standards.").option("--fix", "try to fix issues automatically").option("--fix-staged", "try to fix staged files only");
|
|
454
|
-
if (checker) cmd.addCommand(createDoctorSubcommand(checker));
|
|
535
|
+
if (checker) cmd.addCommand(createDoctorSubcommand(checker, ctx.appPkg));
|
|
455
536
|
cmd.action(async (options = {}) => {
|
|
456
|
-
|
|
457
|
-
|
|
537
|
+
await runToolCommand(ctx, {
|
|
538
|
+
name: "jsc",
|
|
539
|
+
kind: "jsc",
|
|
540
|
+
provider: checker,
|
|
541
|
+
run: (p) => p.check(options)
|
|
542
|
+
});
|
|
458
543
|
});
|
|
459
544
|
if (checker) cmd.addHelpText("afterAll", `\nUnder the hood, this command uses the ${checker.ui} CLI to check the code.`);
|
|
460
545
|
return cmd;
|
|
@@ -464,10 +549,14 @@ function createJsCheckCommand(ctx) {
|
|
|
464
549
|
function createLintCommand(ctx) {
|
|
465
550
|
const linter = ctx.registry.get("lint");
|
|
466
551
|
const cmd = createCommand("lint").summary(`check & fix lint errors${pluginAnnotation(linter)}`).description("Checks the code for linting issues and optionally fixes them, ensuring it adheres to the defined quality standards.").option("-c, --check", "check if the code is valid", true).option("--fix", "try to fix all the code");
|
|
467
|
-
if (linter) cmd.addCommand(createDoctorSubcommand(linter));
|
|
552
|
+
if (linter) cmd.addCommand(createDoctorSubcommand(linter, ctx.appPkg));
|
|
468
553
|
cmd.action(async (options = {}) => {
|
|
469
|
-
|
|
470
|
-
|
|
554
|
+
await runToolCommand(ctx, {
|
|
555
|
+
name: "lint",
|
|
556
|
+
kind: "lint",
|
|
557
|
+
provider: linter,
|
|
558
|
+
run: (p) => p.lint(options)
|
|
559
|
+
});
|
|
471
560
|
});
|
|
472
561
|
if (linter) cmd.addHelpText("afterAll", `\nUnder the hood, this command uses the ${linter.ui} CLI to lint the code.`);
|
|
473
562
|
return cmd;
|
|
@@ -478,12 +567,16 @@ function createPackCommand(ctx) {
|
|
|
478
567
|
const packer = ctx.registry.get("pack");
|
|
479
568
|
const cmd = createCommand("pack").summary(`pack a ts library${pluginAnnotation(packer)}`).description("Compiles TypeScript code into JavaScript and generates type declaration files, packaging the library for distribution.");
|
|
480
569
|
if (packer) {
|
|
481
|
-
cmd.addCommand(createDoctorSubcommand(packer));
|
|
570
|
+
cmd.addCommand(createDoctorSubcommand(packer, ctx.appPkg));
|
|
482
571
|
cmd.addHelpText("afterAll", `\nUnder the hood, this command uses the ${packer.ui} CLI to pack the project.`);
|
|
483
572
|
}
|
|
484
573
|
cmd.action(async () => {
|
|
485
|
-
|
|
486
|
-
|
|
574
|
+
await runToolCommand(ctx, {
|
|
575
|
+
name: "pack",
|
|
576
|
+
kind: "pack",
|
|
577
|
+
provider: packer,
|
|
578
|
+
run: (p) => p.pack()
|
|
579
|
+
});
|
|
487
580
|
});
|
|
488
581
|
return cmd;
|
|
489
582
|
}
|
|
@@ -1143,48 +1236,37 @@ async function pathExists(p) {
|
|
|
1143
1236
|
//#endregion
|
|
1144
1237
|
//#region src/program/commands/tscheck.ts
|
|
1145
1238
|
const getPreScript = (scripts) => scripts?.pretsc ?? scripts?.pretypecheck;
|
|
1146
|
-
async function typecheckAt({ dir, scripts, log, shell, tsc }) {
|
|
1147
|
-
log.debug(`checking types at ${dir}`);
|
|
1148
|
-
const shellAt = cwd === dir ? shell : shell.at(dir);
|
|
1149
|
-
try {
|
|
1150
|
-
const preScript = getPreScript(scripts);
|
|
1151
|
-
if (preScript) {
|
|
1152
|
-
log.start(`Running pre-script: ${preScript}`);
|
|
1153
|
-
await shellAt.run(preScript, [], { shell: true });
|
|
1154
|
-
log.success("Pre-script completed");
|
|
1155
|
-
}
|
|
1156
|
-
log.start("Type checking started");
|
|
1157
|
-
if (cwd === dir) await tsc.check();
|
|
1158
|
-
else await tsc.check({ cwd: dir });
|
|
1159
|
-
log.success("Typecheck completed");
|
|
1160
|
-
} catch (error) {
|
|
1161
|
-
log.error("Typecheck failed");
|
|
1162
|
-
throw error;
|
|
1163
|
-
}
|
|
1164
|
-
}
|
|
1165
1239
|
function createTsCheckCommand(ctx) {
|
|
1166
1240
|
const { appPkg, shell } = ctx;
|
|
1167
1241
|
const tsc = ctx.registry.get("tsc");
|
|
1168
1242
|
const cmd = createCommand("tsc").alias("tscheck").summary(`check typescript errors${pluginAnnotation(tsc)}`).description("Checks the TypeScript code for type errors, ensuring that the code adheres to the defined type constraints and helps catch potential issues before runtime.");
|
|
1169
1243
|
if (tsc) {
|
|
1170
|
-
cmd.addCommand(createDoctorSubcommand(tsc));
|
|
1244
|
+
cmd.addCommand(createDoctorSubcommand(tsc, ctx.appPkg));
|
|
1171
1245
|
cmd.addHelpText("afterAll", `\nUnder the hood, this command uses the ${tsc.ui} CLI to check the code.`);
|
|
1172
1246
|
}
|
|
1173
1247
|
cmd.action(async () => {
|
|
1174
1248
|
if (!tsc) throw missingPluginError("tsc");
|
|
1175
1249
|
const isTsProject = (dir) => appPkg.hasFile("tsconfig.json", dir);
|
|
1250
|
+
const typecheckTask = (label, dir, scripts) => reportTask(label, async () => {
|
|
1251
|
+
const preScript = getPreScript(scripts);
|
|
1252
|
+
if (preScript) {
|
|
1253
|
+
const pre = await shell.at(dir).runCaptured(preScript, [], {
|
|
1254
|
+
shell: true,
|
|
1255
|
+
throwOnError: false
|
|
1256
|
+
});
|
|
1257
|
+
if ((pre.exitCode ?? 0) !== 0) return {
|
|
1258
|
+
ok: false,
|
|
1259
|
+
output: `pre-script \`${preScript}\` failed\n${[pre.stdout, pre.stderr].map((s) => s?.trim()).filter(Boolean).join("\n")}`
|
|
1260
|
+
};
|
|
1261
|
+
}
|
|
1262
|
+
return tsc.check({ cwd: dir });
|
|
1263
|
+
});
|
|
1176
1264
|
if (!appPkg.isMonorepo()) {
|
|
1177
1265
|
if (!isTsProject(appPkg.dirPath)) {
|
|
1178
1266
|
logger.info("No tsconfig.json found, skipping typecheck");
|
|
1179
1267
|
return;
|
|
1180
1268
|
}
|
|
1181
|
-
await
|
|
1182
|
-
shell,
|
|
1183
|
-
tsc,
|
|
1184
|
-
dir: appPkg.dirPath,
|
|
1185
|
-
scripts: appPkg.packageJson.scripts,
|
|
1186
|
-
log: logger
|
|
1187
|
-
});
|
|
1269
|
+
if (!(await runBoard([typecheckTask(targetLabel("tsc", tsc, appPkg), appPkg.dirPath, appPkg.packageJson.scripts)])).ok) process.exitCode = 1;
|
|
1188
1270
|
return;
|
|
1189
1271
|
}
|
|
1190
1272
|
const tsProjects = (await appPkg.getWorkspaceProjects()).filter((project) => isTsProject(project.rootDir));
|
|
@@ -1192,16 +1274,7 @@ function createTsCheckCommand(ctx) {
|
|
|
1192
1274
|
logger.warn("No ts projects found in the monorepo, skipping typecheck");
|
|
1193
1275
|
return;
|
|
1194
1276
|
}
|
|
1195
|
-
await
|
|
1196
|
-
shell,
|
|
1197
|
-
tsc,
|
|
1198
|
-
dir: p.rootDir,
|
|
1199
|
-
scripts: p.manifest.scripts,
|
|
1200
|
-
log: logger.child({
|
|
1201
|
-
tag: p.manifest.name,
|
|
1202
|
-
namespace: "typecheck"
|
|
1203
|
-
})
|
|
1204
|
-
})));
|
|
1277
|
+
if (!(await runBoard(tsProjects.map((p) => typecheckTask(p.manifest.name ?? p.rootDir, p.rootDir, p.manifest.scripts)), { title: fanoutTitle("tsc", tsc, tsProjects.length, "packages") })).ok) process.exitCode = 1;
|
|
1205
1278
|
});
|
|
1206
1279
|
return cmd;
|
|
1207
1280
|
}
|