@cosmicdrift/kumiko-cli 0.290.0 → 0.292.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/package.json +7 -6
- package/src/changes.json +7 -0
- package/src/commands/check.ts +194 -0
- package/src/commands/index.ts +10 -1
- package/src/index.ts +4 -4
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cosmicdrift/kumiko-cli",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "CLI for Kumiko apps. `bunx @cosmicdrift/kumiko-cli new app <name>` scaffolds a runnable workspace; `add feature <name>` mounts a feature; `agent lint`, `project` and `consumer` operate on an existing app.",
|
|
3
|
+
"version": "0.292.0",
|
|
4
|
+
"description": "CLI for Kumiko apps. `bunx @cosmicdrift/kumiko-cli new app <name>` scaffolds a runnable workspace; `add feature <name>` mounts a feature; `check` runs boot validation plus the guard suites with the step list derived from kumiko.json; `agent lint`, `project` and `consumer` operate on an existing app.",
|
|
5
5
|
"license": "BUSL-1.1",
|
|
6
6
|
"author": "Marc Frost <marc@cosmicdriftgamestudio.com>",
|
|
7
7
|
"repository": {
|
|
@@ -35,10 +35,11 @@
|
|
|
35
35
|
}
|
|
36
36
|
},
|
|
37
37
|
"dependencies": {
|
|
38
|
-
"@cosmicdrift/kumiko-bundled-features": "0.
|
|
39
|
-
"@cosmicdrift/kumiko-
|
|
40
|
-
"@cosmicdrift/kumiko-
|
|
41
|
-
"@cosmicdrift/kumiko-
|
|
38
|
+
"@cosmicdrift/kumiko-bundled-features": "0.292.0",
|
|
39
|
+
"@cosmicdrift/kumiko-dev-server": "0.292.0",
|
|
40
|
+
"@cosmicdrift/kumiko-framework": "0.292.0",
|
|
41
|
+
"@cosmicdrift/kumiko-guards": "0.292.0",
|
|
42
|
+
"@cosmicdrift/kumiko-repo-manifest": "0.292.0",
|
|
42
43
|
"zod": "^4.4.3"
|
|
43
44
|
},
|
|
44
45
|
"publishConfig": {
|
package/src/changes.json
CHANGED
|
@@ -1,4 +1,11 @@
|
|
|
1
1
|
[
|
|
2
|
+
{
|
|
3
|
+
"version": "0.292.0",
|
|
4
|
+
"type": "improvement",
|
|
5
|
+
"title": "New `kumiko check`: boot validation plus the guard suites in one command, step list derived from kumiko.json",
|
|
6
|
+
"detail": "Until now every consumer repo assembled the same chain by hand — `kumiko-schema validate` plus `kumiko-guards guards|ui|checks` in package.json, CI YAML or check-wt.sh, in four different shapes, each with its own hand-written presence gate (`|| true`, `::error::`, `bun add --no-save`). `kumiko check` derives the step list declaratively from the kumiko.json manifest instead: `kind: \"app\"` adds boot validation (validateBoot over the composed FEATURES in kumiko/schema.ts), any kind other than \"tooling\" adds the AST guards and the repo checks, declared uiRoots add the UI guards, and a \"tooling\" repo says out loud that it has nothing to check. `--explain` prints the resolved step list with the reason for each step and passes through to the guards own --explain without running a suite. When @cosmicdrift/kumiko-guards cannot be loaded the command fails with an install hint instead of skipping silently.",
|
|
7
|
+
"migration": "No action required — additive. A repo that today calls `kumiko-schema validate` and `kumiko-guards guards|ui|checks` separately in package.json, CI YAML or check-wt.sh replaces that chain with `bun kumiko check`; the step list then follows from kumiko.json (kind, uiRoots) instead of from the script. Private bins without a public counterpart (kumiko-guard-comment-lang, kumiko-guard-ui) stay as their own step next to it for now."
|
|
8
|
+
},
|
|
2
9
|
{
|
|
3
10
|
"version": "0.4.0",
|
|
4
11
|
"type": "improvement",
|
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
import { existsSync } from "node:fs";
|
|
2
|
+
import { join, resolve } from "node:path";
|
|
3
|
+
import { REPO_MANIFEST_FILE, type RepoManifest } from "@cosmicdrift/kumiko-repo-manifest";
|
|
4
|
+
import type { CliCommand, CliCommandContext } from "./types";
|
|
5
|
+
|
|
6
|
+
const APP_SCHEMA_FILE = "kumiko/schema.ts";
|
|
7
|
+
|
|
8
|
+
export type CheckStepId = "boot" | "guards" | "ui" | "checks";
|
|
9
|
+
|
|
10
|
+
export type CheckStep = {
|
|
11
|
+
readonly id: CheckStepId;
|
|
12
|
+
readonly label: string;
|
|
13
|
+
readonly why: string;
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
export const CHECK_FLAGS = ["--explain"] as const;
|
|
17
|
+
|
|
18
|
+
/** Facts about the checked-out repo that the manifest does not state. */
|
|
19
|
+
export type RepoFacts = {
|
|
20
|
+
/** A `kind: "app"` repo without one is an app by role only (an Astro site, a
|
|
21
|
+
* docs repo) — there is no composed feature set for validateBoot to check. */
|
|
22
|
+
readonly hasAppSchema: boolean;
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
export function resolveCheckSteps(manifest: RepoManifest, facts: RepoFacts): readonly CheckStep[] {
|
|
26
|
+
if (manifest.kind === "tooling") return [];
|
|
27
|
+
const steps: CheckStep[] = [];
|
|
28
|
+
if (manifest.kind === "app" && facts.hasAppSchema) {
|
|
29
|
+
steps.push({
|
|
30
|
+
id: "boot",
|
|
31
|
+
label: "Boot validation",
|
|
32
|
+
why: `kind "app" with ${APP_SCHEMA_FILE} — validateBoot over its composed FEATURES`,
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
steps.push({
|
|
36
|
+
id: "guards",
|
|
37
|
+
label: "AST guards",
|
|
38
|
+
why: `kind "${manifest.kind}" — sourceRoots ${manifest.sourceRoots.join(", ")}`,
|
|
39
|
+
});
|
|
40
|
+
if (manifest.uiRoots !== undefined && manifest.uiRoots.length > 0) {
|
|
41
|
+
steps.push({
|
|
42
|
+
id: "ui",
|
|
43
|
+
label: "UI guards",
|
|
44
|
+
why: `uiRoots ${manifest.uiRoots.join(", ")}`,
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
steps.push({
|
|
48
|
+
id: "checks",
|
|
49
|
+
label: "Repo checks",
|
|
50
|
+
why: `kind "${manifest.kind}" — testGlobs ${manifest.testGlobs.join(", ")}`,
|
|
51
|
+
});
|
|
52
|
+
return steps;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export type GuardsCli = Pick<
|
|
56
|
+
typeof import("@cosmicdrift/kumiko-guards"),
|
|
57
|
+
"cliFlagsError" | "findLocalRepo" | "runGuardsCli" | "runRepoChecksCli" | "runUiGuardsCli"
|
|
58
|
+
>;
|
|
59
|
+
|
|
60
|
+
export type SchemaCli = Pick<
|
|
61
|
+
typeof import("@cosmicdrift/kumiko-framework/schema-cli"),
|
|
62
|
+
"runSchemaCli"
|
|
63
|
+
>;
|
|
64
|
+
|
|
65
|
+
export type CheckDeps = {
|
|
66
|
+
readonly loadGuards: () => Promise<GuardsCli>;
|
|
67
|
+
readonly loadSchemaCli: () => Promise<SchemaCli>;
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
// Lazy: `kumiko new app` must not pay for ts-morph or the framework runtime.
|
|
71
|
+
const DEFAULT_DEPS: CheckDeps = {
|
|
72
|
+
loadGuards: () => import("@cosmicdrift/kumiko-guards"),
|
|
73
|
+
loadSchemaCli: () => import("@cosmicdrift/kumiko-framework/schema-cli"),
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
export async function runCheck(
|
|
77
|
+
ctx: CliCommandContext,
|
|
78
|
+
deps: CheckDeps = DEFAULT_DEPS,
|
|
79
|
+
): Promise<number> {
|
|
80
|
+
// The guard runners resolve their scan root from the ambient cwd — a diverging
|
|
81
|
+
// ctx.cwd would print the step list of one repo and scan another.
|
|
82
|
+
if (resolve(ctx.cwd) !== resolve(process.cwd())) {
|
|
83
|
+
ctx.out.err("");
|
|
84
|
+
ctx.out.err(` kumiko check runs against the process cwd (${process.cwd()}).`);
|
|
85
|
+
ctx.out.err(` Got cwd: ${ctx.cwd}`);
|
|
86
|
+
ctx.out.err("");
|
|
87
|
+
return 1;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
let guards: GuardsCli;
|
|
91
|
+
try {
|
|
92
|
+
guards = await deps.loadGuards();
|
|
93
|
+
} catch (e) {
|
|
94
|
+
ctx.out.err("");
|
|
95
|
+
ctx.out.err(" ✗ @cosmicdrift/kumiko-guards could not be loaded — kumiko check needs it.");
|
|
96
|
+
ctx.out.err(` ${e instanceof Error ? e.message : String(e)}`);
|
|
97
|
+
ctx.out.err(" Fix: `bun add -d @cosmicdrift/kumiko-guards`");
|
|
98
|
+
ctx.out.err("");
|
|
99
|
+
return 1;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
const flagsError = guards.cliFlagsError("check", ctx.argv, CHECK_FLAGS);
|
|
103
|
+
if (flagsError !== undefined) {
|
|
104
|
+
ctx.out.err("");
|
|
105
|
+
ctx.out.err(` ${flagsError}`);
|
|
106
|
+
ctx.out.err("");
|
|
107
|
+
return 1;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
let repo: ReturnType<GuardsCli["findLocalRepo"]>;
|
|
111
|
+
try {
|
|
112
|
+
repo = guards.findLocalRepo(ctx.cwd);
|
|
113
|
+
} catch (e) {
|
|
114
|
+
ctx.out.err("");
|
|
115
|
+
ctx.out.err(` ✗ ${e instanceof Error ? e.message : String(e)}`);
|
|
116
|
+
ctx.out.err("");
|
|
117
|
+
return 1;
|
|
118
|
+
}
|
|
119
|
+
if (repo === undefined) {
|
|
120
|
+
ctx.out.err("");
|
|
121
|
+
ctx.out.err(` ✗ No Kumiko repo above ${ctx.cwd}.`);
|
|
122
|
+
ctx.out.err(` Add a ${REPO_MANIFEST_FILE} at the repo root (kind, sourceRoots, testGlobs).`);
|
|
123
|
+
ctx.out.err("");
|
|
124
|
+
return 1;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
const steps = resolveCheckSteps(repo.manifest, {
|
|
128
|
+
hasAppSchema: existsSync(join(repo.absPath, APP_SCHEMA_FILE)),
|
|
129
|
+
});
|
|
130
|
+
ctx.out.log("");
|
|
131
|
+
ctx.out.log(
|
|
132
|
+
` ${repo.name} — kind "${repo.manifest.kind}", ${steps.length} step(s) from ${REPO_MANIFEST_FILE}`,
|
|
133
|
+
);
|
|
134
|
+
for (const step of steps) {
|
|
135
|
+
ctx.out.log(` ${step.id.padEnd(8)}${step.label} — ${step.why}`);
|
|
136
|
+
}
|
|
137
|
+
ctx.out.log("");
|
|
138
|
+
|
|
139
|
+
if (steps.length === 0) {
|
|
140
|
+
ctx.out.log(` Nothing to check: kind "${repo.manifest.kind}" declares no product code.`);
|
|
141
|
+
ctx.out.log("");
|
|
142
|
+
return 0;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
if (ctx.argv.includes("--explain")) {
|
|
146
|
+
// The guard suite is the only one with its own --explain; pass it through.
|
|
147
|
+
if (steps.some((step) => step.id === "guards")) guards.runGuardsCli(["--explain"]);
|
|
148
|
+
return 0;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
let failed = 0;
|
|
152
|
+
for (const step of steps) {
|
|
153
|
+
ctx.out.log(` ▸ ${step.label}`);
|
|
154
|
+
failed += await runStep(step.id, ctx, guards, deps);
|
|
155
|
+
}
|
|
156
|
+
return failed > 0 ? 1 : 0;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
async function runStep(
|
|
160
|
+
id: CheckStepId,
|
|
161
|
+
ctx: CliCommandContext,
|
|
162
|
+
guards: GuardsCli,
|
|
163
|
+
deps: CheckDeps,
|
|
164
|
+
): Promise<number> {
|
|
165
|
+
switch (id) {
|
|
166
|
+
case "boot": {
|
|
167
|
+
const { runSchemaCli } = await deps.loadSchemaCli();
|
|
168
|
+
return await runSchemaCli(["validate"], ctx.cwd, ctx.out);
|
|
169
|
+
}
|
|
170
|
+
case "guards":
|
|
171
|
+
return guards.runGuardsCli([]);
|
|
172
|
+
case "ui":
|
|
173
|
+
return guards.runUiGuardsCli([]);
|
|
174
|
+
case "checks":
|
|
175
|
+
return await guards.runRepoChecksCli([]);
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
export const checkCommand: CliCommand = {
|
|
180
|
+
id: "check",
|
|
181
|
+
description: "Boot validation + guard suites, step list derived from kumiko.json",
|
|
182
|
+
help: [
|
|
183
|
+
`Derives the step list from ${REPO_MANIFEST_FILE} instead of a hand-written command chain:`,
|
|
184
|
+
"",
|
|
185
|
+
` boot kind "app" + ${APP_SCHEMA_FILE} validateBoot over its composed FEATURES`,
|
|
186
|
+
' guards kind != "tooling" AST guards over sourceRoots',
|
|
187
|
+
" ui uiRoots declared UI guards (App-Mounting 2.0)",
|
|
188
|
+
' checks kind != "tooling" repo checks over sourceRoots + testGlobs',
|
|
189
|
+
"",
|
|
190
|
+
"Flags:",
|
|
191
|
+
" --explain print the resolved step list and the guards' own scan explanation, run nothing",
|
|
192
|
+
].join("\n"),
|
|
193
|
+
run: (ctx) => runCheck(ctx),
|
|
194
|
+
};
|
package/src/commands/index.ts
CHANGED
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
import { agentCommand } from "./agent";
|
|
2
|
+
import { checkCommand } from "./check";
|
|
2
3
|
import { consumerCommand } from "./consumer";
|
|
3
4
|
import { projectCommand } from "./project";
|
|
4
5
|
import type { CliCommand } from "./types";
|
|
5
6
|
|
|
6
7
|
export const APP_COMMANDS: ReadonlyArray<CliCommand> = [
|
|
8
|
+
checkCommand,
|
|
7
9
|
agentCommand,
|
|
8
10
|
projectCommand,
|
|
9
11
|
consumerCommand,
|
|
@@ -13,5 +15,12 @@ export function findAppCommand(id: string): CliCommand | undefined {
|
|
|
13
15
|
return APP_COMMANDS.find((c) => c.id === id);
|
|
14
16
|
}
|
|
15
17
|
|
|
18
|
+
export {
|
|
19
|
+
type CheckDeps,
|
|
20
|
+
type CheckStep,
|
|
21
|
+
type CheckStepId,
|
|
22
|
+
resolveCheckSteps,
|
|
23
|
+
runCheck,
|
|
24
|
+
} from "./check";
|
|
16
25
|
export type { CliCommand, CliCommandContext } from "./types";
|
|
17
|
-
export { agentCommand, consumerCommand, projectCommand };
|
|
26
|
+
export { agentCommand, checkCommand, consumerCommand, projectCommand };
|
package/src/index.ts
CHANGED
|
@@ -4,10 +4,10 @@
|
|
|
4
4
|
// (../bin/cli.ts) is a 3-line wrapper that forwards process.argv. Tests
|
|
5
5
|
// drive runCli directly with a captured Output so no subprocess is needed.
|
|
6
6
|
//
|
|
7
|
-
// Scope: scaffolding (`new app <name>`, `add feature <name>`)
|
|
8
|
-
// app-facing ops commands (`agent`, `project`,
|
|
9
|
-
// maintainer commands (
|
|
10
|
-
// framework repo's bin/kumiko.ts
|
|
7
|
+
// Scope: scaffolding (`new app <name>`, `add feature <name>`), the quality
|
|
8
|
+
// gate (`check`) and the app-facing ops commands (`agent`, `project`,
|
|
9
|
+
// `consumer`). Workspace-only maintainer commands (ci:guards, dev, build, …)
|
|
10
|
+
// stay in the framework repo's bin/kumiko.ts.
|
|
11
11
|
|
|
12
12
|
import { scaffoldApp, scaffoldAppFeature } from "@cosmicdrift/kumiko-dev-server";
|
|
13
13
|
import type { CliCommand } from "./commands";
|