argsbarg 6.0.1 → 6.1.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/CHANGELOG.md +26 -2
- package/README.md +4 -3
- package/docs/api-server.md +1 -1
- package/docs/cli-program.md +28 -0
- package/docs/config-schema.md +15 -13
- package/docs/output-schema.md +76 -73
- package/examples/full-example/README.md +7 -7
- package/examples/full-example/bun.lock +0 -29
- package/examples/full-example/justfile +6 -3
- package/examples/full-example/package.json +0 -2
- package/examples/full-example/src/commands/status/__generated__/index.ts +5 -0
- package/examples/full-example/src/commands/status/command.ts +2 -2
- package/examples/full-example/src/commands/status/types.ts +19 -1
- package/examples/full-example/src/config/__generated__/index.ts +5 -0
- package/examples/full-example/src/program.ts +4 -4
- package/index.d.ts +24 -3
- package/package.json +8 -1
- package/src/cli-tool/full-example-capabilities.test.ts +2 -1
- package/src/cli-tool/post-create.ts +3 -3
- package/src/cli-tool/program.ts +36 -0
- package/src/cli-tool/run-schemagen.ts +23 -0
- package/src/cli-tool/schemagen/cleanup.ts +60 -0
- package/src/cli-tool/schemagen/discover-schema-roots.ts +173 -0
- package/src/cli-tool/schemagen/index.ts +3 -0
- package/src/cli-tool/schemagen/names.ts +22 -0
- package/src/cli-tool/schemagen/run.ts +109 -0
- package/src/cli-tool/schemagen/schemagen.test.ts +143 -0
- package/src/context.ts +20 -2
- package/src/help.ts +2 -0
- package/src/index.ts +1 -0
- package/src/leaf-inputs.test.ts +170 -0
- package/src/leaf-inputs.ts +178 -0
- package/src/mcp/tools.ts +5 -0
- package/src/parse.ts +11 -0
- package/src/types.ts +8 -1
- package/src/validate.ts +43 -0
- package/examples/full-example/schemas/configSchemas.ts +0 -6
- package/examples/full-example/schemas/outputSchemas.ts +0 -6
- package/examples/full-example/scripts/schemagen/discover-schema-roots.test.ts +0 -25
- package/examples/full-example/scripts/schemagen/discover-schema-roots.ts +0 -148
- package/examples/full-example/scripts/schemagen/naming.ts +0 -99
- package/examples/full-example/scripts/schemagen.ts +0 -91
- package/examples/full-example/src/commands/status/schema-types.ts +0 -14
- /package/examples/full-example/{schemas/generated/status.json → src/commands/status/__generated__/outputSchema.json} +0 -0
- /package/examples/full-example/{schemas/generated/app-config.json → src/config/__generated__/configSchema.json} +0 -0
- /package/examples/full-example/src/config/{schema-types.ts → types.ts} +0 -0
package/src/validate.ts
CHANGED
|
@@ -247,6 +247,31 @@ function walkNode(node: CliNode, program: CliProgram, isRoot: boolean): void {
|
|
|
247
247
|
if (resolved !== undefined && (typeof resolved !== "object" || resolved === null || Array.isArray(resolved))) {
|
|
248
248
|
throw new CliSchemaValidationError("outputSchema must be a JSON Schema object (not null or an array)");
|
|
249
249
|
}
|
|
250
|
+
const inputSchema = node.inputSchema;
|
|
251
|
+
if (
|
|
252
|
+
inputSchema !== undefined &&
|
|
253
|
+
(typeof inputSchema !== "object" || inputSchema === null || Array.isArray(inputSchema))
|
|
254
|
+
) {
|
|
255
|
+
throw new CliSchemaValidationError("inputSchema must be a JSON Schema object (not null or an array)");
|
|
256
|
+
}
|
|
257
|
+
if (inputSchema !== undefined) {
|
|
258
|
+
const properties = inputSchema.properties;
|
|
259
|
+
if (
|
|
260
|
+
properties !== undefined &&
|
|
261
|
+
(typeof properties !== "object" || properties === null || Array.isArray(properties))
|
|
262
|
+
) {
|
|
263
|
+
throw new CliSchemaValidationError(`inputSchema.properties must be an object on ${node.key}`);
|
|
264
|
+
}
|
|
265
|
+
if (properties) {
|
|
266
|
+
for (const opt of node.options ?? []) {
|
|
267
|
+
if (opt.kind === CliOptionKind.Json && !(opt.name in properties)) {
|
|
268
|
+
throw new CliSchemaValidationError(
|
|
269
|
+
`Json option '${opt.name}' is missing from inputSchema.properties on ${node.key}`,
|
|
270
|
+
);
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
}
|
|
250
275
|
} else {
|
|
251
276
|
const rogue = node as unknown as CliLeaf;
|
|
252
277
|
if (rogue.mcpTool !== undefined) {
|
|
@@ -301,7 +326,22 @@ function walkNode(node: CliNode, program: CliProgram, isRoot: boolean): void {
|
|
|
301
326
|
|
|
302
327
|
function validateOptions(scopeKey: string, options: import("./types.ts").CliOption[]): void {
|
|
303
328
|
const seenShorts = new Set<string>();
|
|
329
|
+
let pipableCount = 0;
|
|
304
330
|
for (const opt of options) {
|
|
331
|
+
if (opt.pipable) {
|
|
332
|
+
pipableCount++;
|
|
333
|
+
if (opt.kind !== CliOptionKind.Json) {
|
|
334
|
+
throw new CliSchemaValidationError(`pipable is only valid on Json kind: ${scopeKey}/${opt.name}`);
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
if (opt.kind === CliOptionKind.Json) {
|
|
338
|
+
if (opt.format !== undefined || opt.pattern !== undefined || opt.default !== undefined) {
|
|
339
|
+
throw new CliSchemaValidationError(
|
|
340
|
+
`Json option cannot use format, pattern, or default: ${scopeKey}/${opt.name}`,
|
|
341
|
+
);
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
|
|
305
345
|
if (opt.required && opt.kind === CliOptionKind.Presence) {
|
|
306
346
|
throw new CliSchemaValidationError(`Presence option cannot be required: ${scopeKey}/${opt.name}`);
|
|
307
347
|
}
|
|
@@ -340,6 +380,9 @@ function validateOptions(scopeKey: string, options: import("./types.ts").CliOpti
|
|
|
340
380
|
validateOptionValueMetadata(scopeKey, opt);
|
|
341
381
|
}
|
|
342
382
|
}
|
|
383
|
+
if (pipableCount > 1) {
|
|
384
|
+
throw new CliSchemaValidationError(`At most one pipable Json option per command: ${scopeKey}`);
|
|
385
|
+
}
|
|
343
386
|
}
|
|
344
387
|
|
|
345
388
|
function validateOptionValueMetadata(scopeKey: string, opt: import("./types.ts").CliOption): void {
|
|
@@ -1,6 +0,0 @@
|
|
|
1
|
-
// Auto-generated by scripts/schemagen.ts — do not edit by hand.
|
|
2
|
-
|
|
3
|
-
import app_config from "./generated/app-config.json";
|
|
4
|
-
|
|
5
|
-
/** JSON Schema for program.appConfig.jsonSchema from `AppConfig`. */
|
|
6
|
-
export const APP_CONFIG_JSON_SCHEMA = app_config as Record<string, unknown>;
|
|
@@ -1,25 +0,0 @@
|
|
|
1
|
-
import { describe, expect, test } from "bun:test";
|
|
2
|
-
import { join } from "node:path";
|
|
3
|
-
import { discoverSchemaRoots } from "./discover-schema-roots.ts";
|
|
4
|
-
|
|
5
|
-
const projectRoot = join(import.meta.dir, "../..");
|
|
6
|
-
|
|
7
|
-
describe("discover-schema-roots", () => {
|
|
8
|
-
test("finds AppConfig and StatusJsonOutput in schema-types.ts files", () => {
|
|
9
|
-
const roots = discoverSchemaRoots(projectRoot);
|
|
10
|
-
const config = roots.find((r) => r.kind === "config");
|
|
11
|
-
const output = roots.find((r) => r.kind === "output");
|
|
12
|
-
expect(config).toMatchObject({
|
|
13
|
-
typeName: "AppConfig",
|
|
14
|
-
path: "src/config/schema-types.ts",
|
|
15
|
-
outfile: "app-config.json",
|
|
16
|
-
exportName: "APP_CONFIG_JSON_SCHEMA",
|
|
17
|
-
});
|
|
18
|
-
expect(output).toMatchObject({
|
|
19
|
-
typeName: "StatusJsonOutput",
|
|
20
|
-
path: "src/commands/status/schema-types.ts",
|
|
21
|
-
outfile: "status.json",
|
|
22
|
-
exportName: "STATUS_JSON_OUTPUT_SCHEMA",
|
|
23
|
-
});
|
|
24
|
-
});
|
|
25
|
-
});
|
|
@@ -1,148 +0,0 @@
|
|
|
1
|
-
/*
|
|
2
|
-
Discovers schema roots in schema-types.ts files via configType / inputType / outputType exports.
|
|
3
|
-
Copy per consumer repo — see docs/output-schema.md and docs/config-schema.md.
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
import { readdirSync, readFileSync, statSync } from "node:fs";
|
|
7
|
-
import { join, relative } from "node:path";
|
|
8
|
-
import {
|
|
9
|
-
configSchemaExportName,
|
|
10
|
-
inputSchemaExportName,
|
|
11
|
-
outfileForConfigType,
|
|
12
|
-
outfileForInputType,
|
|
13
|
-
outfileForOutputType,
|
|
14
|
-
outputSchemaExportName,
|
|
15
|
-
} from "./naming.ts";
|
|
16
|
-
|
|
17
|
-
export type SchemaRootKind = "config" | "input" | "output";
|
|
18
|
-
|
|
19
|
-
export type SchemaRole = "configType" | "inputType" | "outputType";
|
|
20
|
-
|
|
21
|
-
export interface SchemaRoot {
|
|
22
|
-
kind: SchemaRootKind;
|
|
23
|
-
typeName: string;
|
|
24
|
-
/** Path relative to project root (e.g. src/config/schema-types.ts). */
|
|
25
|
-
path: string;
|
|
26
|
-
outfile: string;
|
|
27
|
-
exportName: string;
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
const SCHEMA_TYPES_FILE = "schema-types.ts";
|
|
31
|
-
|
|
32
|
-
const ROLE_EXPORT_RE = /export\s+type\s+(configType|inputType|outputType)\s*=\s*(\w+)/g;
|
|
33
|
-
|
|
34
|
-
const ROLE_TO_KIND: Record<SchemaRole, SchemaRootKind> = {
|
|
35
|
-
configType: "config",
|
|
36
|
-
inputType: "input",
|
|
37
|
-
outputType: "output",
|
|
38
|
-
};
|
|
39
|
-
|
|
40
|
-
function listSchemaTypesFiles(srcDir: string, baseDir: string, out: string[]): void {
|
|
41
|
-
for (const ent of readdirSync(srcDir)) {
|
|
42
|
-
const full = join(srcDir, ent);
|
|
43
|
-
const st = statSync(full);
|
|
44
|
-
if (st.isDirectory()) {
|
|
45
|
-
listSchemaTypesFiles(full, baseDir, out);
|
|
46
|
-
continue;
|
|
47
|
-
}
|
|
48
|
-
if (ent === SCHEMA_TYPES_FILE) {
|
|
49
|
-
out.push(relative(baseDir, full));
|
|
50
|
-
}
|
|
51
|
-
}
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
/** True when `typeName` is declared in this file (not a re-export alias to another module). */
|
|
55
|
-
function isTypeDefinedInFile(text: string, typeName: string): boolean {
|
|
56
|
-
if (new RegExp(`export\\s+interface\\s+${typeName}\\b`).test(text)) {
|
|
57
|
-
return true;
|
|
58
|
-
}
|
|
59
|
-
if (new RegExp(`export\\s+type\\s+${typeName}\\s*=`).test(text)) {
|
|
60
|
-
return !["configType", "inputType", "outputType"].includes(typeName);
|
|
61
|
-
}
|
|
62
|
-
return false;
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
function rootForRole(role: SchemaRole, typeName: string, path: string): SchemaRoot {
|
|
66
|
-
const kind = ROLE_TO_KIND[role];
|
|
67
|
-
if (kind === "config") {
|
|
68
|
-
return {
|
|
69
|
-
kind,
|
|
70
|
-
typeName,
|
|
71
|
-
path,
|
|
72
|
-
outfile: outfileForConfigType(typeName),
|
|
73
|
-
exportName: configSchemaExportName(typeName),
|
|
74
|
-
};
|
|
75
|
-
}
|
|
76
|
-
if (kind === "input") {
|
|
77
|
-
return {
|
|
78
|
-
kind,
|
|
79
|
-
typeName,
|
|
80
|
-
path,
|
|
81
|
-
outfile: outfileForInputType(typeName),
|
|
82
|
-
exportName: inputSchemaExportName(typeName),
|
|
83
|
-
};
|
|
84
|
-
}
|
|
85
|
-
return {
|
|
86
|
-
kind,
|
|
87
|
-
typeName,
|
|
88
|
-
path,
|
|
89
|
-
outfile: outfileForOutputType(typeName),
|
|
90
|
-
exportName: outputSchemaExportName(typeName),
|
|
91
|
-
};
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
function discoverFromFile(path: string, text: string): SchemaRoot[] {
|
|
95
|
-
const rolesSeen = new Set<SchemaRole>();
|
|
96
|
-
const roots: SchemaRoot[] = [];
|
|
97
|
-
|
|
98
|
-
for (const match of text.matchAll(ROLE_EXPORT_RE)) {
|
|
99
|
-
const role = match[1] as SchemaRole | undefined;
|
|
100
|
-
const typeName = match[2];
|
|
101
|
-
if (!role || !typeName) {
|
|
102
|
-
continue;
|
|
103
|
-
}
|
|
104
|
-
if (rolesSeen.has(role)) {
|
|
105
|
-
throw new Error(`${path}: duplicate export type ${role}`);
|
|
106
|
-
}
|
|
107
|
-
rolesSeen.add(role);
|
|
108
|
-
if (!isTypeDefinedInFile(text, typeName)) {
|
|
109
|
-
continue;
|
|
110
|
-
}
|
|
111
|
-
roots.push(rootForRole(role, typeName, path));
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
return roots;
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
/** Find all schema roots under `src/` in files named schema-types.ts. */
|
|
118
|
-
export function discoverSchemaRoots(projectRoot: string): SchemaRoot[] {
|
|
119
|
-
const srcDir = join(projectRoot, "src");
|
|
120
|
-
const files: string[] = [];
|
|
121
|
-
listSchemaTypesFiles(srcDir, projectRoot, files);
|
|
122
|
-
|
|
123
|
-
const roots: SchemaRoot[] = [];
|
|
124
|
-
const typeOwners = new Map<string, string>();
|
|
125
|
-
|
|
126
|
-
for (const relPath of files.sort()) {
|
|
127
|
-
const text = readFileSync(join(projectRoot, relPath), "utf8");
|
|
128
|
-
for (const root of discoverFromFile(relPath, text)) {
|
|
129
|
-
const prev = typeOwners.get(root.typeName);
|
|
130
|
-
if (prev) {
|
|
131
|
-
throw new Error(
|
|
132
|
-
`${relPath}: duplicate schema root type ${root.typeName} (already declared in ${prev})`,
|
|
133
|
-
);
|
|
134
|
-
}
|
|
135
|
-
typeOwners.set(root.typeName, relPath);
|
|
136
|
-
roots.push(root);
|
|
137
|
-
}
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
const configRoots = roots.filter((r) => r.kind === "config");
|
|
141
|
-
if (configRoots.length > 1) {
|
|
142
|
-
throw new Error(
|
|
143
|
-
`multiple config schema roots: ${configRoots.map((r) => `${r.typeName} (${r.path})`).join(", ")}`,
|
|
144
|
-
);
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
return roots;
|
|
148
|
-
}
|
|
@@ -1,99 +0,0 @@
|
|
|
1
|
-
/*
|
|
2
|
-
Maps discovered schema root type names to generated filenames and bridge export constants.
|
|
3
|
-
Matches conventions in docs/output-schema.md and docs/config-schema.md.
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
/** kebab-case from PascalCase segments. */
|
|
7
|
-
export function camelToKebab(name: string): string {
|
|
8
|
-
return name
|
|
9
|
-
.replace(/([a-z0-9])([A-Z])/g, "$1-$2")
|
|
10
|
-
.replace(/([A-Z]+)([A-Z][a-z])/g, "$1-$2")
|
|
11
|
-
.toLowerCase();
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
/** SCREAMING_SNAKE from PascalCase. */
|
|
15
|
-
export function camelToScreamingSnake(name: string): string {
|
|
16
|
-
return camelToKebab(name).replace(/-/g, "_").toUpperCase();
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
/** Generated JSON filename for a tool-input schema root. */
|
|
20
|
-
export function outfileForInputType(typeName: string): string {
|
|
21
|
-
if (typeName.endsWith("ToolInput")) {
|
|
22
|
-
return `${camelToKebab(typeName.slice(0, -"ToolInput".length))}-tool-input.json`;
|
|
23
|
-
}
|
|
24
|
-
return `${camelToKebab(typeName)}-tool-input.json`;
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
/** Bridge export constant for a tool-input schema root. */
|
|
28
|
-
export function inputSchemaExportName(typeName: string): string {
|
|
29
|
-
if (typeName.endsWith("ToolInput")) {
|
|
30
|
-
const base = typeName.slice(0, -"ToolInput".length);
|
|
31
|
-
return `${camelToScreamingSnake(base)}_TOOL_INPUT_SCHEMA`;
|
|
32
|
-
}
|
|
33
|
-
return `${camelToScreamingSnake(typeName)}_TOOL_INPUT_SCHEMA`;
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
/** Generated JSON filename for an output-schema root type. */
|
|
37
|
-
export function outfileForOutputType(typeName: string): string {
|
|
38
|
-
if (typeName.endsWith("JsonOutput")) {
|
|
39
|
-
return `${camelToKebab(typeName.slice(0, -"JsonOutput".length))}.json`;
|
|
40
|
-
}
|
|
41
|
-
if (typeName.endsWith("OpResult")) {
|
|
42
|
-
return `${camelToKebab(typeName.slice(0, -"OpResult".length))}-op-result.json`;
|
|
43
|
-
}
|
|
44
|
-
if (typeName.endsWith("Output")) {
|
|
45
|
-
return `${camelToKebab(typeName.slice(0, -"Output".length))}.json`;
|
|
46
|
-
}
|
|
47
|
-
if (typeName.endsWith("Result")) {
|
|
48
|
-
return `${camelToKebab(typeName.slice(0, -"Result".length))}.json`;
|
|
49
|
-
}
|
|
50
|
-
return `${camelToKebab(typeName)}.json`;
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
/** Bridge export constant for an output-schema root. */
|
|
54
|
-
export function outputSchemaExportName(typeName: string): string {
|
|
55
|
-
if (typeName.endsWith("JsonOutput")) {
|
|
56
|
-
const base = typeName.slice(0, -"JsonOutput".length);
|
|
57
|
-
return `${camelToScreamingSnake(base)}_JSON_OUTPUT_SCHEMA`;
|
|
58
|
-
}
|
|
59
|
-
if (typeName.endsWith("OpResult")) {
|
|
60
|
-
const base = typeName.slice(0, -"OpResult".length);
|
|
61
|
-
return `${camelToScreamingSnake(base)}_OP_RESULT_OUTPUT_SCHEMA`;
|
|
62
|
-
}
|
|
63
|
-
if (typeName.endsWith("Output")) {
|
|
64
|
-
const base = typeName.slice(0, -"Output".length);
|
|
65
|
-
return `${camelToScreamingSnake(base)}_OUTPUT_SCHEMA`;
|
|
66
|
-
}
|
|
67
|
-
if (typeName.endsWith("Result")) {
|
|
68
|
-
const base = typeName.slice(0, -"Result".length);
|
|
69
|
-
return `${camelToScreamingSnake(base)}_RESULT_OUTPUT_SCHEMA`;
|
|
70
|
-
}
|
|
71
|
-
return `${camelToScreamingSnake(typeName)}_OUTPUT_SCHEMA`;
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
/** Generated JSON filename for a config-schema root (typically AppConfig → app-config.json). */
|
|
75
|
-
export function outfileForConfigType(typeName: string): string {
|
|
76
|
-
if (typeName.endsWith("Config")) {
|
|
77
|
-
return `${camelToKebab(typeName.slice(0, -"Config".length))}-config.json`;
|
|
78
|
-
}
|
|
79
|
-
return `${camelToKebab(typeName)}-config.json`;
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
/** Bridge export constant for a config-schema root (AppConfig → APP_CONFIG_JSON_SCHEMA). */
|
|
83
|
-
export function configSchemaExportName(typeName: string): string {
|
|
84
|
-
if (typeName.endsWith("Config")) {
|
|
85
|
-
const base = typeName.slice(0, -"Config".length);
|
|
86
|
-
return `${camelToScreamingSnake(base)}_CONFIG_JSON_SCHEMA`;
|
|
87
|
-
}
|
|
88
|
-
return `${camelToScreamingSnake(typeName)}_CONFIG_JSON_SCHEMA`;
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
/** Import path basename for a generated JSON file (no extension). */
|
|
92
|
-
export function jsonImportBasename(outfile: string): string {
|
|
93
|
-
return outfile.replace(/\.json$/, "");
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
/** Safe import binding for a generated JSON file (no extension, hyphens → underscores). */
|
|
97
|
-
export function jsonImportVar(outfile: string): string {
|
|
98
|
-
return jsonImportBasename(outfile).replace(/-/g, "_");
|
|
99
|
-
}
|
|
@@ -1,91 +0,0 @@
|
|
|
1
|
-
/*
|
|
2
|
-
Generate JSON Schema artifacts and bridge modules from discovered schema-types.ts roots.
|
|
3
|
-
*/
|
|
4
|
-
|
|
5
|
-
import { mkdirSync, writeFileSync } from "node:fs";
|
|
6
|
-
import { dirname, join } from "node:path";
|
|
7
|
-
import { createGenerator } from "ts-json-schema-generator";
|
|
8
|
-
import { discoverSchemaRoots, type SchemaRoot } from "./schemagen/discover-schema-roots.ts";
|
|
9
|
-
import { jsonImportVar } from "./schemagen/naming.ts";
|
|
10
|
-
|
|
11
|
-
const projectRoot = join(import.meta.dir, "..");
|
|
12
|
-
const generatedDir = join(projectRoot, "schemas", "generated");
|
|
13
|
-
|
|
14
|
-
function generateJson(root: SchemaRoot): Record<string, unknown> {
|
|
15
|
-
const generator = createGenerator({
|
|
16
|
-
path: join(projectRoot, root.path),
|
|
17
|
-
type: root.typeName,
|
|
18
|
-
tsconfig: join(projectRoot, "tsconfig.json"),
|
|
19
|
-
topRef: false,
|
|
20
|
-
skipTypeCheck: false,
|
|
21
|
-
jsDoc: "extended",
|
|
22
|
-
additionalProperties: root.kind === "config" ? false : undefined,
|
|
23
|
-
});
|
|
24
|
-
const schema = generator.createSchema(root.typeName) as Record<string, unknown>;
|
|
25
|
-
if (root.kind === "config") {
|
|
26
|
-
schema.additionalProperties = false;
|
|
27
|
-
}
|
|
28
|
-
return schema;
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
function writeBridge(path: string, scriptName: string, roots: SchemaRoot[], banner: string): void {
|
|
32
|
-
/** Sort by import binding so Biome `organizeImports` does not reorder the bridge file. */
|
|
33
|
-
const sorted = [...roots].sort((left, right) =>
|
|
34
|
-
jsonImportVar(left.outfile).localeCompare(jsonImportVar(right.outfile)),
|
|
35
|
-
);
|
|
36
|
-
const lines = [`// Auto-generated by scripts/${scriptName} — do not edit by hand.`, ""];
|
|
37
|
-
for (const root of sorted) {
|
|
38
|
-
const varName = jsonImportVar(root.outfile);
|
|
39
|
-
lines.push(`import ${varName} from "./generated/${root.outfile}";`);
|
|
40
|
-
}
|
|
41
|
-
if (sorted.length > 0) {
|
|
42
|
-
lines.push("");
|
|
43
|
-
}
|
|
44
|
-
for (const root of sorted) {
|
|
45
|
-
const varName = jsonImportVar(root.outfile);
|
|
46
|
-
lines.push(`/** ${banner} \`${root.typeName}\`. */`);
|
|
47
|
-
lines.push(`export const ${root.exportName} = ${varName} as Record<string, unknown>;`);
|
|
48
|
-
lines.push("");
|
|
49
|
-
}
|
|
50
|
-
writeFileSync(path, `${lines.join("\n")}`);
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
const roots = discoverSchemaRoots(projectRoot);
|
|
54
|
-
const configRoots = roots.filter((r) => r.kind === "config");
|
|
55
|
-
const inputRoots = roots.filter((r) => r.kind === "input");
|
|
56
|
-
const outputRoots = roots.filter((r) => r.kind === "output");
|
|
57
|
-
|
|
58
|
-
mkdirSync(generatedDir, { recursive: true });
|
|
59
|
-
|
|
60
|
-
for (const root of roots) {
|
|
61
|
-
const schema = generateJson(root);
|
|
62
|
-
const outPath = join(generatedDir, root.outfile);
|
|
63
|
-
mkdirSync(dirname(outPath), { recursive: true });
|
|
64
|
-
writeFileSync(outPath, `${JSON.stringify(schema, null, 2)}\n`);
|
|
65
|
-
console.log(`wrote schemas/generated/${root.outfile} (${root.typeName})`);
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
writeBridge(
|
|
69
|
-
join(projectRoot, "schemas", "configSchemas.ts"),
|
|
70
|
-
"schemagen.ts",
|
|
71
|
-
configRoots,
|
|
72
|
-
"JSON Schema for program.appConfig.jsonSchema from",
|
|
73
|
-
);
|
|
74
|
-
if (inputRoots.length > 0) {
|
|
75
|
-
writeBridge(
|
|
76
|
-
join(projectRoot, "schemas", "inputSchemas.ts"),
|
|
77
|
-
"schemagen.ts",
|
|
78
|
-
inputRoots,
|
|
79
|
-
"JSON Schema for leaf inputSchema from",
|
|
80
|
-
);
|
|
81
|
-
}
|
|
82
|
-
writeBridge(
|
|
83
|
-
join(projectRoot, "schemas", "outputSchemas.ts"),
|
|
84
|
-
"schemagen.ts",
|
|
85
|
-
outputRoots,
|
|
86
|
-
"JSON Schema for leaf outputSchema from",
|
|
87
|
-
);
|
|
88
|
-
|
|
89
|
-
console.log(
|
|
90
|
-
`config roots: ${configRoots.length}, input roots: ${inputRoots.length}, output roots: ${outputRoots.length}`,
|
|
91
|
-
);
|
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
/** JSON stdout for `full-example status --json`. */
|
|
2
|
-
export interface StatusJsonOutput {
|
|
3
|
-
/** Resolved AWS region. */
|
|
4
|
-
defaultRegion?: string;
|
|
5
|
-
/** Resolved retry count. */
|
|
6
|
-
maxRetries?: number;
|
|
7
|
-
/** Whether apiToken is set (value never included). */
|
|
8
|
-
apiTokenSet: boolean;
|
|
9
|
-
/** App version from program root. */
|
|
10
|
-
version: string;
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
/** Schemagen root for leaf outputSchema. */
|
|
14
|
-
export type outputType = StatusJsonOutput;
|
|
File without changes
|
|
File without changes
|
|
File without changes
|