klasik 1.0.12 โ 1.0.14
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 +133 -6
- package/USAGE_EXAMPLES.md +221 -0
- package/dist/cli.js +4 -0
- package/dist/k8s-client-generator.d.ts +5 -0
- package/dist/k8s-client-generator.js +2 -1
- package/dist/spec-downloader.d.ts +32 -1
- package/dist/spec-downloader.js +223 -39
- package/package.json +3 -1
- package/test-runtime.js +0 -72
- package/test-runtime.ts +0 -80
package/README.md
CHANGED
|
@@ -6,6 +6,9 @@ Download OpenAPI specifications from remote URLs and generate TypeScript clients
|
|
|
6
6
|
|
|
7
7
|
- ๐ฅ **Download OpenAPI specs** from remote URLs or local files
|
|
8
8
|
- ๐ **Multiple input formats** - HTTP/HTTPS URLs, file:// URLs, absolute/relative paths
|
|
9
|
+
- ๐ **JSON and YAML support** - Automatically parse and handle both formats
|
|
10
|
+
- ๐ **Authentication support** - Custom headers including Bearer tokens and API keys
|
|
11
|
+
- ๐ **External reference resolution** - Automatically download referenced schema files (`$ref`)
|
|
9
12
|
- ๐ **Automatic transformation** - Converts API responses to class instances using class-transformer
|
|
10
13
|
- ๐ฏ **Type-safe** - Full TypeScript support with decorators
|
|
11
14
|
- ๐ ๏ธ **Configurable** - Custom headers, timeouts, and templates
|
|
@@ -25,17 +28,23 @@ npm install klasik
|
|
|
25
28
|
Generate a TypeScript client from a remote OpenAPI spec:
|
|
26
29
|
|
|
27
30
|
```bash
|
|
31
|
+
# JSON spec
|
|
28
32
|
npx klasik generate \
|
|
29
33
|
--url https://raw.githubusercontent.com/kubernetes/kubernetes/master/api/openapi-spec/swagger.json \
|
|
30
34
|
--output ./k8s-client
|
|
35
|
+
|
|
36
|
+
# YAML spec
|
|
37
|
+
npx klasik generate \
|
|
38
|
+
--url https://api.example.com/openapi.yaml \
|
|
39
|
+
--output ./client
|
|
31
40
|
```
|
|
32
41
|
|
|
33
|
-
Generate from a local OpenAPI spec file:
|
|
42
|
+
Generate from a local OpenAPI spec file (JSON or YAML):
|
|
34
43
|
|
|
35
44
|
```bash
|
|
36
45
|
# Absolute path
|
|
37
46
|
npx klasik generate \
|
|
38
|
-
--url /path/to/openapi.
|
|
47
|
+
--url /path/to/openapi.yaml \
|
|
39
48
|
--output ./client
|
|
40
49
|
|
|
41
50
|
# Relative path
|
|
@@ -45,16 +54,22 @@ npx klasik generate \
|
|
|
45
54
|
|
|
46
55
|
# file:// URL
|
|
47
56
|
npx klasik generate \
|
|
48
|
-
--url file:///path/to/openapi.
|
|
57
|
+
--url file:///path/to/openapi.yaml \
|
|
49
58
|
--output ./client
|
|
50
59
|
```
|
|
51
60
|
|
|
52
|
-
Download an OpenAPI spec without generating:
|
|
61
|
+
Download an OpenAPI spec without generating (supports JSON and YAML):
|
|
53
62
|
|
|
54
63
|
```bash
|
|
64
|
+
# Download JSON spec
|
|
55
65
|
npx klasik download \
|
|
56
66
|
--url https://api.example.com/openapi.json \
|
|
57
67
|
--output ./specs/api-spec.json
|
|
68
|
+
|
|
69
|
+
# Download YAML spec
|
|
70
|
+
npx klasik download \
|
|
71
|
+
--url https://api.example.com/openapi.yaml \
|
|
72
|
+
--output ./specs/api-spec.yaml
|
|
58
73
|
```
|
|
59
74
|
|
|
60
75
|
### Programmatic Usage
|
|
@@ -81,6 +96,9 @@ Generate a TypeScript client from an OpenAPI spec (remote URL or local file).
|
|
|
81
96
|
- Supports: `https://...`, `http://...`, `file://...`, `/absolute/path`, `./relative/path`
|
|
82
97
|
- `-o, --output <dir>` - Output directory for generated client code (required)
|
|
83
98
|
- `-H, --header <header...>` - Custom headers for HTTP requests (format: "Key: Value")
|
|
99
|
+
- Can be used multiple times for multiple headers
|
|
100
|
+
- Perfect for authorization: `--header "Authorization: Bearer token"`
|
|
101
|
+
- `-r, --resolve-refs` - Resolve and download external `$ref` references
|
|
84
102
|
- `-t, --template <dir>` - Custom template directory
|
|
85
103
|
- `-k, --keep-spec` - Keep the downloaded spec file after generation
|
|
86
104
|
- `--timeout <ms>` - Request timeout in milliseconds for HTTP requests (default: 30000)
|
|
@@ -88,13 +106,27 @@ Generate a TypeScript client from an OpenAPI spec (remote URL or local file).
|
|
|
88
106
|
**Examples:**
|
|
89
107
|
|
|
90
108
|
```bash
|
|
91
|
-
# From remote URL
|
|
109
|
+
# From remote URL with authorization
|
|
92
110
|
klasik generate \
|
|
93
111
|
--url https://api.example.com/openapi.json \
|
|
94
112
|
--output ./client \
|
|
95
113
|
--header "Authorization: Bearer token123" \
|
|
96
114
|
--timeout 60000
|
|
97
115
|
|
|
116
|
+
# With external reference resolution
|
|
117
|
+
klasik generate \
|
|
118
|
+
--url https://api.example.com/openapi.json \
|
|
119
|
+
--output ./client \
|
|
120
|
+
--header "Authorization: Bearer token123" \
|
|
121
|
+
--resolve-refs
|
|
122
|
+
|
|
123
|
+
# Multiple headers
|
|
124
|
+
klasik generate \
|
|
125
|
+
--url https://api.example.com/openapi.json \
|
|
126
|
+
--output ./client \
|
|
127
|
+
--header "Authorization: Bearer token" \
|
|
128
|
+
--header "X-API-Version: v1"
|
|
129
|
+
|
|
98
130
|
# From local file
|
|
99
131
|
klasik generate \
|
|
100
132
|
--url ./my-spec.json \
|
|
@@ -114,15 +146,24 @@ Download an OpenAPI spec from a remote URL without generating code.
|
|
|
114
146
|
- `-u, --url <url>` - Remote URL to download the OpenAPI spec from (required)
|
|
115
147
|
- `-o, --output <file>` - Output file path for the downloaded spec (required)
|
|
116
148
|
- `-H, --header <header...>` - Custom headers for the request
|
|
149
|
+
- `-r, --resolve-refs` - Resolve and download external `$ref` references
|
|
117
150
|
- `--timeout <ms>` - Request timeout in milliseconds (default: 30000)
|
|
118
151
|
|
|
119
|
-
**
|
|
152
|
+
**Examples:**
|
|
120
153
|
|
|
121
154
|
```bash
|
|
155
|
+
# Download spec only
|
|
122
156
|
klasik download \
|
|
123
157
|
--url https://api.example.com/openapi.json \
|
|
124
158
|
--output ./specs/api-spec.json \
|
|
125
159
|
--header "Authorization: Bearer token123"
|
|
160
|
+
|
|
161
|
+
# Download spec with all external references
|
|
162
|
+
klasik download \
|
|
163
|
+
--url https://api.example.com/openapi.json \
|
|
164
|
+
--output ./specs/api-spec.json \
|
|
165
|
+
--header "Authorization: Bearer token123" \
|
|
166
|
+
--resolve-refs
|
|
126
167
|
```
|
|
127
168
|
|
|
128
169
|
## Programmatic API
|
|
@@ -141,6 +182,7 @@ await generator.generate({
|
|
|
141
182
|
'Authorization': 'Bearer token123',
|
|
142
183
|
'X-Custom-Header': 'value'
|
|
143
184
|
},
|
|
185
|
+
resolveReferences: true, // Download external $ref files
|
|
144
186
|
templateDir: './custom-templates', // Optional
|
|
145
187
|
keepSpec: true, // Keep downloaded spec file
|
|
146
188
|
timeout: 60000 // Request timeout in ms
|
|
@@ -160,6 +202,7 @@ const specPath = await downloader.download({
|
|
|
160
202
|
headers: {
|
|
161
203
|
'Authorization': 'Bearer token123'
|
|
162
204
|
},
|
|
205
|
+
resolveReferences: true, // Download external $ref files
|
|
163
206
|
timeout: 30000
|
|
164
207
|
});
|
|
165
208
|
|
|
@@ -235,6 +278,90 @@ const user = response.data; // user is instanceof User โ
|
|
|
235
278
|
console.log(user.name); // Fully typed!
|
|
236
279
|
```
|
|
237
280
|
|
|
281
|
+
## External Reference Resolution
|
|
282
|
+
|
|
283
|
+
Klasik can automatically resolve and download external `$ref` references in your OpenAPI specs. This is useful when your spec splits schemas across multiple files.
|
|
284
|
+
|
|
285
|
+
### How It Works
|
|
286
|
+
|
|
287
|
+
When you enable `--resolve-refs` (CLI) or `resolveReferences: true` (programmatic), klasik will:
|
|
288
|
+
|
|
289
|
+
1. **Parse the main spec** for external `$ref` references
|
|
290
|
+
2. **Download all referenced files** preserving directory structure
|
|
291
|
+
3. **Recursively resolve nested references** in downloaded files
|
|
292
|
+
4. **Use the same authentication** for all downloads
|
|
293
|
+
|
|
294
|
+
### Example OpenAPI Spec with External References
|
|
295
|
+
|
|
296
|
+
```json
|
|
297
|
+
{
|
|
298
|
+
"openapi": "3.0.0",
|
|
299
|
+
"paths": {
|
|
300
|
+
"/users": {
|
|
301
|
+
"get": {
|
|
302
|
+
"responses": {
|
|
303
|
+
"200": {
|
|
304
|
+
"content": {
|
|
305
|
+
"application/json": {
|
|
306
|
+
"schema": {
|
|
307
|
+
"$ref": "./schemas/users-orgs.yaml#/components/schemas/Org"
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
```
|
|
318
|
+
|
|
319
|
+
With `--resolve-refs`, klasik will automatically download `./schemas/users-orgs.yaml` and any files it references.
|
|
320
|
+
|
|
321
|
+
### Output Structure
|
|
322
|
+
|
|
323
|
+
Referenced files are downloaded preserving the directory structure:
|
|
324
|
+
|
|
325
|
+
```
|
|
326
|
+
output/
|
|
327
|
+
โโโ openapi-spec.json # Main spec
|
|
328
|
+
โโโ schemas/
|
|
329
|
+
โโโ users-orgs.yaml # Referenced schema
|
|
330
|
+
โโโ common/
|
|
331
|
+
โโโ types.yaml # Nested reference
|
|
332
|
+
```
|
|
333
|
+
|
|
334
|
+
### Supported Reference Types
|
|
335
|
+
|
|
336
|
+
- **Relative paths**: `./schemas/user.yaml`, `../common/types.yaml`
|
|
337
|
+
- **Remote URLs**: `https://api.example.com/schemas/user.yaml`
|
|
338
|
+
- **Mixed**: Main spec from HTTPS, references can be relative or absolute
|
|
339
|
+
|
|
340
|
+
### CLI Example
|
|
341
|
+
|
|
342
|
+
```bash
|
|
343
|
+
# Download spec with all external references
|
|
344
|
+
npx klasik generate \
|
|
345
|
+
--url https://api.example.com/openapi.json \
|
|
346
|
+
--output ./client \
|
|
347
|
+
--header "Authorization: Bearer token" \
|
|
348
|
+
--resolve-refs
|
|
349
|
+
```
|
|
350
|
+
|
|
351
|
+
### Programmatic Example
|
|
352
|
+
|
|
353
|
+
```typescript
|
|
354
|
+
import { K8sClientGenerator } from 'klasik';
|
|
355
|
+
|
|
356
|
+
await new K8sClientGenerator().generate({
|
|
357
|
+
specUrl: 'https://api.example.com/openapi.json',
|
|
358
|
+
outputDir: './client',
|
|
359
|
+
headers: { 'Authorization': 'Bearer token' },
|
|
360
|
+
resolveReferences: true,
|
|
361
|
+
keepSpec: true // Keep all downloaded files
|
|
362
|
+
});
|
|
363
|
+
```
|
|
364
|
+
|
|
238
365
|
## Advanced Configuration
|
|
239
366
|
|
|
240
367
|
### Custom Error Handling
|
|
@@ -0,0 +1,221 @@
|
|
|
1
|
+
# Klasik Usage Examples
|
|
2
|
+
|
|
3
|
+
## JSON and YAML Support
|
|
4
|
+
|
|
5
|
+
Klasik automatically detects and parses both JSON and YAML OpenAPI specifications. The original format is preserved when downloading.
|
|
6
|
+
|
|
7
|
+
### CLI Examples
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
# JSON spec
|
|
11
|
+
npx klasik generate \
|
|
12
|
+
--url https://api.example.com/openapi.json \
|
|
13
|
+
--output ./client
|
|
14
|
+
|
|
15
|
+
# YAML spec
|
|
16
|
+
npx klasik generate \
|
|
17
|
+
--url https://api.example.com/openapi.yaml \
|
|
18
|
+
--output ./client
|
|
19
|
+
|
|
20
|
+
# Local YAML file
|
|
21
|
+
npx klasik generate \
|
|
22
|
+
--url ./specs/openapi.yaml \
|
|
23
|
+
--output ./client
|
|
24
|
+
|
|
25
|
+
# Download and keep YAML format
|
|
26
|
+
npx klasik download \
|
|
27
|
+
--url https://api.example.com/openapi.yaml \
|
|
28
|
+
--output ./specs/api-spec.yaml \
|
|
29
|
+
--resolve-refs
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
### Programmatic Examples
|
|
33
|
+
|
|
34
|
+
```typescript
|
|
35
|
+
import { K8sClientGenerator } from 'klasik';
|
|
36
|
+
|
|
37
|
+
// Works with both JSON and YAML
|
|
38
|
+
await new K8sClientGenerator().generate({
|
|
39
|
+
specUrl: 'https://api.example.com/openapi.yaml',
|
|
40
|
+
outputDir: './client'
|
|
41
|
+
});
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
### Mixed Formats with References
|
|
45
|
+
|
|
46
|
+
Klasik handles specs where the main file is in one format and referenced files are in another:
|
|
47
|
+
|
|
48
|
+
```yaml
|
|
49
|
+
# openapi.yaml (main spec in YAML)
|
|
50
|
+
openapi: 3.0.0
|
|
51
|
+
paths:
|
|
52
|
+
/users:
|
|
53
|
+
get:
|
|
54
|
+
responses:
|
|
55
|
+
'200':
|
|
56
|
+
content:
|
|
57
|
+
application/json:
|
|
58
|
+
schema:
|
|
59
|
+
$ref: './schemas/users.json#/components/schemas/User' # JSON reference
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
Both formats work seamlessly together!
|
|
63
|
+
|
|
64
|
+
## Using Custom Headers (Authorization)
|
|
65
|
+
|
|
66
|
+
### CLI Example - Authorization Token
|
|
67
|
+
|
|
68
|
+
```bash
|
|
69
|
+
# Using Bearer token
|
|
70
|
+
npx klasik generate \
|
|
71
|
+
--url https://api.example.com/openapi.json \
|
|
72
|
+
--output ./client \
|
|
73
|
+
--header "Authorization: Bearer your-token-here"
|
|
74
|
+
|
|
75
|
+
# Using API key
|
|
76
|
+
npx klasik generate \
|
|
77
|
+
--url https://api.example.com/openapi.json \
|
|
78
|
+
--output ./client \
|
|
79
|
+
--header "X-API-Key: your-api-key"
|
|
80
|
+
|
|
81
|
+
# Multiple headers
|
|
82
|
+
npx klasik generate \
|
|
83
|
+
--url https://api.example.com/openapi.json \
|
|
84
|
+
--output ./client \
|
|
85
|
+
--header "Authorization: Bearer your-token" \
|
|
86
|
+
--header "X-Custom-Header: custom-value"
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
### Programmatic Example - Authorization Token
|
|
90
|
+
|
|
91
|
+
```typescript
|
|
92
|
+
import { K8sClientGenerator } from 'klasik';
|
|
93
|
+
|
|
94
|
+
const generator = new K8sClientGenerator();
|
|
95
|
+
|
|
96
|
+
await generator.generate({
|
|
97
|
+
specUrl: 'https://api.example.com/openapi.json',
|
|
98
|
+
outputDir: './client',
|
|
99
|
+
headers: {
|
|
100
|
+
'Authorization': 'Bearer your-token-here',
|
|
101
|
+
'X-API-Key': 'your-api-key'
|
|
102
|
+
}
|
|
103
|
+
});
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
## Resolving External References
|
|
107
|
+
|
|
108
|
+
### CLI Example - With Reference Resolution
|
|
109
|
+
|
|
110
|
+
```bash
|
|
111
|
+
# Download spec and all external $ref files
|
|
112
|
+
npx klasik generate \
|
|
113
|
+
--url https://api.example.com/openapi.json \
|
|
114
|
+
--output ./client \
|
|
115
|
+
--resolve-refs \
|
|
116
|
+
--header "Authorization: Bearer your-token"
|
|
117
|
+
|
|
118
|
+
# Download only (no code generation)
|
|
119
|
+
npx klasik download \
|
|
120
|
+
--url https://api.example.com/openapi.json \
|
|
121
|
+
--output ./specs/api-spec.json \
|
|
122
|
+
--resolve-refs \
|
|
123
|
+
--header "Authorization: Bearer your-token"
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
### Programmatic Example - With Reference Resolution
|
|
127
|
+
|
|
128
|
+
```typescript
|
|
129
|
+
import { K8sClientGenerator } from 'klasik';
|
|
130
|
+
|
|
131
|
+
const generator = new K8sClientGenerator();
|
|
132
|
+
|
|
133
|
+
await generator.generate({
|
|
134
|
+
specUrl: 'https://api.example.com/openapi.json',
|
|
135
|
+
outputDir: './client',
|
|
136
|
+
resolveReferences: true,
|
|
137
|
+
headers: {
|
|
138
|
+
'Authorization': 'Bearer your-token-here'
|
|
139
|
+
}
|
|
140
|
+
});
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
## How Reference Resolution Works
|
|
144
|
+
|
|
145
|
+
When you enable `--resolve-refs` (or `resolveReferences: true`), klasik will:
|
|
146
|
+
|
|
147
|
+
1. **Parse the main OpenAPI spec** for external `$ref` references like:
|
|
148
|
+
```yaml
|
|
149
|
+
schema:
|
|
150
|
+
$ref: './schemas/users-orgs.yaml#/components/schemas/Org'
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
2. **Download all referenced files** preserving the directory structure:
|
|
154
|
+
```
|
|
155
|
+
output/
|
|
156
|
+
โโโ openapi-spec.json (main spec)
|
|
157
|
+
โโโ schemas/
|
|
158
|
+
โโโ users-orgs.yaml (referenced schema)
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
3. **Recursively resolve nested references** - if `users-orgs.yaml` has its own `$ref` to other files, those will be downloaded too
|
|
162
|
+
|
|
163
|
+
4. **Use the same headers** for all downloads (useful when all files require authentication)
|
|
164
|
+
|
|
165
|
+
### Supported Reference Types
|
|
166
|
+
|
|
167
|
+
- **Relative paths**: `./schemas/user.yaml`, `../common/types.yaml`
|
|
168
|
+
- **Remote URLs**: `https://api.example.com/schemas/user.yaml`
|
|
169
|
+
- **Mixed**: Main spec from HTTPS, references can be relative or absolute
|
|
170
|
+
|
|
171
|
+
### Example OpenAPI Spec with External References
|
|
172
|
+
|
|
173
|
+
```json
|
|
174
|
+
{
|
|
175
|
+
"openapi": "3.0.0",
|
|
176
|
+
"info": {
|
|
177
|
+
"title": "My API",
|
|
178
|
+
"version": "1.0.0"
|
|
179
|
+
},
|
|
180
|
+
"paths": {
|
|
181
|
+
"/users": {
|
|
182
|
+
"get": {
|
|
183
|
+
"responses": {
|
|
184
|
+
"200": {
|
|
185
|
+
"content": {
|
|
186
|
+
"application/json": {
|
|
187
|
+
"schema": {
|
|
188
|
+
"$ref": "./schemas/users.yaml#/components/schemas/UserList"
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
When you run with `--resolve-refs`, klasik will automatically download `./schemas/users.yaml`.
|
|
201
|
+
|
|
202
|
+
## Complete Example
|
|
203
|
+
|
|
204
|
+
```bash
|
|
205
|
+
# Full workflow with private API
|
|
206
|
+
npx klasik generate \
|
|
207
|
+
--url https://private-api.example.com/v1/openapi.json \
|
|
208
|
+
--output ./generated-client \
|
|
209
|
+
--header "Authorization: Bearer eyJhbGc..." \
|
|
210
|
+
--header "X-API-Version: v1" \
|
|
211
|
+
--resolve-refs \
|
|
212
|
+
--keep-spec \
|
|
213
|
+
--timeout 60000
|
|
214
|
+
```
|
|
215
|
+
|
|
216
|
+
This will:
|
|
217
|
+
- Download the OpenAPI spec with authorization headers
|
|
218
|
+
- Resolve and download all external schema references
|
|
219
|
+
- Generate the TypeScript client
|
|
220
|
+
- Keep all downloaded specs in the output directory
|
|
221
|
+
- Use a 60-second timeout for all HTTP requests
|
package/dist/cli.js
CHANGED
|
@@ -51,6 +51,7 @@ program
|
|
|
51
51
|
.option('-H, --header <header...>', 'Custom headers for the request (format: "Key: Value")')
|
|
52
52
|
.option('-t, --template <dir>', 'Custom template directory')
|
|
53
53
|
.option('-k, --keep-spec', 'Keep the downloaded spec file after generation', false)
|
|
54
|
+
.option('-r, --resolve-refs', 'Resolve and download external $ref references', false)
|
|
54
55
|
.option('--timeout <ms>', 'Request timeout in milliseconds', '30000')
|
|
55
56
|
.action(async (options) => {
|
|
56
57
|
try {
|
|
@@ -80,6 +81,7 @@ program
|
|
|
80
81
|
headers: Object.keys(headers).length > 0 ? headers : undefined,
|
|
81
82
|
templateDir: options.template,
|
|
82
83
|
keepSpec: options.keepSpec,
|
|
84
|
+
resolveReferences: options.resolveRefs,
|
|
83
85
|
timeout: parseInt(options.timeout, 10),
|
|
84
86
|
});
|
|
85
87
|
process.exit(0);
|
|
@@ -95,6 +97,7 @@ program
|
|
|
95
97
|
.requiredOption('-u, --url <url>', 'Remote URL to download the OpenAPI spec from')
|
|
96
98
|
.requiredOption('-o, --output <file>', 'Output file path for the downloaded spec')
|
|
97
99
|
.option('-H, --header <header...>', 'Custom headers for the request (format: "Key: Value")')
|
|
100
|
+
.option('-r, --resolve-refs', 'Resolve and download external $ref references', false)
|
|
98
101
|
.option('--timeout <ms>', 'Request timeout in milliseconds', '30000')
|
|
99
102
|
.action(async (options) => {
|
|
100
103
|
try {
|
|
@@ -115,6 +118,7 @@ program
|
|
|
115
118
|
url: options.url,
|
|
116
119
|
outputPath: path.resolve(options.output),
|
|
117
120
|
headers: Object.keys(headers).length > 0 ? headers : undefined,
|
|
121
|
+
resolveReferences: options.resolveRefs,
|
|
118
122
|
timeout: parseInt(options.timeout, 10),
|
|
119
123
|
});
|
|
120
124
|
process.exit(0);
|
|
@@ -28,6 +28,11 @@ export interface K8sClientGeneratorOptions {
|
|
|
28
28
|
* @default false
|
|
29
29
|
*/
|
|
30
30
|
keepSpec?: boolean;
|
|
31
|
+
/**
|
|
32
|
+
* Whether to resolve and download external $ref references
|
|
33
|
+
* @default false
|
|
34
|
+
*/
|
|
35
|
+
resolveReferences?: boolean;
|
|
31
36
|
/**
|
|
32
37
|
* Request timeout for downloading spec in milliseconds
|
|
33
38
|
* @default 30000
|
|
@@ -46,7 +46,7 @@ class K8sClientGenerator {
|
|
|
46
46
|
* Generate TypeScript client from a remote OpenAPI spec URL
|
|
47
47
|
*/
|
|
48
48
|
async generate(options) {
|
|
49
|
-
const { specUrl, outputDir, mode = 'full', headers, templateDir, keepSpec = false, timeout, } = options;
|
|
49
|
+
const { specUrl, outputDir, mode = 'full', headers, templateDir, keepSpec = false, resolveReferences = false, timeout, } = options;
|
|
50
50
|
let specPath;
|
|
51
51
|
try {
|
|
52
52
|
// Step 1: Download the OpenAPI spec
|
|
@@ -55,6 +55,7 @@ class K8sClientGenerator {
|
|
|
55
55
|
url: specUrl,
|
|
56
56
|
headers,
|
|
57
57
|
timeout,
|
|
58
|
+
resolveReferences,
|
|
58
59
|
// If keepSpec is true, save the spec in the output directory
|
|
59
60
|
outputPath: keepSpec ? path.join(outputDir, 'openapi-spec.json') : undefined,
|
|
60
61
|
};
|
|
@@ -22,8 +22,19 @@ export interface DownloadOptions {
|
|
|
22
22
|
* @default 30000
|
|
23
23
|
*/
|
|
24
24
|
timeout?: number;
|
|
25
|
+
/**
|
|
26
|
+
* Whether to resolve and download external $ref references
|
|
27
|
+
* @default false
|
|
28
|
+
*/
|
|
29
|
+
resolveReferences?: boolean;
|
|
30
|
+
/**
|
|
31
|
+
* Base URL for resolving relative references (used when main spec is from HTTP)
|
|
32
|
+
* If not provided, will be derived from the main spec URL
|
|
33
|
+
*/
|
|
34
|
+
baseUrl?: string;
|
|
25
35
|
}
|
|
26
36
|
export declare class SpecDownloader {
|
|
37
|
+
private downloadedRefs;
|
|
27
38
|
/**
|
|
28
39
|
* Download an OpenAPI specification from a URL or load from a local file
|
|
29
40
|
* @param options Download options
|
|
@@ -43,11 +54,31 @@ export declare class SpecDownloader {
|
|
|
43
54
|
*/
|
|
44
55
|
private generateTempPath;
|
|
45
56
|
/**
|
|
46
|
-
* Validate that the
|
|
57
|
+
* Validate that the parsed content is an OpenAPI spec
|
|
47
58
|
*/
|
|
48
59
|
private validateSpec;
|
|
49
60
|
/**
|
|
50
61
|
* Clean up temporary files
|
|
51
62
|
*/
|
|
52
63
|
cleanupTemp(): void;
|
|
64
|
+
/**
|
|
65
|
+
* Get base URL from a full URL (removes the filename)
|
|
66
|
+
*/
|
|
67
|
+
private getBaseUrl;
|
|
68
|
+
/**
|
|
69
|
+
* Resolve and download all external $ref references in the spec
|
|
70
|
+
*/
|
|
71
|
+
private resolveExternalRefs;
|
|
72
|
+
/**
|
|
73
|
+
* Find all external $ref values in the spec
|
|
74
|
+
*/
|
|
75
|
+
private findExternalRefs;
|
|
76
|
+
/**
|
|
77
|
+
* Check if a $ref is external (references another file)
|
|
78
|
+
*/
|
|
79
|
+
private isExternalRef;
|
|
80
|
+
/**
|
|
81
|
+
* Download a single reference file
|
|
82
|
+
*/
|
|
83
|
+
private downloadReference;
|
|
53
84
|
}
|
package/dist/spec-downloader.js
CHANGED
|
@@ -40,17 +40,27 @@ exports.SpecDownloader = void 0;
|
|
|
40
40
|
const axios_1 = __importDefault(require("axios"));
|
|
41
41
|
const fs = __importStar(require("fs"));
|
|
42
42
|
const path = __importStar(require("path"));
|
|
43
|
+
const yaml = __importStar(require("js-yaml"));
|
|
43
44
|
class SpecDownloader {
|
|
45
|
+
constructor() {
|
|
46
|
+
this.downloadedRefs = new Set();
|
|
47
|
+
}
|
|
44
48
|
/**
|
|
45
49
|
* Download an OpenAPI specification from a URL or load from a local file
|
|
46
50
|
* @param options Download options
|
|
47
51
|
* @returns Path to the spec file
|
|
48
52
|
*/
|
|
49
53
|
async download(options) {
|
|
50
|
-
const { url, outputPath, headers, timeout = 30000 } = options;
|
|
54
|
+
const { url, outputPath, headers, timeout = 30000, resolveReferences = false, baseUrl } = options;
|
|
55
|
+
// Reset downloaded refs tracking for each new download
|
|
56
|
+
this.downloadedRefs.clear();
|
|
51
57
|
// Check if it's a local file path
|
|
52
58
|
if (this.isLocalFile(url)) {
|
|
53
|
-
|
|
59
|
+
const specPath = await this.loadLocalFile(url, outputPath);
|
|
60
|
+
if (resolveReferences) {
|
|
61
|
+
await this.resolveExternalRefs(specPath, headers, timeout, baseUrl || path.dirname(specPath));
|
|
62
|
+
}
|
|
63
|
+
return specPath;
|
|
54
64
|
}
|
|
55
65
|
// Otherwise, download from HTTP/HTTPS
|
|
56
66
|
console.log(`Downloading OpenAPI spec from ${url}...`);
|
|
@@ -69,25 +79,39 @@ class SpecDownloader {
|
|
|
69
79
|
}
|
|
70
80
|
// Parse and validate the spec
|
|
71
81
|
let specContent;
|
|
82
|
+
let parsedSpec;
|
|
72
83
|
if (typeof response.data === 'string') {
|
|
73
|
-
// Try to parse as JSON
|
|
84
|
+
// Try to parse as JSON first
|
|
74
85
|
try {
|
|
75
|
-
|
|
76
|
-
specContent = JSON.stringify(
|
|
86
|
+
parsedSpec = JSON.parse(response.data);
|
|
87
|
+
specContent = JSON.stringify(parsedSpec, null, 2);
|
|
77
88
|
}
|
|
78
89
|
catch {
|
|
79
|
-
// If not JSON,
|
|
80
|
-
|
|
90
|
+
// If not JSON, try YAML
|
|
91
|
+
try {
|
|
92
|
+
parsedSpec = yaml.load(response.data);
|
|
93
|
+
// Keep YAML format if input was YAML
|
|
94
|
+
specContent = response.data;
|
|
95
|
+
}
|
|
96
|
+
catch {
|
|
97
|
+
throw new Error('Failed to parse spec: not valid JSON or YAML');
|
|
98
|
+
}
|
|
81
99
|
}
|
|
82
100
|
}
|
|
83
101
|
else {
|
|
102
|
+
parsedSpec = response.data;
|
|
84
103
|
specContent = JSON.stringify(response.data, null, 2);
|
|
85
104
|
}
|
|
86
105
|
// Validate it's an OpenAPI spec
|
|
87
|
-
this.validateSpec(
|
|
106
|
+
this.validateSpec(parsedSpec);
|
|
88
107
|
// Write to file
|
|
89
108
|
fs.writeFileSync(specPath, specContent, 'utf-8');
|
|
90
109
|
console.log(`OpenAPI spec downloaded successfully to ${specPath}`);
|
|
110
|
+
// Resolve references if requested
|
|
111
|
+
if (resolveReferences) {
|
|
112
|
+
const resolvedBaseUrl = baseUrl || this.getBaseUrl(url);
|
|
113
|
+
await this.resolveExternalRefs(specPath, headers, timeout, resolvedBaseUrl);
|
|
114
|
+
}
|
|
91
115
|
return specPath;
|
|
92
116
|
}
|
|
93
117
|
catch (error) {
|
|
@@ -140,24 +164,29 @@ class SpecDownloader {
|
|
|
140
164
|
}
|
|
141
165
|
// Read the file
|
|
142
166
|
const content = fs.readFileSync(resolvedPath, 'utf-8');
|
|
167
|
+
// Parse the content (JSON or YAML)
|
|
168
|
+
let parsedSpec;
|
|
169
|
+
try {
|
|
170
|
+
parsedSpec = JSON.parse(content);
|
|
171
|
+
}
|
|
172
|
+
catch {
|
|
173
|
+
try {
|
|
174
|
+
parsedSpec = yaml.load(content);
|
|
175
|
+
}
|
|
176
|
+
catch {
|
|
177
|
+
throw new Error(`Failed to parse spec at ${resolvedPath}: not valid JSON or YAML`);
|
|
178
|
+
}
|
|
179
|
+
}
|
|
143
180
|
// Validate it's an OpenAPI spec
|
|
144
|
-
this.validateSpec(
|
|
181
|
+
this.validateSpec(parsedSpec);
|
|
145
182
|
// If outputPath is provided, copy to that location
|
|
146
183
|
if (outputPath) {
|
|
147
184
|
const dir = path.dirname(outputPath);
|
|
148
185
|
if (!fs.existsSync(dir)) {
|
|
149
186
|
fs.mkdirSync(dir, { recursive: true });
|
|
150
187
|
}
|
|
151
|
-
//
|
|
152
|
-
|
|
153
|
-
try {
|
|
154
|
-
const parsed = JSON.parse(content);
|
|
155
|
-
formattedContent = JSON.stringify(parsed, null, 2);
|
|
156
|
-
}
|
|
157
|
-
catch {
|
|
158
|
-
// Not JSON, keep original
|
|
159
|
-
}
|
|
160
|
-
fs.writeFileSync(outputPath, formattedContent, 'utf-8');
|
|
188
|
+
// Keep the original format (JSON or YAML)
|
|
189
|
+
fs.writeFileSync(outputPath, content, 'utf-8');
|
|
161
190
|
console.log(`OpenAPI spec copied to ${outputPath}`);
|
|
162
191
|
return outputPath;
|
|
163
192
|
}
|
|
@@ -175,28 +204,22 @@ class SpecDownloader {
|
|
|
175
204
|
return path.join(process.cwd(), '.tmp', filename);
|
|
176
205
|
}
|
|
177
206
|
/**
|
|
178
|
-
* Validate that the
|
|
207
|
+
* Validate that the parsed content is an OpenAPI spec
|
|
179
208
|
*/
|
|
180
|
-
validateSpec(
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
// Check for OpenAPI version
|
|
184
|
-
if (!spec.openapi && !spec.swagger) {
|
|
185
|
-
throw new Error('Invalid OpenAPI spec: missing "openapi" or "swagger" field');
|
|
186
|
-
}
|
|
187
|
-
// Check for required fields
|
|
188
|
-
if (!spec.info) {
|
|
189
|
-
throw new Error('Invalid OpenAPI spec: missing "info" field');
|
|
190
|
-
}
|
|
191
|
-
if (!spec.paths && !spec.components) {
|
|
192
|
-
throw new Error('Invalid OpenAPI spec: must have either "paths" or "components"');
|
|
193
|
-
}
|
|
209
|
+
validateSpec(spec) {
|
|
210
|
+
if (!spec || typeof spec !== 'object') {
|
|
211
|
+
throw new Error('Invalid OpenAPI spec: not a valid object');
|
|
194
212
|
}
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
213
|
+
// Check for OpenAPI version
|
|
214
|
+
if (!spec.openapi && !spec.swagger) {
|
|
215
|
+
throw new Error('Invalid OpenAPI spec: missing "openapi" or "swagger" field');
|
|
216
|
+
}
|
|
217
|
+
// Check for required fields
|
|
218
|
+
if (!spec.info) {
|
|
219
|
+
throw new Error('Invalid OpenAPI spec: missing "info" field');
|
|
220
|
+
}
|
|
221
|
+
if (!spec.paths && !spec.components) {
|
|
222
|
+
throw new Error('Invalid OpenAPI spec: must have either "paths" or "components"');
|
|
200
223
|
}
|
|
201
224
|
}
|
|
202
225
|
/**
|
|
@@ -209,5 +232,166 @@ class SpecDownloader {
|
|
|
209
232
|
console.log('Cleaned up temporary files');
|
|
210
233
|
}
|
|
211
234
|
}
|
|
235
|
+
/**
|
|
236
|
+
* Get base URL from a full URL (removes the filename)
|
|
237
|
+
*/
|
|
238
|
+
getBaseUrl(url) {
|
|
239
|
+
try {
|
|
240
|
+
const urlObj = new URL(url);
|
|
241
|
+
const pathParts = urlObj.pathname.split('/');
|
|
242
|
+
pathParts.pop(); // Remove filename
|
|
243
|
+
urlObj.pathname = pathParts.join('/');
|
|
244
|
+
return urlObj.toString();
|
|
245
|
+
}
|
|
246
|
+
catch {
|
|
247
|
+
// If URL parsing fails, return the original URL
|
|
248
|
+
return url;
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
/**
|
|
252
|
+
* Resolve and download all external $ref references in the spec
|
|
253
|
+
*/
|
|
254
|
+
async resolveExternalRefs(specPath, headers, timeout, baseUrl) {
|
|
255
|
+
console.log('Resolving external references...');
|
|
256
|
+
const content = fs.readFileSync(specPath, 'utf-8');
|
|
257
|
+
let spec;
|
|
258
|
+
try {
|
|
259
|
+
spec = JSON.parse(content);
|
|
260
|
+
}
|
|
261
|
+
catch {
|
|
262
|
+
try {
|
|
263
|
+
spec = yaml.load(content);
|
|
264
|
+
}
|
|
265
|
+
catch {
|
|
266
|
+
console.log('Spec is not valid JSON or YAML, skipping reference resolution');
|
|
267
|
+
return;
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
const refs = this.findExternalRefs(spec);
|
|
271
|
+
if (refs.length === 0) {
|
|
272
|
+
console.log('No external references found');
|
|
273
|
+
return;
|
|
274
|
+
}
|
|
275
|
+
console.log(`Found ${refs.length} external reference(s)`);
|
|
276
|
+
const specDir = path.dirname(specPath);
|
|
277
|
+
const isRemoteSpec = !this.isLocalFile(baseUrl || '');
|
|
278
|
+
for (const ref of refs) {
|
|
279
|
+
await this.downloadReference(ref, specDir, baseUrl, isRemoteSpec, headers, timeout);
|
|
280
|
+
}
|
|
281
|
+
console.log('All external references resolved successfully');
|
|
282
|
+
}
|
|
283
|
+
/**
|
|
284
|
+
* Find all external $ref values in the spec
|
|
285
|
+
*/
|
|
286
|
+
findExternalRefs(obj, refs = []) {
|
|
287
|
+
if (typeof obj !== 'object' || obj === null) {
|
|
288
|
+
return refs;
|
|
289
|
+
}
|
|
290
|
+
if (Array.isArray(obj)) {
|
|
291
|
+
for (const item of obj) {
|
|
292
|
+
this.findExternalRefs(item, refs);
|
|
293
|
+
}
|
|
294
|
+
return refs;
|
|
295
|
+
}
|
|
296
|
+
for (const [key, value] of Object.entries(obj)) {
|
|
297
|
+
if (key === '$ref' && typeof value === 'string') {
|
|
298
|
+
// Check if it's an external reference (contains a file path)
|
|
299
|
+
if (this.isExternalRef(value)) {
|
|
300
|
+
// Remove the fragment (#/...) part if present
|
|
301
|
+
const refPath = value.split('#')[0];
|
|
302
|
+
if (refPath && !refs.includes(refPath)) {
|
|
303
|
+
refs.push(refPath);
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
else {
|
|
308
|
+
this.findExternalRefs(value, refs);
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
return refs;
|
|
312
|
+
}
|
|
313
|
+
/**
|
|
314
|
+
* Check if a $ref is external (references another file)
|
|
315
|
+
*/
|
|
316
|
+
isExternalRef(ref) {
|
|
317
|
+
// External refs start with ./ or ../ or / or http:// or https://
|
|
318
|
+
return (ref.startsWith('./') ||
|
|
319
|
+
ref.startsWith('../') ||
|
|
320
|
+
ref.startsWith('http://') ||
|
|
321
|
+
ref.startsWith('https://') ||
|
|
322
|
+
(ref.startsWith('/') && !ref.startsWith('/#')));
|
|
323
|
+
}
|
|
324
|
+
/**
|
|
325
|
+
* Download a single reference file
|
|
326
|
+
*/
|
|
327
|
+
async downloadReference(ref, specDir, baseUrl, isRemoteSpec, headers, timeout) {
|
|
328
|
+
// Skip if already downloaded
|
|
329
|
+
if (this.downloadedRefs.has(ref)) {
|
|
330
|
+
return;
|
|
331
|
+
}
|
|
332
|
+
this.downloadedRefs.add(ref);
|
|
333
|
+
let refUrl;
|
|
334
|
+
let outputPath;
|
|
335
|
+
if (isRemoteSpec && baseUrl) {
|
|
336
|
+
// For remote specs, construct the full URL
|
|
337
|
+
refUrl = new URL(ref, baseUrl).toString();
|
|
338
|
+
// Preserve the directory structure in the output
|
|
339
|
+
const refPath = ref.split('#')[0];
|
|
340
|
+
outputPath = path.join(specDir, refPath);
|
|
341
|
+
}
|
|
342
|
+
else {
|
|
343
|
+
// For local specs, resolve relative to the spec directory
|
|
344
|
+
refUrl = ref;
|
|
345
|
+
outputPath = path.resolve(specDir, ref.split('#')[0]);
|
|
346
|
+
}
|
|
347
|
+
console.log(` - Downloading reference: ${ref}`);
|
|
348
|
+
try {
|
|
349
|
+
// Ensure output directory exists
|
|
350
|
+
const outputDir = path.dirname(outputPath);
|
|
351
|
+
if (!fs.existsSync(outputDir)) {
|
|
352
|
+
fs.mkdirSync(outputDir, { recursive: true });
|
|
353
|
+
}
|
|
354
|
+
if (this.isLocalFile(refUrl) && !isRemoteSpec) {
|
|
355
|
+
// Copy local file
|
|
356
|
+
const sourcePath = path.resolve(specDir, refUrl.split('#')[0]);
|
|
357
|
+
if (fs.existsSync(sourcePath)) {
|
|
358
|
+
const content = fs.readFileSync(sourcePath, 'utf-8');
|
|
359
|
+
fs.writeFileSync(outputPath, content, 'utf-8');
|
|
360
|
+
console.log(` โ Copied: ${sourcePath} -> ${outputPath}`);
|
|
361
|
+
// Recursively resolve references in the downloaded file
|
|
362
|
+
await this.resolveExternalRefs(outputPath, headers, timeout, path.dirname(sourcePath));
|
|
363
|
+
}
|
|
364
|
+
else {
|
|
365
|
+
console.warn(` โ Reference file not found: ${sourcePath}`);
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
else {
|
|
369
|
+
// Download from HTTP/HTTPS
|
|
370
|
+
const response = await axios_1.default.get(refUrl, {
|
|
371
|
+
headers,
|
|
372
|
+
timeout: timeout || 30000,
|
|
373
|
+
responseType: 'text',
|
|
374
|
+
});
|
|
375
|
+
let content = response.data;
|
|
376
|
+
if (typeof content !== 'string') {
|
|
377
|
+
content = JSON.stringify(content, null, 2);
|
|
378
|
+
}
|
|
379
|
+
fs.writeFileSync(outputPath, content, 'utf-8');
|
|
380
|
+
console.log(` โ Downloaded: ${refUrl}`);
|
|
381
|
+
// Recursively resolve references in the downloaded file
|
|
382
|
+
const refBaseUrl = this.getBaseUrl(refUrl);
|
|
383
|
+
await this.resolveExternalRefs(outputPath, headers, timeout, refBaseUrl);
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
catch (error) {
|
|
387
|
+
if (axios_1.default.isAxiosError(error)) {
|
|
388
|
+
console.error(` โ Failed to download ${ref}: ${error.message}`);
|
|
389
|
+
}
|
|
390
|
+
else {
|
|
391
|
+
console.error(` โ Failed to process ${ref}:`, error);
|
|
392
|
+
}
|
|
393
|
+
// Continue with other references even if one fails
|
|
394
|
+
}
|
|
395
|
+
}
|
|
212
396
|
}
|
|
213
397
|
exports.SpecDownloader = SpecDownloader;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "klasik",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.14",
|
|
4
4
|
"description": "Download OpenAPI specs from remote URLs and generate TypeScript clients with class-transformer support",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
@@ -28,11 +28,13 @@
|
|
|
28
28
|
"axios": "^1.6.0",
|
|
29
29
|
"class-transformer": "^0.5.1",
|
|
30
30
|
"commander": "^11.0.0",
|
|
31
|
+
"js-yaml": "^4.1.0",
|
|
31
32
|
"openapi-class-transformer": "^1.0.11",
|
|
32
33
|
"reflect-metadata": "^0.2.2"
|
|
33
34
|
},
|
|
34
35
|
"devDependencies": {
|
|
35
36
|
"@types/jest": "^29.5.0",
|
|
37
|
+
"@types/js-yaml": "^4.0.9",
|
|
36
38
|
"@types/node": "^20.0.0",
|
|
37
39
|
"jest": "^29.5.0",
|
|
38
40
|
"ts-jest": "^29.1.0",
|
package/test-runtime.js
DELETED
|
@@ -1,72 +0,0 @@
|
|
|
1
|
-
var _a, _b, _c;
|
|
2
|
-
import 'reflect-metadata';
|
|
3
|
-
import { plainToInstance } from 'class-transformer';
|
|
4
|
-
import { Capability } from './test-discriminated-output/models/capability.js';
|
|
5
|
-
import { HelmComponent } from './test-discriminated-output/models/helm-component.js';
|
|
6
|
-
import { KustomizeComponent } from './test-discriminated-output/models/kustomize-component.js';
|
|
7
|
-
import { ManifestComponent } from './test-discriminated-output/models/manifest-component.js';
|
|
8
|
-
// Test data with discriminated union
|
|
9
|
-
var plainData = {
|
|
10
|
-
name: 'my-app',
|
|
11
|
-
description: 'Application deployment',
|
|
12
|
-
components: [
|
|
13
|
-
{
|
|
14
|
-
type: 'helm',
|
|
15
|
-
chartName: 'nginx',
|
|
16
|
-
version: '1.0.0',
|
|
17
|
-
values: { replicas: 3 }
|
|
18
|
-
},
|
|
19
|
-
{
|
|
20
|
-
type: 'kustomize',
|
|
21
|
-
path: './overlays/production',
|
|
22
|
-
namespace: 'prod'
|
|
23
|
-
},
|
|
24
|
-
{
|
|
25
|
-
type: 'manifest',
|
|
26
|
-
manifests: [
|
|
27
|
-
'apiVersion: v1\nkind: ConfigMap',
|
|
28
|
-
'apiVersion: v1\nkind: Service'
|
|
29
|
-
]
|
|
30
|
-
}
|
|
31
|
-
]
|
|
32
|
-
};
|
|
33
|
-
console.log('๐งช Testing Discriminated Union Runtime Transformation\n');
|
|
34
|
-
// Transform using plainToInstance
|
|
35
|
-
var capability = plainToInstance(Capability, plainData);
|
|
36
|
-
// Verify instance types
|
|
37
|
-
console.log('โ
Verification Results:');
|
|
38
|
-
console.log(' capability instanceof Capability:', capability instanceof Capability);
|
|
39
|
-
console.log(' capability.name:', capability.name);
|
|
40
|
-
console.log(' capability.components.length:', capability.components.length);
|
|
41
|
-
console.log('');
|
|
42
|
-
console.log('โ
Component Type Verification:');
|
|
43
|
-
console.log(' components[0] instanceof HelmComponent:', capability.components[0] instanceof HelmComponent);
|
|
44
|
-
console.log(' components[1] instanceof KustomizeComponent:', capability.components[1] instanceof KustomizeComponent);
|
|
45
|
-
console.log(' components[2] instanceof ManifestComponent:', capability.components[2] instanceof ManifestComponent);
|
|
46
|
-
console.log('');
|
|
47
|
-
console.log('โ
Property Access:');
|
|
48
|
-
console.log(' HelmComponent.chartName:', capability.components[0].chartName);
|
|
49
|
-
console.log(' KustomizeComponent.path:', capability.components[1].path);
|
|
50
|
-
console.log(' ManifestComponent.manifests.length:', (_a = capability.components[2].manifests) === null || _a === void 0 ? void 0 : _a.length);
|
|
51
|
-
console.log('');
|
|
52
|
-
console.log('โ
attributeTypeMap Check:');
|
|
53
|
-
var componentsMetadata = Capability.attributeTypeMap.find(function (m) { return m.name === 'components'; });
|
|
54
|
-
console.log(' type:', componentsMetadata === null || componentsMetadata === void 0 ? void 0 : componentsMetadata.type);
|
|
55
|
-
console.log(' modelClasses:', (_b = componentsMetadata === null || componentsMetadata === void 0 ? void 0 : componentsMetadata.modelClasses) === null || _b === void 0 ? void 0 : _b.map(function (c) { return c.name; }));
|
|
56
|
-
console.log(' discriminatorProperty:', componentsMetadata === null || componentsMetadata === void 0 ? void 0 : componentsMetadata.discriminatorProperty);
|
|
57
|
-
console.log('');
|
|
58
|
-
// Final verification
|
|
59
|
-
var allCorrect = capability instanceof Capability &&
|
|
60
|
-
capability.components[0] instanceof HelmComponent &&
|
|
61
|
-
capability.components[1] instanceof KustomizeComponent &&
|
|
62
|
-
capability.components[2] instanceof ManifestComponent &&
|
|
63
|
-
capability.components[0].chartName === 'nginx' &&
|
|
64
|
-
capability.components[1].path === './overlays/production' &&
|
|
65
|
-
((_c = capability.components[2].manifests) === null || _c === void 0 ? void 0 : _c.length) === 2;
|
|
66
|
-
if (allCorrect) {
|
|
67
|
-
console.log('๐ SUCCESS! All discriminated union transformations working correctly!');
|
|
68
|
-
}
|
|
69
|
-
else {
|
|
70
|
-
console.log('โ FAILED! Some transformations did not work as expected.');
|
|
71
|
-
process.exit(1);
|
|
72
|
-
}
|
package/test-runtime.ts
DELETED
|
@@ -1,80 +0,0 @@
|
|
|
1
|
-
import 'reflect-metadata';
|
|
2
|
-
import { plainToInstance } from 'class-transformer';
|
|
3
|
-
import { Capability } from './test-discriminated-output/models/capability.js';
|
|
4
|
-
import { HelmComponent } from './test-discriminated-output/models/helm-component.js';
|
|
5
|
-
import { KustomizeComponent } from './test-discriminated-output/models/kustomize-component.js';
|
|
6
|
-
import { ManifestComponent } from './test-discriminated-output/models/manifest-component.js';
|
|
7
|
-
|
|
8
|
-
// Test data with discriminated union
|
|
9
|
-
const plainData = {
|
|
10
|
-
name: 'my-app',
|
|
11
|
-
description: 'Application deployment',
|
|
12
|
-
components: [
|
|
13
|
-
{
|
|
14
|
-
type: 'helm',
|
|
15
|
-
chartName: 'nginx',
|
|
16
|
-
version: '1.0.0',
|
|
17
|
-
values: { replicas: 3 }
|
|
18
|
-
},
|
|
19
|
-
{
|
|
20
|
-
type: 'kustomize',
|
|
21
|
-
path: './overlays/production',
|
|
22
|
-
namespace: 'prod'
|
|
23
|
-
},
|
|
24
|
-
{
|
|
25
|
-
type: 'manifest',
|
|
26
|
-
manifests: [
|
|
27
|
-
'apiVersion: v1\nkind: ConfigMap',
|
|
28
|
-
'apiVersion: v1\nkind: Service'
|
|
29
|
-
]
|
|
30
|
-
}
|
|
31
|
-
]
|
|
32
|
-
};
|
|
33
|
-
|
|
34
|
-
console.log('๐งช Testing Discriminated Union Runtime Transformation\n');
|
|
35
|
-
|
|
36
|
-
// Transform using plainToInstance
|
|
37
|
-
const capability = plainToInstance(Capability, plainData);
|
|
38
|
-
|
|
39
|
-
// Verify instance types
|
|
40
|
-
console.log('โ
Verification Results:');
|
|
41
|
-
console.log(' capability instanceof Capability:', capability instanceof Capability);
|
|
42
|
-
console.log(' capability.name:', capability.name);
|
|
43
|
-
console.log(' capability.components.length:', capability.components.length);
|
|
44
|
-
console.log('');
|
|
45
|
-
|
|
46
|
-
console.log('โ
Component Type Verification:');
|
|
47
|
-
console.log(' components[0] instanceof HelmComponent:', capability.components[0] instanceof HelmComponent);
|
|
48
|
-
console.log(' components[1] instanceof KustomizeComponent:', capability.components[1] instanceof KustomizeComponent);
|
|
49
|
-
console.log(' components[2] instanceof ManifestComponent:', capability.components[2] instanceof ManifestComponent);
|
|
50
|
-
console.log('');
|
|
51
|
-
|
|
52
|
-
console.log('โ
Property Access:');
|
|
53
|
-
console.log(' HelmComponent.chartName:', (capability.components[0] as HelmComponent).chartName);
|
|
54
|
-
console.log(' KustomizeComponent.path:', (capability.components[1] as KustomizeComponent).path);
|
|
55
|
-
console.log(' ManifestComponent.manifests.length:', (capability.components[2] as ManifestComponent).manifests?.length);
|
|
56
|
-
console.log('');
|
|
57
|
-
|
|
58
|
-
console.log('โ
attributeTypeMap Check:');
|
|
59
|
-
const componentsMetadata = Capability.attributeTypeMap.find(m => m.name === 'components');
|
|
60
|
-
console.log(' type:', componentsMetadata?.type);
|
|
61
|
-
console.log(' modelClasses:', (componentsMetadata as any)?.modelClasses?.map((c: any) => c.name));
|
|
62
|
-
console.log(' discriminatorProperty:', (componentsMetadata as any)?.discriminatorProperty);
|
|
63
|
-
console.log('');
|
|
64
|
-
|
|
65
|
-
// Final verification
|
|
66
|
-
const allCorrect =
|
|
67
|
-
capability instanceof Capability &&
|
|
68
|
-
capability.components[0] instanceof HelmComponent &&
|
|
69
|
-
capability.components[1] instanceof KustomizeComponent &&
|
|
70
|
-
capability.components[2] instanceof ManifestComponent &&
|
|
71
|
-
(capability.components[0] as HelmComponent).chartName === 'nginx' &&
|
|
72
|
-
(capability.components[1] as KustomizeComponent).path === './overlays/production' &&
|
|
73
|
-
(capability.components[2] as ManifestComponent).manifests?.length === 2;
|
|
74
|
-
|
|
75
|
-
if (allCorrect) {
|
|
76
|
-
console.log('๐ SUCCESS! All discriminated union transformations working correctly!');
|
|
77
|
-
} else {
|
|
78
|
-
console.log('โ FAILED! Some transformations did not work as expected.');
|
|
79
|
-
process.exit(1);
|
|
80
|
-
}
|