@amritk/mjst 0.7.1 β†’ 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 CHANGED
@@ -95,22 +95,159 @@ npx mjst --config ./mjst.config.json
95
95
  ## Configuration reference
96
96
 
97
97
  <!-- config-table-start -->
98
- | | Property | CLI Flag | Type | Required | Default | Description |
99
- |:---:|:---|:---|:---:|:---:|:---:|:---|
100
- | πŸ“„ | `schema` | `--schema <path>` | `string` | β€” | β€” | 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. |
101
- | πŸ—‚οΈ | `schemaDir` | `--schema-dir <dir>` | `string` | β€” | β€” | 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. |
102
- | πŸ”Œ | `input` | `--input <format>` | `string` | β€” | `"json"` | 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. |
103
- | πŸ“¦ | `export` | `--export <name>` | `string` | β€” | β€” | 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. |
104
- | πŸ“ | `outDir` | `--out-dir <dir>` | `string` | β€” | β€” | 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'. |
105
- | πŸ“„ | `outFile` | `--out-file <file>` | `string` | β€” | β€” | 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'. |
106
- | 🏷️ | `typesOnly` | `--types-only` | `boolean` | β€” | `false` | Generate only TypeScript type definitions without parser functions. Useful when you only need the type shapes and do not need runtime validation. |
107
- | πŸ”¨ | `build` | `--build` | `boolean` | β€” | `false` | 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. |
108
- | ⚠️ | `logWarnings` | `--log-warnings` | `boolean` | β€” | `false` | 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. |
109
- | 🚫 | `strict` | `--strict` | `boolean` | β€” | `false` | 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. |
110
- | πŸ”’ | `readonly` | `--readonly` | `boolean` | β€” | `false` | 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. |
111
- | 🧰 | `helpers` | `--helpers <mode>` | `string` | β€” | β€” | 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'. |
112
- | 🏷️ | `typeSuffix` | `--type-suffix <suffix>` | `string` | β€” | `""` | 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. |
113
- | βš™οΈ | `config` | `--config <path>` | `string` | β€” | β€” | 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. |
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 &lt;path&gt;</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 &lt;dir&gt;</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 &lt;format&gt;</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 &lt;name&gt;</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 &lt;dir&gt;</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 &lt;file&gt;</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 &lt;mode&gt;</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 &lt;suffix&gt;</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 &lt;path&gt;</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
  ---
@@ -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
@@ -92692,7 +92692,9 @@ var parseCliArgs = (args2) => {
92692
92692
  if (equalsIndex !== -1) {
92693
92693
  const key2 = toCamelCase(arg.slice(2, equalsIndex));
92694
92694
  const value10 = arg.slice(equalsIndex + 1);
92695
- if (BOOLEAN_KEYS.has(key2)) {
92695
+ if (key2 === "banner") {
92696
+ config3.banner = value10 === "false" ? false : value10 === "true" ? true : value10;
92697
+ } else if (BOOLEAN_KEYS.has(key2)) {
92696
92698
  assignBoolean(config3, key2, value10 !== "false");
92697
92699
  } else {
92698
92700
  assignValue(config3, key2, value10);
@@ -92700,6 +92702,16 @@ var parseCliArgs = (args2) => {
92700
92702
  continue;
92701
92703
  }
92702
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
+ }
92703
92715
  if (BOOLEAN_KEYS.has(key)) {
92704
92716
  assignBoolean(config3, key, true);
92705
92717
  continue;
@@ -92747,6 +92759,18 @@ var loadSchema = async (config3, schemaPath) => {
92747
92759
  const source = await loadSchemaModule(schemaPath, config3.export);
92748
92760
  return getAdapter(inputFormat).toJSONSchema(source);
92749
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
+ };
92750
92774
  var writeGeneratedFile = async (baseDir, filename, content) => {
92751
92775
  const filePath = join8(baseDir, filename);
92752
92776
  await mkdir(dirname2(filePath), { recursive: true });
@@ -92803,7 +92827,9 @@ var runSingle = async (config3, schemaPath, outputDir) => {
92803
92827
  console.log(`Root type: ${rootTypeName}`);
92804
92828
  const files = await buildSchema(schema, rootTypeName, undefined, config3.typesOnly, config3.logWarnings, config3.strict, helpersMode, undefined, config3.readonly, config3.typeSuffix);
92805
92829
  for (const file2 of files) {
92806
- await writeGeneratedFile(outputDir, file2.filename, file2.content);
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);
92807
92833
  console.log(`Generated: ${file2.filename}`);
92808
92834
  }
92809
92835
  if (config3.build) {
@@ -92820,7 +92846,8 @@ var runSingleFile = async (config3, schemaPath, outFilePath) => {
92820
92846
  const rootTypeName = deriveRootTypeName(schema);
92821
92847
  console.log(`Root type: ${rootTypeName}`);
92822
92848
  const files = await buildSchema(schema, rootTypeName, undefined, config3.typesOnly, config3.logWarnings, config3.strict, config3.helpers ?? "package", undefined, config3.readonly, config3.typeSuffix);
92823
- await writeFile(outFilePath, combineGeneratedFiles(files), "utf-8");
92849
+ const combined = combineGeneratedFiles(files);
92850
+ await writeFile(outFilePath, config3.banner ? resolveBanner(config3.banner) + combined : combined, "utf-8");
92824
92851
  console.log(`Generated: ${relative(process.cwd(), outFilePath)}`);
92825
92852
  if (config3.build) {
92826
92853
  await buildOutput(outputDir, [basename(outFilePath)], config3.typesOnly);
@@ -92860,7 +92887,8 @@ ${relPath} β†’ ${relNoExt}/ (root type: ${rootTypeName})`);
92860
92887
  sharedHelpers.set(file2.filename, file2.content);
92861
92888
  continue;
92862
92889
  }
92863
- await writeGeneratedFile(schemaSubDir, file2.filename, file2.content);
92890
+ const content = config3.banner ? resolveBanner(config3.banner) + file2.content : file2.content;
92891
+ await writeGeneratedFile(schemaSubDir, file2.filename, content);
92864
92892
  const relativeFilename = join8(relNoExt, file2.filename);
92865
92893
  writtenTsFiles.push(relativeFilename);
92866
92894
  console.log(`Generated: ${relativeFilename}`);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@amritk/mjst",
3
- "version": "0.7.1",
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",
@@ -43,8 +43,8 @@
43
43
  },
44
44
  "dependencies": {
45
45
  "@amritk/adapters": "0.2.4",
46
- "@amritk/generate-parsers": "0.6.1",
47
- "@amritk/generate-markdown": "0.2.1",
46
+ "@amritk/generate-parsers": "0.6.2",
47
+ "@amritk/generate-markdown": "0.2.2",
48
48
  "@amritk/helpers": "0.6.1"
49
49
  },
50
50
  "devDependencies": {
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
- await writeGeneratedFile(outputDir, file.filename, file.content)
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
- await writeFile(outFilePath, combineGeneratedFiles(files), 'utf-8')
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
- await writeGeneratedFile(schemaSubDir, file.filename, file.content)
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
  })
@@ -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 (BOOLEAN_KEYS.has(key as keyof MutableConfig)) {
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)