@cosmicdrift/kumiko-cli 0.410.0 → 0.412.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 +12 -12
- package/src/commands/api-docs.ts +321 -0
- package/src/commands/index.ts +3 -0
- package/src/commands/load-app-config.ts +3 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cosmicdrift/kumiko-cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.412.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; `handbuch build` renders the HTML handbuch.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "Marc Frost <marc@cosmicdriftgamestudio.com>",
|
|
@@ -36,19 +36,19 @@
|
|
|
36
36
|
}
|
|
37
37
|
},
|
|
38
38
|
"dependencies": {
|
|
39
|
-
"@cosmicdrift/kumiko-dev-server": "0.
|
|
40
|
-
"@cosmicdrift/kumiko-feature-ast": "0.
|
|
41
|
-
"@cosmicdrift/kumiko-guards": "0.
|
|
42
|
-
"@cosmicdrift/kumiko-server-runtime": "0.
|
|
43
|
-
"@cosmicdrift/kumiko-testing": "0.
|
|
39
|
+
"@cosmicdrift/kumiko-dev-server": "0.412.0",
|
|
40
|
+
"@cosmicdrift/kumiko-feature-ast": "0.412.0",
|
|
41
|
+
"@cosmicdrift/kumiko-guards": "0.412.0",
|
|
42
|
+
"@cosmicdrift/kumiko-server-runtime": "0.412.0",
|
|
43
|
+
"@cosmicdrift/kumiko-testing": "0.412.0",
|
|
44
44
|
"marked": "^18.1.2",
|
|
45
45
|
"zod": "^4.6.7"
|
|
46
46
|
},
|
|
47
47
|
"peerDependencies": {
|
|
48
48
|
"sharp": "^0.35.5",
|
|
49
|
-
"@cosmicdrift/kumiko-bundled-features": "0.
|
|
50
|
-
"@cosmicdrift/kumiko-framework": "0.
|
|
51
|
-
"@cosmicdrift/kumiko-renderer": "0.
|
|
49
|
+
"@cosmicdrift/kumiko-bundled-features": "0.412.0",
|
|
50
|
+
"@cosmicdrift/kumiko-framework": "0.412.0",
|
|
51
|
+
"@cosmicdrift/kumiko-renderer": "0.412.0"
|
|
52
52
|
},
|
|
53
53
|
"peerDependenciesMeta": {
|
|
54
54
|
"sharp": {
|
|
@@ -65,9 +65,9 @@
|
|
|
65
65
|
}
|
|
66
66
|
},
|
|
67
67
|
"devDependencies": {
|
|
68
|
-
"@cosmicdrift/kumiko-bundled-features": "0.
|
|
69
|
-
"@cosmicdrift/kumiko-framework": "0.
|
|
70
|
-
"@cosmicdrift/kumiko-renderer": "0.
|
|
68
|
+
"@cosmicdrift/kumiko-bundled-features": "0.412.0",
|
|
69
|
+
"@cosmicdrift/kumiko-framework": "0.412.0",
|
|
70
|
+
"@cosmicdrift/kumiko-renderer": "0.412.0",
|
|
71
71
|
"sharp": "^0.35.5"
|
|
72
72
|
},
|
|
73
73
|
"publishConfig": {
|
|
@@ -0,0 +1,321 @@
|
|
|
1
|
+
import {
|
|
2
|
+
existsSync,
|
|
3
|
+
mkdirSync,
|
|
4
|
+
readdirSync,
|
|
5
|
+
readFileSync,
|
|
6
|
+
rmSync,
|
|
7
|
+
statSync,
|
|
8
|
+
writeFileSync,
|
|
9
|
+
} from "node:fs";
|
|
10
|
+
import { basename, dirname, isAbsolute, join, relative, resolve } from "node:path";
|
|
11
|
+
import { parseArgs } from "node:util";
|
|
12
|
+
import { type AppApiDocsConfig, allAppFeatures } from "@cosmicdrift/kumiko-framework/config";
|
|
13
|
+
import * as z from "zod";
|
|
14
|
+
import { loadAppConfig } from "./load-app-config";
|
|
15
|
+
import type { CliCommand, CliCommandContext } from "./types";
|
|
16
|
+
|
|
17
|
+
const USAGE = "Usage: kumiko api-docs [--out <dir>] [--locales en,de] [--check]";
|
|
18
|
+
const DEFAULT_OUT_DIR = "api-docs";
|
|
19
|
+
const DEFAULT_EXAMPLES_DIR = "api-examples";
|
|
20
|
+
const INDEX_FILE = "index.json";
|
|
21
|
+
const WEBHOOK_EXAMPLES_SUBDIR = "webhooks";
|
|
22
|
+
const WEBHOOK_EXAMPLES_PREFIX = `${WEBHOOK_EXAMPLES_SUBDIR}/`;
|
|
23
|
+
|
|
24
|
+
const OPTIONS = {
|
|
25
|
+
out: { type: "string" },
|
|
26
|
+
locales: { type: "string" },
|
|
27
|
+
check: { type: "boolean" },
|
|
28
|
+
} as const;
|
|
29
|
+
|
|
30
|
+
const IndexFileSchema = z.object({ pages: z.array(z.object({ path: z.string() })) });
|
|
31
|
+
const PackageJsonSchema = z.object({ name: z.string().optional(), version: z.string().optional() });
|
|
32
|
+
|
|
33
|
+
function usageError(ctx: CliCommandContext, message: string): number {
|
|
34
|
+
ctx.out.err("");
|
|
35
|
+
ctx.out.err(` ${message}`);
|
|
36
|
+
ctx.out.err(` ${USAGE}`);
|
|
37
|
+
ctx.out.err("");
|
|
38
|
+
return 2;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function isInside(parentDir: string, candidate: string): boolean {
|
|
42
|
+
const rel = relative(parentDir, candidate);
|
|
43
|
+
return rel !== "" && !rel.startsWith("..") && !isAbsolute(rel);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function readJson(path: string): unknown {
|
|
47
|
+
try {
|
|
48
|
+
return JSON.parse(readFileSync(path, "utf8"));
|
|
49
|
+
} catch (error) {
|
|
50
|
+
throw new Error(`${path}: ${error instanceof Error ? error.message : String(error)}`);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/** Slash-separated paths of all files under `dir`; dotfiles (.DS_Store, .git) are not ours. */
|
|
55
|
+
function listFiles(dir: string, prefix = ""): string[] {
|
|
56
|
+
return readdirSync(dir, { withFileTypes: true }).flatMap((entry) => {
|
|
57
|
+
if (entry.name.startsWith(".")) return [];
|
|
58
|
+
const path = prefix === "" ? entry.name : `${prefix}/${entry.name}`;
|
|
59
|
+
return entry.isDirectory() ? listFiles(join(dir, entry.name), path) : [path];
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function readOutDir(outDir: string): Map<string, string> {
|
|
64
|
+
if (!existsSync(outDir)) return new Map();
|
|
65
|
+
return new Map(listFiles(outDir).map((path) => [path, readFileSync(join(outDir, path), "utf8")]));
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/** The bundled-features package exposes no package.json entry, so walk up from a resolvable one. */
|
|
69
|
+
function frameworkVersion(): string {
|
|
70
|
+
let dir = dirname(
|
|
71
|
+
Bun.resolveSync("@cosmicdrift/kumiko-bundled-features/agent-tools", import.meta.dir),
|
|
72
|
+
);
|
|
73
|
+
for (;;) {
|
|
74
|
+
const candidate = join(dir, "package.json");
|
|
75
|
+
if (existsSync(candidate)) {
|
|
76
|
+
const parsed = PackageJsonSchema.parse(readJson(candidate));
|
|
77
|
+
if (parsed.name === "@cosmicdrift/kumiko-bundled-features" && parsed.version !== undefined) {
|
|
78
|
+
return parsed.version;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
const parent = dirname(dir);
|
|
82
|
+
if (parent === dir) throw new Error("Cannot determine the kumiko-bundled-features version.");
|
|
83
|
+
dir = parent;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function appSource(cwd: string): { name: string; version: string } {
|
|
88
|
+
const path = join(cwd, "package.json");
|
|
89
|
+
const parsed = existsSync(path) ? PackageJsonSchema.parse(readJson(path)) : {};
|
|
90
|
+
return { name: parsed.name ?? basename(cwd), version: parsed.version ?? "0.0.0" };
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/** Files the previous run listed in its index.json, so stale pages can go without touching foreign files. */
|
|
94
|
+
function previouslyGenerated(outDir: string): string[] {
|
|
95
|
+
const indexPath = join(outDir, INDEX_FILE);
|
|
96
|
+
if (!existsSync(indexPath)) return [];
|
|
97
|
+
try {
|
|
98
|
+
const parsed = IndexFileSchema.safeParse(readJson(indexPath));
|
|
99
|
+
return parsed.success
|
|
100
|
+
? [INDEX_FILE, ...parsed.data.pages.map((page) => page.path)]
|
|
101
|
+
: [INDEX_FILE];
|
|
102
|
+
} catch {
|
|
103
|
+
// skip: an unreadable index.json is rewritten below, so there is nothing stale to learn from it
|
|
104
|
+
return [INDEX_FILE];
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/** `undefined` after reporting the first recording that cannot be read or does not match the schema. */
|
|
109
|
+
function readExamples<T>(
|
|
110
|
+
ctx: CliCommandContext,
|
|
111
|
+
dir: string,
|
|
112
|
+
schema: z.ZodType<T>,
|
|
113
|
+
include: (fileName: string) => boolean = () => true,
|
|
114
|
+
): { fileName: string; file: T }[] | undefined {
|
|
115
|
+
if (!existsSync(dir)) return [];
|
|
116
|
+
const examples: { fileName: string; file: T }[] = [];
|
|
117
|
+
for (const fileName of listFiles(dir)
|
|
118
|
+
.filter((name) => name.endsWith(".json") && include(name))
|
|
119
|
+
.sort()) {
|
|
120
|
+
try {
|
|
121
|
+
const result = schema.safeParse(readJson(join(dir, fileName)));
|
|
122
|
+
if (!result.success) {
|
|
123
|
+
const issues = result.error.issues.map(
|
|
124
|
+
(i) => `${i.path.join(".") || "(root)"}: ${i.message}`,
|
|
125
|
+
);
|
|
126
|
+
ctx.out.err(` ✗ ${fileName}: ${issues.join("; ")}`);
|
|
127
|
+
return undefined;
|
|
128
|
+
}
|
|
129
|
+
examples.push({ fileName, file: result.data });
|
|
130
|
+
} catch (error) {
|
|
131
|
+
ctx.out.err(` ✗ ${error instanceof Error ? error.message : String(error)}`);
|
|
132
|
+
return undefined;
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
return examples;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
function writeOutput(outDir: string, files: ReadonlyMap<string, string>): void {
|
|
139
|
+
for (const stale of previouslyGenerated(outDir)) {
|
|
140
|
+
const target = resolve(outDir, stale);
|
|
141
|
+
if (
|
|
142
|
+
!files.has(stale) &&
|
|
143
|
+
isInside(outDir, target) &&
|
|
144
|
+
existsSync(target) &&
|
|
145
|
+
statSync(target).isFile()
|
|
146
|
+
) {
|
|
147
|
+
rmSync(target);
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
for (const [path, content] of files) {
|
|
151
|
+
const target = join(outDir, path);
|
|
152
|
+
mkdirSync(dirname(target), { recursive: true });
|
|
153
|
+
writeFileSync(target, content);
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
type Values = { out?: string; locales?: string; check?: boolean };
|
|
158
|
+
type AgentTools = typeof import("@cosmicdrift/kumiko-bundled-features/agent-tools");
|
|
159
|
+
|
|
160
|
+
function errorBlock(ctx: CliCommandContext, message: string): number {
|
|
161
|
+
ctx.out.err("");
|
|
162
|
+
ctx.out.err(` ${message}`);
|
|
163
|
+
ctx.out.err("");
|
|
164
|
+
return 1;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
/** A usage message, or undefined when the locales are fine. */
|
|
168
|
+
function localeProblem(locales: readonly string[], tools: AgentTools): string | undefined {
|
|
169
|
+
const unsupported = locales.filter((locale) => !tools.isApiDocsLocale(locale));
|
|
170
|
+
if (locales.length > 0 && unsupported.length === 0 && new Set(locales).size === locales.length) {
|
|
171
|
+
return undefined;
|
|
172
|
+
}
|
|
173
|
+
const detail = unsupported.length > 0 ? `, got unsupported: ${unsupported.join(", ")}` : "";
|
|
174
|
+
return `--locales needs unique values out of ${tools.apiDocsLocales.join(", ")}${detail}.`;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
function report(
|
|
178
|
+
ctx: CliCommandContext,
|
|
179
|
+
tools: AgentTools,
|
|
180
|
+
generated: ReturnType<AgentTools["generateApiDocs"]>,
|
|
181
|
+
target: { outDir: string; onDisk: ReadonlyMap<string, string>; check: boolean },
|
|
182
|
+
): number {
|
|
183
|
+
const { files, problems } = generated;
|
|
184
|
+
const problemLines = problems.map(tools.formatApiDocsProblem);
|
|
185
|
+
if (target.check) {
|
|
186
|
+
const diff = tools.diffApiDocsOutput(files, target.onDisk);
|
|
187
|
+
const lines = [
|
|
188
|
+
...problemLines,
|
|
189
|
+
...diff.missing.map((path) => `[missing] ${path}`),
|
|
190
|
+
...diff.changed.map((path) => `[changed] ${path}`),
|
|
191
|
+
...diff.extra.map((path) => `[extra] ${path}`),
|
|
192
|
+
];
|
|
193
|
+
for (const line of lines) ctx.out.log(` ${line}`);
|
|
194
|
+
if (lines.length === 0) ctx.out.log(" ✓ API docs are up to date.");
|
|
195
|
+
return lines.length === 0 ? 0 : 1;
|
|
196
|
+
}
|
|
197
|
+
if (problems.some((problem) => problem.kind === "patFeatureMissing")) {
|
|
198
|
+
for (const line of problemLines) ctx.out.err(` ${line}`);
|
|
199
|
+
return 1;
|
|
200
|
+
}
|
|
201
|
+
writeOutput(target.outDir, files);
|
|
202
|
+
for (const line of problemLines) ctx.out.log(` warning ${line}`);
|
|
203
|
+
ctx.out.log(` ✓ ${files.size} files written to ${target.outDir}`);
|
|
204
|
+
return 0;
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
type Target = {
|
|
208
|
+
locales: readonly string[];
|
|
209
|
+
baseUrl: string;
|
|
210
|
+
outDir: string;
|
|
211
|
+
onDisk: Map<string, string>;
|
|
212
|
+
check: boolean;
|
|
213
|
+
};
|
|
214
|
+
|
|
215
|
+
/** The settings of this run, or the exit code after reporting what is wrong. */
|
|
216
|
+
function resolveTarget(
|
|
217
|
+
ctx: CliCommandContext,
|
|
218
|
+
tools: AgentTools,
|
|
219
|
+
apiDocs: AppApiDocsConfig,
|
|
220
|
+
values: Values,
|
|
221
|
+
): Target | number {
|
|
222
|
+
const locales = values.locales?.split(",").map((locale) => locale.trim()) ??
|
|
223
|
+
apiDocs.locales ?? ["en"];
|
|
224
|
+
const badLocales = localeProblem(locales, tools);
|
|
225
|
+
if (badLocales !== undefined) return usageError(ctx, badLocales);
|
|
226
|
+
if (apiDocs.baseUrl === undefined) {
|
|
227
|
+
return errorBlock(
|
|
228
|
+
ctx,
|
|
229
|
+
'apiDocs.baseUrl is missing in kumiko.config.ts, e.g. apiDocs: { baseUrl: "https://api.example.com" }.',
|
|
230
|
+
);
|
|
231
|
+
}
|
|
232
|
+
const outDir = resolve(ctx.cwd, values.out ?? apiDocs.outDir ?? DEFAULT_OUT_DIR);
|
|
233
|
+
if (!isInside(ctx.cwd, outDir)) {
|
|
234
|
+
return usageError(
|
|
235
|
+
ctx,
|
|
236
|
+
`Refusing to write into ${outDir}: it must be a directory inside the working directory.`,
|
|
237
|
+
);
|
|
238
|
+
}
|
|
239
|
+
const onDisk = readOutDir(outDir);
|
|
240
|
+
const check = values.check === true;
|
|
241
|
+
if (!check && onDisk.size > 0 && !onDisk.has(INDEX_FILE)) {
|
|
242
|
+
return errorBlock(
|
|
243
|
+
ctx,
|
|
244
|
+
`${outDir} is not empty and holds no ${INDEX_FILE}, so it was not generated by this command. Pick another --out.`,
|
|
245
|
+
);
|
|
246
|
+
}
|
|
247
|
+
return { locales, baseUrl: apiDocs.baseUrl, outDir, onDisk, check };
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
async function generate(
|
|
251
|
+
ctx: CliCommandContext,
|
|
252
|
+
config: NonNullable<Awaited<ReturnType<typeof loadAppConfig>>>,
|
|
253
|
+
values: Values,
|
|
254
|
+
): Promise<number> {
|
|
255
|
+
const tools = await import("@cosmicdrift/kumiko-bundled-features/agent-tools");
|
|
256
|
+
const { createRegistry } = await import("@cosmicdrift/kumiko-framework/engine");
|
|
257
|
+
const { composeFeatures } = await import("@cosmicdrift/kumiko-server-runtime");
|
|
258
|
+
|
|
259
|
+
const apiDocs = config.apiDocs ?? {};
|
|
260
|
+
const target = resolveTarget(ctx, tools, apiDocs, values);
|
|
261
|
+
if (typeof target === "number") return target;
|
|
262
|
+
const { locales, baseUrl, outDir, onDisk, check } = target;
|
|
263
|
+
|
|
264
|
+
const examplesDir = resolve(ctx.cwd, apiDocs.examplesDir ?? DEFAULT_EXAMPLES_DIR);
|
|
265
|
+
const examples = readExamples(
|
|
266
|
+
ctx,
|
|
267
|
+
examplesDir,
|
|
268
|
+
tools.ApiExampleFileSchema,
|
|
269
|
+
(fileName) => !fileName.startsWith(WEBHOOK_EXAMPLES_PREFIX),
|
|
270
|
+
);
|
|
271
|
+
if (examples === undefined) return 1;
|
|
272
|
+
const webhookExamples = readExamples(
|
|
273
|
+
ctx,
|
|
274
|
+
join(examplesDir, WEBHOOK_EXAMPLES_SUBDIR),
|
|
275
|
+
tools.WebhookExampleFileSchema,
|
|
276
|
+
);
|
|
277
|
+
if (webhookExamples === undefined) return 1;
|
|
278
|
+
|
|
279
|
+
try {
|
|
280
|
+
const generated = tools.generateApiDocs({
|
|
281
|
+
// The bundled foundation (user, tenant, ...) that the token feature requires is only mounted with includeBundled.
|
|
282
|
+
registry: createRegistry(
|
|
283
|
+
composeFeatures(allAppFeatures(config), { includeBundled: config.includeBundled === true }),
|
|
284
|
+
),
|
|
285
|
+
config: {
|
|
286
|
+
...apiDocs,
|
|
287
|
+
locales,
|
|
288
|
+
baseUrl,
|
|
289
|
+
appName: apiDocs.appName ?? appSource(ctx.cwd).name,
|
|
290
|
+
},
|
|
291
|
+
source: { framework: frameworkVersion(), app: appSource(ctx.cwd) },
|
|
292
|
+
examples,
|
|
293
|
+
webhookExamples,
|
|
294
|
+
});
|
|
295
|
+
return report(ctx, tools, generated, { outDir, onDisk, check });
|
|
296
|
+
} catch (error) {
|
|
297
|
+
return errorBlock(ctx, `✗ ${error instanceof Error ? error.message : String(error)}`);
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
async function run(ctx: CliCommandContext): Promise<number> {
|
|
302
|
+
let values: Values;
|
|
303
|
+
try {
|
|
304
|
+
values = parseArgs({ args: [...ctx.argv], options: OPTIONS }).values;
|
|
305
|
+
} catch (error) {
|
|
306
|
+
return usageError(ctx, error instanceof Error ? error.message : String(error));
|
|
307
|
+
}
|
|
308
|
+
const configPath = join(ctx.cwd, "kumiko.config.ts");
|
|
309
|
+
if (!(await Bun.file(configPath).exists())) {
|
|
310
|
+
return errorBlock(ctx, `kumiko.config.ts not found at: ${configPath}`);
|
|
311
|
+
}
|
|
312
|
+
const config = await loadAppConfig(ctx, configPath);
|
|
313
|
+
return config === null ? 1 : generate(ctx, config, values);
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
export const apiDocsCommand: CliCommand = {
|
|
317
|
+
id: "api-docs",
|
|
318
|
+
description: "Generate API documentation for personal access tokens",
|
|
319
|
+
help: `${USAGE}\n\nReads kumiko.config.ts in cwd and writes Markdown pages (overview, tokens,\none reference page per token scope), openapi.json and index.json to --out\n(default: apiDocs.outDir, else ${DEFAULT_OUT_DIR}). Recorded request/response examples are read\nfrom apiDocs.examplesDir (default ${DEFAULT_EXAMPLES_DIR}), recorded webhook payloads from its webhooks/ subdirectory.\n\nOptions:\n --out <dir> Output directory inside the working directory\n --locales en,de Locales to generate (en, de, es, fr, it)\n --check Write nothing; report problems and differences to the disk, exit 1 on any\n`,
|
|
320
|
+
run,
|
|
321
|
+
};
|
package/src/commands/index.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { agentCommand } from "./agent";
|
|
2
|
+
import { apiDocsCommand } from "./api-docs";
|
|
2
3
|
import { checkCommand } from "./check";
|
|
3
4
|
import { codemodCommand } from "./codemod";
|
|
4
5
|
import { consumerCommand } from "./consumer";
|
|
@@ -15,6 +16,7 @@ export const APP_COMMANDS: ReadonlyArray<CliCommand> = [
|
|
|
15
16
|
syncManagedCommand,
|
|
16
17
|
portsCommand,
|
|
17
18
|
agentCommand,
|
|
19
|
+
apiDocsCommand,
|
|
18
20
|
projectCommand,
|
|
19
21
|
consumerCommand,
|
|
20
22
|
handbuchCommand,
|
|
@@ -36,6 +38,7 @@ export {
|
|
|
36
38
|
export type { CliCommand, CliCommandContext } from "./types";
|
|
37
39
|
export {
|
|
38
40
|
agentCommand,
|
|
41
|
+
apiDocsCommand,
|
|
39
42
|
checkCommand,
|
|
40
43
|
codemodCommand,
|
|
41
44
|
consumerCommand,
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { AppApiDocsConfig } from "@cosmicdrift/kumiko-framework/config";
|
|
1
2
|
import type { FeatureDefinition } from "@cosmicdrift/kumiko-framework/engine";
|
|
2
3
|
import type { CliCommandContext } from "./types";
|
|
3
4
|
|
|
@@ -6,6 +7,8 @@ import type { CliCommandContext } from "./types";
|
|
|
6
7
|
export type AppConfig = {
|
|
7
8
|
readonly features: readonly FeatureDefinition[];
|
|
8
9
|
readonly prodFeatures?: readonly FeatureDefinition[];
|
|
10
|
+
readonly includeBundled?: boolean;
|
|
11
|
+
readonly apiDocs?: AppApiDocsConfig;
|
|
9
12
|
};
|
|
10
13
|
|
|
11
14
|
function isAppConfig(value: unknown): value is AppConfig {
|