@pajecawav/json2code 0.0.0-stage → 0.2.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.
Files changed (82) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +180 -2
  3. package/dist/cli.d.mts +1 -0
  4. package/dist/cli.mjs +131 -0
  5. package/dist/cli.mjs.map +1 -0
  6. package/dist/core/config.mjs +53 -0
  7. package/dist/core/config.mjs.map +1 -0
  8. package/dist/core/discovery/index.d.mts +10 -0
  9. package/dist/core/discovery/index.d.mts.map +1 -0
  10. package/dist/core/discovery/index.mjs +83 -0
  11. package/dist/core/discovery/index.mjs.map +1 -0
  12. package/dist/core/generate.d.mts +11 -0
  13. package/dist/core/generate.d.mts.map +1 -0
  14. package/dist/core/generate.mjs +89 -0
  15. package/dist/core/generate.mjs.map +1 -0
  16. package/dist/core/ir/types.d.mts +100 -0
  17. package/dist/core/ir/types.d.mts.map +1 -0
  18. package/dist/core/ir/visit.d.mts +8 -0
  19. package/dist/core/ir/visit.d.mts.map +1 -0
  20. package/dist/core/ir/visit.mjs +38 -0
  21. package/dist/core/ir/visit.mjs.map +1 -0
  22. package/dist/core/linker/index.d.mts +32 -0
  23. package/dist/core/linker/index.d.mts.map +1 -0
  24. package/dist/core/linker/index.mjs +104 -0
  25. package/dist/core/linker/index.mjs.map +1 -0
  26. package/dist/core/printer/abstract.d.mts +26 -0
  27. package/dist/core/printer/abstract.d.mts.map +1 -0
  28. package/dist/core/types.d.mts +76 -0
  29. package/dist/core/types.d.mts.map +1 -0
  30. package/dist/core/types.mjs +28 -0
  31. package/dist/core/types.mjs.map +1 -0
  32. package/dist/core/walker/abstract.d.mts +18 -0
  33. package/dist/core/walker/abstract.d.mts.map +1 -0
  34. package/dist/core/walker/json-schema.d.mts +10 -0
  35. package/dist/core/walker/json-schema.d.mts.map +1 -0
  36. package/dist/core/walker/json-schema.mjs +311 -0
  37. package/dist/core/walker/json-schema.mjs.map +1 -0
  38. package/dist/core/walker/ref-resolver.d.mts +22 -0
  39. package/dist/core/walker/ref-resolver.d.mts.map +1 -0
  40. package/dist/core/walker/ref-resolver.mjs +66 -0
  41. package/dist/core/walker/ref-resolver.mjs.map +1 -0
  42. package/dist/core/writer/index.d.mts +19 -0
  43. package/dist/core/writer/index.d.mts.map +1 -0
  44. package/dist/core/writer/index.mjs +84 -0
  45. package/dist/core/writer/index.mjs.map +1 -0
  46. package/dist/diagnostics/errors.d.mts +39 -0
  47. package/dist/diagnostics/errors.d.mts.map +1 -0
  48. package/dist/diagnostics/errors.mjs +50 -0
  49. package/dist/diagnostics/errors.mjs.map +1 -0
  50. package/dist/diagnostics/logger.d.mts +10 -0
  51. package/dist/diagnostics/logger.d.mts.map +1 -0
  52. package/dist/diagnostics/logger.mjs +18 -0
  53. package/dist/diagnostics/logger.mjs.map +1 -0
  54. package/dist/index.d.mts +24 -0
  55. package/dist/index.d.mts.map +1 -0
  56. package/dist/index.mjs +26 -0
  57. package/dist/index.mjs.map +1 -0
  58. package/dist/package.mjs +6 -0
  59. package/dist/package.mjs.map +1 -0
  60. package/dist/targets/index.d.mts +9 -0
  61. package/dist/targets/index.d.mts.map +1 -0
  62. package/dist/targets/index.mjs +30 -0
  63. package/dist/targets/index.mjs.map +1 -0
  64. package/dist/targets/shared.mjs +137 -0
  65. package/dist/targets/shared.mjs.map +1 -0
  66. package/dist/targets/typescript/printer.d.mts +13 -0
  67. package/dist/targets/typescript/printer.d.mts.map +1 -0
  68. package/dist/targets/typescript/printer.mjs +47 -0
  69. package/dist/targets/typescript/printer.mjs.map +1 -0
  70. package/dist/targets/valibot/printer.d.mts +21 -0
  71. package/dist/targets/valibot/printer.d.mts.map +1 -0
  72. package/dist/targets/valibot/printer.mjs +198 -0
  73. package/dist/targets/valibot/printer.mjs.map +1 -0
  74. package/dist/targets/zod/lazy.d.mts +29 -0
  75. package/dist/targets/zod/lazy.d.mts.map +1 -0
  76. package/dist/targets/zod/lazy.mjs +100 -0
  77. package/dist/targets/zod/lazy.mjs.map +1 -0
  78. package/dist/targets/zod/printer.d.mts +21 -0
  79. package/dist/targets/zod/printer.d.mts.map +1 -0
  80. package/dist/targets/zod/printer.mjs +193 -0
  81. package/dist/targets/zod/printer.mjs.map +1 -0
  82. package/package.json +71 -3
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 pajecawav
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,3 +1,181 @@
1
- # Temporary Holding Version
1
+ # json2code
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ Generate TypeScript code from JSON Schema — type aliases for the `typescript` target, [zod v4](https://zod.dev) schemas for the `zod` target, [valibot v1](https://valibot.dev) schemas for the `valibot` target. One pass, multiple targets, deterministic output.
4
+
5
+ - **Mirror layout** — `schemas/user.json` → `src/generated/typescript/user.ts`, imports between files resolved from `$ref`
6
+ - **Cycles without topological sort** — recursive schemas work in every target (`z.lazy` / `v.lazy` with SCC analysis)
7
+ - **Standard Schema** — the `zod` and `valibot` targets emit schemas that implement the [Standard Schema](https://standardschema.dev) interface
8
+ - **Extensible** — bring your own walker (any source format) and printer (any output layout) via `defineGenerator`
9
+
10
+ ## Install
11
+
12
+ ```sh
13
+ pnpm add -D json2code
14
+ ```
15
+
16
+ ## Quickstart
17
+
18
+ ```
19
+ project/
20
+ ├── schemas/
21
+ │ ├── common.json
22
+ │ └── user.json # { "$ref": "common.json#/$defs/Id" } …
23
+ └── json2code.config.ts
24
+ ```
25
+
26
+ ```ts
27
+ // json2code.config.ts
28
+ import { defineConfig } from "json2code";
29
+
30
+ export default defineConfig({
31
+ input: ["schemas"],
32
+ output: "src/generated",
33
+ targets: ["typescript", "zod"],
34
+ });
35
+ ```
36
+
37
+ ```sh
38
+ json2code # uses the config file
39
+ json2code schemas -o src/generated -t typescript,zod,valibot # or all via flags
40
+ ```
41
+
42
+ ```ts
43
+ // src/generated/zod/user.ts (excerpt)
44
+ import { z } from "zod";
45
+ import { Id } from "./common.js";
46
+
47
+ export const User = z.object({ id: Id, email: z.email().optional() });
48
+ export type User = z.infer<typeof User>;
49
+ ```
50
+
51
+ ## CLI
52
+
53
+ ```
54
+ json2code [input...] -o <dir> [flags]
55
+ ```
56
+
57
+ | Flag | Description |
58
+ | ------------------------------ | ------------------------------------------------------------------------------------- |
59
+ | `[input...]` | Globs and/or folders (default: `schemas/**/*.json`) |
60
+ | `-o, --output <dir>` | Output directory |
61
+ | `-t, --target <list>` | Comma-separated targets: `typescript`, `zod`, `valibot` (default: `typescript`) |
62
+ | `--config <path>` | Config file path (default: `json2code.config.*` in cwd) |
63
+ | `--import-extension <ext>` | Extension in relative imports: `js` \| `ts` \| `none` (default: `js`) |
64
+ | `--readonly-arrays` | Emit `readonly T[]` in the typescript target |
65
+ | `--naming <style>` | Export name style: `origin` \| `camel` \| `pascal` (default: `pascal`) |
66
+ | `--export-suffix <str>` | Literal appended to every export name, after the naming style (e.g. `Schema`) |
67
+ | `--header <template>` | Header template with `{{version}}` / `{{date}}` placeholders (`\n` escapes a newline) |
68
+ | `--no-header` | Don't emit the file header |
69
+ | `--zod-import-path <spec>` | Import specifier for zod in the zod target (default: `zod`) |
70
+ | `--valibot-import-path <spec>` | Import specifier for valibot in the valibot target (default: `valibot`) |
71
+ | `--no-defaults` | Don't apply JSON Schema `default` in validator targets |
72
+ | `--dry-run` | Print the plan, write nothing |
73
+ | `--clean` | Remove stale `@generated` files from previous runs |
74
+ | `-V, --verbose` | Verbose logging |
75
+
76
+ CLI flags override values from the config file.
77
+
78
+ Objects render multi-line with JSDoc comments derived from JSON Schema descriptions:
79
+
80
+ ```ts
81
+ export type User = {
82
+ /** Unique identifier */
83
+ id: string;
84
+ email?: string;
85
+ };
86
+ ```
87
+
88
+ ## API
89
+
90
+ ```ts
91
+ import { generate } from "json2code";
92
+
93
+ await generate({
94
+ input: ["schemas/**/*.json"],
95
+ output: "src/generated",
96
+ targets: ["typescript", "zod"],
97
+ options: {
98
+ importExtension: "js",
99
+ readonlyArrays: false,
100
+ importPaths: { zod: "zod", valibot: "valibot" },
101
+ defaults: true,
102
+ naming: "pascal", // or "origin" / "camel"
103
+ exportSuffix: "", // e.g. "Schema" → export const UserSchema = ...
104
+ header: "// generated by json2code v{{version}} on {{date}}", // or false
105
+ },
106
+ clean: true,
107
+ });
108
+ ```
109
+
110
+ Returns `{ written, deleted, diagnostics }`. Generation is deterministic: identical input produces byte-identical output, so checking generated code into VCS is diff-friendly. (The built-in `{{date}}` header placeholder is the one exception — it changes per day.)
111
+
112
+ ## Custom generators
113
+
114
+ A target is a walker (source → IR) plus a printer (IR → code). Both are replaceable:
115
+
116
+ ```ts
117
+ import { defineGenerator, generate } from "json2code";
118
+
119
+ const bundle = defineGenerator({
120
+ name: "bundle",
121
+ // optional; default — JsonSchemaWalker
122
+ walker: {
123
+ name: "my-walker",
124
+ async walk(files, ctx) {
125
+ return files.map(f => ({
126
+ docId: f.docId,
127
+ sourcePath: f.path,
128
+ exports: new Map([["Stub", { kind: "unknown" }]]),
129
+ exportOrder: ["Stub"],
130
+ rootExportName: "Stub",
131
+ }));
132
+ },
133
+ },
134
+ // required; receives ALL docs at once so cross-file logic is possible
135
+ printer: {
136
+ name: "bundle-printer",
137
+ async print(docs, ctx) {
138
+ return [
139
+ {
140
+ path: "bundle/all.ts",
141
+ content: docs
142
+ .flatMap(d => d.exportOrder.map(n => `export type ${n} = unknown;`))
143
+ .join("\n"),
144
+ },
145
+ ];
146
+ },
147
+ },
148
+ // optional; default — mirror the input structure under `<name>/`
149
+ mapOutput: docId => `bundle/${docId}`,
150
+ });
151
+
152
+ await generate({ input: ["schemas"], output: "out", targets: [bundle] });
153
+ ```
154
+
155
+ Contracts (`AbstractWalker`, `AbstractPrinter`, IR types) are exported from the package root. Until v1.0 they may change with a minor bump.
156
+
157
+ ## Diagnostics
158
+
159
+ Errors are aggregated across files and reported with context: `[file] → [$ref] → reason`.
160
+
161
+ | Code | Meaning |
162
+ | --------------------- | ---------------------------------------------------- |
163
+ | `J2C_FILE_NOT_FOUND` | No input files matched |
164
+ | `J2C_INVALID_JSON` | Schema is not valid JSON |
165
+ | `J2C_INVALID_REF` | `$ref` target does not exist |
166
+ | `J2C_EXTERNAL_REF` | External/URL refs are not supported |
167
+ | `J2C_NAME_COLLISION` | Two files map to the same export name |
168
+ | `J2C_UNKNOWN_KEYWORD` | Unsupported keyword → mapped to `unknown` + warning |
169
+ | `J2C_OUTPUT_DIRTY` | Output contains non-generated files blocking a write |
170
+
171
+ ## Development
172
+
173
+ ```sh
174
+ pnpm build # tsdown → dist/
175
+ pnpm test # vitest
176
+ pnpm lint # tsc + oxlint + prettier + publint
177
+ ```
178
+
179
+ ## License
180
+
181
+ MIT
package/dist/cli.d.mts ADDED
@@ -0,0 +1 @@
1
+ export {}
package/dist/cli.mjs ADDED
@@ -0,0 +1,131 @@
1
+ #!/usr/bin/env node
2
+ import { Json2CodeError } from "./diagnostics/errors.mjs";
3
+ import { NAMING_STYLES } from "./core/types.mjs";
4
+ import { version } from "./package.mjs";
5
+ import { generate } from "./core/generate.mjs";
6
+ import { loadFileConfig } from "./core/config.mjs";
7
+ import { defineCommand, runMain } from "citty";
8
+ //#region src/cli.ts
9
+ const command = defineCommand({
10
+ meta: {
11
+ name: "json2code",
12
+ version,
13
+ description: "Generate TypeScript code from JSON Schema."
14
+ },
15
+ args: {
16
+ output: {
17
+ type: "string",
18
+ alias: "o",
19
+ description: "Output directory"
20
+ },
21
+ target: {
22
+ type: "string",
23
+ alias: "t",
24
+ description: "Target(s), comma-separated (typescript,zod,valibot)"
25
+ },
26
+ config: {
27
+ type: "string",
28
+ description: "Path to config file (default: json2code.config.* in cwd)"
29
+ },
30
+ "import-extension": {
31
+ type: "string",
32
+ description: "Import extension: js|ts|none"
33
+ },
34
+ "readonly-arrays": {
35
+ type: "boolean",
36
+ description: "Emit readonly arrays"
37
+ },
38
+ "zod-import-path": {
39
+ type: "string",
40
+ description: "Zod import specifier"
41
+ },
42
+ "valibot-import-path": {
43
+ type: "string",
44
+ description: "Valibot import specifier"
45
+ },
46
+ "no-defaults": {
47
+ type: "boolean",
48
+ description: "Don't apply JSON Schema `default` in validator targets"
49
+ },
50
+ naming: {
51
+ type: "string",
52
+ description: `Export name style: ${NAMING_STYLES.join("|")} (default: pascal)`
53
+ },
54
+ "export-suffix": {
55
+ type: "string",
56
+ description: "Literal appended to every export name (e.g. Schema → UsersUserSchema)"
57
+ },
58
+ header: {
59
+ type: "string",
60
+ description: "Header template ({{version}}, {{date}} placeholders, \"\\n\" for newlines); empty disables"
61
+ },
62
+ "no-header": {
63
+ type: "boolean",
64
+ description: "Omit the file header"
65
+ },
66
+ "dry-run": {
67
+ type: "boolean",
68
+ description: "Don't write, show the plan"
69
+ },
70
+ clean: {
71
+ type: "boolean",
72
+ description: "Remove stale generated files"
73
+ },
74
+ verbose: {
75
+ type: "boolean",
76
+ alias: "V",
77
+ description: "Verbose logging"
78
+ }
79
+ },
80
+ async run({ args }) {
81
+ const { config: file } = await loadFileConfig(args.config);
82
+ const input = args._.length > 0 ? [...args._] : file.input ? [...file.input] : ["schemas/**/*.json"];
83
+ const output = args.output ?? file.output;
84
+ if (!output) {
85
+ console.error("error: missing output directory (-o or `output` in the config file)");
86
+ process.exit(1);
87
+ }
88
+ const targets = args.target ? args.target.split(",").map((s) => s.trim()).filter(Boolean) : file.targets ?? ["typescript"];
89
+ const namingArg = args.naming ?? file.options?.naming;
90
+ if (namingArg !== void 0 && !NAMING_STYLES.includes(namingArg)) {
91
+ console.error(`error: invalid naming style "${namingArg}" (expected one of: ${NAMING_STYLES.join(", ")})`);
92
+ process.exit(1);
93
+ }
94
+ const rawHeader = args.header;
95
+ const headerArg = rawHeader === false || args["no-header"] === true ? false : typeof rawHeader === "string" ? rawHeader.replaceAll("\\n", "\n") : file.options?.header;
96
+ const config = {
97
+ input,
98
+ output,
99
+ targets,
100
+ options: {
101
+ importExtension: args["import-extension"] ?? file.options?.importExtension,
102
+ readonlyArrays: args["readonly-arrays"] ?? file.options?.readonlyArrays,
103
+ importPaths: {
104
+ ...file.options?.importPaths,
105
+ ...args["zod-import-path"] ? { zod: args["zod-import-path"] } : {},
106
+ ...args["valibot-import-path"] ? { valibot: args["valibot-import-path"] } : {}
107
+ },
108
+ defaults: !(args["no-defaults"] ?? false) && (file.options?.defaults ?? true),
109
+ naming: namingArg,
110
+ exportSuffix: args["export-suffix"] ?? file.options?.exportSuffix,
111
+ header: headerArg
112
+ },
113
+ clean: args.clean ?? file.clean,
114
+ dryRun: args["dry-run"] ?? file.dryRun,
115
+ verbose: args.verbose ?? file.verbose
116
+ };
117
+ try {
118
+ await generate(config);
119
+ process.exit(0);
120
+ } catch (e) {
121
+ if (e instanceof Json2CodeError) process.exit(1);
122
+ console.error(e);
123
+ process.exit(1);
124
+ }
125
+ }
126
+ });
127
+ await runMain(command);
128
+ //#endregion
129
+ export {};
130
+
131
+ //# sourceMappingURL=cli.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cli.mjs","names":["pkg.version"],"sources":["../src/cli.ts"],"sourcesContent":["#!/usr/bin/env node\nimport { defineCommand, runMain } from \"citty\";\nimport pkg from \"../package.json\" with { type: \"json\" };\nimport { loadFileConfig } from \"./core/config.js\";\nimport { generate } from \"./core/generate.js\";\nimport type { GenerateConfig, ImportExtension, NamingStyle, TargetName } from \"./core/types.js\";\nimport { NAMING_STYLES } from \"./core/types.js\";\nimport { Json2CodeError } from \"./diagnostics/errors.js\";\n\nconst command = defineCommand({\n\tmeta: {\n\t\tname: \"json2code\",\n\t\tversion: pkg.version,\n\t\tdescription: \"Generate TypeScript code from JSON Schema.\",\n\t},\n\targs: {\n\t\toutput: { type: \"string\", alias: \"o\", description: \"Output directory\" },\n\t\ttarget: {\n\t\t\ttype: \"string\",\n\t\t\talias: \"t\",\n\t\t\tdescription: \"Target(s), comma-separated (typescript,zod,valibot)\",\n\t\t},\n\t\tconfig: {\n\t\t\ttype: \"string\",\n\t\t\tdescription: \"Path to config file (default: json2code.config.* in cwd)\",\n\t\t},\n\t\t\"import-extension\": {\n\t\t\ttype: \"string\",\n\t\t\tdescription: \"Import extension: js|ts|none\",\n\t\t},\n\t\t\"readonly-arrays\": { type: \"boolean\", description: \"Emit readonly arrays\" },\n\t\t\"zod-import-path\": { type: \"string\", description: \"Zod import specifier\" },\n\t\t\"valibot-import-path\": { type: \"string\", description: \"Valibot import specifier\" },\n\t\t\"no-defaults\": {\n\t\t\ttype: \"boolean\",\n\t\t\tdescription: \"Don't apply JSON Schema `default` in validator targets\",\n\t\t},\n\t\tnaming: {\n\t\t\ttype: \"string\",\n\t\t\tdescription: `Export name style: ${NAMING_STYLES.join(\"|\")} (default: pascal)`,\n\t\t},\n\t\t\"export-suffix\": {\n\t\t\ttype: \"string\",\n\t\t\tdescription: \"Literal appended to every export name (e.g. Schema → UsersUserSchema)\",\n\t\t},\n\t\theader: {\n\t\t\ttype: \"string\",\n\t\t\tdescription:\n\t\t\t\t'Header template ({{version}}, {{date}} placeholders, \"\\\\n\" for newlines); empty disables',\n\t\t},\n\t\t\"no-header\": { type: \"boolean\", description: \"Omit the file header\" },\n\t\t\"dry-run\": { type: \"boolean\", description: \"Don't write, show the plan\" },\n\t\tclean: { type: \"boolean\", description: \"Remove stale generated files\" },\n\t\tverbose: { type: \"boolean\", alias: \"V\", description: \"Verbose logging\" },\n\t},\n\tasync run({ args }) {\n\t\tconst { config: file } = await loadFileConfig(args.config);\n\t\tconst input =\n\t\t\targs._.length > 0 ? [...args._] : file.input ? [...file.input] : [\"schemas/**/*.json\"];\n\t\tconst output = args.output ?? file.output;\n\t\tif (!output) {\n\t\t\tconsole.error(\"error: missing output directory (-o or `output` in the config file)\");\n\t\t\tprocess.exit(1);\n\t\t}\n\t\tconst targets = (\n\t\t\targs.target\n\t\t\t\t? args.target\n\t\t\t\t\t\t.split(\",\")\n\t\t\t\t\t\t.map(s => s.trim())\n\t\t\t\t\t\t.filter(Boolean)\n\t\t\t\t: (file.targets ?? [\"typescript\"])\n\t\t) as TargetName[];\n\t\tconst namingArg = (args.naming as NamingStyle | undefined) ?? file.options?.naming;\n\t\tif (namingArg !== undefined && !NAMING_STYLES.includes(namingArg)) {\n\t\t\tconsole.error(\n\t\t\t\t`error: invalid naming style \"${namingArg}\" (expected one of: ${NAMING_STYLES.join(\", \")})`,\n\t\t\t);\n\t\t\tprocess.exit(1);\n\t\t}\n\t\t// citty maps `--no-header` to `header === false`; `--header <text>` gives a string\n\t\tconst rawHeader = args.header as string | boolean | undefined;\n\t\tconst headerArg =\n\t\t\trawHeader === false || args[\"no-header\"] === true\n\t\t\t\t? false\n\t\t\t\t: typeof rawHeader === \"string\"\n\t\t\t\t\t? rawHeader.replaceAll(\"\\\\n\", \"\\n\")\n\t\t\t\t\t: file.options?.header;\n\t\tconst config: GenerateConfig = {\n\t\t\tinput,\n\t\t\toutput,\n\t\t\ttargets,\n\t\t\toptions: {\n\t\t\t\timportExtension:\n\t\t\t\t\t(args[\"import-extension\"] as ImportExtension | undefined) ??\n\t\t\t\t\tfile.options?.importExtension,\n\t\t\t\treadonlyArrays: args[\"readonly-arrays\"] ?? file.options?.readonlyArrays,\n\t\t\t\timportPaths: {\n\t\t\t\t\t...file.options?.importPaths,\n\t\t\t\t\t...(args[\"zod-import-path\"] ? { zod: args[\"zod-import-path\"] } : {}),\n\t\t\t\t\t...(args[\"valibot-import-path\"]\n\t\t\t\t\t\t? { valibot: args[\"valibot-import-path\"] }\n\t\t\t\t\t\t: {}),\n\t\t\t\t},\n\t\t\t\tdefaults: !(args[\"no-defaults\"] ?? false) && (file.options?.defaults ?? true),\n\t\t\t\tnaming: namingArg,\n\t\t\t\texportSuffix: args[\"export-suffix\"] ?? file.options?.exportSuffix,\n\t\t\t\theader: headerArg,\n\t\t\t},\n\t\t\tclean: args.clean ?? file.clean,\n\t\t\tdryRun: args[\"dry-run\"] ?? file.dryRun,\n\t\t\tverbose: args.verbose ?? file.verbose,\n\t\t};\n\t\ttry {\n\t\t\tawait generate(config);\n\t\t\tprocess.exit(0);\n\t\t} catch (e) {\n\t\t\tif (e instanceof Json2CodeError) {\n\t\t\t\tprocess.exit(1);\n\t\t\t}\n\t\t\tconsole.error(e);\n\t\t\tprocess.exit(1);\n\t\t}\n\t},\n});\n\nawait runMain(command);\n"],"mappings":"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"}
@@ -0,0 +1,53 @@
1
+ import path from "node:path";
2
+ import { access, constants } from "node:fs/promises";
3
+ import process from "node:process";
4
+ import { createJiti } from "jiti";
5
+ //#region src/core/config.ts
6
+ const CONFIG_CANDIDATES = [
7
+ "json2code.config.ts",
8
+ "json2code.config.mts",
9
+ "json2code.config.js",
10
+ "json2code.config.mjs",
11
+ "json2code.config.json"
12
+ ];
13
+ /**
14
+ * Load the config file: `explicitPath` if given, otherwise the first
15
+ * `json2code.config.{ts,mts,js,mjs,json}` found in `cwd`. Returns an empty
16
+ * config when nothing is found.
17
+ */
18
+ async function loadFileConfig(explicitPath, cwd = process.cwd()) {
19
+ const jiti = createJiti(import.meta.url);
20
+ if (explicitPath) {
21
+ const file = path.resolve(cwd, explicitPath);
22
+ return {
23
+ config: await readConfig(jiti, file),
24
+ path: file
25
+ };
26
+ }
27
+ for (const name of CONFIG_CANDIDATES) {
28
+ const file = path.resolve(cwd, name);
29
+ if (await accessible(file)) return {
30
+ config: await readConfig(jiti, file),
31
+ path: file
32
+ };
33
+ }
34
+ return { config: {} };
35
+ }
36
+ async function accessible(file) {
37
+ try {
38
+ await access(file, constants.R_OK);
39
+ return true;
40
+ } catch {
41
+ return false;
42
+ }
43
+ }
44
+ async function readConfig(jiti, file) {
45
+ const mod = await jiti.import(file);
46
+ const raw = typeof mod === "object" && mod !== null && "default" in mod ? mod.default : mod;
47
+ if (typeof raw !== "object" || raw === null) throw new TypeError(`Config file ${path.basename(file)} must export a config object.`);
48
+ return raw;
49
+ }
50
+ //#endregion
51
+ export { loadFileConfig };
52
+
53
+ //# sourceMappingURL=config.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config.mjs","names":[],"sources":["../../src/core/config.ts"],"sourcesContent":["import { access, constants } from \"node:fs/promises\";\nimport path from \"node:path\";\nimport process from \"node:process\";\nimport { createJiti } from \"jiti\";\nimport type { GenerateOptions, TargetSpec } from \"./types.js\";\n\n/** Everything a `json2code.config.*` file may set. All fields optional. */\nexport interface FileConfig {\n\treadonly input?: readonly string[];\n\treadonly output?: string;\n\treadonly targets?: readonly TargetSpec[];\n\treadonly options?: Partial<GenerateOptions>;\n\treadonly clean?: boolean;\n\treadonly dryRun?: boolean;\n\treadonly verbose?: boolean;\n}\n\nconst CONFIG_CANDIDATES = [\n\t\"json2code.config.ts\",\n\t\"json2code.config.mts\",\n\t\"json2code.config.js\",\n\t\"json2code.config.mjs\",\n\t\"json2code.config.json\",\n] as const;\n\n/**\n * Load the config file: `explicitPath` if given, otherwise the first\n * `json2code.config.{ts,mts,js,mjs,json}` found in `cwd`. Returns an empty\n * config when nothing is found.\n */\nexport async function loadFileConfig(\n\texplicitPath?: string,\n\tcwd: string = process.cwd(),\n): Promise<{ config: FileConfig; path?: string }> {\n\tconst jiti = createJiti(import.meta.url);\n\tif (explicitPath) {\n\t\tconst file = path.resolve(cwd, explicitPath);\n\t\treturn { config: await readConfig(jiti, file), path: file };\n\t}\n\tfor (const name of CONFIG_CANDIDATES) {\n\t\tconst file = path.resolve(cwd, name);\n\t\tif (await accessible(file)) {\n\t\t\treturn { config: await readConfig(jiti, file), path: file };\n\t\t}\n\t}\n\treturn { config: {} };\n}\n\nasync function accessible(file: string): Promise<boolean> {\n\ttry {\n\t\tawait access(file, constants.R_OK);\n\t\treturn true;\n\t} catch {\n\t\treturn false;\n\t}\n}\n\nasync function readConfig(jiti: ReturnType<typeof createJiti>, file: string): Promise<FileConfig> {\n\tconst mod: unknown = await jiti.import(file);\n\tconst raw = typeof mod === \"object\" && mod !== null && \"default\" in mod ? mod.default : mod;\n\tif (typeof raw !== \"object\" || raw === null) {\n\t\tthrow new TypeError(`Config file ${path.basename(file)} must export a config object.`);\n\t}\n\treturn raw;\n}\n"],"mappings":";;;;;AAiBA,MAAM,oBAAoB;CACzB;CACA;CACA;CACA;CACA;AACD;;;;;;AAOA,eAAsB,eACrB,cACA,MAAc,QAAQ,IAAI,GACuB;CACjD,MAAM,OAAO,WAAW,YAAY,GAAG;CACvC,IAAI,cAAc;EACjB,MAAM,OAAO,KAAK,QAAQ,KAAK,YAAY;EAC3C,OAAO;GAAE,QAAQ,MAAM,WAAW,MAAM,IAAI;GAAG,MAAM;EAAK;CAC3D;CACA,KAAK,MAAM,QAAQ,mBAAmB;EACrC,MAAM,OAAO,KAAK,QAAQ,KAAK,IAAI;EACnC,IAAI,MAAM,WAAW,IAAI,GACxB,OAAO;GAAE,QAAQ,MAAM,WAAW,MAAM,IAAI;GAAG,MAAM;EAAK;CAE5D;CACA,OAAO,EAAE,QAAQ,CAAC,EAAE;AACrB;AAEA,eAAe,WAAW,MAAgC;CACzD,IAAI;EACH,MAAM,OAAO,MAAM,UAAU,IAAI;EACjC,OAAO;CACR,QAAQ;EACP,OAAO;CACR;AACD;AAEA,eAAe,WAAW,MAAqC,MAAmC;CACjG,MAAM,MAAe,MAAM,KAAK,OAAO,IAAI;CAC3C,MAAM,MAAM,OAAO,QAAQ,YAAY,QAAQ,QAAQ,aAAa,MAAM,IAAI,UAAU;CACxF,IAAI,OAAO,QAAQ,YAAY,QAAQ,MACtC,MAAM,IAAI,UAAU,eAAe,KAAK,SAAS,IAAI,EAAE,8BAA8B;CAEtF,OAAO;AACR"}
@@ -0,0 +1,10 @@
1
+ import { Logger } from "../../diagnostics/logger.mjs";
2
+ import { SchemaFile } from "../types.mjs";
3
+ //#region src/core/discovery/index.d.ts
4
+ export interface DiscoveryResult {
5
+ readonly files: readonly SchemaFile[];
6
+ readonly rootDir: string;
7
+ }
8
+ export declare function discover(input: string | string[], cwd: string, logger: Logger): Promise<DiscoveryResult>;
9
+ //#endregion
10
+ //# sourceMappingURL=index.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.mts","names":[],"sources":["../../../src/core/discovery/index.ts"],"mappings":";;;iBASiB;WACP,gBAAgB;WAChB;;wBAGY,SACrB,0BACA,aACA,QAAQ,SACN,QAAQ"}
@@ -0,0 +1,83 @@
1
+ import { DiagnosticCode, Json2CodeError, createDiagnostic } from "../../diagnostics/errors.mjs";
2
+ import path from "node:path";
3
+ import { readFile, stat } from "node:fs/promises";
4
+ import fg from "fast-glob";
5
+ //#region src/core/discovery/index.ts
6
+ const GLOB_CHARS = /[*?[\]{}]/;
7
+ async function discover(input, cwd, logger) {
8
+ const entries = Array.isArray(input) ? input : [input];
9
+ const matched = [];
10
+ for (const entry of entries) matched.push(...await expandInput(entry, cwd));
11
+ const unique = [...new Set(matched)].toSorted();
12
+ if (unique.length === 0) throw new Json2CodeError([createDiagnostic(DiagnosticCode.FileNotFound, "error", `No JSON Schema files matched input: ${entries.join(", ")}`)]);
13
+ const rootDir = commonDir(unique);
14
+ logger.debug(`discovery rootDir=${rootDir}, ${unique.length} files`);
15
+ const files = [];
16
+ for (const abs of unique) {
17
+ const docId = toDocId(abs, rootDir);
18
+ const content = await readFile(abs, "utf8");
19
+ files.push({
20
+ path: abs,
21
+ docId,
22
+ content
23
+ });
24
+ }
25
+ return {
26
+ files,
27
+ rootDir
28
+ };
29
+ }
30
+ async function expandInput(entry, cwd) {
31
+ const abs = path.resolve(cwd, entry);
32
+ if (!GLOB_CHARS.test(entry)) {
33
+ const kind = await pathKind(abs);
34
+ if (kind === "dir") return fg("**/*.json", {
35
+ cwd: abs,
36
+ absolute: true,
37
+ onlyFiles: true,
38
+ ignore: ["**/node_modules/**"]
39
+ });
40
+ if (kind === "file") return [abs];
41
+ return [];
42
+ }
43
+ return fg(entry, {
44
+ cwd,
45
+ absolute: true,
46
+ onlyFiles: true,
47
+ ignore: ["**/node_modules/**"]
48
+ });
49
+ }
50
+ async function pathKind(p) {
51
+ try {
52
+ const s = await stat(p);
53
+ return s.isFile() ? "file" : s.isDirectory() ? "dir" : "missing";
54
+ } catch {
55
+ return "missing";
56
+ }
57
+ }
58
+ /** Longest common directory of a set of absolute paths. */
59
+ function commonDir(paths) {
60
+ const dirs = paths.map((p) => path.dirname(p).split(path.sep));
61
+ const first = dirs[0];
62
+ let i = 0;
63
+ while (i < first.length) {
64
+ const seg = first[i];
65
+ if (seg === void 0) break;
66
+ let ok = true;
67
+ for (let j = 1; j < dirs.length; j++) if (dirs[j][i] !== seg) {
68
+ ok = false;
69
+ break;
70
+ }
71
+ if (!ok) break;
72
+ i++;
73
+ }
74
+ const common = first.slice(0, i).join(path.sep);
75
+ return common === "" ? path.sep : common;
76
+ }
77
+ function toDocId(absPath, rootDir) {
78
+ return path.relative(rootDir, absPath).split(path.sep).join("/");
79
+ }
80
+ //#endregion
81
+ export { discover };
82
+
83
+ //# sourceMappingURL=index.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.mjs","names":[],"sources":["../../../src/core/discovery/index.ts"],"sourcesContent":["import { readFile, stat } from \"node:fs/promises\";\nimport path from \"node:path\";\nimport fg from \"fast-glob\";\nimport { createDiagnostic, DiagnosticCode, Json2CodeError } from \"../../diagnostics/errors.js\";\nimport type { Logger } from \"../../diagnostics/logger.js\";\nimport type { SchemaFile } from \"../types.js\";\n\nconst GLOB_CHARS = /[*?[\\]{}]/;\n\nexport interface DiscoveryResult {\n\treadonly files: readonly SchemaFile[];\n\treadonly rootDir: string;\n}\n\nexport async function discover(\n\tinput: string | string[],\n\tcwd: string,\n\tlogger: Logger,\n): Promise<DiscoveryResult> {\n\tconst entries = Array.isArray(input) ? input : [input];\n\tconst matched: string[] = [];\n\n\tfor (const entry of entries) {\n\t\tmatched.push(...(await expandInput(entry, cwd)));\n\t}\n\n\tconst unique = [...new Set(matched)].toSorted();\n\tif (unique.length === 0) {\n\t\tthrow new Json2CodeError([\n\t\t\tcreateDiagnostic(\n\t\t\t\tDiagnosticCode.FileNotFound,\n\t\t\t\t\"error\",\n\t\t\t\t`No JSON Schema files matched input: ${entries.join(\", \")}`,\n\t\t\t),\n\t\t]);\n\t}\n\n\tconst rootDir = commonDir(unique);\n\tlogger.debug(`discovery rootDir=${rootDir}, ${unique.length} files`);\n\n\tconst files: SchemaFile[] = [];\n\tfor (const abs of unique) {\n\t\tconst docId = toDocId(abs, rootDir);\n\t\tconst content = await readFile(abs, \"utf8\");\n\t\tfiles.push({ path: abs, docId, content });\n\t}\n\treturn { files, rootDir };\n}\n\nasync function expandInput(entry: string, cwd: string): Promise<string[]> {\n\tconst abs = path.resolve(cwd, entry);\n\tconst isGlob = GLOB_CHARS.test(entry);\n\tif (!isGlob) {\n\t\tconst kind = await pathKind(abs);\n\t\tif (kind === \"dir\") {\n\t\t\treturn fg(\"**/*.json\", {\n\t\t\t\tcwd: abs,\n\t\t\t\tabsolute: true,\n\t\t\t\tonlyFiles: true,\n\t\t\t\tignore: [\"**/node_modules/**\"],\n\t\t\t});\n\t\t}\n\t\tif (kind === \"file\") {\n\t\t\treturn [abs];\n\t\t}\n\t\treturn [];\n\t}\n\treturn fg(entry, { cwd, absolute: true, onlyFiles: true, ignore: [\"**/node_modules/**\"] });\n}\n\nasync function pathKind(p: string): Promise<\"file\" | \"dir\" | \"missing\"> {\n\ttry {\n\t\tconst s = await stat(p);\n\t\treturn s.isFile() ? \"file\" : s.isDirectory() ? \"dir\" : \"missing\";\n\t} catch {\n\t\treturn \"missing\";\n\t}\n}\n\n/** Longest common directory of a set of absolute paths. */\nfunction commonDir(paths: readonly string[]): string {\n\tconst dirs = paths.map(p => path.dirname(p).split(path.sep));\n\tconst first = dirs[0]!;\n\tlet i = 0;\n\twhile (i < first.length) {\n\t\tconst seg = first[i];\n\t\tif (seg === undefined) {\n\t\t\tbreak;\n\t\t}\n\t\tlet ok = true;\n\t\tfor (let j = 1; j < dirs.length; j++) {\n\t\t\tif (dirs[j]![i] !== seg) {\n\t\t\t\tok = false;\n\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\t\tif (!ok) {\n\t\t\tbreak;\n\t\t}\n\t\ti++;\n\t}\n\tconst common = first.slice(0, i).join(path.sep);\n\treturn common === \"\" ? path.sep : common;\n}\n\nfunction toDocId(absPath: string, rootDir: string): string {\n\tconst rel = path.relative(rootDir, absPath);\n\treturn rel.split(path.sep).join(\"/\");\n}\n"],"mappings":";;;;;AAOA,MAAM,aAAa;AAOnB,eAAsB,SACrB,OACA,KACA,QAC2B;CAC3B,MAAM,UAAU,MAAM,QAAQ,KAAK,IAAI,QAAQ,CAAC,KAAK;CACrD,MAAM,UAAoB,CAAC;CAE3B,KAAK,MAAM,SAAS,SACnB,QAAQ,KAAK,GAAI,MAAM,YAAY,OAAO,GAAG,CAAE;CAGhD,MAAM,SAAS,CAAC,GAAG,IAAI,IAAI,OAAO,CAAC,CAAC,CAAC,SAAS;CAC9C,IAAI,OAAO,WAAW,GACrB,MAAM,IAAI,eAAe,CACxB,iBACC,eAAe,cACf,SACA,uCAAuC,QAAQ,KAAK,IAAI,GACzD,CACD,CAAC;CAGF,MAAM,UAAU,UAAU,MAAM;CAChC,OAAO,MAAM,qBAAqB,QAAQ,IAAI,OAAO,OAAO,OAAO;CAEnE,MAAM,QAAsB,CAAC;CAC7B,KAAK,MAAM,OAAO,QAAQ;EACzB,MAAM,QAAQ,QAAQ,KAAK,OAAO;EAClC,MAAM,UAAU,MAAM,SAAS,KAAK,MAAM;EAC1C,MAAM,KAAK;GAAE,MAAM;GAAK;GAAO;EAAQ,CAAC;CACzC;CACA,OAAO;EAAE;EAAO;CAAQ;AACzB;AAEA,eAAe,YAAY,OAAe,KAAgC;CACzE,MAAM,MAAM,KAAK,QAAQ,KAAK,KAAK;CAEnC,IAAI,CADW,WAAW,KAAK,KACrB,GAAG;EACZ,MAAM,OAAO,MAAM,SAAS,GAAG;EAC/B,IAAI,SAAS,OACZ,OAAO,GAAG,aAAa;GACtB,KAAK;GACL,UAAU;GACV,WAAW;GACX,QAAQ,CAAC,oBAAoB;EAC9B,CAAC;EAEF,IAAI,SAAS,QACZ,OAAO,CAAC,GAAG;EAEZ,OAAO,CAAC;CACT;CACA,OAAO,GAAG,OAAO;EAAE;EAAK,UAAU;EAAM,WAAW;EAAM,QAAQ,CAAC,oBAAoB;CAAE,CAAC;AAC1F;AAEA,eAAe,SAAS,GAAgD;CACvE,IAAI;EACH,MAAM,IAAI,MAAM,KAAK,CAAC;EACtB,OAAO,EAAE,OAAO,IAAI,SAAS,EAAE,YAAY,IAAI,QAAQ;CACxD,QAAQ;EACP,OAAO;CACR;AACD;;AAGA,SAAS,UAAU,OAAkC;CACpD,MAAM,OAAO,MAAM,KAAI,MAAK,KAAK,QAAQ,CAAC,CAAC,CAAC,MAAM,KAAK,GAAG,CAAC;CAC3D,MAAM,QAAQ,KAAK;CACnB,IAAI,IAAI;CACR,OAAO,IAAI,MAAM,QAAQ;EACxB,MAAM,MAAM,MAAM;EAClB,IAAI,QAAQ,KAAA,GACX;EAED,IAAI,KAAK;EACT,KAAK,IAAI,IAAI,GAAG,IAAI,KAAK,QAAQ,KAChC,IAAI,KAAK,EAAE,CAAE,OAAO,KAAK;GACxB,KAAK;GACL;EACD;EAED,IAAI,CAAC,IACJ;EAED;CACD;CACA,MAAM,SAAS,MAAM,MAAM,GAAG,CAAC,CAAC,CAAC,KAAK,KAAK,GAAG;CAC9C,OAAO,WAAW,KAAK,KAAK,MAAM;AACnC;AAEA,SAAS,QAAQ,SAAiB,SAAyB;CAE1D,OADY,KAAK,SAAS,SAAS,OAC1B,CAAC,CAAC,MAAM,KAAK,GAAG,CAAC,CAAC,KAAK,GAAG;AACpC"}
@@ -0,0 +1,11 @@
1
+ import { Diagnostic } from "../diagnostics/errors.mjs";
2
+ import { GenerateConfig } from "./types.mjs";
3
+ //#region src/core/generate.d.ts
4
+ export interface GenerateResult {
5
+ readonly written: readonly string[];
6
+ readonly deleted: readonly string[];
7
+ readonly diagnostics: readonly Diagnostic[];
8
+ }
9
+ export declare function generate(config: GenerateConfig): Promise<GenerateResult>;
10
+ //#endregion
11
+ //# sourceMappingURL=generate.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"generate.d.mts","names":[],"sources":["../../src/core/generate.ts"],"mappings":";;;iBA0BiB;WACP;WACA;WACA,sBAAsB;;wBAKV,SAAS,QAAQ,iBAAiB,QAAQ"}
@@ -0,0 +1,89 @@
1
+ import { DiagnosticCode, Json2CodeError, createDiagnostic, formatDiagnostic, partitionDiagnostics } from "../diagnostics/errors.mjs";
2
+ import { createLogger } from "../diagnostics/logger.mjs";
3
+ import { resolveTarget } from "../targets/index.mjs";
4
+ import { discover } from "./discovery/index.mjs";
5
+ import { Linker } from "./linker/index.mjs";
6
+ import { DEFAULT_OPTIONS, NAMING_STYLES } from "./types.mjs";
7
+ import { JsonSchemaWalker } from "./walker/json-schema.mjs";
8
+ import { writeFiles } from "./writer/index.mjs";
9
+ import path from "node:path";
10
+ //#region src/core/generate.ts
11
+ const defaultWalker = new JsonSchemaWalker();
12
+ async function generate(config) {
13
+ const logger = createLogger(config.verbose ?? false);
14
+ const cwd = process.cwd();
15
+ const output = path.resolve(cwd, config.output);
16
+ const options = mergeOptions(config.options);
17
+ const allDiagnostics = [];
18
+ const allFiles = [];
19
+ const { files, rootDir } = await discover(config.input, cwd, logger);
20
+ if (files.length === 0) throw new Json2CodeError([createDiagnostic(DiagnosticCode.FileNotFound, "error", "No JSON Schema files matched the input.")]);
21
+ const resolved = {
22
+ ...options,
23
+ output,
24
+ rootDir
25
+ };
26
+ const walkCache = /* @__PURE__ */ new Map();
27
+ for (const spec of normalizeTargets(config.targets)) {
28
+ const def = resolveTarget(spec);
29
+ const walker = def.walker ?? defaultWalker;
30
+ let docsPromise = walkCache.get(walker);
31
+ if (!docsPromise) {
32
+ const wctx = {
33
+ logger,
34
+ options: resolved,
35
+ diagnostics: allDiagnostics
36
+ };
37
+ docsPromise = walker.walk(files, wctx);
38
+ walkCache.set(walker, docsPromise);
39
+ }
40
+ const docs = await docsPromise;
41
+ const mapOutput = def.mapOutput ?? ((docId) => Linker.defaultMapOutput(def.name, docId));
42
+ const pctx = new Linker({
43
+ options: resolved,
44
+ targetName: def.name,
45
+ mapOutput,
46
+ diagnostics: allDiagnostics
47
+ }).createPrintContext(logger, allDiagnostics);
48
+ const printed = await def.printer.print(docs, pctx);
49
+ for (const f of printed) allFiles.push(f);
50
+ }
51
+ const { errors, warnings } = partitionDiagnostics(allDiagnostics);
52
+ for (const w of warnings) logger.warn(formatDiagnostic(w));
53
+ if (errors.length > 0) {
54
+ for (const e of errors) logger.error(formatDiagnostic(e));
55
+ throw new Json2CodeError(errors);
56
+ }
57
+ return {
58
+ ...await writeFiles(allFiles, {
59
+ output,
60
+ dryRun: config.dryRun ?? false,
61
+ clean: config.clean ?? false,
62
+ logger,
63
+ header: options.header
64
+ }),
65
+ diagnostics: allDiagnostics
66
+ };
67
+ }
68
+ function normalizeTargets(targets) {
69
+ return targets.length > 0 ? targets : ["typescript"];
70
+ }
71
+ function mergeOptions(opts) {
72
+ const naming = opts?.naming ?? DEFAULT_OPTIONS.naming;
73
+ if (!NAMING_STYLES.includes(naming)) throw new TypeError(`Invalid naming style: ${JSON.stringify(naming)} (expected one of ${NAMING_STYLES.join(", ")})`);
74
+ const defined = Object.fromEntries(Object.entries(opts ?? {}).filter(([, v]) => v !== void 0));
75
+ const importPaths = {
76
+ ...DEFAULT_OPTIONS.importPaths,
77
+ ...defined.importPaths
78
+ };
79
+ return {
80
+ ...DEFAULT_OPTIONS,
81
+ ...defined,
82
+ importPaths,
83
+ naming
84
+ };
85
+ }
86
+ //#endregion
87
+ export { generate };
88
+
89
+ //# sourceMappingURL=generate.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"generate.mjs","names":[],"sources":["../../src/core/generate.ts"],"sourcesContent":["import path from \"node:path\";\nimport {\n\tcreateDiagnostic,\n\tDiagnosticCode,\n\tformatDiagnostic,\n\tJson2CodeError,\n\tpartitionDiagnostics,\n\ttype Diagnostic,\n} from \"../diagnostics/errors.js\";\nimport { createLogger } from \"../diagnostics/logger.js\";\nimport { resolveTarget } from \"../targets/index.js\";\nimport { discover } from \"./discovery/index.js\";\nimport type { SourceDoc } from \"./ir/types.js\";\nimport { Linker } from \"./linker/index.js\";\nimport type {\n\tGenerateConfig,\n\tGenerateOptions,\n\tPrintedFile,\n\tResolvedOptions,\n\tTargetSpec,\n} from \"./types.js\";\nimport { DEFAULT_OPTIONS, NAMING_STYLES } from \"./types.js\";\nimport type { AbstractWalker, WalkerContext } from \"./walker/abstract.js\";\nimport { JsonSchemaWalker } from \"./walker/json-schema.js\";\nimport { writeFiles } from \"./writer/index.js\";\n\nexport interface GenerateResult {\n\treadonly written: readonly string[];\n\treadonly deleted: readonly string[];\n\treadonly diagnostics: readonly Diagnostic[];\n}\n\nconst defaultWalker = new JsonSchemaWalker();\n\nexport async function generate(config: GenerateConfig): Promise<GenerateResult> {\n\tconst logger = createLogger(config.verbose ?? false);\n\tconst cwd = process.cwd();\n\tconst output = path.resolve(cwd, config.output);\n\tconst options = mergeOptions(config.options);\n\n\tconst allDiagnostics: Diagnostic[] = [];\n\tconst allFiles: PrintedFile[] = [];\n\n\tconst { files, rootDir } = await discover(config.input, cwd, logger);\n\tif (files.length === 0) {\n\t\tthrow new Json2CodeError([\n\t\t\tcreateDiagnostic(\n\t\t\t\tDiagnosticCode.FileNotFound,\n\t\t\t\t\"error\",\n\t\t\t\t\"No JSON Schema files matched the input.\",\n\t\t\t),\n\t\t]);\n\t}\n\tconst resolved: ResolvedOptions = { ...options, output, rootDir };\n\n\tconst walkCache = new Map<AbstractWalker, Promise<SourceDoc[]>>();\n\tfor (const spec of normalizeTargets(config.targets)) {\n\t\tconst def = resolveTarget(spec);\n\t\tconst walker = def.walker ?? defaultWalker;\n\t\t// share one walk per walker instance: multi-target runs don't duplicate diagnostics\n\t\tlet docsPromise = walkCache.get(walker);\n\t\tif (!docsPromise) {\n\t\t\tconst wctx: WalkerContext = { logger, options: resolved, diagnostics: allDiagnostics };\n\t\t\tdocsPromise = walker.walk(files, wctx);\n\t\t\twalkCache.set(walker, docsPromise);\n\t\t}\n\t\tconst docs = await docsPromise;\n\n\t\tconst mapOutput =\n\t\t\tdef.mapOutput ?? ((docId: string) => Linker.defaultMapOutput(def.name, docId));\n\t\tconst linker = new Linker({\n\t\t\toptions: resolved,\n\t\t\ttargetName: def.name,\n\t\t\tmapOutput,\n\t\t\tdiagnostics: allDiagnostics,\n\t\t});\n\t\tconst pctx = linker.createPrintContext(logger, allDiagnostics);\n\t\tconst printed = await def.printer.print(docs, pctx);\n\t\tfor (const f of printed) {\n\t\t\tallFiles.push(f);\n\t\t}\n\t}\n\n\tconst { errors, warnings } = partitionDiagnostics(allDiagnostics);\n\tfor (const w of warnings) {\n\t\tlogger.warn(formatDiagnostic(w));\n\t}\n\tif (errors.length > 0) {\n\t\tfor (const e of errors) {\n\t\t\tlogger.error(formatDiagnostic(e));\n\t\t}\n\t\tthrow new Json2CodeError(errors);\n\t}\n\n\tconst result = await writeFiles(allFiles, {\n\t\toutput,\n\t\tdryRun: config.dryRun ?? false,\n\t\tclean: config.clean ?? false,\n\t\tlogger,\n\t\theader: options.header,\n\t});\n\treturn { ...result, diagnostics: allDiagnostics };\n}\n\nfunction normalizeTargets(targets: TargetSpec[]): TargetSpec[] {\n\treturn targets.length > 0 ? targets : [\"typescript\"];\n}\n\nfunction mergeOptions(opts?: Partial<GenerateOptions>): GenerateOptions {\n\tconst naming = opts?.naming ?? DEFAULT_OPTIONS.naming;\n\tif (!NAMING_STYLES.includes(naming)) {\n\t\tthrow new TypeError(\n\t\t\t`Invalid naming style: ${JSON.stringify(naming)} (expected one of ${NAMING_STYLES.join(\", \")})`,\n\t\t);\n\t}\n\t// drop explicitly-undefined keys so they don't override defaults when spreading\n\tconst defined = Object.fromEntries(\n\t\tObject.entries(opts ?? {}).filter(([, v]) => v !== undefined),\n\t) as Partial<GenerateOptions>;\n\t// importPaths is nested: merge per-target keys so a partial object keeps defaults\n\tconst importPaths = { ...DEFAULT_OPTIONS.importPaths, ...defined.importPaths };\n\treturn {\n\t\t...DEFAULT_OPTIONS,\n\t\t...defined,\n\t\timportPaths,\n\t\tnaming,\n\t};\n}\n"],"mappings":";;;;;;;;;;AAgCA,MAAM,gBAAgB,IAAI,iBAAiB;AAE3C,eAAsB,SAAS,QAAiD;CAC/E,MAAM,SAAS,aAAa,OAAO,WAAW,KAAK;CACnD,MAAM,MAAM,QAAQ,IAAI;CACxB,MAAM,SAAS,KAAK,QAAQ,KAAK,OAAO,MAAM;CAC9C,MAAM,UAAU,aAAa,OAAO,OAAO;CAE3C,MAAM,iBAA+B,CAAC;CACtC,MAAM,WAA0B,CAAC;CAEjC,MAAM,EAAE,OAAO,YAAY,MAAM,SAAS,OAAO,OAAO,KAAK,MAAM;CACnE,IAAI,MAAM,WAAW,GACpB,MAAM,IAAI,eAAe,CACxB,iBACC,eAAe,cACf,SACA,yCACD,CACD,CAAC;CAEF,MAAM,WAA4B;EAAE,GAAG;EAAS;EAAQ;CAAQ;CAEhE,MAAM,4BAAY,IAAI,IAA0C;CAChE,KAAK,MAAM,QAAQ,iBAAiB,OAAO,OAAO,GAAG;EACpD,MAAM,MAAM,cAAc,IAAI;EAC9B,MAAM,SAAS,IAAI,UAAU;EAE7B,IAAI,cAAc,UAAU,IAAI,MAAM;EACtC,IAAI,CAAC,aAAa;GACjB,MAAM,OAAsB;IAAE;IAAQ,SAAS;IAAU,aAAa;GAAe;GACrF,cAAc,OAAO,KAAK,OAAO,IAAI;GACrC,UAAU,IAAI,QAAQ,WAAW;EAClC;EACA,MAAM,OAAO,MAAM;EAEnB,MAAM,YACL,IAAI,eAAe,UAAkB,OAAO,iBAAiB,IAAI,MAAM,KAAK;EAO7E,MAAM,OAAO,IANM,OAAO;GACzB,SAAS;GACT,YAAY,IAAI;GAChB;GACA,aAAa;EACd,CACkB,CAAC,CAAC,mBAAmB,QAAQ,cAAc;EAC7D,MAAM,UAAU,MAAM,IAAI,QAAQ,MAAM,MAAM,IAAI;EAClD,KAAK,MAAM,KAAK,SACf,SAAS,KAAK,CAAC;CAEjB;CAEA,MAAM,EAAE,QAAQ,aAAa,qBAAqB,cAAc;CAChE,KAAK,MAAM,KAAK,UACf,OAAO,KAAK,iBAAiB,CAAC,CAAC;CAEhC,IAAI,OAAO,SAAS,GAAG;EACtB,KAAK,MAAM,KAAK,QACf,OAAO,MAAM,iBAAiB,CAAC,CAAC;EAEjC,MAAM,IAAI,eAAe,MAAM;CAChC;CASA,OAAO;EAAE,GAAG,MAPS,WAAW,UAAU;GACzC;GACA,QAAQ,OAAO,UAAU;GACzB,OAAO,OAAO,SAAS;GACvB;GACA,QAAQ,QAAQ;EACjB,CAAC;EACmB,aAAa;CAAe;AACjD;AAEA,SAAS,iBAAiB,SAAqC;CAC9D,OAAO,QAAQ,SAAS,IAAI,UAAU,CAAC,YAAY;AACpD;AAEA,SAAS,aAAa,MAAkD;CACvE,MAAM,SAAS,MAAM,UAAU,gBAAgB;CAC/C,IAAI,CAAC,cAAc,SAAS,MAAM,GACjC,MAAM,IAAI,UACT,yBAAyB,KAAK,UAAU,MAAM,EAAE,oBAAoB,cAAc,KAAK,IAAI,EAAE,EAC9F;CAGD,MAAM,UAAU,OAAO,YACtB,OAAO,QAAQ,QAAQ,CAAC,CAAC,CAAC,CAAC,QAAQ,GAAG,OAAO,MAAM,KAAA,CAAS,CAC7D;CAEA,MAAM,cAAc;EAAE,GAAG,gBAAgB;EAAa,GAAG,QAAQ;CAAY;CAC7E,OAAO;EACN,GAAG;EACH,GAAG;EACH;EACA;CACD;AACD"}