@shepherdjerred/helm-types 1.1.0 → 1.2.0-dev.891
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 +11 -2
- package/dist/cli.js +4570 -3203
- package/dist/index.js +8 -20585
- package/package.json +5 -1
- package/src/chart-fetcher.ts +37 -16
- package/src/chart-info-parser.ts +54 -34
- package/src/cli.ts +76 -58
- package/src/code-generator.ts +57 -22
- package/src/comment-parser.ts +79 -55
- package/src/config.ts +12 -5
- package/src/helm-types.ts +16 -46
- package/src/index.ts +14 -1
- package/src/interface-generator.ts +58 -23
- package/src/schemas.ts +3 -1
- package/src/type-converter-helpers.ts +180 -0
- package/src/type-converter.ts +273 -300
- package/src/type-inference.ts +302 -194
- package/src/utils.ts +2 -2
- package/src/yaml-comment-filters.ts +103 -0
- package/src/yaml-comment-regex-parser.ts +150 -0
- package/src/yaml-comments.ts +216 -508
- package/src/yaml-preprocess.ts +235 -0
package/src/type-converter.ts
CHANGED
|
@@ -1,85 +1,31 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
|
|
1
|
+
import type {
|
|
2
|
+
JSONSchemaProperty,
|
|
3
|
+
TypeScriptInterface,
|
|
4
|
+
TypeProperty,
|
|
5
|
+
} from "./types.ts";
|
|
6
|
+
import type { HelmValue } from "./schemas.ts";
|
|
3
7
|
import {
|
|
4
8
|
StringSchema,
|
|
5
|
-
ActualNumberSchema,
|
|
6
|
-
ActualBooleanSchema,
|
|
7
9
|
NullSchema,
|
|
8
10
|
UndefinedSchema,
|
|
9
11
|
ArraySchema,
|
|
10
|
-
StringBooleanSchema,
|
|
11
12
|
HelmValueSchema,
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
// Create limits property with the same type but different name for the nested interface
|
|
29
|
-
const limitsTypeName = requestsProp.type.replace("Requests", "Limits");
|
|
30
|
-
|
|
31
|
-
// If there's a nested interface, create a copy for limits
|
|
32
|
-
if (requestsProp.nested) {
|
|
33
|
-
const limitsNested: TypeScriptInterface = {
|
|
34
|
-
name: limitsTypeName,
|
|
35
|
-
properties: { ...requestsProp.nested.properties },
|
|
36
|
-
allowArbitraryProps: requestsProp.nested.allowArbitraryProps,
|
|
37
|
-
};
|
|
38
|
-
iface.properties["limits"] = {
|
|
39
|
-
type: limitsTypeName,
|
|
40
|
-
optional: true,
|
|
41
|
-
nested: limitsNested,
|
|
42
|
-
description: "Kubernetes resource limits (memory, cpu, etc.)",
|
|
43
|
-
};
|
|
44
|
-
} else {
|
|
45
|
-
// No nested interface, just copy the type
|
|
46
|
-
iface.properties["limits"] = {
|
|
47
|
-
type: requestsProp.type,
|
|
48
|
-
optional: true,
|
|
49
|
-
description: "Kubernetes resource limits (memory, cpu, etc.)",
|
|
50
|
-
};
|
|
51
|
-
}
|
|
52
|
-
}
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
// If we have limits but not requests, add requests with the same structure
|
|
56
|
-
if (hasLimits && !hasRequests) {
|
|
57
|
-
const limitsProp = iface.properties["limits"];
|
|
58
|
-
if (limitsProp) {
|
|
59
|
-
const requestsTypeName = limitsProp.type.replace("Limits", "Requests");
|
|
60
|
-
|
|
61
|
-
if (limitsProp.nested) {
|
|
62
|
-
const requestsNested: TypeScriptInterface = {
|
|
63
|
-
name: requestsTypeName,
|
|
64
|
-
properties: { ...limitsProp.nested.properties },
|
|
65
|
-
allowArbitraryProps: limitsProp.nested.allowArbitraryProps,
|
|
66
|
-
};
|
|
67
|
-
iface.properties["requests"] = {
|
|
68
|
-
type: requestsTypeName,
|
|
69
|
-
optional: true,
|
|
70
|
-
nested: requestsNested,
|
|
71
|
-
description: "Kubernetes resource requests (memory, cpu, etc.)",
|
|
72
|
-
};
|
|
73
|
-
} else {
|
|
74
|
-
iface.properties["requests"] = {
|
|
75
|
-
type: limitsProp.type,
|
|
76
|
-
optional: true,
|
|
77
|
-
description: "Kubernetes resource requests (memory, cpu, etc.)",
|
|
78
|
-
};
|
|
79
|
-
}
|
|
80
|
-
}
|
|
81
|
-
}
|
|
82
|
-
}
|
|
13
|
+
ActualBooleanSchema,
|
|
14
|
+
ActualNumberSchema,
|
|
15
|
+
StringBooleanSchema,
|
|
16
|
+
} from "./schemas.ts";
|
|
17
|
+
import { shouldAllowArbitraryProps, isK8sResourceSpec } from "./config.ts";
|
|
18
|
+
import {
|
|
19
|
+
sanitizePropertyName,
|
|
20
|
+
sanitizeTypeName,
|
|
21
|
+
capitalizeFirst,
|
|
22
|
+
} from "./utils.ts";
|
|
23
|
+
import type { PropertyConversionContext } from "./type-converter-helpers.ts";
|
|
24
|
+
import {
|
|
25
|
+
mergeDescriptions,
|
|
26
|
+
inferPrimitiveType,
|
|
27
|
+
augmentK8sResourceSpec,
|
|
28
|
+
} from "./type-converter-helpers.ts";
|
|
83
29
|
|
|
84
30
|
/**
|
|
85
31
|
* Convert JSON schema type to TypeScript type string
|
|
@@ -99,7 +45,11 @@ export function jsonSchemaToTypeScript(schema: JSONSchemaProperty): string {
|
|
|
99
45
|
|
|
100
46
|
// Handle enum
|
|
101
47
|
if (schema.enum) {
|
|
102
|
-
return schema.enum
|
|
48
|
+
return schema.enum
|
|
49
|
+
.map((v) =>
|
|
50
|
+
StringSchema.safeParse(v).success ? `"${String(v)}"` : String(v),
|
|
51
|
+
)
|
|
52
|
+
.join(" | ");
|
|
103
53
|
}
|
|
104
54
|
|
|
105
55
|
// Handle array type
|
|
@@ -109,8 +59,9 @@ export function jsonSchemaToTypeScript(schema: JSONSchemaProperty): string {
|
|
|
109
59
|
}
|
|
110
60
|
|
|
111
61
|
// Handle basic types
|
|
112
|
-
|
|
113
|
-
|
|
62
|
+
const stringTypeCheck = StringSchema.safeParse(schema.type);
|
|
63
|
+
if (stringTypeCheck.success) {
|
|
64
|
+
switch (stringTypeCheck.data) {
|
|
114
65
|
case "string":
|
|
115
66
|
return "string";
|
|
116
67
|
case "number":
|
|
@@ -134,7 +85,9 @@ export function jsonSchemaToTypeScript(schema: JSONSchemaProperty): string {
|
|
|
134
85
|
if (arrayTypeCheck.success) {
|
|
135
86
|
return arrayTypeCheck.data
|
|
136
87
|
.map((t: unknown) => {
|
|
137
|
-
if (!StringSchema.safeParse(t).success)
|
|
88
|
+
if (!StringSchema.safeParse(t).success) {
|
|
89
|
+
return "unknown";
|
|
90
|
+
}
|
|
138
91
|
const typeStr = String(t);
|
|
139
92
|
switch (typeStr) {
|
|
140
93
|
case "string":
|
|
@@ -165,7 +118,10 @@ export function jsonSchemaToTypeScript(schema: JSONSchemaProperty): string {
|
|
|
165
118
|
*/
|
|
166
119
|
export function inferTypeFromValue(value: unknown): string | null {
|
|
167
120
|
// Check null/undefined
|
|
168
|
-
if (
|
|
121
|
+
if (
|
|
122
|
+
NullSchema.safeParse(value).success ||
|
|
123
|
+
UndefinedSchema.safeParse(value).success
|
|
124
|
+
) {
|
|
169
125
|
return null;
|
|
170
126
|
}
|
|
171
127
|
|
|
@@ -188,7 +144,11 @@ export function inferTypeFromValue(value: unknown): string | null {
|
|
|
188
144
|
const stringCheck = StringSchema.safeParse(value);
|
|
189
145
|
if (stringCheck.success) {
|
|
190
146
|
const trimmed = stringCheck.data.trim();
|
|
191
|
-
if (
|
|
147
|
+
if (
|
|
148
|
+
trimmed !== "" &&
|
|
149
|
+
!Number.isNaN(Number(trimmed)) &&
|
|
150
|
+
Number.isFinite(Number(trimmed))
|
|
151
|
+
) {
|
|
192
152
|
return "number";
|
|
193
153
|
}
|
|
194
154
|
}
|
|
@@ -214,7 +174,10 @@ export function inferTypeFromValue(value: unknown): string | null {
|
|
|
214
174
|
/**
|
|
215
175
|
* Check if inferred type is compatible with schema type
|
|
216
176
|
*/
|
|
217
|
-
export function typesAreCompatible(
|
|
177
|
+
export function typesAreCompatible(
|
|
178
|
+
inferredType: string,
|
|
179
|
+
schemaType: string,
|
|
180
|
+
): boolean {
|
|
218
181
|
// Exact match
|
|
219
182
|
if (inferredType === schemaType) {
|
|
220
183
|
return true;
|
|
@@ -222,7 +185,9 @@ export function typesAreCompatible(inferredType: string, schemaType: string): bo
|
|
|
222
185
|
|
|
223
186
|
// Check if the inferred type is part of a union in the schema
|
|
224
187
|
// For example: schemaType might be "number | \"default\"" and inferredType is "string"
|
|
225
|
-
const schemaTypes = schemaType
|
|
188
|
+
const schemaTypes = schemaType
|
|
189
|
+
.split("|")
|
|
190
|
+
.map((t) => t.trim().replaceAll(/^["']|["']$/g, ""));
|
|
226
191
|
|
|
227
192
|
// If schema is a union, check if inferred type is compatible with any part
|
|
228
193
|
if (schemaTypes.length > 1) {
|
|
@@ -262,222 +227,271 @@ export function typesAreCompatible(inferredType: string, schemaType: string): bo
|
|
|
262
227
|
/**
|
|
263
228
|
* Convert Helm values to TypeScript interface
|
|
264
229
|
*/
|
|
265
|
-
export function convertToTypeScriptInterface(
|
|
266
|
-
values: HelmValue
|
|
267
|
-
interfaceName: string
|
|
268
|
-
schema?: JSONSchemaProperty | null
|
|
269
|
-
yamlComments?: Map<string, string
|
|
270
|
-
keyPrefix
|
|
271
|
-
chartName?: string
|
|
272
|
-
): TypeScriptInterface {
|
|
230
|
+
export function convertToTypeScriptInterface(options: {
|
|
231
|
+
values: HelmValue;
|
|
232
|
+
interfaceName: string;
|
|
233
|
+
schema?: JSONSchemaProperty | null;
|
|
234
|
+
yamlComments?: Map<string, string>;
|
|
235
|
+
keyPrefix?: string;
|
|
236
|
+
chartName?: string;
|
|
237
|
+
}): TypeScriptInterface {
|
|
238
|
+
const keyPrefix = options.keyPrefix ?? "";
|
|
273
239
|
const properties: Record<string, TypeProperty> = {};
|
|
274
|
-
const schemaProps = schema?.properties;
|
|
240
|
+
const schemaProps = options.schema?.properties;
|
|
275
241
|
|
|
276
|
-
for (const [key, value] of Object.entries(values)) {
|
|
242
|
+
for (const [key, value] of Object.entries(options.values)) {
|
|
277
243
|
const sanitizedKey = sanitizePropertyName(key);
|
|
278
244
|
const typeNameSuffix = sanitizeTypeName(key);
|
|
279
245
|
const propertySchema = schemaProps?.[key];
|
|
280
246
|
const fullKey = keyPrefix ? `${keyPrefix}.${key}` : key;
|
|
281
|
-
const yamlComment = yamlComments?.get(fullKey);
|
|
247
|
+
const yamlComment = options.yamlComments?.get(fullKey);
|
|
282
248
|
|
|
283
|
-
properties[sanitizedKey] = convertValueToProperty(
|
|
249
|
+
properties[sanitizedKey] = convertValueToProperty({
|
|
284
250
|
value,
|
|
285
|
-
`${interfaceName}${capitalizeFirst(typeNameSuffix)}`,
|
|
286
|
-
propertySchema,
|
|
287
|
-
key,
|
|
251
|
+
nestedTypeName: `${options.interfaceName}${capitalizeFirst(typeNameSuffix)}`,
|
|
252
|
+
schema: propertySchema,
|
|
253
|
+
propertyName: key,
|
|
288
254
|
yamlComment,
|
|
289
|
-
yamlComments,
|
|
255
|
+
yamlComments: options.yamlComments,
|
|
290
256
|
fullKey,
|
|
291
|
-
chartName,
|
|
292
|
-
);
|
|
257
|
+
chartName: options.chartName,
|
|
258
|
+
});
|
|
293
259
|
}
|
|
294
260
|
|
|
295
261
|
// Check if this interface should allow arbitrary properties
|
|
296
|
-
const allowArbitraryProps =
|
|
297
|
-
|
|
298
|
-
|
|
262
|
+
const allowArbitraryProps =
|
|
263
|
+
options.chartName != null && options.chartName !== ""
|
|
264
|
+
? shouldAllowArbitraryProps(
|
|
265
|
+
keyPrefix,
|
|
266
|
+
options.chartName,
|
|
267
|
+
keyPrefix.split(".").pop() ?? "",
|
|
268
|
+
options.yamlComments?.get(keyPrefix),
|
|
269
|
+
)
|
|
270
|
+
: false;
|
|
299
271
|
|
|
300
272
|
return {
|
|
301
|
-
name: interfaceName,
|
|
273
|
+
name: options.interfaceName,
|
|
302
274
|
properties,
|
|
303
275
|
allowArbitraryProps,
|
|
304
276
|
};
|
|
305
277
|
}
|
|
306
278
|
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
yamlComment?: string,
|
|
313
|
-
yamlComments?: Map<string, string>,
|
|
314
|
-
fullKey?: string,
|
|
315
|
-
chartName?: string,
|
|
279
|
+
/**
|
|
280
|
+
* Convert a value to a TypeProperty using JSON schema information
|
|
281
|
+
*/
|
|
282
|
+
function convertWithSchema(
|
|
283
|
+
ctx: PropertyConversionContext & { schema: JSONSchemaProperty },
|
|
316
284
|
): TypeProperty {
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
285
|
+
const {
|
|
286
|
+
value,
|
|
287
|
+
nestedTypeName,
|
|
288
|
+
schema,
|
|
289
|
+
propertyName,
|
|
290
|
+
yamlComment,
|
|
291
|
+
yamlComments,
|
|
292
|
+
fullKey,
|
|
293
|
+
chartName,
|
|
294
|
+
} = ctx;
|
|
295
|
+
|
|
296
|
+
// Infer the type from the actual value for comparison
|
|
297
|
+
const inferredType = inferTypeFromValue(value);
|
|
298
|
+
const schemaType = jsonSchemaToTypeScript(schema);
|
|
299
|
+
|
|
300
|
+
// Warn about type mismatches
|
|
301
|
+
if (
|
|
302
|
+
inferredType != null &&
|
|
303
|
+
inferredType !== "" &&
|
|
304
|
+
!typesAreCompatible(inferredType, schemaType)
|
|
305
|
+
) {
|
|
306
|
+
const propName =
|
|
307
|
+
propertyName != null && propertyName !== "" ? `'${propertyName}': ` : "";
|
|
308
|
+
console.warn(
|
|
309
|
+
` ⚠️ Type mismatch for ${propName}Schema says '${schemaType}' but value suggests '${inferredType}' (value: ${String(value).slice(0, 50)})`,
|
|
310
|
+
);
|
|
311
|
+
}
|
|
330
312
|
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
if (yamlComment) {
|
|
334
|
-
if (description) {
|
|
335
|
-
// If both exist, combine them
|
|
336
|
-
description = `${yamlComment}\n\n${description}`;
|
|
337
|
-
} else {
|
|
338
|
-
description = yamlComment;
|
|
339
|
-
}
|
|
340
|
-
}
|
|
341
|
-
const defaultValue = schema.default !== undefined ? schema.default : value;
|
|
342
|
-
|
|
343
|
-
// If schema defines it as an object with properties, recurse
|
|
344
|
-
const helmValueCheckForProps = HelmValueSchema.safeParse(value);
|
|
345
|
-
if (schema.properties && helmValueCheckForProps.success) {
|
|
346
|
-
const nestedInterface = convertToTypeScriptInterface(
|
|
347
|
-
helmValueCheckForProps.data,
|
|
348
|
-
nestedTypeName,
|
|
349
|
-
schema,
|
|
350
|
-
yamlComments,
|
|
351
|
-
fullKey,
|
|
352
|
-
chartName,
|
|
353
|
-
);
|
|
354
|
-
return {
|
|
355
|
-
type: nestedTypeName,
|
|
356
|
-
optional: true,
|
|
357
|
-
nested: nestedInterface,
|
|
358
|
-
description,
|
|
359
|
-
default: defaultValue,
|
|
360
|
-
};
|
|
361
|
-
}
|
|
313
|
+
const description = mergeDescriptions(schema.description, yamlComment);
|
|
314
|
+
const defaultValue = schema.default === undefined ? value : schema.default;
|
|
362
315
|
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
default: defaultValue,
|
|
383
|
-
};
|
|
384
|
-
}
|
|
316
|
+
// If schema defines it as an object with properties, recurse
|
|
317
|
+
const helmValueCheckForProps = HelmValueSchema.safeParse(value);
|
|
318
|
+
if (schema.properties && helmValueCheckForProps.success) {
|
|
319
|
+
const nestedInterface = convertToTypeScriptInterface({
|
|
320
|
+
values: helmValueCheckForProps.data,
|
|
321
|
+
interfaceName: nestedTypeName,
|
|
322
|
+
schema,
|
|
323
|
+
yamlComments,
|
|
324
|
+
keyPrefix: fullKey,
|
|
325
|
+
chartName,
|
|
326
|
+
});
|
|
327
|
+
return {
|
|
328
|
+
type: nestedTypeName,
|
|
329
|
+
optional: true,
|
|
330
|
+
nested: nestedInterface,
|
|
331
|
+
description,
|
|
332
|
+
default: defaultValue,
|
|
333
|
+
};
|
|
334
|
+
}
|
|
385
335
|
|
|
386
|
-
|
|
336
|
+
// Handle object types without explicit properties
|
|
337
|
+
const helmValueCheckForObject = HelmValueSchema.safeParse(value);
|
|
338
|
+
if (schemaType === "object" && helmValueCheckForObject.success) {
|
|
339
|
+
const nestedInterface = convertToTypeScriptInterface({
|
|
340
|
+
values: helmValueCheckForObject.data,
|
|
341
|
+
interfaceName: nestedTypeName,
|
|
342
|
+
yamlComments,
|
|
343
|
+
keyPrefix: fullKey,
|
|
344
|
+
chartName,
|
|
345
|
+
});
|
|
346
|
+
return {
|
|
347
|
+
type: nestedTypeName,
|
|
348
|
+
optional: true,
|
|
349
|
+
nested: nestedInterface,
|
|
350
|
+
description,
|
|
351
|
+
default: defaultValue,
|
|
352
|
+
};
|
|
387
353
|
}
|
|
388
354
|
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
355
|
+
return {
|
|
356
|
+
type: schemaType,
|
|
357
|
+
optional: true,
|
|
358
|
+
description,
|
|
359
|
+
default: defaultValue,
|
|
360
|
+
};
|
|
361
|
+
}
|
|
392
362
|
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
363
|
+
/**
|
|
364
|
+
* Infer array element type from sampled elements
|
|
365
|
+
*/
|
|
366
|
+
function inferArrayType(
|
|
367
|
+
ctx: PropertyConversionContext,
|
|
368
|
+
arrayValue: unknown[],
|
|
369
|
+
): TypeProperty {
|
|
370
|
+
const { nestedTypeName } = ctx;
|
|
371
|
+
|
|
372
|
+
if (arrayValue.length === 0) {
|
|
373
|
+
return { type: "unknown[]", optional: true };
|
|
396
374
|
}
|
|
397
375
|
|
|
398
|
-
//
|
|
399
|
-
const
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
if (arrayValue.length === 0) {
|
|
403
|
-
return { type: "unknown[]", optional: true };
|
|
404
|
-
}
|
|
376
|
+
// Sample multiple elements for better type inference
|
|
377
|
+
const elementTypes = new Set<string>();
|
|
378
|
+
const elementTypeProps: TypeProperty[] = [];
|
|
379
|
+
const sampleSize = Math.min(arrayValue.length, 3);
|
|
405
380
|
|
|
406
|
-
|
|
407
|
-
const
|
|
408
|
-
|
|
409
|
-
|
|
381
|
+
for (let i = 0; i < sampleSize; i++) {
|
|
382
|
+
const elementType = convertValueToProperty({
|
|
383
|
+
value: arrayValue[i],
|
|
384
|
+
nestedTypeName,
|
|
385
|
+
});
|
|
386
|
+
elementTypes.add(elementType.type);
|
|
387
|
+
elementTypeProps.push(elementType);
|
|
388
|
+
}
|
|
410
389
|
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
}
|
|
390
|
+
// If all elements have the same type, use that
|
|
391
|
+
if (elementTypes.size === 1) {
|
|
392
|
+
return inferUniformArrayType(ctx, elementTypes, elementTypeProps);
|
|
393
|
+
}
|
|
416
394
|
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
const arrayElementTypeName = `${nestedTypeName}Element`;
|
|
426
|
-
|
|
427
|
-
// Check if array elements should allow arbitrary properties
|
|
428
|
-
// Array elements inherit extensibility from their parent array
|
|
429
|
-
const allowArbitraryProps =
|
|
430
|
-
chartName && fullKey
|
|
431
|
-
? shouldAllowArbitraryProps(fullKey, chartName, propertyName ?? "", yamlComment)
|
|
432
|
-
: false;
|
|
433
|
-
|
|
434
|
-
const arrayElementInterface: TypeScriptInterface = {
|
|
435
|
-
name: arrayElementTypeName,
|
|
436
|
-
properties: elementProp.nested.properties,
|
|
437
|
-
allowArbitraryProps,
|
|
438
|
-
};
|
|
439
|
-
|
|
440
|
-
return {
|
|
441
|
-
type: `${arrayElementTypeName}[]`,
|
|
442
|
-
optional: true,
|
|
443
|
-
nested: arrayElementInterface,
|
|
444
|
-
};
|
|
445
|
-
} else {
|
|
446
|
-
return {
|
|
447
|
-
type: `${elementType}[]`,
|
|
448
|
-
optional: true,
|
|
449
|
-
};
|
|
450
|
-
}
|
|
451
|
-
}
|
|
452
|
-
}
|
|
395
|
+
// If mixed types, use union type for common cases
|
|
396
|
+
const types = [...elementTypes].toSorted();
|
|
397
|
+
if (
|
|
398
|
+
types.length <= 3 &&
|
|
399
|
+
types.every((t) => ["string", "number", "boolean"].includes(t))
|
|
400
|
+
) {
|
|
401
|
+
return { type: `(${types.join(" | ")})[]`, optional: true };
|
|
402
|
+
}
|
|
453
403
|
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
if (types.length <= 3 && types.every((t) => ["string", "number", "boolean"].includes(t))) {
|
|
457
|
-
return {
|
|
458
|
-
type: `(${types.join(" | ")})[]`,
|
|
459
|
-
optional: true,
|
|
460
|
-
};
|
|
461
|
-
}
|
|
404
|
+
return { type: "unknown[]", optional: true };
|
|
405
|
+
}
|
|
462
406
|
|
|
463
|
-
|
|
407
|
+
/**
|
|
408
|
+
* Build TypeProperty for a uniform-type array
|
|
409
|
+
*/
|
|
410
|
+
function inferUniformArrayType(
|
|
411
|
+
ctx: PropertyConversionContext,
|
|
412
|
+
elementTypes: Set<string>,
|
|
413
|
+
elementTypeProps: TypeProperty[],
|
|
414
|
+
): TypeProperty {
|
|
415
|
+
const { nestedTypeName, chartName, fullKey, propertyName, yamlComment } = ctx;
|
|
416
|
+
const elementType = [...elementTypes][0];
|
|
417
|
+
const elementProp = elementTypeProps[0];
|
|
418
|
+
if (elementType == null || elementType === "" || !elementProp) {
|
|
464
419
|
return { type: "unknown[]", optional: true };
|
|
465
420
|
}
|
|
466
421
|
|
|
422
|
+
if (elementProp.nested) {
|
|
423
|
+
const arrayElementTypeName = `${nestedTypeName}Element`;
|
|
424
|
+
const allowArbitraryProps =
|
|
425
|
+
chartName != null && chartName !== "" && fullKey != null && fullKey !== ""
|
|
426
|
+
? shouldAllowArbitraryProps(
|
|
427
|
+
fullKey,
|
|
428
|
+
chartName,
|
|
429
|
+
propertyName ?? "",
|
|
430
|
+
yamlComment,
|
|
431
|
+
)
|
|
432
|
+
: false;
|
|
433
|
+
const arrayElementInterface: TypeScriptInterface = {
|
|
434
|
+
name: arrayElementTypeName,
|
|
435
|
+
properties: elementProp.nested.properties,
|
|
436
|
+
allowArbitraryProps,
|
|
437
|
+
};
|
|
438
|
+
return {
|
|
439
|
+
type: `${arrayElementTypeName}[]`,
|
|
440
|
+
optional: true,
|
|
441
|
+
nested: arrayElementInterface,
|
|
442
|
+
};
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
return { type: `${elementType}[]`, optional: true };
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
function convertValueToProperty(opts: PropertyConversionContext): TypeProperty {
|
|
449
|
+
const {
|
|
450
|
+
value,
|
|
451
|
+
nestedTypeName,
|
|
452
|
+
schema,
|
|
453
|
+
propertyName,
|
|
454
|
+
yamlComment,
|
|
455
|
+
yamlComments,
|
|
456
|
+
fullKey,
|
|
457
|
+
chartName,
|
|
458
|
+
} = opts;
|
|
459
|
+
|
|
460
|
+
// If we have a JSON schema for this property, prefer it over inference
|
|
461
|
+
if (schema) {
|
|
462
|
+
return convertWithSchema({ ...opts, schema });
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
// Check for null/undefined first
|
|
466
|
+
if (
|
|
467
|
+
NullSchema.safeParse(value).success ||
|
|
468
|
+
UndefinedSchema.safeParse(value).success
|
|
469
|
+
) {
|
|
470
|
+
return { type: "unknown", optional: true };
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
// Check for array (before coercion checks)
|
|
474
|
+
const arrayResult = ArraySchema.safeParse(value);
|
|
475
|
+
if (arrayResult.success) {
|
|
476
|
+
return inferArrayType(opts, arrayResult.data);
|
|
477
|
+
}
|
|
478
|
+
|
|
467
479
|
// Check for object (before primitive coercion checks)
|
|
468
480
|
const objectResult = HelmValueSchema.safeParse(value);
|
|
469
481
|
if (objectResult.success) {
|
|
470
|
-
const nestedInterface = convertToTypeScriptInterface(
|
|
471
|
-
objectResult.data,
|
|
472
|
-
nestedTypeName,
|
|
473
|
-
undefined,
|
|
482
|
+
const nestedInterface = convertToTypeScriptInterface({
|
|
483
|
+
values: objectResult.data,
|
|
484
|
+
interfaceName: nestedTypeName,
|
|
474
485
|
yamlComments,
|
|
475
|
-
fullKey,
|
|
486
|
+
keyPrefix: fullKey,
|
|
476
487
|
chartName,
|
|
477
|
-
);
|
|
488
|
+
});
|
|
478
489
|
|
|
479
|
-
|
|
480
|
-
|
|
490
|
+
if (
|
|
491
|
+
propertyName != null &&
|
|
492
|
+
propertyName !== "" &&
|
|
493
|
+
isK8sResourceSpec(propertyName)
|
|
494
|
+
) {
|
|
481
495
|
augmentK8sResourceSpec(nestedInterface);
|
|
482
496
|
}
|
|
483
497
|
|
|
@@ -490,47 +504,6 @@ function convertValueToProperty(
|
|
|
490
504
|
};
|
|
491
505
|
}
|
|
492
506
|
|
|
493
|
-
//
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
// Check for actual runtime boolean (true/false)
|
|
497
|
-
if (ActualBooleanSchema.safeParse(value).success) {
|
|
498
|
-
return { type: "boolean", optional: true, description: yamlComment, default: value };
|
|
499
|
-
}
|
|
500
|
-
|
|
501
|
-
// Check for actual runtime number
|
|
502
|
-
if (ActualNumberSchema.safeParse(value).success) {
|
|
503
|
-
return { type: "number", optional: true, description: yamlComment, default: value };
|
|
504
|
-
}
|
|
505
|
-
|
|
506
|
-
// Check if it's a string that represents a boolean ("true", "FALSE", etc.)
|
|
507
|
-
if (StringBooleanSchema.safeParse(value).success) {
|
|
508
|
-
return { type: "boolean", optional: true, description: yamlComment, default: value };
|
|
509
|
-
}
|
|
510
|
-
|
|
511
|
-
// Check if it's a string that represents a number ("15", "0", etc.)
|
|
512
|
-
// Only treat non-empty strings that parse as numbers as numbers
|
|
513
|
-
const stringCheckForNumber = StringSchema.safeParse(value);
|
|
514
|
-
if (stringCheckForNumber.success) {
|
|
515
|
-
const trimmed = stringCheckForNumber.data.trim();
|
|
516
|
-
// Don't treat empty strings or purely whitespace as numbers
|
|
517
|
-
if (trimmed !== "" && !isNaN(Number(trimmed)) && isFinite(Number(trimmed))) {
|
|
518
|
-
return { type: "number", optional: true, description: yamlComment, default: value };
|
|
519
|
-
}
|
|
520
|
-
}
|
|
521
|
-
|
|
522
|
-
// Check for plain string (strings that don't look like numbers or booleans)
|
|
523
|
-
const stringCheckForPlain = StringSchema.safeParse(value);
|
|
524
|
-
if (stringCheckForPlain.success) {
|
|
525
|
-
// Special case: "default" is often used as a sentinel value in Helm charts
|
|
526
|
-
// that can be overridden with actual typed values (numbers, booleans, etc.)
|
|
527
|
-
if (stringCheckForPlain.data === "default") {
|
|
528
|
-
return { type: "string | number | boolean", optional: true, description: yamlComment, default: value };
|
|
529
|
-
}
|
|
530
|
-
return { type: "string", optional: true, description: yamlComment, default: value };
|
|
531
|
-
}
|
|
532
|
-
|
|
533
|
-
// Fallback for any unrecognized type
|
|
534
|
-
console.warn(`Unrecognized value type for: ${String(value)}, using 'unknown'`);
|
|
535
|
-
return { type: "unknown", optional: true, description: yamlComment };
|
|
507
|
+
// Infer primitive type from runtime value
|
|
508
|
+
return inferPrimitiveType(value, yamlComment);
|
|
536
509
|
}
|