@amritk/mjst 0.7.0 β 0.7.2
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/README.md +153 -16
- package/dist/cli-config.d.ts +7 -0
- package/dist/cli.js +63 -8
- package/package.json +5 -5
- package/src/cli-config.ts +7 -0
- package/src/cli.ts +16 -3
- package/src/parse-cli-args.test.ts +21 -0
- package/src/parse-cli-args.ts +18 -1
package/README.md
CHANGED
|
@@ -95,22 +95,159 @@ npx mjst --config ./mjst.config.json
|
|
|
95
95
|
## Configuration reference
|
|
96
96
|
|
|
97
97
|
<!-- config-table-start -->
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
98
|
+
<table>
|
|
99
|
+
<thead>
|
|
100
|
+
<tr>
|
|
101
|
+
<th>Property</th>
|
|
102
|
+
<th>CLI Flag</th>
|
|
103
|
+
<th>Type</th>
|
|
104
|
+
<th align="center">Required</th>
|
|
105
|
+
<th align="center">Default</th>
|
|
106
|
+
</tr>
|
|
107
|
+
</thead>
|
|
108
|
+
<tbody>
|
|
109
|
+
<tr>
|
|
110
|
+
<td>π <code>schema</code></td>
|
|
111
|
+
<td><code>--schema <path></code></td>
|
|
112
|
+
<td><code>string</code></td>
|
|
113
|
+
<td align="center">β</td>
|
|
114
|
+
<td align="center">β</td>
|
|
115
|
+
</tr>
|
|
116
|
+
<tr>
|
|
117
|
+
<td colspan="5">Path to the schema to process. With the default 'json' input this is a JSON Schema file that is read and parsed. With any other input format it is a JS/TS module that exports a schema, which is loaded and converted to JSON Schema via the matching adapter. Either 'schema' or 'schemaDir' is required.</td>
|
|
118
|
+
</tr>
|
|
119
|
+
<tr>
|
|
120
|
+
<td>ποΈ <code>schemaDir</code></td>
|
|
121
|
+
<td><code>--schema-dir <dir></code></td>
|
|
122
|
+
<td><code>string</code></td>
|
|
123
|
+
<td align="center">β</td>
|
|
124
|
+
<td align="center">β</td>
|
|
125
|
+
</tr>
|
|
126
|
+
<tr>
|
|
127
|
+
<td colspan="5">Path to a directory of JSON Schema files. When set, the CLI walks the directory recursively, generates parsers for every '*.json' schema it finds, and mirrors the directory layout under outDir (each schema lands in its own subdirectory). The runtime helpers are emitted once into a shared outDir/_helpers/ that every nested parser imports from. Mutually exclusive with 'schema'; when both are present 'schemaDir' wins. Only JSON Schema input is supported in this mode.</td>
|
|
128
|
+
</tr>
|
|
129
|
+
<tr>
|
|
130
|
+
<td>π <code>input</code></td>
|
|
131
|
+
<td><code>--input <format></code></td>
|
|
132
|
+
<td><code>string</code></td>
|
|
133
|
+
<td align="center">β</td>
|
|
134
|
+
<td align="center"><code>"json"</code></td>
|
|
135
|
+
</tr>
|
|
136
|
+
<tr>
|
|
137
|
+
<td colspan="5">Source format of the schema. 'json' (default) reads a JSON Schema file directly. Any other format loads 'schema' as a module and converts it to JSON Schema with the matching adapter. Supported: 'typebox', 'zod' (zod v4+), 'valibot' (with @valibot/to-json-schema), and 'effect' β each requires the corresponding library installed in your project.</td>
|
|
138
|
+
</tr>
|
|
139
|
+
<tr>
|
|
140
|
+
<td>π¦ <code>export</code></td>
|
|
141
|
+
<td><code>--export <name></code></td>
|
|
142
|
+
<td><code>string</code></td>
|
|
143
|
+
<td align="center">β</td>
|
|
144
|
+
<td align="center">β</td>
|
|
145
|
+
</tr>
|
|
146
|
+
<tr>
|
|
147
|
+
<td colspan="5">Which export of the schema module to use when 'input' is not 'json'. Defaults to the default export, or the sole named export when the module has exactly one.</td>
|
|
148
|
+
</tr>
|
|
149
|
+
<tr>
|
|
150
|
+
<td>π <code>outDir</code></td>
|
|
151
|
+
<td><code>--out-dir <dir></code></td>
|
|
152
|
+
<td><code>string</code></td>
|
|
153
|
+
<td align="center">β</td>
|
|
154
|
+
<td align="center">β</td>
|
|
155
|
+
</tr>
|
|
156
|
+
<tr>
|
|
157
|
+
<td colspan="5">Output directory for generated TypeScript files. The directory is created automatically if it does not exist. Subdirectories are created as needed when a generated file includes a nested path. Mutually exclusive with 'outFile'.</td>
|
|
158
|
+
</tr>
|
|
159
|
+
<tr>
|
|
160
|
+
<td>π <code>outFile</code></td>
|
|
161
|
+
<td><code>--out-file <file></code></td>
|
|
162
|
+
<td><code>string</code></td>
|
|
163
|
+
<td align="center">β</td>
|
|
164
|
+
<td align="center">β</td>
|
|
165
|
+
</tr>
|
|
166
|
+
<tr>
|
|
167
|
+
<td colspan="5">Output everything to a single TypeScript file instead of a directory. Every generated definition is concatenated into one self-contained file and the cross-file imports are dropped. Mutually exclusive with 'outDir'. Currently supported only together with 'typesOnly'.</td>
|
|
168
|
+
</tr>
|
|
169
|
+
<tr>
|
|
170
|
+
<td>π·οΈ <code>typesOnly</code></td>
|
|
171
|
+
<td><code>--types-only</code></td>
|
|
172
|
+
<td><code>boolean</code></td>
|
|
173
|
+
<td align="center">β</td>
|
|
174
|
+
<td align="center"><code>false</code></td>
|
|
175
|
+
</tr>
|
|
176
|
+
<tr>
|
|
177
|
+
<td colspan="5">Generate only TypeScript type definitions without parser functions. Useful when you only need the type shapes and do not need runtime validation.</td>
|
|
178
|
+
</tr>
|
|
179
|
+
<tr>
|
|
180
|
+
<td>π¨ <code>build</code></td>
|
|
181
|
+
<td><code>--build</code></td>
|
|
182
|
+
<td><code>boolean</code></td>
|
|
183
|
+
<td align="center">β</td>
|
|
184
|
+
<td align="center"><code>false</code></td>
|
|
185
|
+
</tr>
|
|
186
|
+
<tr>
|
|
187
|
+
<td colspan="5">Compile the generated TypeScript files to .js and .d.ts output. A temporary tsconfig is written to the output directory, tsc is invoked, and the intermediate .ts source files are removed when compilation succeeds.</td>
|
|
188
|
+
</tr>
|
|
189
|
+
<tr>
|
|
190
|
+
<td>β οΈ <code>logWarnings</code></td>
|
|
191
|
+
<td><code>--log-warnings</code></td>
|
|
192
|
+
<td><code>boolean</code></td>
|
|
193
|
+
<td align="center">β</td>
|
|
194
|
+
<td align="center"><code>false</code></td>
|
|
195
|
+
</tr>
|
|
196
|
+
<tr>
|
|
197
|
+
<td colspan="5">Emit a console.warn in the generated parsers for every input key that is not declared in the schema's properties. Useful for detecting schema drift or unexpected data shapes at runtime.</td>
|
|
198
|
+
</tr>
|
|
199
|
+
<tr>
|
|
200
|
+
<td>π« <code>strict</code></td>
|
|
201
|
+
<td><code>--strict</code></td>
|
|
202
|
+
<td><code>boolean</code></td>
|
|
203
|
+
<td align="center">β</td>
|
|
204
|
+
<td align="center"><code>false</code></td>
|
|
205
|
+
</tr>
|
|
206
|
+
<tr>
|
|
207
|
+
<td colspan="5">Generate parsers that throw on type/shape mismatches (wrong type, missing required property, enum/pattern/min/max violations) instead of coercing invalid input to default values. Unknown extra keys are still allowed.</td>
|
|
208
|
+
</tr>
|
|
209
|
+
<tr>
|
|
210
|
+
<td>π <code>readonly</code></td>
|
|
211
|
+
<td><code>--readonly</code></td>
|
|
212
|
+
<td><code>boolean</code></td>
|
|
213
|
+
<td align="center">β</td>
|
|
214
|
+
<td align="center"><code>false</code></td>
|
|
215
|
+
</tr>
|
|
216
|
+
<tr>
|
|
217
|
+
<td colspan="5">Emit every property, array, and record in the generated type definitions as readonly, producing deeply immutable types. Affects type definitions only; the generated parsers still build and return plain objects.</td>
|
|
218
|
+
</tr>
|
|
219
|
+
<tr>
|
|
220
|
+
<td>π§° <code>helpers</code></td>
|
|
221
|
+
<td><code>--helpers <mode></code></td>
|
|
222
|
+
<td><code>string</code></td>
|
|
223
|
+
<td align="center">β</td>
|
|
224
|
+
<td align="center">β</td>
|
|
225
|
+
</tr>
|
|
226
|
+
<tr>
|
|
227
|
+
<td colspan="5">Controls how generated parsers reference their runtime helpers. 'package' emits imports from @amritk/helpers (requires it to be installed in the consumer project). 'embedded' ships the helper source under outDir/_helpers/ so the output is self-contained. When omitted, the CLI auto-detects: it picks 'package' if @amritk/helpers resolves from outDir, otherwise 'embedded'.</td>
|
|
228
|
+
</tr>
|
|
229
|
+
<tr>
|
|
230
|
+
<td>π·οΈ <code>typeSuffix</code></td>
|
|
231
|
+
<td><code>--type-suffix <suffix></code></td>
|
|
232
|
+
<td><code>string</code></td>
|
|
233
|
+
<td align="center">β</td>
|
|
234
|
+
<td align="center"><code>""</code></td>
|
|
235
|
+
</tr>
|
|
236
|
+
<tr>
|
|
237
|
+
<td colspan="5">Suffix appended to every generated type name derived from a $ref (e.g. 'Object' turns Contact into ContactObject). Defaults to no suffix. The root type name is used verbatim and is unaffected.</td>
|
|
238
|
+
</tr>
|
|
239
|
+
<tr>
|
|
240
|
+
<td>βοΈ <code>config</code></td>
|
|
241
|
+
<td><code>--config <path></code></td>
|
|
242
|
+
<td><code>string</code></td>
|
|
243
|
+
<td align="center">β</td>
|
|
244
|
+
<td align="center">β</td>
|
|
245
|
+
</tr>
|
|
246
|
+
<tr>
|
|
247
|
+
<td colspan="5">Path to a JSON config file. Keys match the option names in this schema (schema, schemaDir, outDir, outFile, typesOnly, build, logWarnings, strict, readonly, helpers, typeSuffix). CLI flags take precedence over config file values.</td>
|
|
248
|
+
</tr>
|
|
249
|
+
</tbody>
|
|
250
|
+
</table>
|
|
114
251
|
<!-- config-table-end -->
|
|
115
252
|
|
|
116
253
|
---
|
package/dist/cli-config.d.ts
CHANGED
|
@@ -73,4 +73,11 @@ export type CliConfig = {
|
|
|
73
73
|
* (no suffix). The root type name is used verbatim and is unaffected.
|
|
74
74
|
*/
|
|
75
75
|
readonly typeSuffix?: string;
|
|
76
|
+
/**
|
|
77
|
+
* Prepends a comment header to every generated file (excluding `_helpers/`).
|
|
78
|
+
* - `true` β use the default message: "This file was auto-generated by @amritk/mjstβ¦"
|
|
79
|
+
* - A string β use that text as the message body (wrapped in a JSDoc block).
|
|
80
|
+
* - `false` / omitted β no header.
|
|
81
|
+
*/
|
|
82
|
+
readonly banner?: boolean | string;
|
|
76
83
|
};
|
package/dist/cli.js
CHANGED
|
@@ -90440,6 +90440,31 @@ var getUnbrandedType = (schema, options = {}) => {
|
|
|
90440
90440
|
case "object":
|
|
90441
90441
|
if (schema.properties) {
|
|
90442
90442
|
const readonlyPrefix = options.readonly ? "readonly " : "";
|
|
90443
|
+
const hasDescriptions = Object.values(schema.properties).some((p) => isSchemaObject2(p) && (typeof p.description === "string" || typeof p.$comment === "string"));
|
|
90444
|
+
if (hasDescriptions) {
|
|
90445
|
+
let properties2 = "";
|
|
90446
|
+
let first22 = true;
|
|
90447
|
+
for (const key in schema.properties) {
|
|
90448
|
+
const propSchema = schema.properties[key];
|
|
90449
|
+
const isRequired = schema.required?.includes(key) ?? false;
|
|
90450
|
+
const optional3 = isRequired ? "" : "?";
|
|
90451
|
+
const propType = getTypeScriptType(propSchema, options);
|
|
90452
|
+
const inlineDescription = isSchemaObject2(propSchema) && typeof propSchema.description === "string" ? propSchema.description : isSchemaObject2(propSchema) && typeof propSchema.$comment === "string" ? propSchema.$comment : undefined;
|
|
90453
|
+
if (!first22)
|
|
90454
|
+
properties2 += `
|
|
90455
|
+
`;
|
|
90456
|
+
first22 = false;
|
|
90457
|
+
if (inlineDescription) {
|
|
90458
|
+
properties2 += " /** " + inlineDescription + ` */
|
|
90459
|
+
` + readonlyPrefix + safeKey(key) + optional3 + ": " + propType + ";";
|
|
90460
|
+
} else {
|
|
90461
|
+
properties2 += " " + readonlyPrefix + safeKey(key) + optional3 + ": " + propType + ";";
|
|
90462
|
+
}
|
|
90463
|
+
}
|
|
90464
|
+
return `{
|
|
90465
|
+
` + properties2 + `
|
|
90466
|
+
}`;
|
|
90467
|
+
}
|
|
90443
90468
|
let properties = "";
|
|
90444
90469
|
let first5 = true;
|
|
90445
90470
|
for (const key in schema.properties) {
|
|
@@ -90481,8 +90506,9 @@ var generateTypeDefinition = (schema, typeName, options = {}) => {
|
|
|
90481
90506
|
if (!isObjectLikeSchema(schema)) {
|
|
90482
90507
|
const tsType = getTypeScriptType(schema, options);
|
|
90483
90508
|
let result = "";
|
|
90484
|
-
|
|
90485
|
-
|
|
90509
|
+
const topLevelComment = isSchemaObject2(schema) && typeof schema.description === "string" && schema.description || isSchemaObject2(schema) && typeof schema.$comment === "string" && schema.$comment || undefined;
|
|
90510
|
+
if (topLevelComment) {
|
|
90511
|
+
result += buildJsDocBlock(typeName, topLevelComment);
|
|
90486
90512
|
}
|
|
90487
90513
|
result += `export type ${typeName} = ${tsType};`;
|
|
90488
90514
|
return result;
|
|
@@ -90493,9 +90519,10 @@ var generateTypeDefinition = (schema, typeName, options = {}) => {
|
|
|
90493
90519
|
const conditionalThenRef = conditionalResult?.thenRef ?? null;
|
|
90494
90520
|
let jsDocTitle;
|
|
90495
90521
|
let jsDocDescription;
|
|
90496
|
-
|
|
90522
|
+
const topLevelComment = isSchemaObject2(schema) && typeof schema.description === "string" && schema.description || isSchemaObject2(schema) && typeof schema.$comment === "string" && schema.$comment || undefined;
|
|
90523
|
+
if (topLevelComment) {
|
|
90497
90524
|
jsDocTitle = typeName;
|
|
90498
|
-
jsDocDescription =
|
|
90525
|
+
jsDocDescription = topLevelComment;
|
|
90499
90526
|
}
|
|
90500
90527
|
const hasProperties2 = normalizedSchema.properties && Object.keys(normalizedSchema.properties).length > 0;
|
|
90501
90528
|
const hasAdditionalProperties2 = normalizedSchema.additionalProperties && typeof normalizedSchema.additionalProperties === "object";
|
|
@@ -92665,7 +92692,9 @@ var parseCliArgs = (args2) => {
|
|
|
92665
92692
|
if (equalsIndex !== -1) {
|
|
92666
92693
|
const key2 = toCamelCase(arg.slice(2, equalsIndex));
|
|
92667
92694
|
const value10 = arg.slice(equalsIndex + 1);
|
|
92668
|
-
if (
|
|
92695
|
+
if (key2 === "banner") {
|
|
92696
|
+
config3.banner = value10 === "false" ? false : value10 === "true" ? true : value10;
|
|
92697
|
+
} else if (BOOLEAN_KEYS.has(key2)) {
|
|
92669
92698
|
assignBoolean(config3, key2, value10 !== "false");
|
|
92670
92699
|
} else {
|
|
92671
92700
|
assignValue(config3, key2, value10);
|
|
@@ -92673,6 +92702,16 @@ var parseCliArgs = (args2) => {
|
|
|
92673
92702
|
continue;
|
|
92674
92703
|
}
|
|
92675
92704
|
const key = toCamelCase(arg.slice(2));
|
|
92705
|
+
if (key === "banner") {
|
|
92706
|
+
const nextArg = args2[i + 1];
|
|
92707
|
+
if (nextArg && !nextArg.startsWith("--")) {
|
|
92708
|
+
config3.banner = nextArg;
|
|
92709
|
+
i++;
|
|
92710
|
+
} else {
|
|
92711
|
+
config3.banner = true;
|
|
92712
|
+
}
|
|
92713
|
+
continue;
|
|
92714
|
+
}
|
|
92676
92715
|
if (BOOLEAN_KEYS.has(key)) {
|
|
92677
92716
|
assignBoolean(config3, key, true);
|
|
92678
92717
|
continue;
|
|
@@ -92720,6 +92759,18 @@ var loadSchema = async (config3, schemaPath) => {
|
|
|
92720
92759
|
const source = await loadSchemaModule(schemaPath, config3.export);
|
|
92721
92760
|
return getAdapter(inputFormat).toJSONSchema(source);
|
|
92722
92761
|
};
|
|
92762
|
+
var DEFAULT_BANNER_TEXT = `This file was auto-generated by @amritk/mjst.
|
|
92763
|
+
* Do not edit it manually β your changes will be overwritten on the next generation.`;
|
|
92764
|
+
var resolveBanner = (banner) => {
|
|
92765
|
+
if (!banner)
|
|
92766
|
+
return "";
|
|
92767
|
+
const text = banner === true ? DEFAULT_BANNER_TEXT : banner;
|
|
92768
|
+
return `/**
|
|
92769
|
+
* ${text}
|
|
92770
|
+
*/
|
|
92771
|
+
|
|
92772
|
+
`;
|
|
92773
|
+
};
|
|
92723
92774
|
var writeGeneratedFile = async (baseDir, filename, content) => {
|
|
92724
92775
|
const filePath = join8(baseDir, filename);
|
|
92725
92776
|
await mkdir(dirname2(filePath), { recursive: true });
|
|
@@ -92776,7 +92827,9 @@ var runSingle = async (config3, schemaPath, outputDir) => {
|
|
|
92776
92827
|
console.log(`Root type: ${rootTypeName}`);
|
|
92777
92828
|
const files = await buildSchema(schema, rootTypeName, undefined, config3.typesOnly, config3.logWarnings, config3.strict, helpersMode, undefined, config3.readonly, config3.typeSuffix);
|
|
92778
92829
|
for (const file2 of files) {
|
|
92779
|
-
|
|
92830
|
+
const isHelper = file2.filename.startsWith("_helpers/");
|
|
92831
|
+
const content = config3.banner && !isHelper ? resolveBanner(config3.banner) + file2.content : file2.content;
|
|
92832
|
+
await writeGeneratedFile(outputDir, file2.filename, content);
|
|
92780
92833
|
console.log(`Generated: ${file2.filename}`);
|
|
92781
92834
|
}
|
|
92782
92835
|
if (config3.build) {
|
|
@@ -92793,7 +92846,8 @@ var runSingleFile = async (config3, schemaPath, outFilePath) => {
|
|
|
92793
92846
|
const rootTypeName = deriveRootTypeName(schema);
|
|
92794
92847
|
console.log(`Root type: ${rootTypeName}`);
|
|
92795
92848
|
const files = await buildSchema(schema, rootTypeName, undefined, config3.typesOnly, config3.logWarnings, config3.strict, config3.helpers ?? "package", undefined, config3.readonly, config3.typeSuffix);
|
|
92796
|
-
|
|
92849
|
+
const combined = combineGeneratedFiles(files);
|
|
92850
|
+
await writeFile(outFilePath, config3.banner ? resolveBanner(config3.banner) + combined : combined, "utf-8");
|
|
92797
92851
|
console.log(`Generated: ${relative(process.cwd(), outFilePath)}`);
|
|
92798
92852
|
if (config3.build) {
|
|
92799
92853
|
await buildOutput(outputDir, [basename(outFilePath)], config3.typesOnly);
|
|
@@ -92833,7 +92887,8 @@ ${relPath} β ${relNoExt}/ (root type: ${rootTypeName})`);
|
|
|
92833
92887
|
sharedHelpers.set(file2.filename, file2.content);
|
|
92834
92888
|
continue;
|
|
92835
92889
|
}
|
|
92836
|
-
|
|
92890
|
+
const content = config3.banner ? resolveBanner(config3.banner) + file2.content : file2.content;
|
|
92891
|
+
await writeGeneratedFile(schemaSubDir, file2.filename, content);
|
|
92837
92892
|
const relativeFilename = join8(relNoExt, file2.filename);
|
|
92838
92893
|
writtenTsFiles.push(relativeFilename);
|
|
92839
92894
|
console.log(`Generated: ${relativeFilename}`);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@amritk/mjst",
|
|
3
|
-
"version": "0.7.
|
|
3
|
+
"version": "0.7.2",
|
|
4
4
|
"description": "Generate TypeScript parsers and type definitions from JSON Schemas.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -42,10 +42,10 @@
|
|
|
42
42
|
"generate-readme": "bun run ./scripts/generate-readme.ts"
|
|
43
43
|
},
|
|
44
44
|
"dependencies": {
|
|
45
|
-
"@amritk/adapters": "0.2.
|
|
46
|
-
"@amritk/generate-parsers": "0.6.
|
|
47
|
-
"@amritk/generate-markdown": "0.2.
|
|
48
|
-
"@amritk/helpers": "0.6.
|
|
45
|
+
"@amritk/adapters": "0.2.4",
|
|
46
|
+
"@amritk/generate-parsers": "0.6.2",
|
|
47
|
+
"@amritk/generate-markdown": "0.2.2",
|
|
48
|
+
"@amritk/helpers": "0.6.1"
|
|
49
49
|
},
|
|
50
50
|
"devDependencies": {
|
|
51
51
|
"json-schema-typed": "^8.0.1"
|
package/src/cli-config.ts
CHANGED
|
@@ -74,4 +74,11 @@ export type CliConfig = {
|
|
|
74
74
|
* (no suffix). The root type name is used verbatim and is unaffected.
|
|
75
75
|
*/
|
|
76
76
|
readonly typeSuffix?: string
|
|
77
|
+
/**
|
|
78
|
+
* Prepends a comment header to every generated file (excluding `_helpers/`).
|
|
79
|
+
* - `true` β use the default message: "This file was auto-generated by @amritk/mjstβ¦"
|
|
80
|
+
* - A string β use that text as the message body (wrapped in a JSDoc block).
|
|
81
|
+
* - `false` / omitted β no header.
|
|
82
|
+
*/
|
|
83
|
+
readonly banner?: boolean | string
|
|
77
84
|
}
|
package/src/cli.ts
CHANGED
|
@@ -55,6 +55,15 @@ const loadSchema = async (config: Partial<CliConfig>, schemaPath: string): Promi
|
|
|
55
55
|
return getAdapter(inputFormat).toJSONSchema(source)
|
|
56
56
|
}
|
|
57
57
|
|
|
58
|
+
const DEFAULT_BANNER_TEXT =
|
|
59
|
+
'This file was auto-generated by @amritk/mjst.\n * Do not edit it manually β your changes will be overwritten on the next generation.'
|
|
60
|
+
|
|
61
|
+
const resolveBanner = (banner: boolean | string | undefined): string => {
|
|
62
|
+
if (!banner) return ''
|
|
63
|
+
const text = banner === true ? DEFAULT_BANNER_TEXT : banner
|
|
64
|
+
return `/**\n * ${text}\n */\n\n`
|
|
65
|
+
}
|
|
66
|
+
|
|
58
67
|
/** Writes a generated file, creating any parent directories the filename implies. */
|
|
59
68
|
const writeGeneratedFile = async (baseDir: string, filename: string, content: string): Promise<void> => {
|
|
60
69
|
const filePath = join(baseDir, filename)
|
|
@@ -151,7 +160,9 @@ const runSingle = async (config: Partial<CliConfig>, schemaPath: string, outputD
|
|
|
151
160
|
)
|
|
152
161
|
|
|
153
162
|
for (const file of files) {
|
|
154
|
-
|
|
163
|
+
const isHelper = file.filename.startsWith('_helpers/')
|
|
164
|
+
const content = config.banner && !isHelper ? resolveBanner(config.banner) + file.content : file.content
|
|
165
|
+
await writeGeneratedFile(outputDir, file.filename, content)
|
|
155
166
|
console.log(`Generated: ${file.filename}`)
|
|
156
167
|
}
|
|
157
168
|
|
|
@@ -192,7 +203,8 @@ const runSingleFile = async (config: Partial<CliConfig>, schemaPath: string, out
|
|
|
192
203
|
config.typeSuffix,
|
|
193
204
|
)
|
|
194
205
|
|
|
195
|
-
|
|
206
|
+
const combined = combineGeneratedFiles(files)
|
|
207
|
+
await writeFile(outFilePath, config.banner ? resolveBanner(config.banner) + combined : combined, 'utf-8')
|
|
196
208
|
console.log(`Generated: ${relative(process.cwd(), outFilePath)}`)
|
|
197
209
|
|
|
198
210
|
if (config.build) {
|
|
@@ -263,7 +275,8 @@ const runRecursive = async (config: Partial<CliConfig>, schemaDir: string, outpu
|
|
|
263
275
|
continue
|
|
264
276
|
}
|
|
265
277
|
|
|
266
|
-
|
|
278
|
+
const content = config.banner ? resolveBanner(config.banner) + file.content : file.content
|
|
279
|
+
await writeGeneratedFile(schemaSubDir, file.filename, content)
|
|
267
280
|
const relativeFilename = join(relNoExt, file.filename)
|
|
268
281
|
writtenTsFiles.push(relativeFilename)
|
|
269
282
|
console.log(`Generated: ${relativeFilename}`)
|
|
@@ -271,4 +271,25 @@ describe('parse-cli-args', () => {
|
|
|
271
271
|
expect(parseCliArgs(['--readonly=true'])).toEqual({ readonly: true })
|
|
272
272
|
expect(parseCliArgs(['--readonly=false'])).toEqual({ readonly: false })
|
|
273
273
|
})
|
|
274
|
+
|
|
275
|
+
it('parses --banner with no value as true (default message)', () => {
|
|
276
|
+
expect(parseCliArgs(['--banner'])).toEqual({ banner: true })
|
|
277
|
+
})
|
|
278
|
+
|
|
279
|
+
it('parses --banner=true and --banner=false', () => {
|
|
280
|
+
expect(parseCliArgs(['--banner=true'])).toEqual({ banner: true })
|
|
281
|
+
expect(parseCliArgs(['--banner=false'])).toEqual({ banner: false })
|
|
282
|
+
})
|
|
283
|
+
|
|
284
|
+
it('parses --banner=<text> as a custom message string', () => {
|
|
285
|
+
expect(parseCliArgs(['--banner=Do not edit this file.'])).toEqual({ banner: 'Do not edit this file.' })
|
|
286
|
+
})
|
|
287
|
+
|
|
288
|
+
it('parses --banner <text> (space-separated) as a custom message string', () => {
|
|
289
|
+
expect(parseCliArgs(['--banner', 'Do not edit this file.'])).toEqual({ banner: 'Do not edit this file.' })
|
|
290
|
+
})
|
|
291
|
+
|
|
292
|
+
it('parses --banner followed by a flag as true (default message)', () => {
|
|
293
|
+
expect(parseCliArgs(['--banner', '--out-dir', 'dist'])).toEqual({ banner: true, outDir: 'dist' })
|
|
294
|
+
})
|
|
274
295
|
})
|
package/src/parse-cli-args.ts
CHANGED
|
@@ -19,6 +19,7 @@ type MutableConfig = {
|
|
|
19
19
|
readonly?: boolean
|
|
20
20
|
helpers?: 'package' | 'embedded'
|
|
21
21
|
typeSuffix?: string
|
|
22
|
+
banner?: boolean | string
|
|
22
23
|
}
|
|
23
24
|
|
|
24
25
|
// Boolean flags toggle on by presence and accept `--flag=false` to opt out.
|
|
@@ -127,7 +128,10 @@ export const parseCliArgs = (args: readonly string[]): Partial<CliConfig> => {
|
|
|
127
128
|
if (equalsIndex !== -1) {
|
|
128
129
|
const key = toCamelCase(arg.slice(2, equalsIndex))
|
|
129
130
|
const value = arg.slice(equalsIndex + 1)
|
|
130
|
-
if (
|
|
131
|
+
if (key === 'banner') {
|
|
132
|
+
// --banner=false β false, --banner=true β true, --banner=<text> β custom string
|
|
133
|
+
config.banner = value === 'false' ? false : value === 'true' ? true : value
|
|
134
|
+
} else if (BOOLEAN_KEYS.has(key as keyof MutableConfig)) {
|
|
131
135
|
assignBoolean(config, key, value !== 'false')
|
|
132
136
|
} else {
|
|
133
137
|
assignValue(config, key, value)
|
|
@@ -137,6 +141,19 @@ export const parseCliArgs = (args: readonly string[]): Partial<CliConfig> => {
|
|
|
137
141
|
|
|
138
142
|
const key = toCamelCase(arg.slice(2))
|
|
139
143
|
|
|
144
|
+
// --banner: presence alone enables the default message; an immediately
|
|
145
|
+
// following non-flag argument is treated as a custom message string.
|
|
146
|
+
if (key === 'banner') {
|
|
147
|
+
const nextArg = args[i + 1]
|
|
148
|
+
if (nextArg && !nextArg.startsWith('--')) {
|
|
149
|
+
config.banner = nextArg
|
|
150
|
+
i++
|
|
151
|
+
} else {
|
|
152
|
+
config.banner = true
|
|
153
|
+
}
|
|
154
|
+
continue
|
|
155
|
+
}
|
|
156
|
+
|
|
140
157
|
// Boolean flag: presence alone enables it, no value needed
|
|
141
158
|
if (BOOLEAN_KEYS.has(key as keyof MutableConfig)) {
|
|
142
159
|
assignBoolean(config, key, true)
|