@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 +143 -211
- package/dist/codegen/cli.d.mts +1 -0
- package/dist/codegen/cli.mjs +137 -0
- package/dist/codegen/cli.mjs.map +1 -0
- package/dist/generate-Br6rW6sE.mjs +262 -0
- package/dist/generate-Br6rW6sE.mjs.map +1 -0
- package/dist/generators/index.d.mts +197 -0
- package/dist/generators/index.d.mts.map +1 -0
- package/dist/generators/index.mjs +635 -0
- package/dist/generators/index.mjs.map +1 -0
- package/dist/index.d.mts +1193 -0
- package/dist/index.d.mts.map +1 -0
- package/dist/index.mjs +1923 -0
- package/dist/index.mjs.map +1 -0
- package/dist/runner-BSGqj8LM.mjs +1822 -0
- package/dist/runner-BSGqj8LM.mjs.map +1 -0
- package/dist/runner-D1RCU_go.d.mts +741 -0
- package/dist/runner-D1RCU_go.d.mts.map +1 -0
- package/dist/types-Ch0GQQ5n.mjs +527 -0
- package/dist/types-Ch0GQQ5n.mjs.map +1 -0
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -1,37 +1,32 @@
|
|
|
1
1
|
# ts-xsd
|
|
2
2
|
|
|
3
|
-
[](https://www.npmjs.com/package/@abapify/ts-xsd)
|
|
4
|
+
[](https://github.com/abapify/adt-cli/blob/main/LICENSE)
|
|
4
5
|
|
|
5
|
-
**
|
|
6
|
+
**W3C XSD 1.1 parser, builder, and TypeScript type inference** — a 1:1 TypeScript representation of XML Schema Definition.
|
|
6
7
|
|
|
7
|
-
##
|
|
8
|
+
## Why
|
|
8
9
|
|
|
9
|
-
|
|
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
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
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
|
-
//
|
|
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
|
-
//
|
|
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
|
-
|
|
95
|
-
//
|
|
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 {
|
|
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: '
|
|
132
|
+
prefix: 'xs', // Namespace prefix (default: 'xs')
|
|
123
133
|
pretty: true, // Pretty print (default: true)
|
|
124
|
-
indent: ' ', // Indentation
|
|
134
|
+
indent: ' ', // Indentation string
|
|
125
135
|
});
|
|
126
136
|
```
|
|
127
137
|
|
|
128
|
-
#### `resolveImports(schema
|
|
138
|
+
#### `resolveImports(schema, availableSchemas): Schema`
|
|
129
139
|
|
|
130
|
-
Resolve and link imported schemas
|
|
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
|
|
134
|
-
|
|
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,
|
|
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
|
|
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
|
|
197
|
+
#### `buildXml<T>(schema, data: T, options?: XmlBuildOptions): string`
|
|
182
198
|
|
|
183
|
-
Build XML string from
|
|
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
|
|
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, $
|
|
214
|
+
features: { $xmlns: true, $filename: true },
|
|
199
215
|
exclude: ['annotation'],
|
|
200
216
|
});
|
|
201
|
-
// export
|
|
217
|
+
// export const PersonSchema = { ... } as const;
|
|
202
218
|
```
|
|
203
219
|
|
|
204
|
-
#### `generateInterfaces(schema: Schema, options
|
|
220
|
+
#### `generateInterfaces(schema: Schema, options?): { code: string }`
|
|
205
221
|
|
|
206
|
-
Generate TypeScript interfaces from parsed schema.
|
|
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, //
|
|
212
|
-
rootTypeName: 'MySchema',
|
|
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[]; //
|
|
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
|
|
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
|
|
280
|
-
|
|
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
|
-
|
|
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** -
|
|
367
|
-
2. **Type
|
|
368
|
-
3. **Minimal dependencies**
|
|
369
|
-
4. **Tree-shakeable**
|
|
370
|
-
5. **
|
|
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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"}
|