@cosmicdrift/kumiko-cli 0.333.0 → 0.335.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.333.0",
3
+ "version": "0.335.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.333.0",
39
- "@cosmicdrift/kumiko-dev-server": "0.333.0",
40
- "@cosmicdrift/kumiko-framework": "0.333.0",
41
- "@cosmicdrift/kumiko-guards": "0.333.0",
42
- "@cosmicdrift/kumiko-repo-manifest": "0.333.0",
43
- "@cosmicdrift/kumiko-testing": "0.333.0",
38
+ "@cosmicdrift/kumiko-bundled-features": "0.335.0",
39
+ "@cosmicdrift/kumiko-dev-server": "0.335.0",
40
+ "@cosmicdrift/kumiko-framework": "0.335.0",
41
+ "@cosmicdrift/kumiko-guards": "0.335.0",
42
+ "@cosmicdrift/kumiko-repo-manifest": "0.335.0",
43
+ "@cosmicdrift/kumiko-testing": "0.335.0",
44
44
  "zod": "^4.4.3"
45
45
  },
46
46
  "publishConfig": {
package/src/changes.json CHANGED
@@ -1,4 +1,16 @@
1
1
  [
2
+ {
3
+ "version": "0.335.0",
4
+ "type": "fix",
5
+ "title": "kumiko check runs boot validation against the repo root when started from a subdirectory",
6
+ "detail": "`kumiko check` now runs the boot validation against the repo root instead of the current directory, so running it from a subdirectory no longer reports a missing `kumiko/schema.ts`."
7
+ },
8
+ {
9
+ "version": "0.335.0",
10
+ "type": "fix",
11
+ "title": "kumiko check survives throwing steps, config loading validates its shape",
12
+ "detail": "`kumiko check` counts a step that throws as a failure and still runs the remaining steps, and `--explain` now returns the guards' exit code. The `agent`, `consumer` and `project` commands report a readable message when `kumiko.config.ts` has no default export with a `features` array instead of crashing. Invalid-manifest errors name the root as `<root>` instead of an empty path."
13
+ },
2
14
  {
3
15
  "version": "0.292.0",
4
16
  "type": "improvement",
@@ -1,4 +1,5 @@
1
1
  import { join } from "node:path";
2
+ import { loadAppConfig } from "./load-app-config";
2
3
  import type { CliCommand } from "./types";
3
4
 
4
5
  const LINT_SUBCOMMAND = "lint";
@@ -29,9 +30,8 @@ export const agentCommand: CliCommand = {
29
30
  return 1;
30
31
  }
31
32
 
32
- const config = (await import(configPath)).default as {
33
- features: readonly import("@cosmicdrift/kumiko-framework/engine").FeatureDefinition[];
34
- };
33
+ const config = await loadAppConfig(ctx, configPath);
34
+ if (config === null) return 1;
35
35
  const { findAgentDocGaps, formatAgentDocGap } = await import(
36
36
  "@cosmicdrift/kumiko-bundled-features/agent-tools"
37
37
  );
@@ -144,28 +144,44 @@ export async function runCheck(
144
144
 
145
145
  if (ctx.argv.includes("--explain")) {
146
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;
147
+ return steps.some((step) => step.id === "guards") ? guards.runGuardsCli(["--explain"]) : 0;
149
148
  }
150
149
 
151
150
  let failed = 0;
152
151
  for (const step of steps) {
153
152
  ctx.out.log(` ▸ ${step.label}`);
154
- failed += await runStep(step.id, ctx, guards, deps);
153
+ failed += await runStepReportingThrow(step, ctx, repo.absPath, guards, deps);
155
154
  }
156
155
  return failed > 0 ? 1 : 0;
157
156
  }
158
157
 
158
+ async function runStepReportingThrow(
159
+ step: { readonly id: CheckStepId; readonly label: string },
160
+ ctx: CliCommandContext,
161
+ repoRoot: string,
162
+ guards: GuardsCli,
163
+ deps: CheckDeps,
164
+ ): Promise<number> {
165
+ try {
166
+ return await runStep(step.id, ctx, repoRoot, guards, deps);
167
+ } catch (e) {
168
+ ctx.out.err(` ✗ ${step.label}: ${e instanceof Error ? e.message : String(e)}`);
169
+ return 1;
170
+ }
171
+ }
172
+
159
173
  async function runStep(
160
174
  id: CheckStepId,
161
175
  ctx: CliCommandContext,
176
+ repoRoot: string,
162
177
  guards: GuardsCli,
163
178
  deps: CheckDeps,
164
179
  ): Promise<number> {
165
180
  switch (id) {
166
181
  case "boot": {
167
182
  const { runSchemaCli } = await deps.loadSchemaCli();
168
- return await runSchemaCli(["validate"], ctx.cwd, ctx.out);
183
+ // findLocalRepo walks upwards, so ctx.cwd may be a subdirectory without kumiko/schema.ts.
184
+ return await runSchemaCli(["validate"], repoRoot, ctx.out);
169
185
  }
170
186
  case "guards":
171
187
  return guards.runGuardsCli([]);
@@ -1,4 +1,5 @@
1
1
  import { join } from "node:path";
2
+ import { loadAppConfig } from "./load-app-config";
2
3
  import type { CliCommand, CliCommandContext } from "./types";
3
4
 
4
5
  export const consumerCommand: CliCommand = {
@@ -25,9 +26,8 @@ export const consumerCommand: CliCommand = {
25
26
  return 1;
26
27
  }
27
28
 
28
- const config = (await import(configPath)).default as {
29
- features: readonly import("@cosmicdrift/kumiko-framework/engine").FeatureDefinition[];
30
- };
29
+ const config = await loadAppConfig(ctx, configPath);
30
+ if (config === null) return 1;
31
31
  const { createRegistry } = await import("@cosmicdrift/kumiko-framework/engine");
32
32
  const { createDbConnection } = await import("@cosmicdrift/kumiko-framework/db");
33
33
  const {
@@ -0,0 +1,32 @@
1
+ import type { FeatureDefinition } from "@cosmicdrift/kumiko-framework/engine";
2
+ import type { CliCommandContext } from "./types";
3
+
4
+ export type AppConfig = { readonly features: readonly FeatureDefinition[] };
5
+
6
+ function isAppConfig(value: unknown): value is AppConfig {
7
+ return (
8
+ typeof value === "object" &&
9
+ value !== null &&
10
+ "features" in value &&
11
+ Array.isArray(value.features)
12
+ );
13
+ }
14
+
15
+ // kumiko.config.ts is user-authored: a missing default export or `features`
16
+ // array must produce a readable message, not a TypeError further down.
17
+ export async function loadAppConfig(
18
+ ctx: CliCommandContext,
19
+ configPath: string,
20
+ ): Promise<AppConfig | null> {
21
+ const mod: unknown = await import(configPath);
22
+ const config =
23
+ typeof mod === "object" && mod !== null && "default" in mod ? mod.default : undefined;
24
+ if (!isAppConfig(config)) {
25
+ ctx.out.err("");
26
+ ctx.out.err(` ${configPath} must default-export an object with a "features" array:`);
27
+ ctx.out.err(" export default { features: [myFeature] };");
28
+ ctx.out.err("");
29
+ return null;
30
+ }
31
+ return config;
32
+ }
@@ -1,4 +1,5 @@
1
1
  import { join } from "node:path";
2
+ import { loadAppConfig } from "./load-app-config";
2
3
  import type { CliCommand, CliCommandContext } from "./types";
3
4
 
4
5
  export const projectCommand: CliCommand = {
@@ -30,9 +31,8 @@ export const projectCommand: CliCommand = {
30
31
  return 1;
31
32
  }
32
33
 
33
- const config = (await import(configPath)).default as {
34
- features: readonly import("@cosmicdrift/kumiko-framework/engine").FeatureDefinition[];
35
- };
34
+ const config = await loadAppConfig(ctx, configPath);
35
+ if (config === null) return 1;
36
36
  const { createRegistry } = await import("@cosmicdrift/kumiko-framework/engine");
37
37
  const { createDbConnection } = await import("@cosmicdrift/kumiko-framework/db");
38
38
  const {