@amritk/mjst 0.7.3 → 0.7.5

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.
@@ -11,3 +11,4 @@ import type { GeneratedFile } from '@amritk/generate-parsers';
11
11
  * type-only relative imports (no runtime helpers).
12
12
  */
13
13
  export declare const combineGeneratedFiles: (files: readonly GeneratedFile[]) => string;
14
+ //# sourceMappingURL=combine-files.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"combine-files.d.ts","sourceRoot":"","sources":["../src/combine-files.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,0BAA0B,CAAA;AAO7D;;;;;;;;;;GAUG;AACH,eAAO,MAAM,qBAAqB,UAAW,SAAS,aAAa,EAAE,KAAG,MAwBvE,CAAA"}
@@ -0,0 +1,39 @@
1
+ // Require whitespace after `import` so a property literally named `import` (e.g.
2
+ // `import?: string`) is not mistaken for an import statement.
3
+ const IMPORT_RE = /^import\s/;
4
+ const RELATIVE_IMPORT_RE = /\bfrom\s+['"]\.\.?\//;
5
+ /**
6
+ * Merges the per-schema {@link GeneratedFile}s into a single self-contained
7
+ * TypeScript module. Each generated file imports its sibling definitions via
8
+ * relative paths; since everything now lives in one file those imports are
9
+ * dropped, while any external (package) imports are hoisted to the top and
10
+ * deduplicated. The `index.ts` barrel is skipped because it only re-exports the
11
+ * declarations that are already inlined here.
12
+ *
13
+ * Intended for types-only output, where files contain only type definitions and
14
+ * type-only relative imports (no runtime helpers).
15
+ */
16
+ export const combineGeneratedFiles = (files) => {
17
+ const externalImports = new Set();
18
+ const bodies = [];
19
+ for (const file of files) {
20
+ if (file.filename === 'index.ts')
21
+ continue;
22
+ const bodyLines = [];
23
+ for (const line of file.content.split('\n')) {
24
+ if (IMPORT_RE.test(line.trimStart())) {
25
+ // Relative imports point at sibling files that are now inlined — drop them.
26
+ if (RELATIVE_IMPORT_RE.test(line))
27
+ continue;
28
+ externalImports.add(line.trim());
29
+ continue;
30
+ }
31
+ bodyLines.push(line);
32
+ }
33
+ const body = bodyLines.join('\n').trim();
34
+ if (body)
35
+ bodies.push(body);
36
+ }
37
+ const importBlock = [...externalImports].sort().join('\n');
38
+ return [importBlock, bodies.join('\n\n')].filter(Boolean).join('\n\n') + '\n';
39
+ };
@@ -7,4 +7,5 @@
7
7
  * when the helper sources need to be shipped alongside the generated files
8
8
  * for the output to be self-contained.
9
9
  */
10
- export declare const detectHelpersMode: (outputDir: string) => "package" | "embedded";
10
+ export declare const detectHelpersMode: (outputDir: string) => 'package' | 'embedded';
11
+ //# sourceMappingURL=detect-helpers-mode.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"detect-helpers-mode.d.ts","sourceRoot":"","sources":["../src/detect-helpers-mode.ts"],"names":[],"mappings":"AAGA;;;;;;;;GAQG;AACH,eAAO,MAAM,iBAAiB,cAAe,MAAM,KAAG,SAAS,GAAG,UAQjE,CAAA"}
@@ -0,0 +1,21 @@
1
+ import { createRequire } from 'node:module';
2
+ import { resolve } from 'node:path';
3
+ /**
4
+ * Detects whether `@amritk/helpers` is resolvable from the consumer's output
5
+ * directory using standard Node module resolution (walks up `node_modules`).
6
+ *
7
+ * Returns `'package'` when generated parsers can safely
8
+ * `import { ... } from '@amritk/helpers/...'` at runtime, or `'embedded'`
9
+ * when the helper sources need to be shipped alongside the generated files
10
+ * for the output to be self-contained.
11
+ */
12
+ export const detectHelpersMode = (outputDir) => {
13
+ const req = createRequire(resolve(outputDir, 'noop.js'));
14
+ try {
15
+ req.resolve('@amritk/helpers/is-object');
16
+ return 'package';
17
+ }
18
+ catch {
19
+ return 'embedded';
20
+ }
21
+ };
@@ -1,6 +1,7 @@
1
- import type { CliConfig } from './cli-config';
1
+ import type { CliConfig } from './cli-config.js';
2
2
  /**
3
3
  * Loads a JSON config file and returns the relevant CLI config properties.
4
4
  * The config file should have the same keys as the CLI flags (schema, outDir).
5
5
  */
6
6
  export declare const loadConfig: (configPath: string) => Promise<Partial<CliConfig>>;
7
+ //# sourceMappingURL=load-config.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"load-config.d.ts","sourceRoot":"","sources":["../src/load-config.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,cAAc,CAAA;AAO7C;;;GAGG;AACH,eAAO,MAAM,UAAU,eAAsB,MAAM,KAAG,OAAO,CAAC,OAAO,CAAC,SAAS,CAAC,CAwB/E,CAAA"}
@@ -0,0 +1,30 @@
1
+ import { readFile } from 'node:fs/promises';
2
+ import { resolve } from 'node:path';
3
+ const SOURCE_FORMATS = ['json', 'typebox', 'zod', 'valibot', 'effect'];
4
+ const isSourceFormat = (value) => typeof value === 'string' && SOURCE_FORMATS.includes(value);
5
+ /**
6
+ * Loads a JSON config file and returns the relevant CLI config properties.
7
+ * The config file should have the same keys as the CLI flags (schema, outDir).
8
+ */
9
+ export const loadConfig = async (configPath) => {
10
+ const absolutePath = resolve(configPath);
11
+ const raw = await readFile(absolutePath, 'utf-8');
12
+ const parsed = JSON.parse(raw);
13
+ if (typeof parsed !== 'object' || parsed === null || Array.isArray(parsed)) {
14
+ throw new Error(`Config file must be a JSON object: ${configPath}`);
15
+ }
16
+ const obj = parsed;
17
+ return {
18
+ ...(typeof obj['schema'] === 'string' && { schema: obj['schema'] }),
19
+ ...(typeof obj['schemaDir'] === 'string' && { schemaDir: obj['schemaDir'] }),
20
+ ...(typeof obj['outDir'] === 'string' && { outDir: obj['outDir'] }),
21
+ ...(typeof obj['outFile'] === 'string' && { outFile: obj['outFile'] }),
22
+ ...(isSourceFormat(obj['input']) && { input: obj['input'] }),
23
+ ...(typeof obj['export'] === 'string' && { export: obj['export'] }),
24
+ ...(typeof obj['typesOnly'] === 'boolean' && { typesOnly: obj['typesOnly'] }),
25
+ ...(typeof obj['build'] === 'boolean' && { build: obj['build'] }),
26
+ ...(typeof obj['logWarnings'] === 'boolean' && { logWarnings: obj['logWarnings'] }),
27
+ ...(typeof obj['strict'] === 'boolean' && { strict: obj['strict'] }),
28
+ ...(typeof obj['readonly'] === 'boolean' && { readonly: obj['readonly'] }),
29
+ };
30
+ };
@@ -14,3 +14,4 @@
14
14
  * for a `.ts` path so the cause is not a cryptic module-resolution error.
15
15
  */
16
16
  export declare const loadSchemaModule: (modulePath: string, exportName?: string) => Promise<unknown>;
17
+ //# sourceMappingURL=load-schema-module.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"load-schema-module.d.ts","sourceRoot":"","sources":["../src/load-schema-module.ts"],"names":[],"mappings":"AAEA;;;;;;;;;;;;;;GAcG;AACH,eAAO,MAAM,gBAAgB,eAAsB,MAAM,eAAe,MAAM,KAAG,OAAO,CAAC,OAAO,CAkC/F,CAAA"}
@@ -0,0 +1,45 @@
1
+ import { pathToFileURL } from 'node:url';
2
+ /**
3
+ * Loads a schema authored as a JS/TS module and returns the exported schema value.
4
+ *
5
+ * Export selection, in order of preference:
6
+ * 1. The export named by `exportName`, when provided (`--export <name>`).
7
+ * 2. The module's default export.
8
+ * 3. The sole named export, when there is exactly one.
9
+ *
10
+ * Anything else is ambiguous and throws, so the user can disambiguate explicitly
11
+ * rather than have mjst guess wrong.
12
+ *
13
+ * Importing a `.ts` module requires a TypeScript-capable runtime (Bun, or Node
14
+ * with a loader such as `tsx`). We surface a friendly hint when the import fails
15
+ * for a `.ts` path so the cause is not a cryptic module-resolution error.
16
+ */
17
+ export const loadSchemaModule = async (modulePath, exportName) => {
18
+ let mod;
19
+ try {
20
+ mod = (await import(pathToFileURL(modulePath).href));
21
+ }
22
+ catch (error) {
23
+ const hint = modulePath.endsWith('.ts')
24
+ ? ' Importing a .ts schema requires a TypeScript-capable runtime — run mjst via `bunx`, or install `tsx` and run Node with `--import tsx`.'
25
+ : '';
26
+ throw new Error(`Failed to load schema module at ${modulePath}.${hint}\n${String(error)}`);
27
+ }
28
+ if (exportName) {
29
+ if (!(exportName in mod)) {
30
+ throw new Error(`Schema module ${modulePath} has no export named '${exportName}'.`);
31
+ }
32
+ return mod[exportName];
33
+ }
34
+ if ('default' in mod && mod['default'] !== undefined) {
35
+ return mod['default'];
36
+ }
37
+ const namedKeys = Object.keys(mod).filter((key) => key !== 'default');
38
+ if (namedKeys.length === 1) {
39
+ const key = namedKeys[0];
40
+ if (key)
41
+ return mod[key];
42
+ }
43
+ throw new Error(`Schema module ${modulePath} exports ${namedKeys.length} values (${namedKeys.join(', ') || 'none'}). ` +
44
+ `Specify which one to use with --export <name>.`);
45
+ };
@@ -1,4 +1,4 @@
1
- import type { CliConfig } from './cli-config';
1
+ import type { CliConfig } from './cli-config.js';
2
2
  /**
3
3
  * Parses command-line arguments into a partial CLI config.
4
4
  * Every flag accepts both kebab-case and camelCase (e.g. `--out-dir` and `--outDir`)
@@ -7,3 +7,4 @@ import type { CliConfig } from './cli-config';
7
7
  * provided are returned so they can be layered on top of config file values.
8
8
  */
9
9
  export declare const parseCliArgs: (args: readonly string[]) => Partial<CliConfig>;
10
+ //# sourceMappingURL=parse-cli-args.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"parse-cli-args.d.ts","sourceRoot":"","sources":["../src/parse-cli-args.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,cAAc,CAAA;AA0G7C;;;;;;GAMG;AACH,eAAO,MAAM,YAAY,SAAU,SAAS,MAAM,EAAE,KAAG,OAAO,CAAC,SAAS,CA0DvE,CAAA"}
@@ -0,0 +1,142 @@
1
+ const SOURCE_FORMATS = ['json', 'typebox', 'zod', 'valibot', 'effect'];
2
+ // Boolean flags toggle on by presence and accept `--flag=false` to opt out.
3
+ const BOOLEAN_KEYS = new Set(['typesOnly', 'build', 'logWarnings', 'strict', 'readonly']);
4
+ // Value flags consume the following argument (or `--flag=value`).
5
+ const VALUE_KEYS = new Set([
6
+ 'schema',
7
+ 'schemaDir',
8
+ 'outDir',
9
+ 'outFile',
10
+ 'input',
11
+ 'export',
12
+ 'helpers',
13
+ 'typeSuffix',
14
+ ]);
15
+ /** Normalizes a CLI flag name to its camelCase config key so both `--out-dir` and `--outDir` map to `outDir`. */
16
+ const toCamelCase = (key) => key.replace(/-([a-z])/g, (_, c) => c.toUpperCase());
17
+ const parseHelpersValue = (value) => {
18
+ if (value === 'package' || value === 'embedded')
19
+ return value;
20
+ return undefined;
21
+ };
22
+ const parseInputValue = (value) => SOURCE_FORMATS.includes(value) ? value : undefined;
23
+ /** Assigns a value-flag onto the config. Returns false for unknown keys. */
24
+ const assignValue = (config, key, value) => {
25
+ switch (key) {
26
+ case 'schema':
27
+ config.schema = value;
28
+ return true;
29
+ case 'schemaDir':
30
+ config.schemaDir = value;
31
+ return true;
32
+ case 'outDir':
33
+ config.outDir = value;
34
+ return true;
35
+ case 'outFile':
36
+ config.outFile = value;
37
+ return true;
38
+ case 'export':
39
+ config.export = value;
40
+ return true;
41
+ case 'typeSuffix':
42
+ config.typeSuffix = value;
43
+ return true;
44
+ case 'input': {
45
+ const parsed = parseInputValue(value);
46
+ if (parsed)
47
+ config.input = parsed;
48
+ return true;
49
+ }
50
+ case 'helpers': {
51
+ const parsed = parseHelpersValue(value);
52
+ if (parsed)
53
+ config.helpers = parsed;
54
+ return true;
55
+ }
56
+ default:
57
+ return false;
58
+ }
59
+ };
60
+ /** Assigns a boolean flag onto the config. Returns false for unknown keys. */
61
+ const assignBoolean = (config, key, value) => {
62
+ switch (key) {
63
+ case 'typesOnly':
64
+ config.typesOnly = value;
65
+ return true;
66
+ case 'build':
67
+ config.build = value;
68
+ return true;
69
+ case 'logWarnings':
70
+ config.logWarnings = value;
71
+ return true;
72
+ case 'strict':
73
+ config.strict = value;
74
+ return true;
75
+ case 'readonly':
76
+ config.readonly = value;
77
+ return true;
78
+ default:
79
+ return false;
80
+ }
81
+ };
82
+ /**
83
+ * Parses command-line arguments into a partial CLI config.
84
+ * Every flag accepts both kebab-case and camelCase (e.g. `--out-dir` and `--outDir`)
85
+ * and supports either `--flag value` or `--flag=value` syntax. Boolean flags toggle
86
+ * on by presence and accept `--flag=false` to opt out. Only flags that were explicitly
87
+ * provided are returned so they can be layered on top of config file values.
88
+ */
89
+ export const parseCliArgs = (args) => {
90
+ const config = {};
91
+ for (let i = 0; i < args.length; i++) {
92
+ const arg = args[i];
93
+ if (!arg || !arg.startsWith('--')) {
94
+ continue;
95
+ }
96
+ // Handle --flag=value syntax
97
+ const equalsIndex = arg.indexOf('=');
98
+ if (equalsIndex !== -1) {
99
+ const key = toCamelCase(arg.slice(2, equalsIndex));
100
+ const value = arg.slice(equalsIndex + 1);
101
+ if (key === 'banner') {
102
+ // --banner=false → false, --banner=true → true, --banner=<text> → custom string
103
+ config.banner = value === 'false' ? false : value === 'true' ? true : value;
104
+ }
105
+ else if (BOOLEAN_KEYS.has(key)) {
106
+ assignBoolean(config, key, value !== 'false');
107
+ }
108
+ else {
109
+ assignValue(config, key, value);
110
+ }
111
+ continue;
112
+ }
113
+ const key = toCamelCase(arg.slice(2));
114
+ // --banner: presence alone enables the default message; an immediately
115
+ // following non-flag argument is treated as a custom message string.
116
+ if (key === 'banner') {
117
+ const nextArg = args[i + 1];
118
+ if (nextArg && !nextArg.startsWith('--')) {
119
+ config.banner = nextArg;
120
+ i++;
121
+ }
122
+ else {
123
+ config.banner = true;
124
+ }
125
+ continue;
126
+ }
127
+ // Boolean flag: presence alone enables it, no value needed
128
+ if (BOOLEAN_KEYS.has(key)) {
129
+ assignBoolean(config, key, true);
130
+ continue;
131
+ }
132
+ // Value flag: consume the next argument when it is not another flag
133
+ if (VALUE_KEYS.has(key)) {
134
+ const value = args[i + 1];
135
+ if (value && !value.startsWith('--')) {
136
+ assignValue(config, key, value);
137
+ i++;
138
+ }
139
+ }
140
+ }
141
+ return config;
142
+ };
@@ -6,3 +6,4 @@
6
6
  * sits one directory above both.
7
7
  */
8
8
  export declare const readVersion: () => Promise<string>;
9
+ //# sourceMappingURL=read-version.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"read-version.d.ts","sourceRoot":"","sources":["../src/read-version.ts"],"names":[],"mappings":"AAEA;;;;;;GAMG;AACH,eAAO,MAAM,WAAW,QAAa,OAAO,CAAC,MAAM,CAIlD,CAAA"}
@@ -0,0 +1,13 @@
1
+ import { readFile } from 'node:fs/promises';
2
+ /**
3
+ * Reads the CLI's own version from its `package.json`.
4
+ *
5
+ * Resolved relative to this module so it works whether the code runs from
6
+ * `src/` (dev) or the bundled `dist/cli.js` (published) — `package.json`
7
+ * sits one directory above both.
8
+ */
9
+ export const readVersion = async () => {
10
+ const pkgUrl = new URL('../package.json', import.meta.url);
11
+ const pkg = JSON.parse(await readFile(pkgUrl, 'utf-8'));
12
+ return pkg.version ?? '0.0.0';
13
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@amritk/mjst",
3
- "version": "0.7.3",
3
+ "version": "0.7.5",
4
4
  "description": "Generate TypeScript parsers and type definitions from JSON Schemas.",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -36,16 +36,16 @@
36
36
  "scripts": {
37
37
  "dev": "bun run --conditions=development ./src/cli.ts",
38
38
  "start": "bun run ./src/cli.ts",
39
- "build": "bun run build:code && bun run build:types",
40
- "build:code": "bun build ./src/cli.ts --outdir=dist --target=node",
41
- "build:types": "tsc -p .",
39
+ "build": "tsgo -p tsconfig.build.json && tsc-alias -p tsconfig.build.json -f",
40
+ "types:check": "tsgo -p . --noEmit",
41
+ "test": "NODE_ENV=production vitest run --root ../.. cli",
42
42
  "generate-readme": "bun run ./scripts/generate-readme.ts"
43
43
  },
44
44
  "dependencies": {
45
- "@amritk/adapters": "0.2.5",
46
- "@amritk/generate-parsers": "0.6.3",
47
- "@amritk/generate-markdown": "0.2.3",
48
- "@amritk/helpers": "0.6.2"
45
+ "@amritk/adapters": "0.2.7",
46
+ "@amritk/generate-parsers": "0.7.1",
47
+ "@amritk/generate-markdown": "0.2.4",
48
+ "@amritk/helpers": "0.7.1"
49
49
  },
50
50
  "devDependencies": {
51
51
  "json-schema-typed": "^8.0.1"