@rrlab/cli 1.0.0 → 1.0.1-git-908d2c0.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 +208 -158
- package/dist/{types-DnqiiIxe.d.mts → types-snfbujDH.d.mts} +23 -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 +86 -0
- package/src/program/commands/check.ts +52 -42
- 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 +31 -23
- package/src/types/tool.ts +22 -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,35 +1,43 @@
|
|
|
1
|
-
import type { Doctor,
|
|
1
|
+
import type { Doctor, Formatter, Linter, RunReport, StaticChecker, StaticCheckerOptions } from "#src/plugin/types.ts";
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
|
-
* Synthesises
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
* The check runs lint then format sequentially — interleaved stdout from a
|
|
10
|
-
* parallel run is hard to read for the user. `fixStaged` is dropped because
|
|
11
|
-
* the underlying tools don't have a uniform staged-aware mode.
|
|
4
|
+
* Synthesises the `jsc` capability (`StaticChecker & Doctor`) by composing a
|
|
5
|
+
* separately-registered linter and formatter — used when the plugin set
|
|
6
|
+
* provides `lint` and `format` independently (e.g. oxc) but no plugin claims
|
|
7
|
+
* `jsc`. Runs lint then format sequentially (parallel stdout interleaves badly)
|
|
8
|
+
* and merges their reports into one board row.
|
|
12
9
|
*/
|
|
13
10
|
export function composedJscProvider(linter: Linter & Doctor, formatter: Formatter & Doctor): StaticChecker & Doctor {
|
|
14
11
|
return {
|
|
15
12
|
bin: `${linter.bin}+${formatter.bin}`,
|
|
16
13
|
ui: `${linter.ui} + ${formatter.ui}`,
|
|
17
|
-
async check({ fix }: StaticCheckerOptions) {
|
|
18
|
-
await linter.lint({ fix });
|
|
19
|
-
await formatter.format({ fix });
|
|
14
|
+
async check({ fix }: StaticCheckerOptions): Promise<RunReport> {
|
|
15
|
+
const lintReport = await linter.lint({ fix });
|
|
16
|
+
const formatReport = await formatter.format({ fix });
|
|
17
|
+
return mergeReports([
|
|
18
|
+
{ ui: linter.ui, report: lintReport },
|
|
19
|
+
{ ui: formatter.ui, report: formatReport },
|
|
20
|
+
]);
|
|
20
21
|
},
|
|
21
|
-
async doctor(): Promise<
|
|
22
|
+
async doctor(): Promise<RunReport> {
|
|
22
23
|
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
|
-
};
|
|
24
|
+
return mergeReports([
|
|
25
|
+
{ ui: linter.ui, report: lintRes },
|
|
26
|
+
{ ui: formatter.ui, report: fmtRes },
|
|
27
|
+
]);
|
|
33
28
|
},
|
|
34
29
|
};
|
|
35
30
|
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Folds the lint + format reports into one so the composed `jsc` renders as a
|
|
34
|
+
* single board row: ok only when both passed, with each tool's output kept
|
|
35
|
+
* under its own header so the flushed detail stays attributable.
|
|
36
|
+
*/
|
|
37
|
+
function mergeReports(parts: Array<{ ui: string; report: RunReport }>): RunReport {
|
|
38
|
+
const sections = parts
|
|
39
|
+
.filter((part) => part.report.output.trim())
|
|
40
|
+
.map((part) => `${part.ui}:\n${part.report.output}`)
|
|
41
|
+
.join("\n\n");
|
|
42
|
+
return { ok: parts.every((part) => part.report.ok), output: sections };
|
|
43
|
+
}
|
package/src/types/tool.ts
CHANGED
|
@@ -1,3 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The outcome of a check-family tool (lint / format / static check / type
|
|
3
|
+
* check) captured rather than streamed. `ok` is the tool's exit code — never a
|
|
4
|
+
* guess parsed from output, since tool summaries are unstable and not uniform
|
|
5
|
+
* (tsc and oxfmt emit none) — and `output` is the combined stdout+stderr (color
|
|
6
|
+
* preserved), flushed verbatim under the package label. See decisions/013.
|
|
7
|
+
*/
|
|
8
|
+
export type RunReport = {
|
|
9
|
+
ok: boolean;
|
|
10
|
+
output: string;
|
|
11
|
+
};
|
|
12
|
+
|
|
1
13
|
export type FormatOptions = {
|
|
2
14
|
fix?: boolean;
|
|
3
15
|
};
|
|
@@ -11,38 +23,32 @@ export type StaticCheckerOptions = {
|
|
|
11
23
|
fixStaged?: boolean;
|
|
12
24
|
};
|
|
13
25
|
|
|
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
26
|
export type Doctor = {
|
|
26
27
|
ui: string;
|
|
27
|
-
|
|
28
|
+
/**
|
|
29
|
+
* Verifies the tool is wired correctly. Returns a `RunReport` like every
|
|
30
|
+
* other verb so the board renders it identically — `output` leads with the
|
|
31
|
+
* `$ <bin> --help` liveness command, plus the error if the bin won't run.
|
|
32
|
+
*/
|
|
33
|
+
doctor: () => Promise<RunReport>;
|
|
28
34
|
};
|
|
29
35
|
|
|
30
36
|
export type Formatter = {
|
|
31
37
|
bin: string;
|
|
32
38
|
ui: string;
|
|
33
|
-
format: (options: FormatOptions) => Promise<
|
|
39
|
+
format: (options: FormatOptions) => Promise<RunReport>;
|
|
34
40
|
};
|
|
35
41
|
|
|
36
42
|
export type Linter = {
|
|
37
43
|
bin: string;
|
|
38
44
|
ui: string;
|
|
39
|
-
lint: (options: LintOptions) => Promise<
|
|
45
|
+
lint: (options: LintOptions) => Promise<RunReport>;
|
|
40
46
|
};
|
|
41
47
|
|
|
42
48
|
export type StaticChecker = {
|
|
43
49
|
bin: string;
|
|
44
50
|
ui: string;
|
|
45
|
-
check: (options: StaticCheckerOptions) => Promise<
|
|
51
|
+
check: (options: StaticCheckerOptions) => Promise<RunReport>;
|
|
46
52
|
};
|
|
47
53
|
|
|
48
54
|
export type TypeCheckOptions = {
|
|
@@ -53,5 +59,5 @@ export type TypeCheckOptions = {
|
|
|
53
59
|
export type TypeChecker = {
|
|
54
60
|
bin: string;
|
|
55
61
|
ui: string;
|
|
56
|
-
check: (options?: TypeCheckOptions) => Promise<
|
|
62
|
+
check: (options?: TypeCheckOptions) => Promise<RunReport>;
|
|
57
63
|
};
|