@rrlab/cli 1.0.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 +2 -2
- 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
|
@@ -1,50 +1,14 @@
|
|
|
1
|
-
import { cwd, type ShellService } from "@vlandoss/clibuddy";
|
|
2
|
-
import type { AnyLogger } from "@vlandoss/loggy";
|
|
3
1
|
import { createCommand } from "commander";
|
|
4
|
-
import type { Doctor, TypeChecker } from "#src/plugin/types.ts";
|
|
5
2
|
import type { Context } from "#src/services/ctx.ts";
|
|
6
3
|
import { logger } from "#src/services/logger.ts";
|
|
4
|
+
import { type BoardTask, fanoutTitle, reportTask, runBoard, targetLabel } from "../board.ts";
|
|
7
5
|
import { missingPluginError } from "../missing-plugin.ts";
|
|
8
6
|
import { pluginAnnotation } from "../ui.ts";
|
|
9
7
|
import { createDoctorSubcommand } from "./doctor.ts";
|
|
10
8
|
|
|
11
|
-
type
|
|
12
|
-
dir: string;
|
|
13
|
-
scripts: Record<string, string | undefined> | undefined;
|
|
14
|
-
log: AnyLogger;
|
|
15
|
-
shell: ShellService;
|
|
16
|
-
tsc: TypeChecker & Doctor;
|
|
17
|
-
};
|
|
9
|
+
type Scripts = Record<string, string | undefined> | undefined;
|
|
18
10
|
|
|
19
|
-
const getPreScript = (scripts:
|
|
20
|
-
|
|
21
|
-
async function typecheckAt({ dir, scripts, log, shell, tsc }: TypecheckAtOptions) {
|
|
22
|
-
log.debug(`checking types at ${dir}`);
|
|
23
|
-
|
|
24
|
-
const shellAt = cwd === dir ? shell : shell.at(dir);
|
|
25
|
-
|
|
26
|
-
try {
|
|
27
|
-
const preScript = getPreScript(scripts);
|
|
28
|
-
if (preScript) {
|
|
29
|
-
log.start(`Running pre-script: ${preScript}`);
|
|
30
|
-
// Pre-scripts come from package.json and may contain shell features
|
|
31
|
-
// (`&&`, pipes, env-var substitution) — run them through `/bin/sh -c`.
|
|
32
|
-
await shellAt.run(preScript, [], { shell: true });
|
|
33
|
-
log.success("Pre-script completed");
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
log.start("Type checking started");
|
|
37
|
-
if (cwd === dir) {
|
|
38
|
-
await tsc.check();
|
|
39
|
-
} else {
|
|
40
|
-
await tsc.check({ cwd: dir });
|
|
41
|
-
}
|
|
42
|
-
log.success("Typecheck completed");
|
|
43
|
-
} catch (error) {
|
|
44
|
-
log.error("Typecheck failed");
|
|
45
|
-
throw error;
|
|
46
|
-
}
|
|
47
|
-
}
|
|
11
|
+
const getPreScript = (scripts: Scripts) => scripts?.pretsc ?? scripts?.pretypecheck;
|
|
48
12
|
|
|
49
13
|
export function createTsCheckCommand(ctx: Context) {
|
|
50
14
|
const { appPkg, shell } = ctx;
|
|
@@ -58,7 +22,7 @@ export function createTsCheckCommand(ctx: Context) {
|
|
|
58
22
|
);
|
|
59
23
|
|
|
60
24
|
if (tsc) {
|
|
61
|
-
cmd.addCommand(createDoctorSubcommand(tsc));
|
|
25
|
+
cmd.addCommand(createDoctorSubcommand(tsc, ctx.appPkg));
|
|
62
26
|
cmd.addHelpText("afterAll", `\nUnder the hood, this command uses the ${tsc.ui} CLI to check the code.`);
|
|
63
27
|
}
|
|
64
28
|
|
|
@@ -67,20 +31,36 @@ export function createTsCheckCommand(ctx: Context) {
|
|
|
67
31
|
|
|
68
32
|
const isTsProject = (dir: string) => appPkg.hasFile("tsconfig.json", dir);
|
|
69
33
|
|
|
34
|
+
// A package's `pretsc`/`pretypecheck` runs captured, inside the task, so its
|
|
35
|
+
// output stays grouped with that package. It may use shell features, so it
|
|
36
|
+
// goes through `/bin/sh -c`. A failing pre-script fails the task before tsc.
|
|
37
|
+
const typecheckTask = (label: string, dir: string, scripts: Scripts): BoardTask =>
|
|
38
|
+
reportTask(label, async () => {
|
|
39
|
+
const preScript = getPreScript(scripts);
|
|
40
|
+
if (preScript) {
|
|
41
|
+
const pre = await shell.at(dir).runCaptured(preScript, [], { shell: true, throwOnError: false });
|
|
42
|
+
if ((pre.exitCode ?? 0) !== 0) {
|
|
43
|
+
const output = [pre.stdout, pre.stderr]
|
|
44
|
+
.map((s) => s?.trim())
|
|
45
|
+
.filter(Boolean)
|
|
46
|
+
.join("\n");
|
|
47
|
+
return { ok: false, output: `pre-script \`${preScript}\` failed\n${output}` };
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
return tsc.check({ cwd: dir });
|
|
51
|
+
});
|
|
52
|
+
|
|
70
53
|
if (!appPkg.isMonorepo()) {
|
|
71
54
|
if (!isTsProject(appPkg.dirPath)) {
|
|
72
55
|
logger.info("No tsconfig.json found, skipping typecheck");
|
|
73
56
|
return;
|
|
74
57
|
}
|
|
75
58
|
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
log: logger,
|
|
82
|
-
});
|
|
83
|
-
|
|
59
|
+
// Single package → compact board; the row carries the canonical
|
|
60
|
+
// `tsc (<tool>) · <pkg>` label like every other single-target command.
|
|
61
|
+
const label = targetLabel("tsc", tsc, appPkg);
|
|
62
|
+
const result = await runBoard([typecheckTask(label, appPkg.dirPath, appPkg.packageJson.scripts)]);
|
|
63
|
+
if (!result.ok) process.exitCode = 1;
|
|
84
64
|
return;
|
|
85
65
|
}
|
|
86
66
|
|
|
@@ -92,20 +72,9 @@ export function createTsCheckCommand(ctx: Context) {
|
|
|
92
72
|
return;
|
|
93
73
|
}
|
|
94
74
|
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
shell,
|
|
99
|
-
tsc,
|
|
100
|
-
dir: p.rootDir,
|
|
101
|
-
scripts: p.manifest.scripts,
|
|
102
|
-
log: logger.child({
|
|
103
|
-
tag: p.manifest.name,
|
|
104
|
-
namespace: "typecheck",
|
|
105
|
-
}),
|
|
106
|
-
}),
|
|
107
|
-
),
|
|
108
|
-
);
|
|
75
|
+
const tasks = tsProjects.map((p) => typecheckTask(p.manifest.name ?? p.rootDir, p.rootDir, p.manifest.scripts));
|
|
76
|
+
const result = await runBoard(tasks, { title: fanoutTitle("tsc", tsc, tsProjects.length, "packages") });
|
|
77
|
+
if (!result.ok) process.exitCode = 1;
|
|
109
78
|
});
|
|
110
79
|
|
|
111
80
|
return cmd;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { Doctor,
|
|
1
|
+
import type { Doctor, Formatter, Linter, RunReport, StaticChecker, StaticCheckerOptions } from "#src/plugin/types.ts";
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
4
|
* Synthesises a `StaticChecker & Doctor` (the `jsc` capability) by composing
|
|
@@ -7,29 +7,41 @@ import type { Doctor, DoctorResult, Formatter, Linter, StaticChecker, StaticChec
|
|
|
7
7
|
* prettier) but no single plugin claims `jsc`.
|
|
8
8
|
*
|
|
9
9
|
* The check runs lint then format sequentially — interleaved stdout from a
|
|
10
|
-
* parallel run is hard to read for the user
|
|
10
|
+
* parallel run is hard to read for the user — and merges their reports into one
|
|
11
|
+
* so the board renders the pair as a single row. `fixStaged` is dropped because
|
|
11
12
|
* the underlying tools don't have a uniform staged-aware mode.
|
|
12
13
|
*/
|
|
13
14
|
export function composedJscProvider(linter: Linter & Doctor, formatter: Formatter & Doctor): StaticChecker & Doctor {
|
|
14
15
|
return {
|
|
15
16
|
bin: `${linter.bin}+${formatter.bin}`,
|
|
16
17
|
ui: `${linter.ui} + ${formatter.ui}`,
|
|
17
|
-
async check({ fix }: StaticCheckerOptions) {
|
|
18
|
-
await linter.lint({ fix });
|
|
19
|
-
await formatter.format({ fix });
|
|
18
|
+
async check({ fix }: StaticCheckerOptions): Promise<RunReport> {
|
|
19
|
+
const lintReport = await linter.lint({ fix });
|
|
20
|
+
const formatReport = await formatter.format({ fix });
|
|
21
|
+
return mergeReports([
|
|
22
|
+
{ ui: linter.ui, report: lintReport },
|
|
23
|
+
{ ui: formatter.ui, report: formatReport },
|
|
24
|
+
]);
|
|
20
25
|
},
|
|
21
|
-
async doctor(): Promise<
|
|
26
|
+
async doctor(): Promise<RunReport> {
|
|
22
27
|
const [lintRes, fmtRes] = await Promise.all([linter.doctor(), formatter.doctor()]);
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
output: {
|
|
28
|
-
stdout: `${linter.ui}:\n${lintRes.output.stdout}\n\n${formatter.ui}:\n${fmtRes.output.stdout}`,
|
|
29
|
-
stderr: `${linter.ui}:\n${lintRes.output.stderr}\n\n${formatter.ui}:\n${fmtRes.output.stderr}`,
|
|
30
|
-
exitCode: firstFailure?.output.exitCode ?? 0,
|
|
31
|
-
},
|
|
32
|
-
};
|
|
28
|
+
return mergeReports([
|
|
29
|
+
{ ui: linter.ui, report: lintRes },
|
|
30
|
+
{ ui: formatter.ui, report: fmtRes },
|
|
31
|
+
]);
|
|
33
32
|
},
|
|
34
33
|
};
|
|
35
34
|
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Folds the lint + format reports into one so the composed `jsc` renders as a
|
|
38
|
+
* single board row: ok only when both passed, with each tool's output kept
|
|
39
|
+
* under its own header so the flushed detail stays attributable.
|
|
40
|
+
*/
|
|
41
|
+
function mergeReports(parts: Array<{ ui: string; report: RunReport }>): RunReport {
|
|
42
|
+
const sections = parts
|
|
43
|
+
.filter((part) => part.report.output.trim())
|
|
44
|
+
.map((part) => `${part.ui}:\n${part.report.output}`)
|
|
45
|
+
.join("\n\n");
|
|
46
|
+
return { ok: parts.every((part) => part.report.ok), output: sections };
|
|
47
|
+
}
|
package/src/types/tool.ts
CHANGED
|
@@ -1,3 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The outcome of running a check-family tool (lint / format / static check /
|
|
3
|
+
* type check) captured rather than streamed. `ok` is the tool's own verdict —
|
|
4
|
+
* its exit code, never a guess parsed from output — and `output` is the
|
|
5
|
+
* combined captured stdout+stderr (color preserved), flushed verbatim grouped
|
|
6
|
+
* under the package label. We deliberately do NOT parse tool summaries for
|
|
7
|
+
* warning/error counts: the formats are unstable and not uniform across tools
|
|
8
|
+
* (tsc and oxfmt have no machine output at all). See decisions/013.
|
|
9
|
+
*/
|
|
10
|
+
export type RunReport = {
|
|
11
|
+
ok: boolean;
|
|
12
|
+
output: string;
|
|
13
|
+
};
|
|
14
|
+
|
|
1
15
|
export type FormatOptions = {
|
|
2
16
|
fix?: boolean;
|
|
3
17
|
};
|
|
@@ -11,38 +25,32 @@ export type StaticCheckerOptions = {
|
|
|
11
25
|
fixStaged?: boolean;
|
|
12
26
|
};
|
|
13
27
|
|
|
14
|
-
export type DoctorOutput = {
|
|
15
|
-
stdout: string;
|
|
16
|
-
stderr: string;
|
|
17
|
-
exitCode: number | undefined;
|
|
18
|
-
};
|
|
19
|
-
|
|
20
|
-
export type DoctorResult = {
|
|
21
|
-
ok: boolean;
|
|
22
|
-
output: DoctorOutput;
|
|
23
|
-
};
|
|
24
|
-
|
|
25
28
|
export type Doctor = {
|
|
26
29
|
ui: string;
|
|
27
|
-
|
|
30
|
+
/**
|
|
31
|
+
* Verifies the tool is wired correctly. Returns a `RunReport` like every
|
|
32
|
+
* other verb so the board renders it identically — `output` leads with the
|
|
33
|
+
* `$ <bin> --help` liveness command, plus the error if the bin won't run.
|
|
34
|
+
*/
|
|
35
|
+
doctor: () => Promise<RunReport>;
|
|
28
36
|
};
|
|
29
37
|
|
|
30
38
|
export type Formatter = {
|
|
31
39
|
bin: string;
|
|
32
40
|
ui: string;
|
|
33
|
-
format: (options: FormatOptions) => Promise<
|
|
41
|
+
format: (options: FormatOptions) => Promise<RunReport>;
|
|
34
42
|
};
|
|
35
43
|
|
|
36
44
|
export type Linter = {
|
|
37
45
|
bin: string;
|
|
38
46
|
ui: string;
|
|
39
|
-
lint: (options: LintOptions) => Promise<
|
|
47
|
+
lint: (options: LintOptions) => Promise<RunReport>;
|
|
40
48
|
};
|
|
41
49
|
|
|
42
50
|
export type StaticChecker = {
|
|
43
51
|
bin: string;
|
|
44
52
|
ui: string;
|
|
45
|
-
check: (options: StaticCheckerOptions) => Promise<
|
|
53
|
+
check: (options: StaticCheckerOptions) => Promise<RunReport>;
|
|
46
54
|
};
|
|
47
55
|
|
|
48
56
|
export type TypeCheckOptions = {
|
|
@@ -53,5 +61,5 @@ export type TypeCheckOptions = {
|
|
|
53
61
|
export type TypeChecker = {
|
|
54
62
|
bin: string;
|
|
55
63
|
ui: string;
|
|
56
|
-
check: (options?: TypeCheckOptions) => Promise<
|
|
64
|
+
check: (options?: TypeCheckOptions) => Promise<RunReport>;
|
|
57
65
|
};
|