klasik 1.0.30 → 1.0.34
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.
|
@@ -0,0 +1,536 @@
|
|
|
1
|
+
# Comprehensive Test Checklist for klasik + openapi-class-transformer Fixes
|
|
2
|
+
|
|
3
|
+
## Version Information
|
|
4
|
+
- **klasik**: 1.0.31
|
|
5
|
+
- **openapi-class-transformer**: 1.0.17
|
|
6
|
+
|
|
7
|
+
## Test Environment Setup
|
|
8
|
+
```bash
|
|
9
|
+
# Clean test directory
|
|
10
|
+
rm -rf /tmp/test-klasik-final
|
|
11
|
+
|
|
12
|
+
# Test with Argo CD CRDs
|
|
13
|
+
npx klasik@latest generate-crd \
|
|
14
|
+
--crd-kind-snake-case \
|
|
15
|
+
--url https://raw.githubusercontent.com/argoproj/argo-cd/master/manifests/crds/application-crd.yaml \
|
|
16
|
+
--output /tmp/test-klasik-final/argocd \
|
|
17
|
+
--nestjs-swagger \
|
|
18
|
+
--class-validator
|
|
19
|
+
|
|
20
|
+
# Test with Kustomize JSON Schema
|
|
21
|
+
npx klasik@latest generate-jsonschema \
|
|
22
|
+
-u https://json.schemastore.org/kustomization.json \
|
|
23
|
+
--output /tmp/test-klasik-final/kustomize \
|
|
24
|
+
--nestjs-swagger \
|
|
25
|
+
--class-validator
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
---
|
|
29
|
+
|
|
30
|
+
## Category 1: NestJS @ApiProperty Decorator Issues
|
|
31
|
+
|
|
32
|
+
### ✅ Test 1.1: Primitive Type Casing
|
|
33
|
+
**Issue**: Generated `type: 'string'` instead of `type: String`
|
|
34
|
+
|
|
35
|
+
**Files to check**:
|
|
36
|
+
- `/tmp/test-klasik-final/argocd/application/v1alpha1/models/*.ts`
|
|
37
|
+
- `/tmp/test-klasik-final/kustomize/models/*.ts`
|
|
38
|
+
|
|
39
|
+
**What to verify**:
|
|
40
|
+
```typescript
|
|
41
|
+
// ❌ WRONG (should NOT exist)
|
|
42
|
+
@ApiProperty({ type: 'string' })
|
|
43
|
+
@ApiProperty({ type: 'number' })
|
|
44
|
+
@ApiProperty({ type: 'boolean' })
|
|
45
|
+
|
|
46
|
+
// ✅ CORRECT (should exist)
|
|
47
|
+
@ApiProperty({ type: String })
|
|
48
|
+
@ApiProperty({ type: Number })
|
|
49
|
+
@ApiProperty({ type: Boolean })
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
**Test command**:
|
|
53
|
+
```bash
|
|
54
|
+
# Should return 0 results
|
|
55
|
+
grep -r "type: 'string'" /tmp/test-klasik-final/
|
|
56
|
+
grep -r "type: 'number'" /tmp/test-klasik-final/
|
|
57
|
+
grep -r "type: 'boolean'" /tmp/test-klasik-final/
|
|
58
|
+
|
|
59
|
+
# Should have results
|
|
60
|
+
grep -r "type: String," /tmp/test-klasik-final/ | wc -l
|
|
61
|
+
grep -r "type: Number," /tmp/test-klasik-final/ | wc -l
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
---
|
|
65
|
+
|
|
66
|
+
### ✅ Test 1.2: Array Primitive Types
|
|
67
|
+
**Issue**: Generated `type: () => [string]` instead of `type: [String]`
|
|
68
|
+
|
|
69
|
+
**What to verify**:
|
|
70
|
+
```typescript
|
|
71
|
+
// ❌ WRONG
|
|
72
|
+
@ApiProperty({ type: () => [string] })
|
|
73
|
+
|
|
74
|
+
// ✅ CORRECT
|
|
75
|
+
@ApiProperty({ type: [String] })
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
**Test command**:
|
|
79
|
+
```bash
|
|
80
|
+
# Should return 0 results
|
|
81
|
+
grep -r "type: () => \[string\]" /tmp/test-klasik-final/
|
|
82
|
+
grep -r "type: () => \[number\]" /tmp/test-klasik-final/
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
---
|
|
86
|
+
|
|
87
|
+
### ✅ Test 1.3: Complex Object Types (Dictionary)
|
|
88
|
+
**Issue**: Generated `type: 'object'` which is invalid in @nestjs/swagger@8+
|
|
89
|
+
|
|
90
|
+
**Files to check**:
|
|
91
|
+
- `/tmp/test-klasik-final/kustomize/models/helm-chart.ts` (valuesInline property)
|
|
92
|
+
|
|
93
|
+
**What to verify**:
|
|
94
|
+
```typescript
|
|
95
|
+
// ❌ WRONG
|
|
96
|
+
@ApiProperty({ type: 'object' })
|
|
97
|
+
@ApiProperty({ type: () => [{ [key: string]: any; }] })
|
|
98
|
+
|
|
99
|
+
// ✅ CORRECT
|
|
100
|
+
@ApiProperty({ type: Object })
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
**Test command**:
|
|
104
|
+
```bash
|
|
105
|
+
# Should return 0 results
|
|
106
|
+
grep -r "type: 'object'" /tmp/test-klasik-final/
|
|
107
|
+
grep -r "type: () => \[\{" /tmp/test-klasik-final/
|
|
108
|
+
|
|
109
|
+
# Should have results for Object constructor
|
|
110
|
+
grep -r "type: Object," /tmp/test-klasik-final/kustomize/models/helm-chart.ts
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
---
|
|
114
|
+
|
|
115
|
+
### ✅ Test 1.4: Unescaped Backticks in Descriptions
|
|
116
|
+
**Issue**: Descriptions with backticks weren't escaped, causing syntax errors
|
|
117
|
+
|
|
118
|
+
**Files to check**:
|
|
119
|
+
- `/tmp/test-klasik-final/argocd/application/v1alpha1/models/application-spec-source.ts`
|
|
120
|
+
|
|
121
|
+
**What to verify**:
|
|
122
|
+
```typescript
|
|
123
|
+
// ❌ WRONG
|
|
124
|
+
description: `text `code` here`,
|
|
125
|
+
|
|
126
|
+
// ✅ CORRECT
|
|
127
|
+
description: `text \`code\` here`,
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
**Test command**:
|
|
131
|
+
```bash
|
|
132
|
+
# Find files with descriptions containing backticks
|
|
133
|
+
grep -r "description:" /tmp/test-klasik-final/ | grep '`' | head -20
|
|
134
|
+
|
|
135
|
+
# Check specific case mentioned in issues
|
|
136
|
+
grep -A 5 "ref.*source" /tmp/test-klasik-final/argocd/application/v1alpha1/models/application-spec-source.ts
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
---
|
|
140
|
+
|
|
141
|
+
### ✅ Test 1.5: Undefined Descriptions
|
|
142
|
+
**Issue**: `description: undefined` should be `description: ''`
|
|
143
|
+
|
|
144
|
+
**What to verify**:
|
|
145
|
+
```typescript
|
|
146
|
+
// ❌ WRONG (in @ApiProperty decorators)
|
|
147
|
+
@ApiProperty({
|
|
148
|
+
description: undefined,
|
|
149
|
+
...
|
|
150
|
+
})
|
|
151
|
+
|
|
152
|
+
// ✅ CORRECT
|
|
153
|
+
@ApiProperty({
|
|
154
|
+
description: '',
|
|
155
|
+
...
|
|
156
|
+
})
|
|
157
|
+
|
|
158
|
+
// ⚠️ OK (in attributeTypeMap - metadata should stay as-is)
|
|
159
|
+
{
|
|
160
|
+
"description": undefined, // This is fine
|
|
161
|
+
...
|
|
162
|
+
}
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
**Test command**:
|
|
166
|
+
```bash
|
|
167
|
+
# Count description: undefined in decorators (should be 0)
|
|
168
|
+
grep -B 1 "description: undefined," /tmp/test-klasik-final/**/*.ts | grep "@ApiProperty" -c
|
|
169
|
+
|
|
170
|
+
# Count in attributeTypeMap (should be > 0, this is OK)
|
|
171
|
+
grep -A 5 "attributeTypeMap" /tmp/test-klasik-final/**/*.ts | grep '"description": undefined' | wc -l
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
---
|
|
175
|
+
|
|
176
|
+
### ✅ Test 1.6: Missing Class References
|
|
177
|
+
**Issue**: `type: () => NonExistentClass` where class doesn't exist
|
|
178
|
+
|
|
179
|
+
**Files to check**:
|
|
180
|
+
- `/tmp/test-klasik-final/argocd/application/v1alpha1/models/*replicas-item.ts`
|
|
181
|
+
|
|
182
|
+
**What to verify**:
|
|
183
|
+
```typescript
|
|
184
|
+
// ❌ WRONG
|
|
185
|
+
@ApiProperty({
|
|
186
|
+
type: () => ApplicationSpecSourceKustomizeReplicasItemCount,
|
|
187
|
+
...
|
|
188
|
+
})
|
|
189
|
+
// But ApplicationSpecSourceKustomizeReplicasItemCount doesn't exist!
|
|
190
|
+
|
|
191
|
+
// ✅ CORRECT - should be removed entirely
|
|
192
|
+
@ApiProperty({
|
|
193
|
+
// no type field for union types like number | string
|
|
194
|
+
...
|
|
195
|
+
})
|
|
196
|
+
```
|
|
197
|
+
|
|
198
|
+
**Test command**:
|
|
199
|
+
```bash
|
|
200
|
+
# Check if any @ApiProperty has type references to non-imported classes
|
|
201
|
+
for file in /tmp/test-klasik-final/**/*-replicas-item.ts; do
|
|
202
|
+
echo "Checking $file..."
|
|
203
|
+
# Extract type references from @ApiProperty
|
|
204
|
+
grep -A 3 "@ApiProperty" "$file" | grep "type: ()" | grep -v "import"
|
|
205
|
+
done
|
|
206
|
+
```
|
|
207
|
+
|
|
208
|
+
---
|
|
209
|
+
|
|
210
|
+
## Category 2: AttributeTypeMap Issues
|
|
211
|
+
|
|
212
|
+
### ✅ Test 2.1: Missing Closing Braces
|
|
213
|
+
**Issue**: Last object in array missing closing `}` before `];`
|
|
214
|
+
|
|
215
|
+
**What to verify**:
|
|
216
|
+
```typescript
|
|
217
|
+
// ❌ WRONG
|
|
218
|
+
{
|
|
219
|
+
"vendorExtensions": {}
|
|
220
|
+
]; // Missing }
|
|
221
|
+
|
|
222
|
+
// ✅ CORRECT
|
|
223
|
+
{
|
|
224
|
+
"vendorExtensions": {}
|
|
225
|
+
}
|
|
226
|
+
];
|
|
227
|
+
```
|
|
228
|
+
|
|
229
|
+
**Test command**:
|
|
230
|
+
```bash
|
|
231
|
+
# Check for malformed attributeTypeMap
|
|
232
|
+
grep -B 2 "^ \];" /tmp/test-klasik-final/**/*.ts | grep -A 1 "vendorExtensions"
|
|
233
|
+
|
|
234
|
+
# Should find properly closed objects
|
|
235
|
+
grep -B 3 "^ \];" /tmp/test-klasik-final/**/*.ts | grep " }" | wc -l
|
|
236
|
+
```
|
|
237
|
+
|
|
238
|
+
---
|
|
239
|
+
|
|
240
|
+
## Category 3: Union Types and @Type() Decorators
|
|
241
|
+
|
|
242
|
+
### ✅ Test 3.1: Type-Only Imports Should NOT Have @Type()
|
|
243
|
+
**Issue**: Union types (type aliases) had `@Type()` decorators causing runtime errors
|
|
244
|
+
|
|
245
|
+
**Files to check**:
|
|
246
|
+
- `/tmp/test-klasik-final/kustomize/models/kustomization.ts`
|
|
247
|
+
- `/tmp/test-klasik-final/kustomize/models/patch-json6902.ts`
|
|
248
|
+
|
|
249
|
+
**What to verify**:
|
|
250
|
+
```typescript
|
|
251
|
+
// In patch-json6902.ts - this is a type alias (union)
|
|
252
|
+
import type { PatchJson6902OneOf } from './patch-json6902-one-of.js';
|
|
253
|
+
export type PatchJson6902 = PatchJson6902OneOf | ...;
|
|
254
|
+
|
|
255
|
+
// In kustomization.ts - should NOT have @Type() for this
|
|
256
|
+
@Expose()
|
|
257
|
+
// @Type(() => PatchJson6902) ❌ WRONG - should NOT exist
|
|
258
|
+
'patchesJson6902'?: Array<PatchJson6902>;
|
|
259
|
+
```
|
|
260
|
+
|
|
261
|
+
**Test command**:
|
|
262
|
+
```bash
|
|
263
|
+
# Find all type-only imports
|
|
264
|
+
grep -r "import type {" /tmp/test-klasik-final/ | cut -d':' -f1 | sort -u
|
|
265
|
+
|
|
266
|
+
# For each file with type-only imports, check if @Type() uses them
|
|
267
|
+
for file in $(grep -l "import type {" /tmp/test-klasik-final/**/*.ts); do
|
|
268
|
+
echo "=== Checking $file ==="
|
|
269
|
+
# Extract type-only import names
|
|
270
|
+
TYPE_IMPORTS=$(grep "import type {" "$file" | sed 's/.*{ //; s/ }.*//' | tr ',' '\n' | tr -d ' ')
|
|
271
|
+
|
|
272
|
+
# Check if any @Type() references these
|
|
273
|
+
for type in $TYPE_IMPORTS; do
|
|
274
|
+
if grep -q "@Type(() => $type)" "$file"; then
|
|
275
|
+
echo "❌ ERROR: @Type() decorator found for type-only import: $type"
|
|
276
|
+
fi
|
|
277
|
+
done
|
|
278
|
+
done
|
|
279
|
+
```
|
|
280
|
+
|
|
281
|
+
---
|
|
282
|
+
|
|
283
|
+
### ✅ Test 3.2: Regular Classes SHOULD Have @Type()
|
|
284
|
+
**Issue**: Real classes (not unions) must keep @Type() decorators
|
|
285
|
+
|
|
286
|
+
**Files to check**:
|
|
287
|
+
- `/tmp/test-klasik-final/argocd/application/v1alpha1/models/user.ts` (if exists)
|
|
288
|
+
- Any file with regular class imports
|
|
289
|
+
|
|
290
|
+
**What to verify**:
|
|
291
|
+
```typescript
|
|
292
|
+
// Regular class import (NOT type-only)
|
|
293
|
+
import { Profile } from './profile.js';
|
|
294
|
+
|
|
295
|
+
// Should have @Type() decorator
|
|
296
|
+
@Expose()
|
|
297
|
+
@Type(() => Profile) // ✅ CORRECT - should exist
|
|
298
|
+
'profile'?: Profile;
|
|
299
|
+
```
|
|
300
|
+
|
|
301
|
+
**Test command**:
|
|
302
|
+
```bash
|
|
303
|
+
# Find files with regular imports (not type imports)
|
|
304
|
+
grep -r "^import { " /tmp/test-klasik-final/ | grep -v "import type" | head -20
|
|
305
|
+
|
|
306
|
+
# Verify @Type() exists for complex types
|
|
307
|
+
grep -B 5 "@Type(() =>" /tmp/test-klasik-final/**/*.ts | head -40
|
|
308
|
+
```
|
|
309
|
+
|
|
310
|
+
---
|
|
311
|
+
|
|
312
|
+
## Category 4: ESM Module System Issues
|
|
313
|
+
|
|
314
|
+
### ✅ Test 4.1: .js Extensions in Imports (Individual Files)
|
|
315
|
+
**Issue**: Missing `.js` extensions in ES module imports
|
|
316
|
+
|
|
317
|
+
**What to verify**:
|
|
318
|
+
```typescript
|
|
319
|
+
// ❌ WRONG
|
|
320
|
+
import { Foo } from './foo';
|
|
321
|
+
|
|
322
|
+
// ✅ CORRECT
|
|
323
|
+
import { Foo } from './foo.js';
|
|
324
|
+
```
|
|
325
|
+
|
|
326
|
+
**Test command**:
|
|
327
|
+
```bash
|
|
328
|
+
# Count imports without .js (should be minimal, only class-transformer etc.)
|
|
329
|
+
grep -r "^import.*from '\\./" /tmp/test-klasik-final/ | grep -v "\.js'" | wc -l
|
|
330
|
+
|
|
331
|
+
# All local imports should have .js
|
|
332
|
+
grep -r "^import.*from '\\./" /tmp/test-klasik-final/ | grep "\.js'" | wc -l
|
|
333
|
+
```
|
|
334
|
+
|
|
335
|
+
---
|
|
336
|
+
|
|
337
|
+
### ✅ Test 4.2: .js Extensions in Index Files
|
|
338
|
+
**Issue**: JSON schema generator index.ts didn't have .js extensions
|
|
339
|
+
|
|
340
|
+
**Files to check**:
|
|
341
|
+
- `/tmp/test-klasik-final/kustomize/index.ts`
|
|
342
|
+
- `/tmp/test-klasik-final/argocd/application/v1alpha1/index.ts`
|
|
343
|
+
|
|
344
|
+
**What to verify**:
|
|
345
|
+
```typescript
|
|
346
|
+
// ❌ WRONG
|
|
347
|
+
export * from './models/config-map-args';
|
|
348
|
+
|
|
349
|
+
// ✅ CORRECT
|
|
350
|
+
export * from './models/config-map-args.js';
|
|
351
|
+
```
|
|
352
|
+
|
|
353
|
+
**Test command**:
|
|
354
|
+
```bash
|
|
355
|
+
# Check index files
|
|
356
|
+
cat /tmp/test-klasik-final/kustomize/index.ts | head -10
|
|
357
|
+
cat /tmp/test-klasik-final/argocd/application/v1alpha1/index.ts | head -10
|
|
358
|
+
|
|
359
|
+
# All exports should have .js
|
|
360
|
+
grep "export \* from" /tmp/test-klasik-final/kustomize/index.ts | grep -v "\.js'" && echo "❌ FAIL" || echo "✅ PASS"
|
|
361
|
+
```
|
|
362
|
+
|
|
363
|
+
---
|
|
364
|
+
|
|
365
|
+
## Category 5: TypeScript Compilation
|
|
366
|
+
|
|
367
|
+
### ✅ Test 5.1: No Syntax Errors
|
|
368
|
+
**Issue**: Various syntax errors from all the above issues
|
|
369
|
+
|
|
370
|
+
**Test command**:
|
|
371
|
+
```bash
|
|
372
|
+
cd /tmp/test-klasik-final/kustomize
|
|
373
|
+
npm init -y
|
|
374
|
+
npm install class-transformer @nestjs/swagger reflect-metadata
|
|
375
|
+
|
|
376
|
+
# Create tsconfig.json
|
|
377
|
+
cat > tsconfig.json << 'EOF'
|
|
378
|
+
{
|
|
379
|
+
"compilerOptions": {
|
|
380
|
+
"target": "ES2020",
|
|
381
|
+
"module": "ES2020",
|
|
382
|
+
"moduleResolution": "node16",
|
|
383
|
+
"esModuleInterop": true,
|
|
384
|
+
"skipLibCheck": true,
|
|
385
|
+
"strict": true,
|
|
386
|
+
"isolatedModules": true,
|
|
387
|
+
"emitDecoratorMetadata": true,
|
|
388
|
+
"experimentalDecorators": true,
|
|
389
|
+
"declaration": true,
|
|
390
|
+
"outDir": "./dist"
|
|
391
|
+
},
|
|
392
|
+
"include": ["models/**/*", "index.ts"]
|
|
393
|
+
}
|
|
394
|
+
EOF
|
|
395
|
+
|
|
396
|
+
# Compile - should have NO errors
|
|
397
|
+
npx tsc --noEmit
|
|
398
|
+
```
|
|
399
|
+
|
|
400
|
+
---
|
|
401
|
+
|
|
402
|
+
### ✅ Test 5.2: Specific TypeScript Errors Fixed
|
|
403
|
+
|
|
404
|
+
**Test each error type**:
|
|
405
|
+
|
|
406
|
+
```bash
|
|
407
|
+
cd /tmp/test-klasik-final/kustomize
|
|
408
|
+
|
|
409
|
+
# Test 1: No "type used as value" errors
|
|
410
|
+
npx tsc --noEmit 2>&1 | grep "only refers to a type, but is being used as a value" && echo "❌ FAIL" || echo "✅ PASS"
|
|
411
|
+
|
|
412
|
+
# Test 2: No "missing file extension" errors
|
|
413
|
+
npx tsc --noEmit 2>&1 | grep "need explicit file extensions" && echo "❌ FAIL" || echo "✅ PASS"
|
|
414
|
+
|
|
415
|
+
# Test 3: No "not assignable to type" errors for @ApiProperty
|
|
416
|
+
npx tsc --noEmit 2>&1 | grep "not assignable.*ApiProperty" && echo "❌ FAIL" || echo "✅ PASS"
|
|
417
|
+
|
|
418
|
+
# Test 4: No "Cannot find name" errors
|
|
419
|
+
npx tsc --noEmit 2>&1 | grep "Cannot find name" && echo "❌ FAIL" || echo "✅ PASS"
|
|
420
|
+
```
|
|
421
|
+
|
|
422
|
+
---
|
|
423
|
+
|
|
424
|
+
## Category 6: Runtime Testing
|
|
425
|
+
|
|
426
|
+
### ✅ Test 6.1: class-transformer Deserialization
|
|
427
|
+
**Issue**: Verify classes can be properly transformed
|
|
428
|
+
|
|
429
|
+
**Test file**: Create `/tmp/test-klasik-final/test-runtime.ts`
|
|
430
|
+
|
|
431
|
+
```typescript
|
|
432
|
+
import 'reflect-metadata';
|
|
433
|
+
import { plainToInstance } from 'class-transformer';
|
|
434
|
+
import { Kustomization } from './kustomize/models/kustomization.js';
|
|
435
|
+
|
|
436
|
+
const data = {
|
|
437
|
+
apiVersion: 'kustomize.config.k8s.io/v1beta1',
|
|
438
|
+
kind: 'Kustomization',
|
|
439
|
+
resources: ['deployment.yaml', 'service.yaml']
|
|
440
|
+
};
|
|
441
|
+
|
|
442
|
+
const instance = plainToInstance(Kustomization, data);
|
|
443
|
+
console.log('✅ Successfully created instance:', instance);
|
|
444
|
+
console.log('Has proper class methods:', instance instanceof Kustomization);
|
|
445
|
+
```
|
|
446
|
+
|
|
447
|
+
**Run test**:
|
|
448
|
+
```bash
|
|
449
|
+
npx ts-node --esm /tmp/test-klasik-final/test-runtime.ts
|
|
450
|
+
```
|
|
451
|
+
|
|
452
|
+
---
|
|
453
|
+
|
|
454
|
+
## Summary Checklist
|
|
455
|
+
|
|
456
|
+
Run all tests and check:
|
|
457
|
+
|
|
458
|
+
- [ ] **Test 1.1**: No lowercase primitive types (`'string'`, `'number'`, `'boolean'`)
|
|
459
|
+
- [ ] **Test 1.2**: No array primitive syntax `() => [string]`
|
|
460
|
+
- [ ] **Test 1.3**: No `type: 'object'`, uses `type: Object`
|
|
461
|
+
- [ ] **Test 1.4**: All backticks in descriptions are escaped
|
|
462
|
+
- [ ] **Test 1.5**: No `description: undefined` in decorators
|
|
463
|
+
- [ ] **Test 1.6**: No type references to non-existent classes
|
|
464
|
+
- [ ] **Test 2.1**: All attributeTypeMap arrays properly closed
|
|
465
|
+
- [ ] **Test 3.1**: No `@Type()` for type-only imports (unions)
|
|
466
|
+
- [ ] **Test 3.2**: `@Type()` exists for regular class references
|
|
467
|
+
- [ ] **Test 4.1**: All local imports have `.js` extensions
|
|
468
|
+
- [ ] **Test 4.2**: Index files have `.js` extensions in exports
|
|
469
|
+
- [ ] **Test 5.1**: `tsc --noEmit` passes with no errors
|
|
470
|
+
- [ ] **Test 5.2**: No specific TypeScript compilation errors
|
|
471
|
+
- [ ] **Test 6.1**: Runtime deserialization works
|
|
472
|
+
|
|
473
|
+
---
|
|
474
|
+
|
|
475
|
+
## Quick Test Script
|
|
476
|
+
|
|
477
|
+
```bash
|
|
478
|
+
#!/bin/bash
|
|
479
|
+
set -e
|
|
480
|
+
|
|
481
|
+
echo "🧪 Running comprehensive klasik tests..."
|
|
482
|
+
|
|
483
|
+
# Setup
|
|
484
|
+
rm -rf /tmp/test-klasik-final
|
|
485
|
+
mkdir -p /tmp/test-klasik-final
|
|
486
|
+
|
|
487
|
+
# Generate Argo CD
|
|
488
|
+
echo "📦 Generating Argo CD models..."
|
|
489
|
+
npx klasik@latest generate-crd \
|
|
490
|
+
--crd-kind-snake-case \
|
|
491
|
+
--url https://raw.githubusercontent.com/argoproj/argo-cd/master/manifests/crds/application-crd.yaml \
|
|
492
|
+
--output /tmp/test-klasik-final/argocd \
|
|
493
|
+
--nestjs-swagger \
|
|
494
|
+
--class-validator
|
|
495
|
+
|
|
496
|
+
# Generate Kustomize
|
|
497
|
+
echo "📦 Generating Kustomize models..."
|
|
498
|
+
npx klasik@latest generate-jsonschema \
|
|
499
|
+
-u https://json.schemastore.org/kustomization.json \
|
|
500
|
+
--output /tmp/test-klasik-final/kustomize \
|
|
501
|
+
--nestjs-swagger \
|
|
502
|
+
--class-validator
|
|
503
|
+
|
|
504
|
+
# Run checks
|
|
505
|
+
echo "✅ Test 1.1: Primitive type casing..."
|
|
506
|
+
! grep -r "type: 'string'" /tmp/test-klasik-final/ || exit 1
|
|
507
|
+
|
|
508
|
+
echo "✅ Test 1.3: Complex object types..."
|
|
509
|
+
! grep -r "type: 'object'" /tmp/test-klasik-final/ || exit 1
|
|
510
|
+
|
|
511
|
+
echo "✅ Test 4.2: Index.ts has .js extensions..."
|
|
512
|
+
grep "export \* from" /tmp/test-klasik-final/kustomize/index.ts | grep -q "\.js'" || exit 1
|
|
513
|
+
|
|
514
|
+
echo "✅ Test 5.1: TypeScript compilation..."
|
|
515
|
+
cd /tmp/test-klasik-final/kustomize
|
|
516
|
+
npm init -y > /dev/null 2>&1
|
|
517
|
+
npm install class-transformer @nestjs/swagger reflect-metadata > /dev/null 2>&1
|
|
518
|
+
cat > tsconfig.json << 'EOF'
|
|
519
|
+
{
|
|
520
|
+
"compilerOptions": {
|
|
521
|
+
"target": "ES2020",
|
|
522
|
+
"module": "ES2020",
|
|
523
|
+
"moduleResolution": "node16",
|
|
524
|
+
"skipLibCheck": true,
|
|
525
|
+
"isolatedModules": true,
|
|
526
|
+
"emitDecoratorMetadata": true,
|
|
527
|
+
"experimentalDecorators": true
|
|
528
|
+
}
|
|
529
|
+
}
|
|
530
|
+
EOF
|
|
531
|
+
npx tsc --noEmit || exit 1
|
|
532
|
+
|
|
533
|
+
echo "🎉 All tests passed!"
|
|
534
|
+
```
|
|
535
|
+
|
|
536
|
+
Save as `/tmp/test-klasik.sh` and run with `bash /tmp/test-klasik.sh`
|
|
@@ -83,6 +83,7 @@ export declare class JSONSchemaGenerator {
|
|
|
83
83
|
* Generate index file for all definitions
|
|
84
84
|
* @param outputDir Output directory
|
|
85
85
|
* @param definitions List of definition names
|
|
86
|
+
* @param skipJsExtensions Whether to skip adding .js extensions to exports
|
|
86
87
|
*/
|
|
87
88
|
private generateIndex;
|
|
88
89
|
/**
|
|
@@ -157,7 +157,7 @@ class JSONSchemaGenerator {
|
|
|
157
157
|
}
|
|
158
158
|
// Step 3: Generate index file
|
|
159
159
|
console.log('\nStep 3: Generating index files...');
|
|
160
|
-
this.generateIndex(outputDir, allDefinitions);
|
|
160
|
+
this.generateIndex(outputDir, allDefinitions, skipJsExtensions);
|
|
161
161
|
console.log('\n✅ JSON Schema client generation completed successfully!');
|
|
162
162
|
console.log(`📁 Generated files location: ${outputDir}`);
|
|
163
163
|
}
|
|
@@ -244,16 +244,18 @@ class JSONSchemaGenerator {
|
|
|
244
244
|
* Generate index file for all definitions
|
|
245
245
|
* @param outputDir Output directory
|
|
246
246
|
* @param definitions List of definition names
|
|
247
|
+
* @param skipJsExtensions Whether to skip adding .js extensions to exports
|
|
247
248
|
*/
|
|
248
|
-
generateIndex(outputDir, definitions) {
|
|
249
|
+
generateIndex(outputDir, definitions, skipJsExtensions) {
|
|
249
250
|
// Remove duplicates
|
|
250
251
|
const uniqueDefinitions = Array.from(new Set(definitions));
|
|
251
|
-
// Generate exports
|
|
252
|
+
// Generate exports with .js extensions if not skipped
|
|
253
|
+
const extension = skipJsExtensions ? '' : '.js';
|
|
252
254
|
const exports = uniqueDefinitions
|
|
253
255
|
.map(name => {
|
|
254
256
|
// Convert definition name to file name (ConfigMapArgs → config-map-args)
|
|
255
257
|
const fileName = this.toFileName(name);
|
|
256
|
-
return `export * from './models/${fileName}';`;
|
|
258
|
+
return `export * from './models/${fileName}${extension}';`;
|
|
257
259
|
})
|
|
258
260
|
.join('\n');
|
|
259
261
|
const indexContent = `// Auto-generated index for JSON Schema models\n${exports}\n`;
|
|
@@ -12,6 +12,8 @@
|
|
|
12
12
|
* instead of empty strings
|
|
13
13
|
* 4. Invalid complex object types: Uses invalid arrow function syntax for
|
|
14
14
|
* dictionary types (type: () => [{ [key: string]: any; }])
|
|
15
|
+
* 5. Invalid string literal 'object': @nestjs/swagger@8+ only accepts
|
|
16
|
+
* constructors (Type<unknown> | Function), not string literals
|
|
15
17
|
*
|
|
16
18
|
* This utility transforms:
|
|
17
19
|
* - type: 'string' -> type: String
|
|
@@ -20,7 +22,8 @@
|
|
|
20
22
|
* - type: () => [string] -> type: [String]
|
|
21
23
|
* - type: () => [number] -> type: [Number]
|
|
22
24
|
* - type: () => [boolean] -> type: [Boolean]
|
|
23
|
-
* - type: () => [{ [key: string]: any; }] -> type:
|
|
25
|
+
* - type: () => [{ [key: string]: any; }], -> type: Object,
|
|
26
|
+
* - type: 'object', -> type: Object,
|
|
24
27
|
* - description: `text `code` here` -> description: `text \`code\` here`
|
|
25
28
|
* - description: undefined -> description: ''
|
|
26
29
|
*
|
|
@@ -54,6 +57,19 @@ export declare class NestJsTypeFixer {
|
|
|
54
57
|
* Replace undefined descriptions with empty strings
|
|
55
58
|
*/
|
|
56
59
|
private fixUndefinedDescriptions;
|
|
60
|
+
/**
|
|
61
|
+
* Fix references to non-existent classes in type fields
|
|
62
|
+
*
|
|
63
|
+
* When the generator creates types for union types like number | string,
|
|
64
|
+
* it sometimes references classes that don't exist (e.g., for x-kubernetes-int-or-string).
|
|
65
|
+
* This method detects and removes those invalid type references by checking if the
|
|
66
|
+
* referenced class is actually imported in the file.
|
|
67
|
+
*/
|
|
68
|
+
private fixMissingClassReferences;
|
|
69
|
+
/**
|
|
70
|
+
* Extract all imported class names from import statements
|
|
71
|
+
*/
|
|
72
|
+
private extractImportedClasses;
|
|
57
73
|
/**
|
|
58
74
|
* Get all TypeScript files in a directory recursively
|
|
59
75
|
*/
|
|
@@ -50,6 +50,8 @@ const path = __importStar(require("path"));
|
|
|
50
50
|
* instead of empty strings
|
|
51
51
|
* 4. Invalid complex object types: Uses invalid arrow function syntax for
|
|
52
52
|
* dictionary types (type: () => [{ [key: string]: any; }])
|
|
53
|
+
* 5. Invalid string literal 'object': @nestjs/swagger@8+ only accepts
|
|
54
|
+
* constructors (Type<unknown> | Function), not string literals
|
|
53
55
|
*
|
|
54
56
|
* This utility transforms:
|
|
55
57
|
* - type: 'string' -> type: String
|
|
@@ -58,7 +60,8 @@ const path = __importStar(require("path"));
|
|
|
58
60
|
* - type: () => [string] -> type: [String]
|
|
59
61
|
* - type: () => [number] -> type: [Number]
|
|
60
62
|
* - type: () => [boolean] -> type: [Boolean]
|
|
61
|
-
* - type: () => [{ [key: string]: any; }] -> type:
|
|
63
|
+
* - type: () => [{ [key: string]: any; }], -> type: Object,
|
|
64
|
+
* - type: 'object', -> type: Object,
|
|
62
65
|
* - description: `text `code` here` -> description: `text \`code\` here`
|
|
63
66
|
* - description: undefined -> description: ''
|
|
64
67
|
*
|
|
@@ -141,8 +144,16 @@ class NestJsTypeFixer {
|
|
|
141
144
|
return `type: [${capitalized}]`;
|
|
142
145
|
});
|
|
143
146
|
// Fix invalid complex object types with index signatures
|
|
144
|
-
// type: () => [{ [key: string]: any; }] -> type:
|
|
145
|
-
content = content.replace(/type:\s*\(\)\s*=>\s*\[\{[^\]]*\[key:\s*string\][^\]]*\}\]
|
|
147
|
+
// type: () => [{ [key: string]: any; }], -> type: Object,
|
|
148
|
+
content = content.replace(/type:\s*\(\)\s*=>\s*\[\{[^\]]*\[key:\s*string\][^\]]*\}\],/g, "type: Object,");
|
|
149
|
+
// Fix string literal 'object' which is invalid in @nestjs/swagger@8+
|
|
150
|
+
// @nestjs/swagger@8 only accepts constructors, not string literals
|
|
151
|
+
// type: 'object', -> type: Object,
|
|
152
|
+
content = content.replace(/type:\s*'object',/g, "type: Object,");
|
|
153
|
+
// Fix missing class references (classes that don't exist)
|
|
154
|
+
// For x-kubernetes-int-or-string types, remove the invalid type reference
|
|
155
|
+
// type: () => SomeNonExistentClass, -> (removed)
|
|
156
|
+
content = this.fixMissingClassReferences(content);
|
|
146
157
|
// Fix unescaped backticks in description strings
|
|
147
158
|
content = this.escapeBackticksInDescriptions(content);
|
|
148
159
|
// Fix undefined descriptions
|
|
@@ -171,6 +182,59 @@ class NestJsTypeFixer {
|
|
|
171
182
|
// Only affects @ApiProperty decorators (split-point protects attributeTypeMap)
|
|
172
183
|
return content.replace(/description:\s*undefined/g, "description: ''");
|
|
173
184
|
}
|
|
185
|
+
/**
|
|
186
|
+
* Fix references to non-existent classes in type fields
|
|
187
|
+
*
|
|
188
|
+
* When the generator creates types for union types like number | string,
|
|
189
|
+
* it sometimes references classes that don't exist (e.g., for x-kubernetes-int-or-string).
|
|
190
|
+
* This method detects and removes those invalid type references by checking if the
|
|
191
|
+
* referenced class is actually imported in the file.
|
|
192
|
+
*/
|
|
193
|
+
fixMissingClassReferences(content) {
|
|
194
|
+
// Extract all imported class names from the file
|
|
195
|
+
const importedClasses = this.extractImportedClasses(content);
|
|
196
|
+
// Find all type: () => ClassName references in @ApiProperty decorators
|
|
197
|
+
const typeReferenceRegex = /type:\s*\(\)\s*=>\s*([A-Z][a-zA-Z0-9]*),/g;
|
|
198
|
+
return content.replace(typeReferenceRegex, (match, className) => {
|
|
199
|
+
// If the class is not imported, remove the entire type line
|
|
200
|
+
if (!importedClasses.has(className)) {
|
|
201
|
+
// Remove the entire line including newline
|
|
202
|
+
return '';
|
|
203
|
+
}
|
|
204
|
+
// Keep it if the class is imported
|
|
205
|
+
return match;
|
|
206
|
+
});
|
|
207
|
+
}
|
|
208
|
+
/**
|
|
209
|
+
* Extract all imported class names from import statements
|
|
210
|
+
*/
|
|
211
|
+
extractImportedClasses(content) {
|
|
212
|
+
const importedClasses = new Set();
|
|
213
|
+
// Match various import patterns:
|
|
214
|
+
// import { ClassName } from '...'
|
|
215
|
+
// import { ClassName as Alias } from '...'
|
|
216
|
+
// import ClassName from '...'
|
|
217
|
+
const importRegex = /import\s+(?:type\s+)?(?:\{([^}]+)\}|([A-Z][a-zA-Z0-9]*))\s+from/g;
|
|
218
|
+
let match;
|
|
219
|
+
while ((match = importRegex.exec(content)) !== null) {
|
|
220
|
+
if (match[1]) {
|
|
221
|
+
// Named imports: { Class1, Class2 as Alias, Class3 }
|
|
222
|
+
const namedImports = match[1].split(',').map(s => s.trim());
|
|
223
|
+
namedImports.forEach(imp => {
|
|
224
|
+
// Handle "ClassName" or "ClassName as Alias"
|
|
225
|
+
const className = imp.split(/\s+as\s+/)[0].trim();
|
|
226
|
+
if (className && /^[A-Z]/.test(className)) {
|
|
227
|
+
importedClasses.add(className);
|
|
228
|
+
}
|
|
229
|
+
});
|
|
230
|
+
}
|
|
231
|
+
else if (match[2]) {
|
|
232
|
+
// Default import: import ClassName from '...'
|
|
233
|
+
importedClasses.add(match[2]);
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
return importedClasses;
|
|
237
|
+
}
|
|
174
238
|
/**
|
|
175
239
|
* Get all TypeScript files in a directory recursively
|
|
176
240
|
*/
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "klasik",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.34",
|
|
4
4
|
"description": "Download OpenAPI specs from remote URLs and generate TypeScript clients with class-transformer support - support yaml, json, CustomResourceDefinition formats",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
@@ -33,7 +33,7 @@
|
|
|
33
33
|
"class-transformer": "^0.5.1",
|
|
34
34
|
"commander": "^11.0.0",
|
|
35
35
|
"js-yaml": "^4.1.0",
|
|
36
|
-
"openapi-class-transformer": "^1.0.
|
|
36
|
+
"openapi-class-transformer": "^1.0.21",
|
|
37
37
|
"reflect-metadata": "^0.2.2"
|
|
38
38
|
},
|
|
39
39
|
"devDependencies": {
|