@cosmicdrift/kumiko-cli 0.341.0 → 0.342.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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cosmicdrift/kumiko-cli",
3
- "version": "0.341.0",
3
+ "version": "0.342.0",
4
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>",
@@ -35,12 +35,12 @@
35
35
  }
36
36
  },
37
37
  "dependencies": {
38
- "@cosmicdrift/kumiko-bundled-features": "0.341.0",
39
- "@cosmicdrift/kumiko-dev-server": "0.341.0",
40
- "@cosmicdrift/kumiko-framework": "0.341.0",
41
- "@cosmicdrift/kumiko-guards": "0.341.0",
42
- "@cosmicdrift/kumiko-repo-manifest": "0.341.0",
43
- "@cosmicdrift/kumiko-testing": "0.341.0",
38
+ "@cosmicdrift/kumiko-bundled-features": "0.342.0",
39
+ "@cosmicdrift/kumiko-dev-server": "0.342.0",
40
+ "@cosmicdrift/kumiko-framework": "0.342.0",
41
+ "@cosmicdrift/kumiko-guards": "0.342.0",
42
+ "@cosmicdrift/kumiko-repo-manifest": "0.342.0",
43
+ "@cosmicdrift/kumiko-testing": "0.342.0",
44
44
  "zod": "^4.4.3"
45
45
  },
46
46
  "publishConfig": {
package/src/changes.json CHANGED
@@ -1,4 +1,11 @@
1
1
  [
2
+ {
3
+ "version": "0.342.0",
4
+ "type": "breaking",
5
+ "title": "kumiko check requires uiRoots for kind \"app\"",
6
+ "detail": "`kumiko check` fails a kind \"app\" without uiRoots\nA repo with `kind: \"app\"` and no `uiRoots` used to skip the UI guards silently. `kumiko check` (also with `--explain`) now prints an error and exits 1 before any step runs. Declare `uiRoots`, or set `uiRoots: []` for an app without UI; the step list then shows that the UI guards are skipped on purpose. A repo that relies on the derived manifest also gets the hint to add a `kumiko.json`. Other kinds are unchanged. `kumiko new app` now writes a `kumiko.json` with `uiRoots: [\"src/features/*/web\"]`.",
7
+ "migration": "Add `uiRoots` to the `kumiko.json` of every `kind: \"app\"` repo, for example `[\"src/app\", \"src/features/*/web\"]`, or `[]` for an app without UI. A repo without a `kumiko.json` needs one first, since the derived manifest cannot declare uiRoots.\nWorkspace state at release: kumiko-platform, offlot-app, phronexsis and show-pony declare `kind: \"app\"` without `uiRoots`."
8
+ },
2
9
  {
3
10
  "version": "0.337.0",
4
11
  "type": "fix",
@@ -20,10 +20,31 @@ export type RepoFacts = {
20
20
  /** A `kind: "app"` repo without one is an app by role only (an Astro site, a
21
21
  * docs repo) — there is no composed feature set for validateBoot to check. */
22
22
  readonly hasAppSchema: boolean;
23
+ readonly manifestSource: "file" | "derived";
23
24
  };
24
25
 
25
- export function resolveCheckSteps(manifest: RepoManifest, facts: RepoFacts): readonly CheckStep[] {
26
- if (manifest.kind === "tooling") return [];
26
+ export type CheckPlan =
27
+ | {
28
+ readonly kind: "steps";
29
+ readonly steps: readonly CheckStep[];
30
+ readonly waivers: readonly string[];
31
+ }
32
+ | { readonly kind: "error"; readonly message: string };
33
+
34
+ export function resolveCheckSteps(manifest: RepoManifest, facts: RepoFacts): CheckPlan {
35
+ if (manifest.kind === "tooling") return { kind: "steps", steps: [], waivers: [] };
36
+ if (manifest.kind === "app" && manifest.uiRoots === undefined) {
37
+ // A silent skip would let an app with UI pass without the UI guards ever running.
38
+ const derivedHint = facts.manifestSource === "derived" ? ` Add a ${REPO_MANIFEST_FILE}.` : "";
39
+ return {
40
+ kind: "error",
41
+ message:
42
+ `kind "app" must declare uiRoots in ${REPO_MANIFEST_FILE}: list the UI directories ` +
43
+ '(e.g. ["src/app", "src/features/*/web"]) or set uiRoots: [] for an app without UI.' +
44
+ derivedHint,
45
+ };
46
+ }
47
+ const waivers: string[] = [];
27
48
  const steps: CheckStep[] = [];
28
49
  if (manifest.kind === "app" && facts.hasAppSchema) {
29
50
  steps.push({
@@ -44,12 +65,15 @@ export function resolveCheckSteps(manifest: RepoManifest, facts: RepoFacts): rea
44
65
  why: `uiRoots ${manifest.uiRoots.join(", ")}`,
45
66
  });
46
67
  }
68
+ if (manifest.kind === "app" && manifest.uiRoots?.length === 0) {
69
+ waivers.push("UI guards skipped on purpose: uiRoots is [] (app without UI)");
70
+ }
47
71
  steps.push({
48
72
  id: "checks",
49
73
  label: "Repo checks",
50
74
  why: `kind "${manifest.kind}" — testGlobs ${manifest.testGlobs.join(", ")}`,
51
75
  });
52
- return steps;
76
+ return { kind: "steps", steps, waivers };
53
77
  }
54
78
 
55
79
  export type GuardsCli = Pick<
@@ -124,17 +148,8 @@ export async function runCheck(
124
148
  return 1;
125
149
  }
126
150
 
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("");
151
+ const steps = printCheckPlan(ctx, repo);
152
+ if (steps === undefined) return 1;
138
153
 
139
154
  if (steps.length === 0) {
140
155
  ctx.out.log(` Nothing to check: kind "${repo.manifest.kind}" declares no product code.`);
@@ -155,6 +170,32 @@ export async function runCheck(
155
170
  return failed > 0 ? 1 : 0;
156
171
  }
157
172
 
173
+ type LocalRepo = NonNullable<ReturnType<GuardsCli["findLocalRepo"]>>;
174
+
175
+ /** Returns undefined when the manifest cannot be checked; the reason is already printed. */
176
+ function printCheckPlan(ctx: CliCommandContext, repo: LocalRepo): readonly CheckStep[] | undefined {
177
+ const plan = resolveCheckSteps(repo.manifest, {
178
+ hasAppSchema: existsSync(join(repo.absPath, APP_SCHEMA_FILE)),
179
+ manifestSource: repo.manifestSource,
180
+ });
181
+ if (plan.kind === "error") {
182
+ ctx.out.err("");
183
+ ctx.out.err(` ✗ ${repo.name}: ${plan.message}`);
184
+ ctx.out.err("");
185
+ return undefined;
186
+ }
187
+ ctx.out.log("");
188
+ ctx.out.log(
189
+ ` ${repo.name} — kind "${repo.manifest.kind}", ${plan.steps.length} step(s) from ${REPO_MANIFEST_FILE}`,
190
+ );
191
+ for (const step of plan.steps) {
192
+ ctx.out.log(` ${step.id.padEnd(8)}${step.label} — ${step.why}`);
193
+ }
194
+ for (const waiver of plan.waivers) ctx.out.log(` ${"ui".padEnd(8)}${waiver}`);
195
+ ctx.out.log("");
196
+ return plan.steps;
197
+ }
198
+
158
199
  async function runStepReportingThrow(
159
200
  step: { readonly id: CheckStepId; readonly label: string },
160
201
  ctx: CliCommandContext,
@@ -201,6 +242,7 @@ export const checkCommand: CliCommand = {
201
242
  ` boot kind "app" + ${APP_SCHEMA_FILE} validateBoot over its composed FEATURES`,
202
243
  ' guards kind != "tooling" AST guards over sourceRoots',
203
244
  " ui uiRoots declared UI guards (App-Mounting 2.0)",
245
+ ' kind "app" must declare uiRoots; uiRoots: [] skips the UI guards on purpose',
204
246
  ' checks kind != "tooling" repo checks over sourceRoots + testGlobs',
205
247
  "",
206
248
  "Flags:",