@shepherdjerred/helm-types 1.7.0-dev.13412 → 1.7.0-dev.13599
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/package.json +1 -1
- package/src/code-generator.ts +0 -226
- package/src/type-inference.ts +0 -548
package/package.json
CHANGED
package/src/code-generator.ts
DELETED
|
@@ -1,226 +0,0 @@
|
|
|
1
|
-
import type { TypeScriptInterface } from "./types.ts";
|
|
2
|
-
import {
|
|
3
|
-
StringSchema,
|
|
4
|
-
ArraySchema,
|
|
5
|
-
RecordSchema,
|
|
6
|
-
ActualNumberSchema,
|
|
7
|
-
ActualBooleanSchema,
|
|
8
|
-
} from "./schemas.ts";
|
|
9
|
-
import { capitalizeFirst } from "./utils.ts";
|
|
10
|
-
|
|
11
|
-
/**
|
|
12
|
-
* Generate TypeScript code from interface definition
|
|
13
|
-
*/
|
|
14
|
-
export function generateTypeScriptCode(
|
|
15
|
-
mainInterface: TypeScriptInterface,
|
|
16
|
-
chartName: string,
|
|
17
|
-
): string {
|
|
18
|
-
const interfaces: TypeScriptInterface[] = [];
|
|
19
|
-
|
|
20
|
-
// Collect all nested interfaces
|
|
21
|
-
collectNestedInterfaces(mainInterface, interfaces);
|
|
22
|
-
|
|
23
|
-
let code = `// Generated TypeScript types for ${chartName} Helm chart\n\n`;
|
|
24
|
-
|
|
25
|
-
// Generate all interfaces
|
|
26
|
-
for (const iface of interfaces) {
|
|
27
|
-
code += generateInterfaceCode(iface);
|
|
28
|
-
code += "\n";
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
// Generate parameter type (flattened dot notation)
|
|
32
|
-
code += generateParameterType(mainInterface, chartName);
|
|
33
|
-
|
|
34
|
-
// Add header comment for generated files
|
|
35
|
-
if (code.includes(": any")) {
|
|
36
|
-
code = `// Generated TypeScript types for ${chartName} Helm chart
|
|
37
|
-
|
|
38
|
-
${code.slice(Math.max(0, code.indexOf("\n\n") + 2))}`;
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
return code;
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
function collectNestedInterfaces(
|
|
45
|
-
iface: TypeScriptInterface,
|
|
46
|
-
collected: TypeScriptInterface[],
|
|
47
|
-
): void {
|
|
48
|
-
for (const prop of Object.values(iface.properties)) {
|
|
49
|
-
if (prop.nested) {
|
|
50
|
-
collected.push(prop.nested);
|
|
51
|
-
collectNestedInterfaces(prop.nested, collected);
|
|
52
|
-
}
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
// Add main interface last so dependencies come first
|
|
56
|
-
if (!collected.some((i) => i.name === iface.name)) {
|
|
57
|
-
collected.push(iface);
|
|
58
|
-
}
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
function generateInterfaceCode(iface: TypeScriptInterface): string {
|
|
62
|
-
const hasProperties = Object.keys(iface.properties).length > 0;
|
|
63
|
-
|
|
64
|
-
if (!hasProperties) {
|
|
65
|
-
// Use 'object' for empty interfaces instead of '{}'
|
|
66
|
-
return `export type ${iface.name} = object;\n`;
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
let code = `export type ${iface.name} = {\n`;
|
|
70
|
-
|
|
71
|
-
for (const [key, prop] of Object.entries(iface.properties)) {
|
|
72
|
-
const optional = prop.optional ? "?" : "";
|
|
73
|
-
|
|
74
|
-
// Generate JSDoc comment if we have description or default
|
|
75
|
-
if (
|
|
76
|
-
(prop.description != null && prop.description !== "") ||
|
|
77
|
-
prop.default !== undefined
|
|
78
|
-
) {
|
|
79
|
-
code += ` /**\n`;
|
|
80
|
-
|
|
81
|
-
if (prop.description != null && prop.description !== "") {
|
|
82
|
-
// Format multi-line descriptions properly with " * " prefix
|
|
83
|
-
// Escape */ sequences to prevent premature comment closure
|
|
84
|
-
const escapedDescription = prop.description.replaceAll(
|
|
85
|
-
"*/",
|
|
86
|
-
String.raw`*\/`,
|
|
87
|
-
);
|
|
88
|
-
const descLines = escapedDescription.split("\n");
|
|
89
|
-
for (const line of descLines) {
|
|
90
|
-
code += ` * ${line}\n`;
|
|
91
|
-
}
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
if (prop.default !== undefined) {
|
|
95
|
-
const defaultStr = formatDefaultValue(prop.default);
|
|
96
|
-
const hasDescription =
|
|
97
|
-
prop.description != null && prop.description !== "";
|
|
98
|
-
if (defaultStr !== "" && hasDescription && defaultStr != null) {
|
|
99
|
-
code += ` *\n`;
|
|
100
|
-
}
|
|
101
|
-
if (defaultStr !== "" && defaultStr != null) {
|
|
102
|
-
code += ` * @default ${defaultStr}\n`;
|
|
103
|
-
}
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
code += ` */\n`;
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
code += ` ${key}${optional}: ${prop.type};\n`;
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
code += "};\n";
|
|
113
|
-
return code;
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
/**
|
|
117
|
-
* Format a default value for display in JSDoc
|
|
118
|
-
*/
|
|
119
|
-
function formatDefaultValue(value: unknown): string | null {
|
|
120
|
-
if (value === null) {
|
|
121
|
-
return "null";
|
|
122
|
-
}
|
|
123
|
-
if (value === undefined) {
|
|
124
|
-
return null;
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
// Handle arrays
|
|
128
|
-
const arrayCheck = ArraySchema.safeParse(value);
|
|
129
|
-
if (arrayCheck.success) {
|
|
130
|
-
if (arrayCheck.data.length === 0) {
|
|
131
|
-
return "[]";
|
|
132
|
-
}
|
|
133
|
-
if (arrayCheck.data.length <= 3) {
|
|
134
|
-
try {
|
|
135
|
-
return JSON.stringify(arrayCheck.data);
|
|
136
|
-
} catch {
|
|
137
|
-
return "[...]";
|
|
138
|
-
}
|
|
139
|
-
}
|
|
140
|
-
return `[...] (${String(arrayCheck.data.length)} items)`;
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
// Handle objects
|
|
144
|
-
const recordCheck = RecordSchema.safeParse(value);
|
|
145
|
-
if (recordCheck.success) {
|
|
146
|
-
const keys = Object.keys(recordCheck.data);
|
|
147
|
-
if (keys.length === 0) {
|
|
148
|
-
return "{}";
|
|
149
|
-
}
|
|
150
|
-
if (keys.length <= 3) {
|
|
151
|
-
try {
|
|
152
|
-
return JSON.stringify(recordCheck.data);
|
|
153
|
-
} catch {
|
|
154
|
-
return "{...}";
|
|
155
|
-
}
|
|
156
|
-
}
|
|
157
|
-
return `{...} (${String(keys.length)} keys)`;
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
// Primitives
|
|
161
|
-
const stringCheck = StringSchema.safeParse(value);
|
|
162
|
-
if (stringCheck.success) {
|
|
163
|
-
// Truncate long strings
|
|
164
|
-
if (stringCheck.data.length > 50) {
|
|
165
|
-
return `"${stringCheck.data.slice(0, 47)}..."`;
|
|
166
|
-
}
|
|
167
|
-
return `"${stringCheck.data}"`;
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
// Handle other primitives (numbers, booleans, etc.)
|
|
171
|
-
const numberCheck = ActualNumberSchema.safeParse(value);
|
|
172
|
-
if (numberCheck.success) {
|
|
173
|
-
return String(numberCheck.data);
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
const booleanCheck = ActualBooleanSchema.safeParse(value);
|
|
177
|
-
if (booleanCheck.success) {
|
|
178
|
-
return String(booleanCheck.data);
|
|
179
|
-
}
|
|
180
|
-
|
|
181
|
-
// Fallback for unknown types - try JSON.stringify
|
|
182
|
-
try {
|
|
183
|
-
return JSON.stringify(value);
|
|
184
|
-
} catch {
|
|
185
|
-
return "unknown";
|
|
186
|
-
}
|
|
187
|
-
}
|
|
188
|
-
|
|
189
|
-
function generateParameterType(
|
|
190
|
-
iface: TypeScriptInterface,
|
|
191
|
-
chartName: string,
|
|
192
|
-
): string {
|
|
193
|
-
const parameterKeys = flattenInterfaceKeys(iface);
|
|
194
|
-
|
|
195
|
-
const normalizedChartName = capitalizeFirst(chartName).replaceAll("-", "");
|
|
196
|
-
let code = `export type ${normalizedChartName}HelmParameters = {\n`;
|
|
197
|
-
|
|
198
|
-
for (const key of parameterKeys) {
|
|
199
|
-
code += ` "${key}"?: string;\n`;
|
|
200
|
-
}
|
|
201
|
-
|
|
202
|
-
code += "};\n";
|
|
203
|
-
|
|
204
|
-
return code;
|
|
205
|
-
}
|
|
206
|
-
|
|
207
|
-
function flattenInterfaceKeys(
|
|
208
|
-
iface: TypeScriptInterface,
|
|
209
|
-
prefix = "",
|
|
210
|
-
): string[] {
|
|
211
|
-
const keys: string[] = [];
|
|
212
|
-
|
|
213
|
-
for (const [key, prop] of Object.entries(iface.properties)) {
|
|
214
|
-
// Remove quotes from key for parameter names
|
|
215
|
-
const cleanKey = key.replaceAll('"', "");
|
|
216
|
-
const fullKey = prefix ? `${prefix}.${cleanKey}` : cleanKey;
|
|
217
|
-
|
|
218
|
-
if (prop.nested) {
|
|
219
|
-
keys.push(...flattenInterfaceKeys(prop.nested, fullKey));
|
|
220
|
-
} else {
|
|
221
|
-
keys.push(fullKey);
|
|
222
|
-
}
|
|
223
|
-
}
|
|
224
|
-
|
|
225
|
-
return keys;
|
|
226
|
-
}
|
package/src/type-inference.ts
DELETED
|
@@ -1,548 +0,0 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
JSONSchemaProperty,
|
|
3
|
-
TypeScriptInterface,
|
|
4
|
-
TypeProperty,
|
|
5
|
-
} from "./types.ts";
|
|
6
|
-
import type { HelmValue } from "./schemas.ts";
|
|
7
|
-
import {
|
|
8
|
-
StringSchema,
|
|
9
|
-
ActualNumberSchema,
|
|
10
|
-
ActualBooleanSchema,
|
|
11
|
-
NullSchema,
|
|
12
|
-
UndefinedSchema,
|
|
13
|
-
ArraySchema,
|
|
14
|
-
HelmValueSchema,
|
|
15
|
-
StringBooleanSchema,
|
|
16
|
-
} from "./schemas.ts";
|
|
17
|
-
import {
|
|
18
|
-
capitalizeFirst,
|
|
19
|
-
sanitizePropertyName,
|
|
20
|
-
sanitizeTypeName,
|
|
21
|
-
} from "./utils.ts";
|
|
22
|
-
|
|
23
|
-
/**
|
|
24
|
-
* Convert JSON schema type to TypeScript type string
|
|
25
|
-
*/
|
|
26
|
-
export function jsonSchemaToTypeScript(schema: JSONSchemaProperty): string {
|
|
27
|
-
// Handle oneOf - union of types
|
|
28
|
-
if (schema.oneOf) {
|
|
29
|
-
const types = schema.oneOf.map((s) => jsonSchemaToTypeScript(s));
|
|
30
|
-
return types.join(" | ");
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
// Handle anyOf - union of types
|
|
34
|
-
if (schema.anyOf) {
|
|
35
|
-
const types = schema.anyOf.map((s) => jsonSchemaToTypeScript(s));
|
|
36
|
-
return types.join(" | ");
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
// Handle enum
|
|
40
|
-
if (schema.enum) {
|
|
41
|
-
return schema.enum
|
|
42
|
-
.map((v) =>
|
|
43
|
-
StringSchema.safeParse(v).success ? `"${String(v)}"` : String(v),
|
|
44
|
-
)
|
|
45
|
-
.join(" | ");
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
// Handle array type
|
|
49
|
-
if (schema.type === "array" && schema.items) {
|
|
50
|
-
const itemType = jsonSchemaToTypeScript(schema.items);
|
|
51
|
-
return `${itemType}[]`;
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
// Handle basic types
|
|
55
|
-
const stringTypeCheck = StringSchema.safeParse(schema.type);
|
|
56
|
-
if (stringTypeCheck.success) {
|
|
57
|
-
switch (stringTypeCheck.data) {
|
|
58
|
-
case "string":
|
|
59
|
-
return "string";
|
|
60
|
-
case "number":
|
|
61
|
-
case "integer":
|
|
62
|
-
return "number";
|
|
63
|
-
case "boolean":
|
|
64
|
-
return "boolean";
|
|
65
|
-
case "object":
|
|
66
|
-
return "object";
|
|
67
|
-
case "array":
|
|
68
|
-
return "unknown[]";
|
|
69
|
-
case "null":
|
|
70
|
-
return "null";
|
|
71
|
-
default:
|
|
72
|
-
return "unknown";
|
|
73
|
-
}
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
// Handle multiple types
|
|
77
|
-
const arrayTypeCheck = ArraySchema.safeParse(schema.type);
|
|
78
|
-
if (arrayTypeCheck.success) {
|
|
79
|
-
return arrayTypeCheck.data
|
|
80
|
-
.map((t: unknown) => {
|
|
81
|
-
if (!StringSchema.safeParse(t).success) {
|
|
82
|
-
return "unknown";
|
|
83
|
-
}
|
|
84
|
-
const typeStr = String(t);
|
|
85
|
-
switch (typeStr) {
|
|
86
|
-
case "string":
|
|
87
|
-
return "string";
|
|
88
|
-
case "number":
|
|
89
|
-
case "integer":
|
|
90
|
-
return "number";
|
|
91
|
-
case "boolean":
|
|
92
|
-
return "boolean";
|
|
93
|
-
case "object":
|
|
94
|
-
return "object";
|
|
95
|
-
case "array":
|
|
96
|
-
return "unknown[]";
|
|
97
|
-
case "null":
|
|
98
|
-
return "null";
|
|
99
|
-
default:
|
|
100
|
-
return "unknown";
|
|
101
|
-
}
|
|
102
|
-
})
|
|
103
|
-
.join(" | ");
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
return "unknown";
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
/**
|
|
110
|
-
* Infer TypeScript type from actual runtime value
|
|
111
|
-
*/
|
|
112
|
-
export function inferTypeFromValue(value: unknown): string | null {
|
|
113
|
-
// Check null/undefined
|
|
114
|
-
if (
|
|
115
|
-
NullSchema.safeParse(value).success ||
|
|
116
|
-
UndefinedSchema.safeParse(value).success
|
|
117
|
-
) {
|
|
118
|
-
return null;
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
// Check for actual boolean
|
|
122
|
-
if (ActualBooleanSchema.safeParse(value).success) {
|
|
123
|
-
return "boolean";
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
// Check for actual number
|
|
127
|
-
if (ActualNumberSchema.safeParse(value).success) {
|
|
128
|
-
return "number";
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
// Check if it's a string that looks like a boolean
|
|
132
|
-
if (StringBooleanSchema.safeParse(value).success) {
|
|
133
|
-
return "boolean";
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
// Check if it's a string that looks like a number
|
|
137
|
-
const stringCheck = StringSchema.safeParse(value);
|
|
138
|
-
if (stringCheck.success) {
|
|
139
|
-
const trimmed = stringCheck.data.trim();
|
|
140
|
-
if (
|
|
141
|
-
trimmed !== "" &&
|
|
142
|
-
!Number.isNaN(Number(trimmed)) &&
|
|
143
|
-
Number.isFinite(Number(trimmed))
|
|
144
|
-
) {
|
|
145
|
-
return "number";
|
|
146
|
-
}
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
// Check for array
|
|
150
|
-
if (ArraySchema.safeParse(value).success) {
|
|
151
|
-
return "array";
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
// Check for object
|
|
155
|
-
if (HelmValueSchema.safeParse(value).success) {
|
|
156
|
-
return "object";
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
// Plain string
|
|
160
|
-
if (StringSchema.safeParse(value).success) {
|
|
161
|
-
return "string";
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
return "unknown";
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
/**
|
|
168
|
-
* Check if inferred type is compatible with schema type
|
|
169
|
-
*/
|
|
170
|
-
export function typesAreCompatible(
|
|
171
|
-
inferredType: string,
|
|
172
|
-
schemaType: string,
|
|
173
|
-
): boolean {
|
|
174
|
-
// Exact match
|
|
175
|
-
if (inferredType === schemaType) {
|
|
176
|
-
return true;
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
// Check if the inferred type is part of a union in the schema
|
|
180
|
-
// For example: schemaType might be "number | \"default\"" and inferredType is "string"
|
|
181
|
-
const schemaTypes = schemaType
|
|
182
|
-
.split("|")
|
|
183
|
-
.map((t) => t.trim().replaceAll(/^["']|["']$/g, ""));
|
|
184
|
-
|
|
185
|
-
// If schema is a union, check if inferred type is compatible with any part
|
|
186
|
-
if (schemaTypes.length > 1) {
|
|
187
|
-
for (const st of schemaTypes) {
|
|
188
|
-
// Handle quoted strings in unions (like "default")
|
|
189
|
-
if (inferredType === "string" && st.startsWith('"') && st.endsWith('"')) {
|
|
190
|
-
return true;
|
|
191
|
-
}
|
|
192
|
-
if (st === inferredType) {
|
|
193
|
-
return true;
|
|
194
|
-
}
|
|
195
|
-
// Arrays
|
|
196
|
-
if (inferredType === "array" && st.endsWith("[]")) {
|
|
197
|
-
return true;
|
|
198
|
-
}
|
|
199
|
-
}
|
|
200
|
-
}
|
|
201
|
-
|
|
202
|
-
// Handle array types
|
|
203
|
-
if (inferredType === "array" && schemaType.endsWith("[]")) {
|
|
204
|
-
return true;
|
|
205
|
-
}
|
|
206
|
-
|
|
207
|
-
// Handle specific string literals - if schema expects specific strings and value is a string
|
|
208
|
-
if (inferredType === "string" && schemaType.includes('"')) {
|
|
209
|
-
return true;
|
|
210
|
-
}
|
|
211
|
-
|
|
212
|
-
// unknown is compatible with everything (schema might be less specific)
|
|
213
|
-
if (schemaType === "unknown" || inferredType === "unknown") {
|
|
214
|
-
return true;
|
|
215
|
-
}
|
|
216
|
-
|
|
217
|
-
return false;
|
|
218
|
-
}
|
|
219
|
-
|
|
220
|
-
/**
|
|
221
|
-
* Convert Helm values to TypeScript interface
|
|
222
|
-
*/
|
|
223
|
-
export function convertToTypeScriptInterface(options: {
|
|
224
|
-
values: HelmValue;
|
|
225
|
-
interfaceName: string;
|
|
226
|
-
schema?: JSONSchemaProperty | null;
|
|
227
|
-
yamlComments?: Map<string, string>;
|
|
228
|
-
keyPrefix?: string;
|
|
229
|
-
}): TypeScriptInterface {
|
|
230
|
-
const keyPrefix = options.keyPrefix ?? "";
|
|
231
|
-
const properties: Record<string, TypeProperty> = {};
|
|
232
|
-
const schemaProps = options.schema?.properties;
|
|
233
|
-
|
|
234
|
-
for (const [key, value] of Object.entries(options.values)) {
|
|
235
|
-
const sanitizedKey = sanitizePropertyName(key);
|
|
236
|
-
const typeNameSuffix = sanitizeTypeName(key);
|
|
237
|
-
const propertySchema = schemaProps?.[key];
|
|
238
|
-
const fullKey = keyPrefix ? `${keyPrefix}.${key}` : key;
|
|
239
|
-
const yamlComment = options.yamlComments?.get(fullKey);
|
|
240
|
-
|
|
241
|
-
properties[sanitizedKey] = convertValueToProperty({
|
|
242
|
-
value,
|
|
243
|
-
nestedTypeName: `${options.interfaceName}${capitalizeFirst(typeNameSuffix)}`,
|
|
244
|
-
schema: propertySchema,
|
|
245
|
-
propertyName: key,
|
|
246
|
-
yamlComment,
|
|
247
|
-
yamlComments: options.yamlComments,
|
|
248
|
-
fullKey,
|
|
249
|
-
});
|
|
250
|
-
}
|
|
251
|
-
|
|
252
|
-
return {
|
|
253
|
-
name: options.interfaceName,
|
|
254
|
-
properties,
|
|
255
|
-
};
|
|
256
|
-
}
|
|
257
|
-
|
|
258
|
-
type InferencePropertyContext = {
|
|
259
|
-
value: unknown;
|
|
260
|
-
nestedTypeName: string;
|
|
261
|
-
schema?: JSONSchemaProperty;
|
|
262
|
-
propertyName?: string;
|
|
263
|
-
yamlComment?: string;
|
|
264
|
-
yamlComments?: Map<string, string>;
|
|
265
|
-
fullKey?: string;
|
|
266
|
-
};
|
|
267
|
-
|
|
268
|
-
/**
|
|
269
|
-
* Merge description from schema and YAML comments
|
|
270
|
-
*/
|
|
271
|
-
function mergeDescriptions(
|
|
272
|
-
schemaDescription: string | undefined,
|
|
273
|
-
yamlComment: string | undefined,
|
|
274
|
-
): string | undefined {
|
|
275
|
-
if (yamlComment === "" || yamlComment == null) {
|
|
276
|
-
return schemaDescription;
|
|
277
|
-
}
|
|
278
|
-
return schemaDescription !== "" && schemaDescription != null
|
|
279
|
-
? `${yamlComment}\n\n${schemaDescription}`
|
|
280
|
-
: yamlComment;
|
|
281
|
-
}
|
|
282
|
-
|
|
283
|
-
/**
|
|
284
|
-
* Convert a value to a TypeProperty using JSON schema information
|
|
285
|
-
*/
|
|
286
|
-
function convertWithSchema(
|
|
287
|
-
ctx: InferencePropertyContext & { schema: JSONSchemaProperty },
|
|
288
|
-
): TypeProperty {
|
|
289
|
-
const {
|
|
290
|
-
value,
|
|
291
|
-
nestedTypeName,
|
|
292
|
-
schema,
|
|
293
|
-
propertyName,
|
|
294
|
-
yamlComment,
|
|
295
|
-
yamlComments,
|
|
296
|
-
fullKey,
|
|
297
|
-
} = ctx;
|
|
298
|
-
|
|
299
|
-
const inferredType = inferTypeFromValue(value);
|
|
300
|
-
const schemaType = jsonSchemaToTypeScript(schema);
|
|
301
|
-
|
|
302
|
-
if (
|
|
303
|
-
inferredType !== "" &&
|
|
304
|
-
inferredType != null &&
|
|
305
|
-
!typesAreCompatible(inferredType, schemaType)
|
|
306
|
-
) {
|
|
307
|
-
const propName =
|
|
308
|
-
propertyName !== "" && propertyName != null ? `'${propertyName}': ` : "";
|
|
309
|
-
console.warn(
|
|
310
|
-
` ⚠️ Type mismatch for ${propName}Schema says '${schemaType}' but value suggests '${inferredType}' (value: ${String(value).slice(0, 50)})`,
|
|
311
|
-
);
|
|
312
|
-
}
|
|
313
|
-
|
|
314
|
-
const description = mergeDescriptions(schema.description, yamlComment);
|
|
315
|
-
const defaultValue = schema.default === undefined ? value : schema.default;
|
|
316
|
-
|
|
317
|
-
// If schema defines it as an object with properties, recurse
|
|
318
|
-
const helmValueCheckForProps = HelmValueSchema.safeParse(value);
|
|
319
|
-
if (schema.properties && helmValueCheckForProps.success) {
|
|
320
|
-
const nestedInterface = convertToTypeScriptInterface({
|
|
321
|
-
values: helmValueCheckForProps.data,
|
|
322
|
-
interfaceName: nestedTypeName,
|
|
323
|
-
schema,
|
|
324
|
-
yamlComments,
|
|
325
|
-
keyPrefix: fullKey,
|
|
326
|
-
});
|
|
327
|
-
return {
|
|
328
|
-
type: nestedTypeName,
|
|
329
|
-
optional: true,
|
|
330
|
-
nested: nestedInterface,
|
|
331
|
-
description,
|
|
332
|
-
default: defaultValue,
|
|
333
|
-
};
|
|
334
|
-
}
|
|
335
|
-
|
|
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
|
-
});
|
|
345
|
-
return {
|
|
346
|
-
type: nestedTypeName,
|
|
347
|
-
optional: true,
|
|
348
|
-
nested: nestedInterface,
|
|
349
|
-
description,
|
|
350
|
-
default: defaultValue,
|
|
351
|
-
};
|
|
352
|
-
}
|
|
353
|
-
|
|
354
|
-
return {
|
|
355
|
-
type: schemaType,
|
|
356
|
-
optional: true,
|
|
357
|
-
description,
|
|
358
|
-
default: defaultValue,
|
|
359
|
-
};
|
|
360
|
-
}
|
|
361
|
-
|
|
362
|
-
/**
|
|
363
|
-
* Infer array element type from sampled elements
|
|
364
|
-
*/
|
|
365
|
-
function inferArrayType(
|
|
366
|
-
nestedTypeName: string,
|
|
367
|
-
arrayValue: unknown[],
|
|
368
|
-
): TypeProperty {
|
|
369
|
-
if (arrayValue.length === 0) {
|
|
370
|
-
return { type: "unknown[]", optional: true };
|
|
371
|
-
}
|
|
372
|
-
|
|
373
|
-
const elementTypes = new Set<string>();
|
|
374
|
-
const elementTypeProps: TypeProperty[] = [];
|
|
375
|
-
const sampleSize = Math.min(arrayValue.length, 3);
|
|
376
|
-
|
|
377
|
-
for (let i = 0; i < sampleSize; i++) {
|
|
378
|
-
const elementType = convertValueToProperty({
|
|
379
|
-
value: arrayValue[i],
|
|
380
|
-
nestedTypeName,
|
|
381
|
-
});
|
|
382
|
-
elementTypes.add(elementType.type);
|
|
383
|
-
elementTypeProps.push(elementType);
|
|
384
|
-
}
|
|
385
|
-
|
|
386
|
-
if (elementTypes.size === 1) {
|
|
387
|
-
return inferUniformArrayType(
|
|
388
|
-
elementTypes,
|
|
389
|
-
elementTypeProps,
|
|
390
|
-
nestedTypeName,
|
|
391
|
-
);
|
|
392
|
-
}
|
|
393
|
-
|
|
394
|
-
const types = [...elementTypes].toSorted();
|
|
395
|
-
if (
|
|
396
|
-
types.length <= 3 &&
|
|
397
|
-
types.every((t) => ["string", "number", "boolean"].includes(t))
|
|
398
|
-
) {
|
|
399
|
-
return { type: `(${types.join(" | ")})[]`, optional: true };
|
|
400
|
-
}
|
|
401
|
-
|
|
402
|
-
return { type: "unknown[]", optional: true };
|
|
403
|
-
}
|
|
404
|
-
|
|
405
|
-
/**
|
|
406
|
-
* Build TypeProperty for a uniform-type array
|
|
407
|
-
*/
|
|
408
|
-
function inferUniformArrayType(
|
|
409
|
-
elementTypes: Set<string>,
|
|
410
|
-
elementTypeProps: TypeProperty[],
|
|
411
|
-
nestedTypeName: string,
|
|
412
|
-
): TypeProperty {
|
|
413
|
-
const elementType = [...elementTypes][0];
|
|
414
|
-
const elementProp = elementTypeProps[0];
|
|
415
|
-
if (elementType === "" || !elementProp || elementType == null) {
|
|
416
|
-
return { type: "unknown[]", optional: true };
|
|
417
|
-
}
|
|
418
|
-
|
|
419
|
-
if (elementProp.nested) {
|
|
420
|
-
const arrayElementTypeName = `${nestedTypeName}Element`;
|
|
421
|
-
const arrayElementInterface: TypeScriptInterface = {
|
|
422
|
-
name: arrayElementTypeName,
|
|
423
|
-
properties: elementProp.nested.properties,
|
|
424
|
-
};
|
|
425
|
-
return {
|
|
426
|
-
type: `${arrayElementTypeName}[]`,
|
|
427
|
-
optional: true,
|
|
428
|
-
nested: arrayElementInterface,
|
|
429
|
-
};
|
|
430
|
-
}
|
|
431
|
-
|
|
432
|
-
return { type: `${elementType}[]`, optional: true };
|
|
433
|
-
}
|
|
434
|
-
|
|
435
|
-
/**
|
|
436
|
-
* Infer a primitive TypeProperty from a runtime value (no schema)
|
|
437
|
-
*/
|
|
438
|
-
function inferPrimitiveType(
|
|
439
|
-
value: unknown,
|
|
440
|
-
yamlComment?: string,
|
|
441
|
-
): TypeProperty {
|
|
442
|
-
if (ActualBooleanSchema.safeParse(value).success) {
|
|
443
|
-
return {
|
|
444
|
-
type: "boolean",
|
|
445
|
-
optional: true,
|
|
446
|
-
description: yamlComment,
|
|
447
|
-
default: value,
|
|
448
|
-
};
|
|
449
|
-
}
|
|
450
|
-
|
|
451
|
-
if (ActualNumberSchema.safeParse(value).success) {
|
|
452
|
-
return {
|
|
453
|
-
type: "number",
|
|
454
|
-
optional: true,
|
|
455
|
-
description: yamlComment,
|
|
456
|
-
default: value,
|
|
457
|
-
};
|
|
458
|
-
}
|
|
459
|
-
|
|
460
|
-
if (StringBooleanSchema.safeParse(value).success) {
|
|
461
|
-
return {
|
|
462
|
-
type: "boolean",
|
|
463
|
-
optional: true,
|
|
464
|
-
description: yamlComment,
|
|
465
|
-
default: value,
|
|
466
|
-
};
|
|
467
|
-
}
|
|
468
|
-
|
|
469
|
-
const stringCheckForNumber = StringSchema.safeParse(value);
|
|
470
|
-
if (stringCheckForNumber.success) {
|
|
471
|
-
const trimmed = stringCheckForNumber.data.trim();
|
|
472
|
-
if (
|
|
473
|
-
trimmed !== "" &&
|
|
474
|
-
!Number.isNaN(Number(trimmed)) &&
|
|
475
|
-
Number.isFinite(Number(trimmed))
|
|
476
|
-
) {
|
|
477
|
-
return {
|
|
478
|
-
type: "number",
|
|
479
|
-
optional: true,
|
|
480
|
-
description: yamlComment,
|
|
481
|
-
default: value,
|
|
482
|
-
};
|
|
483
|
-
}
|
|
484
|
-
}
|
|
485
|
-
|
|
486
|
-
const stringCheckForPlain = StringSchema.safeParse(value);
|
|
487
|
-
if (stringCheckForPlain.success) {
|
|
488
|
-
if (stringCheckForPlain.data === "default") {
|
|
489
|
-
return {
|
|
490
|
-
type: "string | number | boolean",
|
|
491
|
-
optional: true,
|
|
492
|
-
description: yamlComment,
|
|
493
|
-
default: value,
|
|
494
|
-
};
|
|
495
|
-
}
|
|
496
|
-
return {
|
|
497
|
-
type: "string",
|
|
498
|
-
optional: true,
|
|
499
|
-
description: yamlComment,
|
|
500
|
-
default: value,
|
|
501
|
-
};
|
|
502
|
-
}
|
|
503
|
-
|
|
504
|
-
console.warn(
|
|
505
|
-
`Unrecognized value type for: ${String(value)}, using 'unknown'`,
|
|
506
|
-
);
|
|
507
|
-
return { type: "unknown", optional: true, description: yamlComment };
|
|
508
|
-
}
|
|
509
|
-
|
|
510
|
-
function convertValueToProperty(opts: InferencePropertyContext): TypeProperty {
|
|
511
|
-
const { value, nestedTypeName, schema, yamlComment, yamlComments, fullKey } =
|
|
512
|
-
opts;
|
|
513
|
-
|
|
514
|
-
if (schema) {
|
|
515
|
-
return convertWithSchema({ ...opts, schema });
|
|
516
|
-
}
|
|
517
|
-
|
|
518
|
-
if (
|
|
519
|
-
NullSchema.safeParse(value).success ||
|
|
520
|
-
UndefinedSchema.safeParse(value).success
|
|
521
|
-
) {
|
|
522
|
-
return { type: "unknown", optional: true };
|
|
523
|
-
}
|
|
524
|
-
|
|
525
|
-
const arrayResult = ArraySchema.safeParse(value);
|
|
526
|
-
if (arrayResult.success) {
|
|
527
|
-
return inferArrayType(nestedTypeName, arrayResult.data);
|
|
528
|
-
}
|
|
529
|
-
|
|
530
|
-
const objectResult = HelmValueSchema.safeParse(value);
|
|
531
|
-
if (objectResult.success) {
|
|
532
|
-
const nestedInterface = convertToTypeScriptInterface({
|
|
533
|
-
values: objectResult.data,
|
|
534
|
-
interfaceName: nestedTypeName,
|
|
535
|
-
yamlComments,
|
|
536
|
-
keyPrefix: fullKey,
|
|
537
|
-
});
|
|
538
|
-
return {
|
|
539
|
-
type: nestedTypeName,
|
|
540
|
-
optional: true,
|
|
541
|
-
nested: nestedInterface,
|
|
542
|
-
description: yamlComment,
|
|
543
|
-
default: value,
|
|
544
|
-
};
|
|
545
|
-
}
|
|
546
|
-
|
|
547
|
-
return inferPrimitiveType(value, yamlComment);
|
|
548
|
-
}
|