@amritk/mjst 0.12.2 → 0.12.3
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/dist/cli.js +280 -362
- package/dist/combine-files.js +24 -35
- package/dist/detect-helpers-mode.js +28 -41
- package/dist/emit-examples.js +18 -28
- package/dist/has-external-refs.js +16 -21
- package/dist/help-text.js +4 -7
- package/dist/lint/resolver.js +79 -112
- package/dist/lint/ruleset-loader.js +30 -28
- package/dist/lint/run.js +153 -194
- package/dist/load-config.js +44 -45
- package/dist/load-schema-module.js +28 -43
- package/dist/load-schema.js +31 -47
- package/dist/parse-cli-args.js +190 -229
- package/dist/read-version.js +8 -12
- package/dist/resolve-import-ext.js +4 -9
- package/package.json +11 -11
package/dist/cli.js
CHANGED
|
@@ -1,395 +1,313 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { execFile } from
|
|
3
|
-
import { mkdir, readdir, unlink, writeFile } from
|
|
4
|
-
import { basename, dirname, join, relative, resolve, sep } from
|
|
5
|
-
import { promisify } from
|
|
6
|
-
import { buildSchema } from
|
|
7
|
-
import { buildValidatorSchema } from
|
|
8
|
-
import { deriveRootTypeName } from
|
|
2
|
+
import { execFile } from "node:child_process";
|
|
3
|
+
import { mkdir, readdir, unlink, writeFile } from "node:fs/promises";
|
|
4
|
+
import { basename, dirname, join, relative, resolve, sep } from "node:path";
|
|
5
|
+
import { promisify } from "node:util";
|
|
6
|
+
import { buildSchema } from "@amritk/generate-parsers";
|
|
7
|
+
import { buildValidatorSchema } from "@amritk/generate-validators";
|
|
8
|
+
import { deriveRootTypeName } from "@amritk/helpers/derive-root-type-name";
|
|
9
9
|
const execFileAsync = promisify(execFile);
|
|
10
|
-
import { combineGeneratedFiles } from
|
|
11
|
-
import { detectHelpersMode } from
|
|
12
|
-
import { emitExamples } from
|
|
13
|
-
import { HELP_TEXT } from
|
|
14
|
-
import { loadConfig } from
|
|
15
|
-
import { loadSchema } from
|
|
16
|
-
import { parseCliArgs } from
|
|
17
|
-
import { readVersion } from
|
|
18
|
-
import { resolveImportExt } from
|
|
19
|
-
|
|
20
|
-
const
|
|
21
|
-
|
|
22
|
-
const isHelpRequest = (args) => args[0] === 'help' || args.includes('--help') || args.includes('-h');
|
|
23
|
-
/** The schema's base filename without extension, used to derive the root type name. */
|
|
24
|
-
const schemaBaseName = (schemaPath) => basename(schemaPath).replace(/\.[^.]+$/, '');
|
|
25
|
-
/**
|
|
26
|
-
* Resolves and reports the helpers mode. An explicit `--helpers` wins; otherwise
|
|
27
|
-
* it is auto-detected from the nearest package.json. When detection falls back to
|
|
28
|
-
* embedded, nudge the user toward declaring `@amritk/helpers` so multiple
|
|
29
|
-
* generated trees can share a single runtime helper copy.
|
|
30
|
-
*/
|
|
10
|
+
import { combineGeneratedFiles } from "./combine-files.js";
|
|
11
|
+
import { detectHelpersMode } from "./detect-helpers-mode.js";
|
|
12
|
+
import { emitExamples } from "./emit-examples.js";
|
|
13
|
+
import { HELP_TEXT } from "./help-text.js";
|
|
14
|
+
import { loadConfig } from "./load-config.js";
|
|
15
|
+
import { loadSchema } from "./load-schema.js";
|
|
16
|
+
import { parseCliArgs } from "./parse-cli-args.js";
|
|
17
|
+
import { readVersion } from "./read-version.js";
|
|
18
|
+
import { resolveImportExt } from "./resolve-import-ext.js";
|
|
19
|
+
const isVersionRequest = (args) => args[0] === "version" || args.includes("--version") || args.includes("-v");
|
|
20
|
+
const isHelpRequest = (args) => args[0] === "help" || args.includes("--help") || args.includes("-h");
|
|
21
|
+
const schemaBaseName = (schemaPath) => basename(schemaPath).replace(/\.[^.]+$/, "");
|
|
31
22
|
const resolveHelpersMode = (config, outputDir) => {
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
return helpersMode;
|
|
23
|
+
const helpersMode = config.helpers ?? detectHelpersMode(outputDir);
|
|
24
|
+
console.log(`Helpers mode: ${helpersMode}${config.helpers ? " (explicit)" : " (auto-detected)"}`);
|
|
25
|
+
if (!config.helpers && helpersMode === "embedded") {
|
|
26
|
+
console.log("Tip: add @amritk/helpers as a dependency to share one runtime helper copy across generated trees (or pass --helpers embedded to silence this).");
|
|
27
|
+
}
|
|
28
|
+
return helpersMode;
|
|
39
29
|
};
|
|
40
|
-
/**
|
|
41
|
-
* Extracts the --config flag value from process args before full parsing.
|
|
42
|
-
* We need this early so we can load the config file first, then overlay CLI flags.
|
|
43
|
-
*/
|
|
44
30
|
const extractConfigPath = (args) => {
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
}
|
|
31
|
+
for (let i = 0; i < args.length; i++) {
|
|
32
|
+
const arg = args[i];
|
|
33
|
+
if (arg === "--config" && args[i + 1] && !args[i + 1]?.startsWith("--")) {
|
|
34
|
+
return args[i + 1];
|
|
35
|
+
}
|
|
36
|
+
if (arg?.startsWith("--config=")) {
|
|
37
|
+
return arg.slice("--config=".length);
|
|
53
38
|
}
|
|
54
|
-
|
|
39
|
+
}
|
|
40
|
+
return void 0;
|
|
55
41
|
};
|
|
56
|
-
const DEFAULT_BANNER_TEXT =
|
|
42
|
+
const DEFAULT_BANNER_TEXT = "This file was auto-generated by @amritk/mjst.\n * Do not edit it manually \u2014 your changes will be overwritten on the next generation.";
|
|
57
43
|
const resolveBanner = (banner) => {
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
44
|
+
if (!banner)
|
|
45
|
+
return "";
|
|
46
|
+
const text = banner === true ? DEFAULT_BANNER_TEXT : banner;
|
|
47
|
+
return `/**
|
|
48
|
+
* ${text}
|
|
49
|
+
*/
|
|
50
|
+
|
|
51
|
+
`;
|
|
62
52
|
};
|
|
63
|
-
/** Writes a generated file, creating any parent directories the filename implies. */
|
|
64
53
|
const writeGeneratedFile = async (baseDir, filename, content) => {
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
54
|
+
const filePath = join(baseDir, filename);
|
|
55
|
+
await mkdir(dirname(filePath), { recursive: true });
|
|
56
|
+
await writeFile(filePath, content, "utf-8");
|
|
68
57
|
};
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
subDir,
|
|
84
|
-
bannerPrefix: config.banner ? resolveBanner(config.banner) : '',
|
|
85
|
-
// Spread conditionally: `exactOptionalPropertyTypes` rejects an explicit
|
|
86
|
-
// `undefined` for the optional `typeSuffix`.
|
|
87
|
-
...(config.typeSuffix !== undefined ? { typeSuffix: config.typeSuffix } : {}),
|
|
88
|
-
});
|
|
89
|
-
for (const path of written) {
|
|
90
|
-
console.log(`Generated example: ${path}`);
|
|
91
|
-
}
|
|
58
|
+
const runExamples = async (config, schema, rootTypeName, outputDir, subDir = "") => {
|
|
59
|
+
const written = await emitExamples({
|
|
60
|
+
schema,
|
|
61
|
+
rootTypeName,
|
|
62
|
+
outputDir,
|
|
63
|
+
subDir,
|
|
64
|
+
bannerPrefix: config.banner ? resolveBanner(config.banner) : "",
|
|
65
|
+
// Spread conditionally: `exactOptionalPropertyTypes` rejects an explicit
|
|
66
|
+
// `undefined` for the optional `typeSuffix`.
|
|
67
|
+
...config.typeSuffix !== void 0 ? { typeSuffix: config.typeSuffix } : {}
|
|
68
|
+
});
|
|
69
|
+
for (const path of written) {
|
|
70
|
+
console.log(`Generated example: ${path}`);
|
|
71
|
+
}
|
|
92
72
|
};
|
|
93
|
-
/**
|
|
94
|
-
* Compiles the generated `.ts` files to `.js`/`.d.ts` with a temporary tsconfig,
|
|
95
|
-
* then removes the intermediate sources. `tsFiles` are paths relative to `outputDir`.
|
|
96
|
-
*/
|
|
97
73
|
const buildOutput = async (outputDir, tsFiles, typesOnly) => {
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
finally {
|
|
133
|
-
await unlink(tsconfigPath);
|
|
134
|
-
}
|
|
135
|
-
// Remove the intermediate .ts files now that .js and .d.ts have been produced
|
|
136
|
-
for (const file of tsFiles) {
|
|
137
|
-
await unlink(join(outputDir, file));
|
|
138
|
-
console.log(`Built: ${file.replace(/\.ts$/, '.js')}`);
|
|
139
|
-
console.log(`Built: ${file.replace(/\.ts$/, '.d.ts')}`);
|
|
140
|
-
}
|
|
141
|
-
console.log(`\nTotal files built: ${tsFiles.length * 2}`);
|
|
74
|
+
const tsconfigContent = JSON.stringify({
|
|
75
|
+
compilerOptions: {
|
|
76
|
+
target: "ESNext",
|
|
77
|
+
module: "ESNext",
|
|
78
|
+
moduleResolution: "bundler",
|
|
79
|
+
declaration: true,
|
|
80
|
+
emitDeclarationOnly: typesOnly,
|
|
81
|
+
skipLibCheck: true
|
|
82
|
+
},
|
|
83
|
+
include: tsFiles.map((file) => `./${file}`)
|
|
84
|
+
}, null, 2);
|
|
85
|
+
const tsconfigPath = join(outputDir, ".tsconfig-mjst-build.json");
|
|
86
|
+
await writeFile(tsconfigPath, tsconfigContent, "utf-8");
|
|
87
|
+
try {
|
|
88
|
+
await execFileAsync("npx", ["tsc", "--project", tsconfigPath]);
|
|
89
|
+
} catch (error) {
|
|
90
|
+
const details = error;
|
|
91
|
+
if (details.code === "ENOENT") {
|
|
92
|
+
throw new Error("Could not run `npx tsc` to compile the generated files: `npx` (or `tsc`) was not found. Install TypeScript (e.g. `npm install -D typescript`) or drop --build to skip compilation.");
|
|
93
|
+
}
|
|
94
|
+
const output = [details.stdout, details.stderr].filter(Boolean).join("\n").trim();
|
|
95
|
+
throw new Error(`TypeScript compilation failed. Check the generated files for errors.${output ? `
|
|
96
|
+
|
|
97
|
+
${output}` : ""}`);
|
|
98
|
+
} finally {
|
|
99
|
+
await unlink(tsconfigPath);
|
|
100
|
+
}
|
|
101
|
+
for (const file of tsFiles) {
|
|
102
|
+
await unlink(join(outputDir, file));
|
|
103
|
+
console.log(`Built: ${file.replace(/\.ts$/, ".js")}`);
|
|
104
|
+
console.log(`Built: ${file.replace(/\.ts$/, ".d.ts")}`);
|
|
105
|
+
}
|
|
106
|
+
console.log(`
|
|
107
|
+
Total files built: ${tsFiles.length * 2}`);
|
|
142
108
|
};
|
|
143
|
-
/** Recursively collects every `*.json` file under `dir`, returning absolute paths. */
|
|
144
109
|
const findJsonSchemas = async (dir) => {
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
results.push(full);
|
|
159
|
-
}
|
|
160
|
-
}
|
|
161
|
-
return results;
|
|
110
|
+
const entries = await readdir(dir, { withFileTypes: true });
|
|
111
|
+
const results = [];
|
|
112
|
+
for (const entry of entries) {
|
|
113
|
+
const full = join(dir, entry.name);
|
|
114
|
+
if (entry.isDirectory()) {
|
|
115
|
+
if (entry.name === "node_modules" || entry.name.startsWith("."))
|
|
116
|
+
continue;
|
|
117
|
+
results.push(...await findJsonSchemas(full));
|
|
118
|
+
} else if (entry.isFile() && entry.name.endsWith(".json")) {
|
|
119
|
+
results.push(full);
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
return results;
|
|
162
123
|
};
|
|
163
|
-
/**
|
|
164
|
-
* Emits validator files (`validateX` / `isX`) for one schema into `validatorsSubDir`
|
|
165
|
-
* (a path relative to `outputDir`). The validators carry the same schema-derived
|
|
166
|
-
* filenames as the parsers, so they live in their own subdirectory to avoid
|
|
167
|
-
* colliding. Returns the written `.ts` paths relative to `outputDir` so the caller
|
|
168
|
-
* can fold them into a `--build` compilation.
|
|
169
|
-
*/
|
|
170
124
|
const runValidators = async (config, schema, rootTypeName, outputDir, validatorsSubDir) => {
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
125
|
+
const files = await buildValidatorSchema(schema, rootTypeName, config.typeSuffix);
|
|
126
|
+
const written = [];
|
|
127
|
+
for (const file of files) {
|
|
128
|
+
const content = config.banner ? resolveBanner(config.banner) + file.content : file.content;
|
|
129
|
+
const relFilename = join(validatorsSubDir, file.filename);
|
|
130
|
+
await writeGeneratedFile(outputDir, relFilename, content);
|
|
131
|
+
written.push(relFilename);
|
|
132
|
+
console.log(`Generated: ${relFilename}`);
|
|
133
|
+
}
|
|
134
|
+
return written;
|
|
181
135
|
};
|
|
182
|
-
/** Generates parsers for a single schema (the original one-schema-in, one-outDir-out flow). */
|
|
183
136
|
const runSingle = async (config, schemaPath, outputDir) => {
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
console.log(`\nTotal files generated: ${files.length + validatorFiles.length}`);
|
|
207
|
-
}
|
|
137
|
+
const schema = await loadSchema(config, schemaPath);
|
|
138
|
+
await mkdir(outputDir, { recursive: true });
|
|
139
|
+
const helpersMode = resolveHelpersMode(config, outputDir);
|
|
140
|
+
const rootTypeName = config.rootType ?? deriveRootTypeName(schema, schemaBaseName(schemaPath));
|
|
141
|
+
console.log(`Root type: ${rootTypeName}`);
|
|
142
|
+
const files = await buildSchema(schema, rootTypeName, void 0, config.typesOnly, config.logWarnings, config.strict, helpersMode, void 0, config.readonly, config.stripUnknown, config.typeSuffix, resolveImportExt(config), config.caseInsensitive);
|
|
143
|
+
for (const file of files) {
|
|
144
|
+
const isHelper = file.filename.startsWith("_helpers/");
|
|
145
|
+
const content = config.banner && !isHelper ? resolveBanner(config.banner) + file.content : file.content;
|
|
146
|
+
await writeGeneratedFile(outputDir, file.filename, content);
|
|
147
|
+
console.log(`Generated: ${file.filename}`);
|
|
148
|
+
}
|
|
149
|
+
const validatorFiles = config.validators ? await runValidators(config, schema, rootTypeName, outputDir, "validators") : [];
|
|
150
|
+
if (config.examples) {
|
|
151
|
+
await runExamples(config, schema, rootTypeName, outputDir);
|
|
152
|
+
}
|
|
153
|
+
if (config.build) {
|
|
154
|
+
await buildOutput(outputDir, [...files.map((f) => f.filename), ...validatorFiles], config.typesOnly);
|
|
155
|
+
} else {
|
|
156
|
+
console.log(`
|
|
157
|
+
Total files generated: ${files.length + validatorFiles.length}`);
|
|
158
|
+
}
|
|
208
159
|
};
|
|
209
|
-
/**
|
|
210
|
-
* Generates a single self-contained file from one schema, concatenating every
|
|
211
|
-
* generated definition (and dropping the cross-file imports that are no longer
|
|
212
|
-
* needed) into `outFilePath`. Currently used for types-only output.
|
|
213
|
-
*/
|
|
214
160
|
const runSingleFile = async (config, schemaPath, outFilePath) => {
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
161
|
+
const schema = await loadSchema(config, schemaPath);
|
|
162
|
+
const outputDir = dirname(outFilePath);
|
|
163
|
+
await mkdir(outputDir, { recursive: true });
|
|
164
|
+
const rootTypeName = config.rootType ?? deriveRootTypeName(schema, schemaBaseName(schemaPath));
|
|
165
|
+
console.log(`Root type: ${rootTypeName}`);
|
|
166
|
+
const files = await buildSchema(schema, rootTypeName, void 0, config.typesOnly, config.logWarnings, config.strict, config.helpers ?? "package", void 0, config.readonly, config.stripUnknown, config.typeSuffix, resolveImportExt(config), config.caseInsensitive);
|
|
167
|
+
const combined = combineGeneratedFiles(files);
|
|
168
|
+
await writeFile(outFilePath, config.banner ? resolveBanner(config.banner) + combined : combined, "utf-8");
|
|
169
|
+
console.log(`Generated: ${relative(process.cwd(), outFilePath)}`);
|
|
170
|
+
if (config.examples) {
|
|
171
|
+
await runExamples(config, schema, rootTypeName, outputDir);
|
|
172
|
+
}
|
|
173
|
+
if (config.build) {
|
|
174
|
+
await buildOutput(outputDir, [basename(outFilePath)], config.typesOnly);
|
|
175
|
+
} else {
|
|
176
|
+
console.log(`
|
|
177
|
+
Total files generated: 1`);
|
|
178
|
+
}
|
|
233
179
|
};
|
|
234
|
-
/**
|
|
235
|
-
* Generates parsers for every JSON Schema under `schemaDir`, mirroring the directory
|
|
236
|
-
* layout into `outDir`. Each schema's output lands in its own subdirectory (so files
|
|
237
|
-
* from different schemas never collide) and the runtime helpers are emitted once into a
|
|
238
|
-
* shared `outDir/_helpers/` that every nested parser imports from.
|
|
239
|
-
*/
|
|
240
180
|
const runRecursive = async (config, schemaDir, outputDir) => {
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
const
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
if (config.validators) {
|
|
280
|
-
// Mirror the parser layout under a top-level `validators/` tree so each
|
|
281
|
-
// schema's validators sit beside their parser counterparts without sharing
|
|
282
|
-
// a filename.
|
|
283
|
-
const validatorFiles = await runValidators(config, schema, rootTypeName, outputDir, join('validators', relNoExt));
|
|
284
|
-
writtenTsFiles.push(...validatorFiles);
|
|
285
|
-
}
|
|
286
|
-
if (config.examples) {
|
|
287
|
-
await runExamples(config, schema, rootTypeName, outputDir, relNoExt);
|
|
288
|
-
}
|
|
289
|
-
}
|
|
290
|
-
for (const [filename, content] of sharedHelpers) {
|
|
291
|
-
await writeGeneratedFile(outputDir, filename, content);
|
|
292
|
-
writtenTsFiles.push(filename);
|
|
293
|
-
console.log(`Generated: ${filename}`);
|
|
294
|
-
}
|
|
295
|
-
if (config.build) {
|
|
296
|
-
await buildOutput(outputDir, writtenTsFiles, config.typesOnly);
|
|
297
|
-
}
|
|
298
|
-
else {
|
|
299
|
-
console.log(`\nTotal files generated: ${writtenTsFiles.length}`);
|
|
181
|
+
if (config.input && config.input !== "json") {
|
|
182
|
+
console.error("Error: --schemaDir only supports JSON Schema files; remove --input or set it to 'json'.");
|
|
183
|
+
process.exit(1);
|
|
184
|
+
}
|
|
185
|
+
const schemaFiles = await findJsonSchemas(schemaDir);
|
|
186
|
+
if (schemaFiles.length === 0) {
|
|
187
|
+
console.error(`Error: no .json schema files found under ${schemaDir}.`);
|
|
188
|
+
process.exit(1);
|
|
189
|
+
}
|
|
190
|
+
await mkdir(outputDir, { recursive: true });
|
|
191
|
+
const helpersMode = resolveHelpersMode(config, outputDir);
|
|
192
|
+
const sharedHelpers = /* @__PURE__ */ new Map();
|
|
193
|
+
const writtenTsFiles = [];
|
|
194
|
+
for (const schemaFile of schemaFiles) {
|
|
195
|
+
const relPath = relative(schemaDir, schemaFile);
|
|
196
|
+
const relNoExt = relPath.slice(0, -".json".length);
|
|
197
|
+
const schemaSubDir = join(outputDir, relNoExt);
|
|
198
|
+
const depth = relNoExt.split(sep).filter(Boolean).length;
|
|
199
|
+
const helpersImportPrefix = "../".repeat(depth);
|
|
200
|
+
const schema = await loadSchema(config, schemaFile);
|
|
201
|
+
const rootTypeName = deriveRootTypeName(schema, schemaBaseName(schemaFile));
|
|
202
|
+
console.log(`
|
|
203
|
+
${relPath} \u2192 ${relNoExt}/ (root type: ${rootTypeName})`);
|
|
204
|
+
const files = await buildSchema(schema, rootTypeName, void 0, config.typesOnly, config.logWarnings, config.strict, helpersMode, helpersImportPrefix, config.readonly, config.stripUnknown, config.typeSuffix, resolveImportExt(config), config.caseInsensitive);
|
|
205
|
+
for (const file of files) {
|
|
206
|
+
if (file.filename.startsWith("_helpers/")) {
|
|
207
|
+
sharedHelpers.set(file.filename, file.content);
|
|
208
|
+
continue;
|
|
209
|
+
}
|
|
210
|
+
const content = config.banner ? resolveBanner(config.banner) + file.content : file.content;
|
|
211
|
+
await writeGeneratedFile(schemaSubDir, file.filename, content);
|
|
212
|
+
const relativeFilename = join(relNoExt, file.filename);
|
|
213
|
+
writtenTsFiles.push(relativeFilename);
|
|
214
|
+
console.log(`Generated: ${relativeFilename}`);
|
|
215
|
+
}
|
|
216
|
+
if (config.validators) {
|
|
217
|
+
const validatorFiles = await runValidators(config, schema, rootTypeName, outputDir, join("validators", relNoExt));
|
|
218
|
+
writtenTsFiles.push(...validatorFiles);
|
|
300
219
|
}
|
|
220
|
+
if (config.examples) {
|
|
221
|
+
await runExamples(config, schema, rootTypeName, outputDir, relNoExt);
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
for (const [filename, content] of sharedHelpers) {
|
|
225
|
+
await writeGeneratedFile(outputDir, filename, content);
|
|
226
|
+
writtenTsFiles.push(filename);
|
|
227
|
+
console.log(`Generated: ${filename}`);
|
|
228
|
+
}
|
|
229
|
+
if (config.build) {
|
|
230
|
+
await buildOutput(outputDir, writtenTsFiles, config.typesOnly);
|
|
231
|
+
} else {
|
|
232
|
+
console.log(`
|
|
233
|
+
Total files generated: ${writtenTsFiles.length}`);
|
|
234
|
+
}
|
|
301
235
|
};
|
|
302
236
|
const run = async () => {
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
// Validators are runtime code (validateX / isX), so they have nothing to emit
|
|
350
|
-
// in the type-only flows.
|
|
351
|
-
if (config.validators && config.typesOnly) {
|
|
352
|
-
console.error('Error: --validators cannot be combined with --types-only, which emits no runtime code.');
|
|
353
|
-
process.exit(1);
|
|
354
|
-
}
|
|
355
|
-
if (config.validators && config.outFile) {
|
|
356
|
-
console.error('Error: --validators cannot be combined with --out-file. Use --out-dir to emit validator files.');
|
|
357
|
-
process.exit(1);
|
|
358
|
-
}
|
|
359
|
-
// Single-file output is the alternative to a directory of files.
|
|
360
|
-
if (config.outFile) {
|
|
361
|
-
if (!config.typesOnly) {
|
|
362
|
-
console.error('Error: --out-file currently supports only --types-only output. Add --types-only or use --out-dir.');
|
|
363
|
-
process.exit(1);
|
|
364
|
-
}
|
|
365
|
-
if (config.schemaDir) {
|
|
366
|
-
console.error('Error: --out-file cannot be combined with --schema-dir; use --out-dir for recursive output.');
|
|
367
|
-
process.exit(1);
|
|
368
|
-
}
|
|
369
|
-
if (!config.schema) {
|
|
370
|
-
console.error('Error: --schema is required. Provide a path to a JSON Schema.');
|
|
371
|
-
process.exit(1);
|
|
372
|
-
}
|
|
373
|
-
await runSingleFile(config, resolve(config.schema), resolve(config.outFile));
|
|
374
|
-
return;
|
|
375
|
-
}
|
|
376
|
-
if (!config.outDir) {
|
|
377
|
-
console.error('Error: --out-dir or --out-file is required. Provide an output destination for generated files.');
|
|
378
|
-
process.exit(1);
|
|
237
|
+
const args = process.argv.slice(2);
|
|
238
|
+
if (args[0] === "lint") {
|
|
239
|
+
const { run: lintRun } = await import("./lint/run.js");
|
|
240
|
+
const result = await lintRun(args.slice(1));
|
|
241
|
+
if (result.stdout)
|
|
242
|
+
process.stdout.write(result.stdout);
|
|
243
|
+
if (result.stderr)
|
|
244
|
+
process.stderr.write(result.stderr);
|
|
245
|
+
process.exit(result.code);
|
|
246
|
+
}
|
|
247
|
+
if (isVersionRequest(args)) {
|
|
248
|
+
console.log(await readVersion());
|
|
249
|
+
return;
|
|
250
|
+
}
|
|
251
|
+
if (args.length === 0 || isHelpRequest(args)) {
|
|
252
|
+
console.log(HELP_TEXT);
|
|
253
|
+
return;
|
|
254
|
+
}
|
|
255
|
+
const configPath = extractConfigPath(args);
|
|
256
|
+
const fileConfig = configPath ? await loadConfig(configPath) : {};
|
|
257
|
+
const cliConfig = parseCliArgs(args);
|
|
258
|
+
const config = { ...fileConfig, ...cliConfig };
|
|
259
|
+
if (config.outDir && config.outFile) {
|
|
260
|
+
console.error("Error: provide only one of --out-dir or --out-file, not both.");
|
|
261
|
+
process.exit(1);
|
|
262
|
+
}
|
|
263
|
+
if (config.rootType && config.schemaDir) {
|
|
264
|
+
console.error("Error: --root-type cannot be combined with --schema-dir; each schema derives its own root type.");
|
|
265
|
+
process.exit(1);
|
|
266
|
+
}
|
|
267
|
+
if (config.build && config.importExt === "ts") {
|
|
268
|
+
console.error("Error: --import-ext ts cannot be combined with --build. Drop --build to keep runnable .ts sources.");
|
|
269
|
+
process.exit(1);
|
|
270
|
+
}
|
|
271
|
+
if (config.validators && config.typesOnly) {
|
|
272
|
+
console.error("Error: --validators cannot be combined with --types-only, which emits no runtime code.");
|
|
273
|
+
process.exit(1);
|
|
274
|
+
}
|
|
275
|
+
if (config.validators && config.outFile) {
|
|
276
|
+
console.error("Error: --validators cannot be combined with --out-file. Use --out-dir to emit validator files.");
|
|
277
|
+
process.exit(1);
|
|
278
|
+
}
|
|
279
|
+
if (config.outFile) {
|
|
280
|
+
if (!config.typesOnly) {
|
|
281
|
+
console.error("Error: --out-file currently supports only --types-only output. Add --types-only or use --out-dir.");
|
|
282
|
+
process.exit(1);
|
|
379
283
|
}
|
|
380
|
-
const outputDir = resolve(config.outDir);
|
|
381
|
-
// schemaDir activates recursive mode and takes precedence over a single schema.
|
|
382
284
|
if (config.schemaDir) {
|
|
383
|
-
|
|
384
|
-
|
|
285
|
+
console.error("Error: --out-file cannot be combined with --schema-dir; use --out-dir for recursive output.");
|
|
286
|
+
process.exit(1);
|
|
385
287
|
}
|
|
386
288
|
if (!config.schema) {
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
}
|
|
390
|
-
await
|
|
289
|
+
console.error("Error: --schema is required. Provide a path to a JSON Schema.");
|
|
290
|
+
process.exit(1);
|
|
291
|
+
}
|
|
292
|
+
await runSingleFile(config, resolve(config.schema), resolve(config.outFile));
|
|
293
|
+
return;
|
|
294
|
+
}
|
|
295
|
+
if (!config.outDir) {
|
|
296
|
+
console.error("Error: --out-dir or --out-file is required. Provide an output destination for generated files.");
|
|
297
|
+
process.exit(1);
|
|
298
|
+
}
|
|
299
|
+
const outputDir = resolve(config.outDir);
|
|
300
|
+
if (config.schemaDir) {
|
|
301
|
+
await runRecursive(config, resolve(config.schemaDir), outputDir);
|
|
302
|
+
return;
|
|
303
|
+
}
|
|
304
|
+
if (!config.schema) {
|
|
305
|
+
console.error("Error: --schema (or --schemaDir) is required. Provide a path to a JSON Schema.");
|
|
306
|
+
process.exit(1);
|
|
307
|
+
}
|
|
308
|
+
await runSingle(config, resolve(config.schema), outputDir);
|
|
391
309
|
};
|
|
392
310
|
run().catch((error) => {
|
|
393
|
-
|
|
394
|
-
|
|
311
|
+
console.error(error instanceof Error ? error.message : error);
|
|
312
|
+
process.exit(1);
|
|
395
313
|
});
|