@shepherdjerred/helm-types 1.1.0 → 1.2.0-dev.893

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.
@@ -1,85 +1,31 @@
1
- import type { JSONSchemaProperty, TypeScriptInterface, TypeProperty } from "./types.js";
2
- import type { HelmValue } from "./schemas.js";
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
- } from "./schemas.js";
13
- import { shouldAllowArbitraryProps, isK8sResourceSpec } from "./config.js";
14
- import { sanitizePropertyName, sanitizeTypeName, capitalizeFirst } from "./utils.js";
15
-
16
- /**
17
- * Augment a Kubernetes resource spec interface with both requests and limits.
18
- * If only one is present, copy its type structure to the other.
19
- */
20
- function augmentK8sResourceSpec(iface: TypeScriptInterface): void {
21
- const hasRequests = "requests" in iface.properties;
22
- const hasLimits = "limits" in iface.properties;
23
-
24
- // If we have requests but not limits, add limits with the same structure
25
- if (hasRequests && !hasLimits) {
26
- const requestsProp = iface.properties["requests"];
27
- if (requestsProp) {
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.map((v) => (StringSchema.safeParse(v).success ? `"${String(v)}"` : String(v))).join(" | ");
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
- if (StringSchema.safeParse(schema.type).success) {
113
- switch (schema.type) {
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) return "unknown";
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 (NullSchema.safeParse(value).success || UndefinedSchema.safeParse(value).success) {
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 (trimmed !== "" && !isNaN(Number(trimmed)) && isFinite(Number(trimmed))) {
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(inferredType: string, schemaType: string): boolean {
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.split("|").map((t) => t.trim().replace(/^["']|["']$/g, ""));
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, // Pass the property name for better warnings
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 = chartName
297
- ? shouldAllowArbitraryProps(keyPrefix, chartName, keyPrefix.split(".").pop() ?? "", yamlComments?.get(keyPrefix))
298
- : false;
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
- function convertValueToProperty(
308
- value: unknown,
309
- nestedTypeName: string,
310
- schema?: JSONSchemaProperty,
311
- propertyName?: string,
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
- // If we have a JSON schema for this property, prefer it over inference
318
- if (schema) {
319
- // First, infer the type from the actual value for comparison
320
- const inferredType = inferTypeFromValue(value);
321
- const schemaType = jsonSchemaToTypeScript(schema);
322
-
323
- // Check if schema and inferred types are in agreement
324
- if (inferredType && !typesAreCompatible(inferredType, schemaType)) {
325
- const propName = propertyName ? `'${propertyName}': ` : "";
326
- console.warn(
327
- ` ⚠️ Type mismatch for ${propName}Schema says '${schemaType}' but value suggests '${inferredType}' (value: ${String(value).substring(0, 50)})`,
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
- // Merge description from schema and YAML comments
332
- let description = schema.description;
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
- // Otherwise, use the schema type directly
364
- const tsType = schemaType;
365
-
366
- // Handle object types without explicit properties
367
- const helmValueCheckForObject = HelmValueSchema.safeParse(value);
368
- if (tsType === "object" && helmValueCheckForObject.success) {
369
- const nestedInterface = convertToTypeScriptInterface(
370
- helmValueCheckForObject.data,
371
- nestedTypeName,
372
- undefined,
373
- yamlComments,
374
- fullKey,
375
- chartName,
376
- );
377
- return {
378
- type: nestedTypeName,
379
- optional: true,
380
- nested: nestedInterface,
381
- description,
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
- return { type: tsType, optional: true, description, default: defaultValue };
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
- // Fall back to runtime type inference when no schema is available
390
- // Use Zod schemas for robust type detection
391
- // IMPORTANT: Check for complex types (arrays, objects) BEFORE primitive types with coercion
355
+ return {
356
+ type: schemaType,
357
+ optional: true,
358
+ description,
359
+ default: defaultValue,
360
+ };
361
+ }
392
362
 
393
- // Check for null/undefined first
394
- if (NullSchema.safeParse(value).success || UndefinedSchema.safeParse(value).success) {
395
- return { type: "unknown", optional: true };
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
- // Check for array (before coercion checks, as coercion can convert arrays to true)
399
- const arrayResult = ArraySchema.safeParse(value);
400
- if (arrayResult.success) {
401
- const arrayValue = arrayResult.data;
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
- // Sample multiple elements for better type inference
407
- const elementTypes = new Set<string>();
408
- const elementTypeProps: TypeProperty[] = [];
409
- const sampleSize = Math.min(arrayValue.length, 3); // Check up to 3 elements
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
- for (let i = 0; i < sampleSize; i++) {
412
- const elementType = convertValueToProperty(arrayValue[i], nestedTypeName);
413
- elementTypes.add(elementType.type);
414
- elementTypeProps.push(elementType);
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
- // If all elements have the same type, use that
418
- if (elementTypes.size === 1) {
419
- const elementType = Array.from(elementTypes)[0];
420
- const elementProp = elementTypeProps[0];
421
- if (elementType && elementProp) {
422
- // For object array elements, we need to create a proper interface for the array element
423
- if (elementProp.nested) {
424
- // Create a new interface name for array elements
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
- // If mixed types, use union type for common cases
455
- const types = Array.from(elementTypes).sort();
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
- // Otherwise fall back to unknown[]
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
- // Augment K8s resource specs with both requests and limits
480
- if (propertyName && isK8sResourceSpec(propertyName)) {
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
- // Now check for primitives - first actual runtime types, then coerced string types
494
- // This prevents objects from being coerced to booleans
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
  }