@abapify/ts-xsd 0.4.0 → 0.4.3

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
@@ -1,37 +1,32 @@
1
1
  # ts-xsd
2
2
 
3
- [![version](https://img.shields.io/github/package-json/v/abapify/adt-cli?filename=packages/ts-xsd/package.json)](https://github.com/abapify/adt-cli/pkgs/npm/%40abapify%2Fts-xsd)
3
+ [![npm version](https://img.shields.io/npm/v/@abapify/ts-xsd)](https://www.npmjs.com/package/@abapify/ts-xsd)
4
+ [![license](https://img.shields.io/npm/l/@abapify/ts-xsd)](https://github.com/abapify/adt-cli/blob/main/LICENSE)
4
5
 
5
- **Core XSD parser, builder, and type inference** with **1:1 TypeScript representation** of W3C XML Schema Definition (XSD) 1.1.
6
+ **W3C XSD 1.1 parser, builder, and TypeScript type inference** — a 1:1 TypeScript representation of XML Schema Definition.
6
7
 
7
- ## Overview
8
+ ## Why
8
9
 
9
- `ts-xsd` is a comprehensive TypeScript library for working with W3C XSD schemas. It provides:
10
+ Working with XSD schemas in TypeScript usually means hand-maintaining types that drift from the source of truth. `ts-xsd` closes that gap:
10
11
 
11
- | Module | Purpose |
12
- | ----------- | ------------------------------------------------------------------- |
13
- | **xsd** | Parse XSD files into typed `Schema` objects, build XSD from objects |
14
- | **infer** | Compile-time TypeScript type inference from schema literals |
15
- | **xml** | Parse/build XML documents using schema definitions |
16
- | **codegen** | Generate TypeScript schema literals from XSD files |
17
-
18
- ### Key Features
19
-
20
- - **Pure W3C XSD 1.1** - Types match the official [XMLSchema.xsd](https://www.w3.org/TR/xmlschema11-1/XMLSchema.xsd) exactly
21
- - **Full roundtrip** - `XSD → Schema → XSD` with semantic preservation
22
- - **Type inference** - `InferSchema<T>` extracts TypeScript types from schema literals
23
- - **Shared types** - Cross-schema type resolution via `$imports`
24
- - **Tree-shakeable** - Only import what you need
25
- - **Zero runtime dependencies** - Only `@xmldom/xmldom` for DOM parsing
12
+ - **Parse** any W3C XSD 1.1 schema into a typed `Schema` object — no invented properties, no shortcuts.
13
+ - **Infer** TypeScript types at compile time from schema literals via `InferSchema<T>`.
14
+ - **Generate** TypeScript interfaces at build time when schemas are too complex for compile-time inference.
15
+ - **Round-trip** `XSD → Schema → XSD` with semantic preservation.
16
+ - **Parse/build XML** documents against a schema definition.
26
17
 
27
18
  ## Installation
28
19
 
29
20
  ```bash
30
21
  npm install @abapify/ts-xsd
31
22
  # or
23
+ pnpm add @abapify/ts-xsd
24
+ # or
32
25
  bun add @abapify/ts-xsd
33
26
  ```
34
27
 
28
+ > **Runtime dependency:** `@xmldom/xmldom` (DOM parsing). `ts-morph` and `zod` are required for codegen features.
29
+
35
30
  ## Quick Start
36
31
 
37
32
  ### Parse and Build XSD
@@ -61,7 +56,6 @@ const xsd = buildXsd(schema, { pretty: true });
61
56
  ```typescript
62
57
  import type { InferSchema } from '@abapify/ts-xsd';
63
58
 
64
- // Define schema as const literal
65
59
  const personSchema = {
66
60
  element: [{ name: 'person', type: 'PersonType' }],
67
61
  complexType: [
@@ -77,22 +71,38 @@ const personSchema = {
77
71
  ],
78
72
  } as const;
79
73
 
80
- // Infer TypeScript type at compile time
74
+ // Infer TypeScript type at compile time — no runtime cost
81
75
  type Person = InferSchema<typeof personSchema>;
82
- // Result: { name: string; age?: number }
76
+ // => { name: string; age?: number }
83
77
  ```
84
78
 
85
- ### Parse XML with Schema
79
+ ### Parse and Build XML with a Schema
86
80
 
87
81
  ```typescript
88
82
  import { parseXml, buildXml } from '@abapify/ts-xsd';
89
83
 
90
84
  const xml = `<person><name>John</name><age>30</age></person>`;
91
85
  const data = parseXml(personSchema, xml);
92
- // data: { name: 'John', age: 30 }
86
+ // => { person: { name: 'John', age: 30 } }
87
+
88
+ const rebuilt = buildXml(personSchema, data, { xmlDecl: false });
89
+ // => <person><name>John</name><age>30</age></person>
90
+ ```
91
+
92
+ ### Generate TypeScript Interfaces from XSD
93
+
94
+ ```typescript
95
+ import { parseXsd, generateInterfaces } from '@abapify/ts-xsd';
96
+
97
+ const schema = parseXsd(xsdContent);
98
+ const { code } = generateInterfaces(schema, {
99
+ flatten: true, // Inline all nested types into one
100
+ addJsDoc: true, // Add JSDoc comments
101
+ rootTypeName: 'MySchema',
102
+ });
93
103
 
94
- const rebuilt = buildXml(personSchema, data);
95
- // rebuilt: <person><name>John</name><age>30</age></person>
104
+ // code is a ready-to-write .ts file:
105
+ // export type MySchema = { person: { name: string; age?: number } };
96
106
  ```
97
107
 
98
108
  ## API Reference
@@ -100,39 +110,49 @@ const rebuilt = buildXml(personSchema, data);
100
110
  ### XSD Module
101
111
 
102
112
  ```typescript
103
- import { parseXsd, buildXsd, type Schema } from '@abapify/ts-xsd';
113
+ import {
114
+ parseXsd,
115
+ buildXsd,
116
+ resolveImports,
117
+ loadSchema,
118
+ type Schema,
119
+ } from '@abapify/ts-xsd';
104
120
  ```
105
121
 
106
122
  #### `parseXsd(xsd: string): Schema`
107
123
 
108
- Parse an XSD XML string into a typed Schema object.
109
-
110
- ```typescript
111
- const schema = parseXsd(xsdString);
112
- console.log(schema.targetNamespace);
113
- console.log(schema.element?.[0].name);
114
- ```
124
+ Parse an XSD XML string into a typed `Schema` object.
115
125
 
116
126
  #### `buildXsd(schema: Schema, options?: BuildOptions): string`
117
127
 
118
- Build an XSD XML string from a Schema object.
128
+ Build an XSD XML string from a `Schema` object.
119
129
 
120
130
  ```typescript
121
131
  const xsd = buildXsd(schema, {
122
- prefix: 'xsd', // Namespace prefix (default: 'xs')
132
+ prefix: 'xs', // Namespace prefix (default: 'xs')
123
133
  pretty: true, // Pretty print (default: true)
124
- indent: ' ', // Indentation (default: ' ')
134
+ indent: ' ', // Indentation string
125
135
  });
126
136
  ```
127
137
 
128
- #### `resolveImports(schema: Schema, resolver: (location: string) => Schema): Schema`
138
+ #### `resolveImports(schema, availableSchemas): Schema`
129
139
 
130
- Resolve and link imported schemas for cross-schema type resolution.
140
+ Resolve and link imported schemas by matching `import.schemaLocation` to `$filename`. Returns a new schema with `$imports` populated.
131
141
 
132
142
  ```typescript
133
- const linkedSchema = resolveImports(schema, (location) => {
134
- return parseXsd(fs.readFileSync(location, 'utf-8'));
135
- });
143
+ const base = { ...parseXsd(baseXsd), $filename: 'base.xsd' };
144
+ const orders = { ...parseXsd(ordersXsd), $filename: 'orders.xsd' };
145
+
146
+ const linked = resolveImports(orders, [base]);
147
+ // linked.$imports = [base]
148
+ ```
149
+
150
+ #### `loadSchema(schemaPath: string, options?: LoaderOptions): Schema`
151
+
152
+ Load and parse an XSD file from disk. Synchronous. Set `autoLink: true` to automatically resolve imports, or `autoResolve: true` to merge everything into one schema.
153
+
154
+ ```typescript
155
+ const schema = loadSchema('/path/to/schema.xsd', { autoLink: true });
136
156
  ```
137
157
 
138
158
  ### Infer Module
@@ -143,15 +163,11 @@ import type { InferSchema, InferElement, SchemaLike } from '@abapify/ts-xsd';
143
163
 
144
164
  #### `InferSchema<T>`
145
165
 
146
- Infer TypeScript type from a schema literal. Returns union of all root element types.
147
-
148
- ```typescript
149
- type Data = InferSchema<typeof mySchema>;
150
- ```
166
+ Infer a TypeScript type from a schema literal (`as const`). Returns a union of all root element types.
151
167
 
152
- #### `InferElement<T, ElementName>`
168
+ #### `InferElement<T, Name>`
153
169
 
154
- Infer type for a specific element by name.
170
+ Infer the type for a specific root element by name.
155
171
 
156
172
  ```typescript
157
173
  type Person = InferElement<typeof schema, 'person'>;
@@ -174,13 +190,13 @@ type Person = InferElement<typeof schema, 'person'>;
174
190
  import { parseXml, buildXml } from '@abapify/ts-xsd';
175
191
  ```
176
192
 
177
- #### `parseXml<T>(schema: SchemaLike, xml: string): T`
193
+ #### `parseXml<T>(schema, xml: string): T`
178
194
 
179
- Parse XML string using schema definition.
195
+ Parse an XML string into a typed object using a schema definition.
180
196
 
181
- #### `buildXml<T>(schema: SchemaLike, data: T): string`
197
+ #### `buildXml<T>(schema, data: T, options?: XmlBuildOptions): string`
182
198
 
183
- Build XML string from data using schema definition.
199
+ Build an XML string from a typed object using a schema definition. Defaults to `xmlDecl: true` (includes `<?xml ...?>` declaration); pass `{ xmlDecl: false }` for bare output.
184
200
 
185
201
  ### Codegen Module
186
202
 
@@ -188,41 +204,88 @@ Build XML string from data using schema definition.
188
204
  import { generateSchemaLiteral, generateInterfaces } from '@abapify/ts-xsd';
189
205
  ```
190
206
 
191
- #### `generateSchemaLiteral(xsd: string, options?: GenerateOptions): string`
207
+ #### `generateSchemaLiteral(xsd: string, options?): string`
192
208
 
193
- Generate TypeScript schema literal from XSD content.
209
+ Generate a TypeScript `as const` schema literal from XSD content — for use with `InferSchema<T>`.
194
210
 
195
211
  ```typescript
196
212
  const code = generateSchemaLiteral(xsdContent, {
197
213
  name: 'PersonSchema',
198
- features: { $xmlns: true, $imports: true },
214
+ features: { $xmlns: true, $filename: true },
199
215
  exclude: ['annotation'],
200
216
  });
201
- // export default { ... } as const;
217
+ // export const PersonSchema = { ... } as const;
202
218
  ```
203
219
 
204
- #### `generateInterfaces(schema: Schema, options?: GenerateInterfacesOptions): GenerateInterfacesResult`
220
+ #### `generateInterfaces(schema: Schema, options?): { code: string }`
205
221
 
206
- Generate TypeScript interfaces from parsed schema. Returns an object with `code` property containing the generated TypeScript code.
222
+ Generate TypeScript interfaces from a parsed schema. Useful when schemas are too complex for compile-time `InferSchema<T>` (TypeScript TS2589 recursion limits).
207
223
 
208
224
  ```typescript
209
225
  const { code } = generateInterfaces(schema, {
210
226
  flatten: true, // Inline all nested types (default: false)
211
- addJsDoc: true, // Add JSDoc comments
212
- rootTypeName: 'MySchema', // Custom root type name
227
+ addJsDoc: true, // JSDoc comments
228
+ rootTypeName: 'MySchema',
229
+ });
230
+ ```
231
+
232
+ | Option | Type | Default | Description |
233
+ | -------------- | --------- | ------- | -------------------------------- |
234
+ | `flatten` | `boolean` | `false` | Inline all nested types into one |
235
+ | `addJsDoc` | `boolean` | `false` | Add JSDoc comments |
236
+ | `rootTypeName` | `string` | auto | Custom name for the root type |
237
+
238
+ ### CLI
239
+
240
+ The package ships a CLI for code generation:
241
+
242
+ ```bash
243
+ # Config-based (recommended) — uses ts-xsd.config.ts in cwd
244
+ npx ts-xsd codegen
245
+
246
+ # Single-file mode
247
+ npx ts-xsd codegen person.xsd
248
+ npx ts-xsd codegen person.xsd ./generated/person-schema.ts
249
+ npx ts-xsd codegen person.xsd --name=PersonSchema
250
+ ```
251
+
252
+ ### Config-Based Generation
253
+
254
+ For multi-schema projects, use the composable generator system:
255
+
256
+ ```typescript
257
+ import {
258
+ defineConfig,
259
+ rawSchema,
260
+ interfaces,
261
+ } from '@abapify/ts-xsd/generators';
262
+
263
+ export default defineConfig({
264
+ sources: {
265
+ base: {
266
+ xsdDir: 'schemas',
267
+ outputDir: 'src/generated',
268
+ schemas: ['base'],
269
+ },
270
+ orders: {
271
+ xsdDir: 'schemas',
272
+ outputDir: 'src/generated',
273
+ schemas: ['orders'],
274
+ autoLink: true,
275
+ },
276
+ },
277
+ generators: [rawSchema(), interfaces({ flatten: true })],
213
278
  });
214
279
  ```
215
280
 
216
281
  ## Schema Structure
217
282
 
218
- The `Schema` type is a 1:1 TypeScript representation of W3C XSD:
283
+ The `Schema` type is a 1:1 TypeScript representation of W3C XSD. Non-W3C extension properties are prefixed with `$`:
219
284
 
220
285
  ```typescript
221
286
  interface Schema {
222
- // Namespace
223
287
  targetNamespace?: string;
224
288
  elementFormDefault?: 'qualified' | 'unqualified';
225
- attributeFormDefault?: 'qualified' | 'unqualified';
226
289
 
227
290
  // Composition
228
291
  import?: Import[];
@@ -236,171 +299,40 @@ interface Schema {
236
299
  attributeGroup?: NamedAttributeGroup[];
237
300
 
238
301
  // Extensions (non-W3C, prefixed with $)
239
- $xmlns?: { [prefix: string]: string };
240
- $imports?: Schema[]; // Resolved imported schemas
241
- $filename?: string; // Source filename
302
+ $xmlns?: { [prefix: string]: string }; // Namespace prefix mappings
303
+ $imports?: Schema[]; // Linked imported schemas
304
+ $filename?: string; // Source filename (for round-trip)
242
305
  }
243
306
  ```
244
307
 
245
308
  ### Cross-Schema Type Resolution
246
309
 
247
- Link schemas together for cross-schema type resolution:
248
-
249
- ```typescript
250
- const adtcore = parseXsd(adtcoreXsd);
251
- const classes = parseXsd(classesXsd);
252
-
253
- // Link schemas via $imports
254
- const linkedClasses = {
255
- ...classes,
256
- $imports: [adtcore],
257
- };
258
-
259
- // Now InferSchema can resolve types from adtcore
260
- type AbapClass = InferSchema<typeof linkedClasses>;
261
- ```
262
-
263
- ## Type Inference Deep Dive
264
-
265
- ### How It Works
266
-
267
- The type inference system uses TypeScript's conditional types to:
268
-
269
- 1. **Find root elements** - Extract element declarations from schema
270
- 2. **Resolve type references** - Look up `complexType` and `simpleType` by name
271
- 3. **Handle inheritance** - Process `complexContent/extension` for type inheritance
272
- 4. **Map XSD to TS** - Convert XSD types to TypeScript equivalents
273
- 5. **Handle optionality** - `minOccurs="0"` → optional property
274
- 6. **Handle arrays** - `maxOccurs="unbounded"` → array type
275
-
276
- ### Example: Complex Schema
310
+ Link schemas via `$imports` to resolve types across schema boundaries:
277
311
 
278
312
  ```typescript
279
- const schema = {
280
- $imports: [baseSchema],
281
- element: [{ name: 'order', type: 'OrderType' }],
282
- complexType: [
283
- {
284
- name: 'OrderType',
285
- complexContent: {
286
- extension: {
287
- base: 'base:BaseEntity', // Inherits from imported schema
288
- sequence: {
289
- element: [
290
- { name: 'items', type: 'ItemType', maxOccurs: 'unbounded' },
291
- { name: 'total', type: 'xs:decimal' },
292
- ],
293
- },
294
- },
295
- },
296
- },
297
- {
298
- name: 'ItemType',
299
- sequence: {
300
- element: [
301
- { name: 'sku', type: 'xs:string' },
302
- { name: 'quantity', type: 'xs:int' },
303
- ],
304
- },
305
- },
306
- ],
307
- } as const;
308
-
309
- type Order = InferSchema<typeof schema>;
310
- // Result:
311
- // {
312
- // ...BaseEntity, // Inherited properties
313
- // items: { sku: string; quantity: number }[];
314
- // total: number;
315
- // }
316
- ```
313
+ const base = { ...parseXsd(baseXsd), $filename: 'base.xsd' };
314
+ const orders = { ...parseXsd(ordersXsd), $filename: 'orders.xsd' };
317
315
 
318
- ## Architecture
316
+ const linked = resolveImports(orders, [base]);
319
317
 
318
+ // InferSchema can now resolve types from `base`
319
+ type OrderData = InferSchema<typeof linked>;
320
320
  ```
321
- @abapify/ts-xsd
322
- ├── src/
323
- │ ├── index.ts # Main exports
324
- │ ├── xsd/ # XSD parsing, building, resolution
325
- │ │ ├── types.ts # W3C 1:1 type definitions
326
- │ │ ├── parse.ts # XSD XML → Schema parser
327
- │ │ ├── build.ts # Schema → XSD XML builder
328
- │ │ ├── resolve.ts # Schema resolver (merges imports, expands inheritance)
329
- │ │ ├── traverser.ts # OO schema traversal with W3C types
330
- │ │ ├── loader.ts # XSD file loading with import resolution
331
- │ │ ├── schema-like.ts # Runtime schema type guards
332
- │ │ └── helpers.ts # Utility functions
333
- │ ├── infer/ # Compile-time type inference
334
- │ │ └── types.ts # InferSchema<T>, InferElement<T>
335
- │ ├── xml/ # XML parsing/building with schemas
336
- │ │ ├── parse.ts # XML → Object parser
337
- │ │ ├── build.ts # Object → XML builder
338
- │ │ ├── typed.ts # Typed schema wrapper
339
- │ │ └── dom-utils.ts # DOM manipulation utilities
340
- │ ├── walker/ # Schema traversal utilities
341
- │ │ └── index.ts # walkElements, walkComplexTypes, findSubstitutes
342
- │ ├── codegen/ # Code generation
343
- │ │ ├── generate.ts # Schema literal generator
344
- │ │ ├── interface-generator.ts # Interface generator API
345
- │ │ ├── ts-morph.ts # TypeScript AST manipulation
346
- │ │ ├── runner.ts # Config-based codegen runner
347
- │ │ └── cli.ts # CLI interface
348
- │ └── generators/ # Generator plugins
349
- │ ├── raw-schema.ts # Schema literal generator
350
- │ ├── interfaces.ts # TypeScript interfaces generator
351
- │ ├── typed-schemas.ts # Typed schema exports
352
- │ └── index-barrel.ts # Index file generator
353
- ```
354
-
355
- ### Key Components
356
-
357
- | Component | Purpose |
358
- | ------------- | ---------------------------------------------------------------------------------- |
359
- | **Resolver** | Merges `$imports`, expands `complexContent/extension`, handles `substitutionGroup` |
360
- | **Traverser** | OO traversal with real W3C XSD types (SchemaTraverser class) |
361
- | **Walker** | Functional iteration over schema elements, types, groups |
362
- | **Loader** | File-based XSD loading with automatic import resolution |
363
321
 
364
322
  ## Design Principles
365
323
 
366
- 1. **Pure W3C XSD** - No invented properties or conveniences
367
- 2. **Type safety** - Full TypeScript support with inference
368
- 3. **Minimal dependencies** - Only `@xmldom/xmldom`
369
- 4. **Tree-shakeable** - Import only what you need
370
- 5. **Tested against W3C** - Verified with official XMLSchema.xsd
371
-
372
- ## Testing
373
-
374
- ```bash
375
- # Run all tests
376
- npx nx test ts-xsd
377
-
378
- # Run with coverage
379
- npx nx test:coverage ts-xsd
380
- ```
381
-
382
- Tests include:
383
-
384
- - Unit tests for parser, builder, and inference
385
- - Integration tests with real XSD files
386
- - W3C XMLSchema.xsd roundtrip verification
387
-
388
- ## Related Packages
389
-
390
- - **[@abapify/adt-schemas](../adt-schemas)** - SAP ADT schemas using ts-xsd
391
- - **[@abapify/adt-contracts](../adt-contracts)** - REST contracts for SAP ADT APIs
392
- - **[@abapify/adt-plugin-abapgit](../adt-plugin-abapgit)** - abapGit plugin schemas
393
-
394
- ## Documentation
395
-
396
- - **[Codegen Guide](./docs/codegen.md)** - Comprehensive code generation documentation
397
- - **[AGENTS.md](./AGENTS.md)** - AI agent guidelines
324
+ 1. **Pure W3C XSD 1.1** — types match the official [XMLSchema.xsd](https://www.w3.org/TR/xmlschema11-1/XMLSchema.xsd) exactly; no invented properties.
325
+ 2. **Type-safe** — full TypeScript support with compile-time inference.
326
+ 3. **Minimal dependencies** — `@xmldom/xmldom` for DOM parsing, `ts-morph` and `zod` for codegen.
327
+ 4. **Tree-shakeable** — import only what you need.
328
+ 5. **Round-trip verified** — tested against the official W3C XMLSchema.xsd.
398
329
 
399
330
  ## References
400
331
 
401
332
  - [W3C XML Schema 1.1 Part 1: Structures](https://www.w3.org/TR/xmlschema11-1/)
402
333
  - [XMLSchema.xsd](https://www.w3.org/TR/xmlschema11-1/XMLSchema.xsd)
334
+ - [Codegen Guide](https://github.com/abapify/adt-cli/tree/main/packages/ts-xsd/docs/codegen.md)
403
335
 
404
336
  ## License
405
337
 
406
- MIT
338
+ [MIT](https://github.com/abapify/adt-cli/blob/main/LICENSE)
@@ -0,0 +1 @@
1
+ export {}
@@ -0,0 +1,137 @@
1
+ #!/usr/bin/env node
2
+ import { t as runCodegen } from "../runner-BSGqj8LM.mjs";
3
+ import { t as generateSchemaFile } from "../generate-Br6rW6sE.mjs";
4
+ import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
5
+ import { basename, dirname, resolve } from "node:path";
6
+ import { pathToFileURL } from "node:url";
7
+
8
+ //#region src/codegen/cli.ts
9
+ /**
10
+ * ts-xsd Codegen CLI
11
+ *
12
+ * Usage:
13
+ * ts-xsd codegen [--config=ts-xsd.config.ts] [--verbose]
14
+ * ts-xsd codegen <input.xsd> [output.ts] [--name=SchemaName]
15
+ *
16
+ * Config-based (recommended):
17
+ * ts-xsd codegen # Uses ts-xsd.config.ts in cwd
18
+ * ts-xsd codegen --config=my-config.ts # Uses custom config
19
+ * ts-xsd codegen --verbose # Verbose output
20
+ *
21
+ * Single-file (legacy):
22
+ * ts-xsd codegen person.xsd
23
+ * ts-xsd codegen person.xsd ./generated/person-schema.ts
24
+ * ts-xsd codegen person.xsd --name=PersonSchema
25
+ */
26
+ function parseArgs(args) {
27
+ const positional = [];
28
+ let config;
29
+ let verbose = false;
30
+ let name;
31
+ for (const arg of args) if (arg.startsWith("--config=")) config = arg.slice(9);
32
+ else if (arg === "--verbose" || arg === "-v") verbose = true;
33
+ else if (arg.startsWith("--name=")) name = arg.slice(7);
34
+ else if (!arg.startsWith("-")) positional.push(arg);
35
+ if (positional[0]?.endsWith(".xsd")) {
36
+ const input = positional[0];
37
+ const defaultOutput = input.replace(/\.xsd$/i, "-schema.ts");
38
+ const output = positional[1] || defaultOutput;
39
+ const defaultName = basename(input, ".xsd").replaceAll(/[-_\s]+(.)?/g, (_, c) => c ? c.toUpperCase() : "").replace(/^./, (s) => s.toLowerCase()) + "Schema";
40
+ return {
41
+ mode: "single",
42
+ input: resolve(input),
43
+ output: resolve(output),
44
+ name: name || defaultName,
45
+ verbose
46
+ };
47
+ }
48
+ return {
49
+ mode: "config",
50
+ config: config || "ts-xsd.config.ts",
51
+ verbose
52
+ };
53
+ }
54
+ async function runConfigMode(options) {
55
+ const configPath = resolve(options.config);
56
+ if (!existsSync(configPath)) {
57
+ console.error(`❌ Config file not found: ${configPath}`);
58
+ console.error("\nCreate a ts-xsd.config.ts file or specify --config=path/to/config.ts");
59
+ process.exit(1);
60
+ }
61
+ console.log(`📦 Loading config: ${configPath}\n`);
62
+ const config = (await import(pathToFileURL(configPath).href)).default;
63
+ const result = await runCodegen(config, {
64
+ rootDir: dirname(configPath),
65
+ verbose: options.verbose
66
+ });
67
+ console.log(`\n📊 Summary:`);
68
+ console.log(` - Schemas processed: ${result.schemas.length}`);
69
+ console.log(` - Files generated: ${result.files.length}`);
70
+ console.log(` - Errors: ${result.errors.length}`);
71
+ if (result.errors.length > 0) {
72
+ console.error("\n❌ Errors:");
73
+ for (const err of result.errors) console.error(` - ${err.schema || "unknown"}: ${err.error.message}`);
74
+ process.exit(1);
75
+ }
76
+ console.log("\n✅ Codegen complete!");
77
+ }
78
+ function runSingleFileMode(options) {
79
+ console.log(`Reading: ${options.input}`);
80
+ const xsdContent = readFileSync(options.input, "utf-8");
81
+ console.log(`Generating schema: ${options.name}`);
82
+ const tsContent = generateSchemaFile(xsdContent, {
83
+ name: options.name,
84
+ comment: `Source: ${basename(options.input)}`
85
+ });
86
+ mkdirSync(dirname(options.output), { recursive: true });
87
+ console.log(`Writing: ${options.output}`);
88
+ writeFileSync(options.output, tsContent);
89
+ console.log(`Done! Generated ${tsContent.length} characters`);
90
+ console.log(`\nUsage in TypeScript:`);
91
+ console.log(` import { ${options.name} } from './${basename(options.output, ".ts")}';`);
92
+ console.log(` import type { InferSchema } from '@abapify/ts-xsd';`);
93
+ console.log(` type MyType = InferSchema<typeof ${options.name}>;`);
94
+ }
95
+ function showHelp() {
96
+ console.log(`
97
+ ts-xsd Codegen CLI
98
+
99
+ Usage:
100
+ ts-xsd codegen [options] Config-based generation (recommended)
101
+ ts-xsd codegen <input.xsd> [output] Single-file generation (legacy)
102
+
103
+ Options:
104
+ --config=FILE Config file path (default: ts-xsd.config.ts)
105
+ --verbose, -v Verbose output
106
+ --name=NAME Schema variable name (single-file mode only)
107
+ --help, -h Show this help
108
+
109
+ Examples:
110
+ # Config-based (recommended)
111
+ ts-xsd codegen # Uses ts-xsd.config.ts
112
+ ts-xsd codegen --config=my-config.ts # Custom config
113
+ ts-xsd codegen --verbose # With verbose output
114
+
115
+ # Single-file (legacy)
116
+ ts-xsd codegen person.xsd
117
+ ts-xsd codegen person.xsd ./out/person.ts --name=PersonSchema
118
+ `);
119
+ }
120
+ async function main() {
121
+ const args = process.argv.slice(2);
122
+ if (args.includes("--help") || args.includes("-h")) {
123
+ showHelp();
124
+ process.exit(0);
125
+ }
126
+ const options = parseArgs(args);
127
+ if (options.mode === "config") await runConfigMode(options);
128
+ else runSingleFileMode(options);
129
+ }
130
+ main().catch((err) => {
131
+ console.error("Fatal error:", err);
132
+ process.exit(1);
133
+ });
134
+
135
+ //#endregion
136
+ export { };
137
+ //# sourceMappingURL=cli.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cli.mjs","names":[],"sources":["../../src/codegen/cli.ts"],"sourcesContent":["#!/usr/bin/env node\n/**\n * ts-xsd Codegen CLI\n *\n * Usage:\n * ts-xsd codegen [--config=ts-xsd.config.ts] [--verbose]\n * ts-xsd codegen <input.xsd> [output.ts] [--name=SchemaName]\n *\n * Config-based (recommended):\n * ts-xsd codegen # Uses ts-xsd.config.ts in cwd\n * ts-xsd codegen --config=my-config.ts # Uses custom config\n * ts-xsd codegen --verbose # Verbose output\n *\n * Single-file (legacy):\n * ts-xsd codegen person.xsd\n * ts-xsd codegen person.xsd ./generated/person-schema.ts\n * ts-xsd codegen person.xsd --name=PersonSchema\n */\n\nimport { existsSync, readFileSync, writeFileSync, mkdirSync } from 'node:fs';\nimport { dirname, basename, resolve } from 'node:path';\nimport { pathToFileURL } from 'node:url';\nimport { generateSchemaFile } from './generate';\nimport { runCodegen } from './runner';\nimport type { CodegenConfig } from './types';\n\ninterface ConfigModeOptions {\n mode: 'config';\n config: string;\n verbose?: boolean;\n}\n\ninterface SingleFileModeOptions {\n mode: 'single';\n input: string;\n output: string;\n name: string;\n verbose?: boolean;\n}\n\ntype CliOptions = ConfigModeOptions | SingleFileModeOptions;\n\nfunction parseArgs(args: string[]): CliOptions {\n const positional: string[] = [];\n let config: string | undefined;\n let verbose = false;\n let name: string | undefined;\n\n for (const arg of args) {\n if (arg.startsWith('--config=')) {\n config = arg.slice(9);\n } else if (arg === '--verbose' || arg === '-v') {\n verbose = true;\n } else if (arg.startsWith('--name=')) {\n name = arg.slice(7);\n } else if (!arg.startsWith('-')) {\n positional.push(arg);\n }\n }\n\n // If first positional arg is an XSD file, use single-file mode\n if (positional[0]?.endsWith('.xsd')) {\n const input = positional[0];\n const defaultOutput = input.replace(/\\.xsd$/i, '-schema.ts');\n const output = positional[1] || defaultOutput;\n const defaultName =\n basename(input, '.xsd')\n .replaceAll(/[-_\\s]+(.)?/g, (_, c) => (c ? c.toUpperCase() : ''))\n .replace(/^./, (s) => s.toLowerCase()) + 'Schema';\n\n return {\n mode: 'single',\n input: resolve(input),\n output: resolve(output),\n name: name || defaultName,\n verbose,\n };\n }\n\n // Config-based mode\n return {\n mode: 'config',\n config: config || 'ts-xsd.config.ts',\n verbose,\n };\n}\n\nasync function runConfigMode(options: ConfigModeOptions) {\n const configPath = resolve(options.config);\n\n if (!existsSync(configPath)) {\n console.error(`❌ Config file not found: ${configPath}`);\n console.error(\n '\\nCreate a ts-xsd.config.ts file or specify --config=path/to/config.ts',\n );\n process.exit(1);\n }\n\n console.log(`📦 Loading config: ${configPath}\\n`);\n\n // Dynamic import of config file\n const configUrl = pathToFileURL(configPath).href;\n const configModule = await import(configUrl);\n const config: CodegenConfig = configModule.default;\n\n const result = await runCodegen(config, {\n rootDir: dirname(configPath),\n verbose: options.verbose,\n });\n\n console.log(`\\n📊 Summary:`);\n console.log(` - Schemas processed: ${result.schemas.length}`);\n console.log(` - Files generated: ${result.files.length}`);\n console.log(` - Errors: ${result.errors.length}`);\n\n if (result.errors.length > 0) {\n console.error('\\n❌ Errors:');\n for (const err of result.errors) {\n console.error(` - ${err.schema || 'unknown'}: ${err.error.message}`);\n }\n process.exit(1);\n }\n\n console.log('\\n✅ Codegen complete!');\n}\n\nfunction runSingleFileMode(options: SingleFileModeOptions) {\n console.log(`Reading: ${options.input}`);\n const xsdContent = readFileSync(options.input, 'utf-8');\n\n console.log(`Generating schema: ${options.name}`);\n const tsContent = generateSchemaFile(xsdContent, {\n name: options.name,\n comment: `Source: ${basename(options.input)}`,\n });\n\n mkdirSync(dirname(options.output), { recursive: true });\n\n console.log(`Writing: ${options.output}`);\n writeFileSync(options.output, tsContent);\n\n console.log(`Done! Generated ${tsContent.length} characters`);\n console.log(`\\nUsage in TypeScript:`);\n console.log(\n ` import { ${options.name} } from './${basename(options.output, '.ts')}';`,\n );\n console.log(` import type { InferSchema } from '@abapify/ts-xsd';`);\n console.log(` type MyType = InferSchema<typeof ${options.name}>;`);\n}\n\nfunction showHelp() {\n console.log(`\nts-xsd Codegen CLI\n\nUsage:\n ts-xsd codegen [options] Config-based generation (recommended)\n ts-xsd codegen <input.xsd> [output] Single-file generation (legacy)\n\nOptions:\n --config=FILE Config file path (default: ts-xsd.config.ts)\n --verbose, -v Verbose output\n --name=NAME Schema variable name (single-file mode only)\n --help, -h Show this help\n\nExamples:\n # Config-based (recommended)\n ts-xsd codegen # Uses ts-xsd.config.ts\n ts-xsd codegen --config=my-config.ts # Custom config\n ts-xsd codegen --verbose # With verbose output\n\n # Single-file (legacy)\n ts-xsd codegen person.xsd\n ts-xsd codegen person.xsd ./out/person.ts --name=PersonSchema\n`);\n}\n\nasync function main() {\n const args = process.argv.slice(2);\n\n if (args.includes('--help') || args.includes('-h')) {\n showHelp();\n process.exit(0);\n }\n\n const options = parseArgs(args);\n\n if (options.mode === 'config') {\n await runConfigMode(options);\n } else {\n runSingleFileMode(options);\n }\n}\n\nmain().catch((err) => {\n console.error('Fatal error:', err);\n process.exit(1);\n});\n"],"mappings":"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"}