@amritk/mjst 0.7.16 → 0.8.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/README.md CHANGED
@@ -232,13 +232,22 @@ npx mjst --config ./mjst.config.json
232
232
  <td colspan="4">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>
233
233
  </tr>
234
234
  <tr>
235
+ <td>🔗 <code>importExt</code></td>
236
+ <td><code>--import-ext &lt;ext&gt;</code></td>
237
+ <td><code>string</code></td>
238
+ <td align="center"><code>"js"</code></td>
239
+ </tr>
240
+ <tr>
241
+ <td colspan="4">Extension emitted on every relative import specifier in the generated output (cross-file $ref imports, the index barrel, and embedded-helper imports). 'js' (default) is the standard TS NodeNext form ('./x.js' resolving to a sibling x.ts), accepted by tsc, Bun, and bundlers, and required by 'build'. 'ts' emits the literal on-disk paths so the generated .ts sources run directly under Node's type stripping (Node 22.6+ with --experimental-strip-types, on by default from Node 23). Incompatible with 'build' — tsc refuses to emit from .ts specifiers.<br><strong>Allowed:</strong> <code>"js"</code>, <code>"ts"</code></td>
242
+ </tr>
243
+ <tr>
235
244
  <td>⚙️ <code>config</code></td>
236
245
  <td><code>--config &lt;path&gt;</code></td>
237
246
  <td><code>string</code></td>
238
247
  <td align="center"></td>
239
248
  </tr>
240
249
  <tr>
241
- <td colspan="4">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.<br><strong>Examples:</strong> <code>"./mjst.config.json"</code></td>
250
+ <td colspan="4">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, importExt). CLI flags take precedence over config file values.<br><strong>Examples:</strong> <code>"./mjst.config.json"</code></td>
242
251
  </tr>
243
252
  </tbody>
244
253
  </table>
@@ -89,4 +89,17 @@ export type CliConfig = {
89
89
  * - `false` / omitted — no header.
90
90
  */
91
91
  readonly banner?: boolean | string;
92
+ /**
93
+ * Extension emitted on every relative import specifier in the generated
94
+ * output (cross-file `$ref` imports, the index barrel, and embedded-helper
95
+ * imports).
96
+ * - `'js'` (default): the standard TS NodeNext form (`./x.js` resolving to a
97
+ * sibling `x.ts`), accepted by tsc, Bun, and bundlers, and required by
98
+ * `build`.
99
+ * - `'ts'`: the literal on-disk paths, so the generated `.ts` sources run
100
+ * directly under Node's type stripping (Node 22.6+ with
101
+ * `--experimental-strip-types`, default from Node 23). Incompatible with
102
+ * `build` — tsc refuses to emit from `.ts` specifiers.
103
+ */
104
+ readonly importExt?: 'js' | 'ts';
92
105
  };
package/dist/cli.js CHANGED
@@ -132,7 +132,7 @@ const runSingle = async (config, schemaPath, outputDir) => {
132
132
  console.log(`Helpers mode: ${helpersMode}${config.helpers ? ' (explicit)' : ' (auto-detected)'}`);
133
133
  const rootTypeName = deriveRootTypeName(schema);
134
134
  console.log(`Root type: ${rootTypeName}`);
135
- const files = await buildSchema(schema, rootTypeName, undefined, config.typesOnly, config.logWarnings, config.strict, helpersMode, undefined, config.readonly, config.stripUnknown, config.typeSuffix);
135
+ const files = await buildSchema(schema, rootTypeName, undefined, config.typesOnly, config.logWarnings, config.strict, helpersMode, undefined, config.readonly, config.stripUnknown, config.typeSuffix, config.importExt);
136
136
  for (const file of files) {
137
137
  const isHelper = file.filename.startsWith('_helpers/');
138
138
  const content = config.banner && !isHelper ? resolveBanner(config.banner) + file.content : file.content;
@@ -157,7 +157,7 @@ const runSingleFile = async (config, schemaPath, outFilePath) => {
157
157
  await mkdir(outputDir, { recursive: true });
158
158
  const rootTypeName = deriveRootTypeName(schema);
159
159
  console.log(`Root type: ${rootTypeName}`);
160
- const files = await buildSchema(schema, rootTypeName, undefined, config.typesOnly, config.logWarnings, config.strict, config.helpers ?? 'package', undefined, config.readonly, config.stripUnknown, config.typeSuffix);
160
+ const files = await buildSchema(schema, rootTypeName, undefined, config.typesOnly, config.logWarnings, config.strict, config.helpers ?? 'package', undefined, config.readonly, config.stripUnknown, config.typeSuffix, config.importExt);
161
161
  const combined = combineGeneratedFiles(files);
162
162
  await writeFile(outFilePath, config.banner ? resolveBanner(config.banner) + combined : combined, 'utf-8');
163
163
  console.log(`Generated: ${relative(process.cwd(), outFilePath)}`);
@@ -202,7 +202,7 @@ const runRecursive = async (config, schemaDir, outputDir) => {
202
202
  const schema = await loadSchema(config, schemaFile);
203
203
  const rootTypeName = deriveRootTypeName(schema);
204
204
  console.log(`\n${relPath} → ${relNoExt}/ (root type: ${rootTypeName})`);
205
- const files = await buildSchema(schema, rootTypeName, undefined, config.typesOnly, config.logWarnings, config.strict, helpersMode, helpersImportPrefix, config.readonly, config.stripUnknown, config.typeSuffix);
205
+ const files = await buildSchema(schema, rootTypeName, undefined, config.typesOnly, config.logWarnings, config.strict, helpersMode, helpersImportPrefix, config.readonly, config.stripUnknown, config.typeSuffix, config.importExt);
206
206
  for (const file of files) {
207
207
  if (file.filename.startsWith('_helpers/')) {
208
208
  sharedHelpers.set(file.filename, file.content);
@@ -243,6 +243,13 @@ const run = async () => {
243
243
  console.error('Error: provide only one of --out-dir or --out-file, not both.');
244
244
  process.exit(1);
245
245
  }
246
+ // tsc cannot emit from `.ts` import specifiers (allowImportingTsExtensions
247
+ // requires noEmit), so compiled output and stripping-ready sources are
248
+ // mutually exclusive.
249
+ if (config.build && config.importExt === 'ts') {
250
+ console.error('Error: --import-ext ts cannot be combined with --build. Drop --build to keep runnable .ts sources.');
251
+ process.exit(1);
252
+ }
246
253
  // Single-file output is the alternative to a directory of files.
247
254
  if (config.outFile) {
248
255
  if (!config.typesOnly) {
@@ -3,6 +3,7 @@ import { resolve } from 'node:path';
3
3
  const SOURCE_FORMATS = ['json', 'typebox', 'zod', 'valibot', 'effect'];
4
4
  const isSourceFormat = (value) => typeof value === 'string' && SOURCE_FORMATS.includes(value);
5
5
  const isHelpersMode = (value) => value === 'package' || value === 'embedded';
6
+ const isImportExt = (value) => value === 'js' || value === 'ts';
6
7
  /**
7
8
  * Loads a JSON config file and returns the relevant CLI config properties.
8
9
  * The config file should have the same keys as the CLI flags (schema, outDir).
@@ -29,6 +30,7 @@ export const loadConfig = async (configPath) => {
29
30
  ...(typeof obj['stripUnknown'] === 'boolean' && { stripUnknown: obj['stripUnknown'] }),
30
31
  ...(typeof obj['readonly'] === 'boolean' && { readonly: obj['readonly'] }),
31
32
  ...(isHelpersMode(obj['helpers']) && { helpers: obj['helpers'] }),
33
+ ...(isImportExt(obj['importExt']) && { importExt: obj['importExt'] }),
32
34
  ...(typeof obj['typeSuffix'] === 'string' && { typeSuffix: obj['typeSuffix'] }),
33
35
  // `banner` is a boolean toggle or a custom header string, mirroring the CLI flag.
34
36
  ...((typeof obj['banner'] === 'boolean' || typeof obj['banner'] === 'string') && { banner: obj['banner'] }),
@@ -18,6 +18,7 @@ const VALUE_KEYS = new Set([
18
18
  'export',
19
19
  'helpers',
20
20
  'typeSuffix',
21
+ 'importExt',
21
22
  ]);
22
23
  // Recognized flags that don't map into CliConfig because they're consumed
23
24
  // earlier in the pipeline (e.g. `--config` is read by `extractConfigPath` to
@@ -31,6 +32,11 @@ const parseHelpersValue = (value) => {
31
32
  return value;
32
33
  return undefined;
33
34
  };
35
+ const parseImportExtValue = (value) => {
36
+ if (value === 'js' || value === 'ts')
37
+ return value;
38
+ return undefined;
39
+ };
34
40
  const parseInputValue = (value) => SOURCE_FORMATS.includes(value) ? value : undefined;
35
41
  /** Assigns a value-flag onto the config. Returns false for unknown keys. */
36
42
  const assignValue = (config, key, value) => {
@@ -69,6 +75,14 @@ const assignValue = (config, key, value) => {
69
75
  config.helpers = parsed;
70
76
  return true;
71
77
  }
78
+ case 'importExt': {
79
+ const parsed = parseImportExtValue(value);
80
+ if (!parsed) {
81
+ throw new Error(`Invalid --import-ext value "${value}". Expected one of: js, ts.`);
82
+ }
83
+ config.importExt = parsed;
84
+ return true;
85
+ }
72
86
  default:
73
87
  return false;
74
88
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@amritk/mjst",
3
- "version": "0.7.16",
3
+ "version": "0.8.0",
4
4
  "description": "Generate TypeScript parsers and type definitions from JSON Schemas.",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -41,10 +41,10 @@
41
41
  "generate-readme": "bun run ./scripts/generate-readme.ts"
42
42
  },
43
43
  "dependencies": {
44
- "@amritk/adapters": "0.2.14",
45
- "@amritk/generate-parsers": "0.13.0",
44
+ "@amritk/adapters": "0.2.15",
45
+ "@amritk/generate-parsers": "0.14.0",
46
46
  "@amritk/generate-markdown": "0.4.1",
47
- "@amritk/helpers": "0.10.3"
47
+ "@amritk/helpers": "0.11.0"
48
48
  },
49
49
  "devDependencies": {
50
50
  "json-schema-typed": "^8.0.1"