@shepherdjerred/helm-types 1.7.0-dev.14406 → 1.7.0-dev.14474

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.
Files changed (94) hide show
  1. package/CHANGELOG.md +69 -0
  2. package/LICENSE +674 -0
  3. package/README.md +82 -63
  4. package/demos/argo-cd-cli.cast +50 -0
  5. package/demos/record-cli-demo.sh +132 -0
  6. package/dist/chart-fetcher.d.ts +14 -0
  7. package/dist/chart-fetcher.d.ts.map +1 -0
  8. package/dist/chart-fetcher.js +105 -0
  9. package/dist/chart-fetcher.js.map +1 -0
  10. package/dist/chart-info-parser.d.ts +6 -0
  11. package/dist/chart-info-parser.d.ts.map +1 -0
  12. package/dist/chart-info-parser.js +50 -0
  13. package/dist/chart-info-parser.js.map +1 -0
  14. package/dist/cli.d.ts +7 -0
  15. package/dist/cli.d.ts.map +1 -0
  16. package/dist/cli.js +140 -22841
  17. package/dist/cli.js.map +1 -0
  18. package/dist/comment-parser.d.ts +10 -0
  19. package/dist/comment-parser.d.ts.map +1 -0
  20. package/dist/comment-parser.js +150 -0
  21. package/dist/comment-parser.js.map +1 -0
  22. package/dist/config.d.ts +24 -0
  23. package/dist/config.d.ts.map +1 -0
  24. package/dist/config.js +169 -0
  25. package/dist/config.js.map +1 -0
  26. package/dist/helm-types.d.ts +16 -0
  27. package/dist/helm-types.d.ts.map +1 -0
  28. package/{src/helm-types.ts → dist/helm-types.js} +1 -1
  29. package/dist/helm-types.js.map +1 -0
  30. package/dist/index.d.ts +25 -0
  31. package/dist/index.d.ts.map +1 -0
  32. package/dist/index.js +22 -23
  33. package/dist/index.js.map +1 -0
  34. package/dist/interface-generator.d.ts +6 -0
  35. package/dist/interface-generator.d.ts.map +1 -0
  36. package/dist/interface-generator.js +181 -0
  37. package/dist/interface-generator.js.map +1 -0
  38. package/dist/schemas.d.ts +13 -0
  39. package/dist/schemas.d.ts.map +1 -0
  40. package/{src/schemas.ts → dist/schemas.js} +12 -23
  41. package/dist/schemas.js.map +1 -0
  42. package/dist/type-converter-helpers.d.ts +20 -0
  43. package/dist/type-converter-helpers.d.ts.map +1 -0
  44. package/dist/type-converter-helpers.js +75 -0
  45. package/dist/type-converter-helpers.js.map +1 -0
  46. package/dist/type-converter.d.ts +26 -0
  47. package/dist/type-converter.d.ts.map +1 -0
  48. package/dist/type-converter.js +378 -0
  49. package/dist/type-converter.js.map +1 -0
  50. package/dist/types.d.ts +35 -0
  51. package/dist/types.d.ts.map +1 -0
  52. package/dist/types.js +3 -0
  53. package/dist/types.js.map +1 -0
  54. package/dist/utils.d.ts +17 -0
  55. package/dist/utils.d.ts.map +1 -0
  56. package/dist/utils.js +69 -0
  57. package/dist/utils.js.map +1 -0
  58. package/dist/yaml-comment-filters.d.ts +7 -0
  59. package/dist/yaml-comment-filters.d.ts.map +1 -0
  60. package/dist/yaml-comment-filters.js +91 -0
  61. package/dist/yaml-comment-filters.js.map +1 -0
  62. package/dist/yaml-comment-regex-parser.d.ts +8 -0
  63. package/dist/yaml-comment-regex-parser.d.ts.map +1 -0
  64. package/dist/yaml-comment-regex-parser.js +120 -0
  65. package/dist/yaml-comment-regex-parser.js.map +1 -0
  66. package/dist/yaml-comments.d.ts +74 -0
  67. package/dist/yaml-comments.d.ts.map +1 -0
  68. package/dist/yaml-comments.js +406 -0
  69. package/dist/yaml-comments.js.map +1 -0
  70. package/dist/yaml-preprocess.d.ts +15 -0
  71. package/dist/yaml-preprocess.d.ts.map +1 -0
  72. package/dist/yaml-preprocess.js +170 -0
  73. package/dist/yaml-preprocess.js.map +1 -0
  74. package/examples/argo-cd/generate.mjs +23 -0
  75. package/package.json +14 -9
  76. package/src/__fixtures__/sample-chart/Chart.yaml +0 -6
  77. package/src/__fixtures__/sample-chart/values.schema.json +0 -32
  78. package/src/__fixtures__/sample-chart/values.yaml +0 -22
  79. package/src/chart-fetcher.ts +0 -221
  80. package/src/chart-info-parser.ts +0 -64
  81. package/src/cli.ts +0 -217
  82. package/src/comment-parser.ts +0 -180
  83. package/src/config.ts +0 -195
  84. package/src/index.ts +0 -28
  85. package/src/interface-generator.ts +0 -238
  86. package/src/reset.d.ts +0 -1
  87. package/src/type-converter-helpers.ts +0 -108
  88. package/src/type-converter.ts +0 -520
  89. package/src/types.ts +0 -41
  90. package/src/utils.ts +0 -76
  91. package/src/yaml-comment-filters.ts +0 -103
  92. package/src/yaml-comment-regex-parser.ts +0 -150
  93. package/src/yaml-comments.ts +0 -507
  94. package/src/yaml-preprocess.ts +0 -235
@@ -1,520 +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
- NullSchema,
10
- UndefinedSchema,
11
- ArraySchema,
12
- HelmValueSchema,
13
- ActualBooleanSchema,
14
- ActualNumberSchema,
15
- StringBooleanSchema,
16
- } from "./schemas.ts";
17
- import {
18
- shouldAllowArbitraryProps,
19
- getWellKnownK8sFieldType,
20
- } from "./config.ts";
21
- import {
22
- sanitizePropertyName,
23
- sanitizeTypeName,
24
- capitalizeFirst,
25
- } from "./utils.ts";
26
- import type { PropertyConversionContext } from "./type-converter-helpers.ts";
27
- import {
28
- mergeDescriptions,
29
- inferPrimitiveType,
30
- } from "./type-converter-helpers.ts";
31
-
32
- /**
33
- * Convert JSON schema type to TypeScript type string
34
- */
35
- export function jsonSchemaToTypeScript(schema: JSONSchemaProperty): string {
36
- // Handle oneOf - union of types
37
- if (schema.oneOf) {
38
- const types = schema.oneOf.map((s) => jsonSchemaToTypeScript(s));
39
- return types.join(" | ");
40
- }
41
-
42
- // Handle anyOf - union of types
43
- if (schema.anyOf) {
44
- const types = schema.anyOf.map((s) => jsonSchemaToTypeScript(s));
45
- return types.join(" | ");
46
- }
47
-
48
- // Handle enum
49
- if (schema.enum) {
50
- return schema.enum
51
- .map((v) =>
52
- StringSchema.safeParse(v).success ? `"${String(v)}"` : String(v),
53
- )
54
- .join(" | ");
55
- }
56
-
57
- // Handle array type
58
- if (schema.type === "array" && schema.items) {
59
- const itemType = jsonSchemaToTypeScript(schema.items);
60
- return `${itemType}[]`;
61
- }
62
-
63
- // Handle basic types
64
- const stringTypeCheck = StringSchema.safeParse(schema.type);
65
- if (stringTypeCheck.success) {
66
- switch (stringTypeCheck.data) {
67
- case "string":
68
- return "string";
69
- case "number":
70
- case "integer":
71
- return "number";
72
- case "boolean":
73
- return "boolean";
74
- case "object":
75
- return "object";
76
- case "array":
77
- return "unknown[]";
78
- case "null":
79
- return "null";
80
- default:
81
- return "unknown";
82
- }
83
- }
84
-
85
- // Handle multiple types
86
- const arrayTypeCheck = ArraySchema.safeParse(schema.type);
87
- if (arrayTypeCheck.success) {
88
- return arrayTypeCheck.data
89
- .map((t: unknown) => {
90
- if (!StringSchema.safeParse(t).success) {
91
- return "unknown";
92
- }
93
- const typeStr = String(t);
94
- switch (typeStr) {
95
- case "string":
96
- return "string";
97
- case "number":
98
- case "integer":
99
- return "number";
100
- case "boolean":
101
- return "boolean";
102
- case "object":
103
- return "object";
104
- case "array":
105
- return "unknown[]";
106
- case "null":
107
- return "null";
108
- default:
109
- return "unknown";
110
- }
111
- })
112
- .join(" | ");
113
- }
114
-
115
- return "unknown";
116
- }
117
-
118
- /**
119
- * Infer TypeScript type from actual runtime value
120
- */
121
- export function inferTypeFromValue(value: unknown): string | null {
122
- // Check null/undefined
123
- if (
124
- NullSchema.safeParse(value).success ||
125
- UndefinedSchema.safeParse(value).success
126
- ) {
127
- return null;
128
- }
129
-
130
- // Check for actual boolean
131
- if (ActualBooleanSchema.safeParse(value).success) {
132
- return "boolean";
133
- }
134
-
135
- // Check for actual number
136
- if (ActualNumberSchema.safeParse(value).success) {
137
- return "number";
138
- }
139
-
140
- // Check if it's a string that looks like a boolean
141
- if (StringBooleanSchema.safeParse(value).success) {
142
- return "boolean";
143
- }
144
-
145
- // Check if it's a string that looks like a number
146
- const stringCheck = StringSchema.safeParse(value);
147
- if (stringCheck.success) {
148
- const trimmed = stringCheck.data.trim();
149
- if (
150
- trimmed !== "" &&
151
- !Number.isNaN(Number(trimmed)) &&
152
- Number.isFinite(Number(trimmed))
153
- ) {
154
- return "number";
155
- }
156
- }
157
-
158
- // Check for array
159
- if (ArraySchema.safeParse(value).success) {
160
- return "array";
161
- }
162
-
163
- // Check for object
164
- if (HelmValueSchema.safeParse(value).success) {
165
- return "object";
166
- }
167
-
168
- // Plain string
169
- if (StringSchema.safeParse(value).success) {
170
- return "string";
171
- }
172
-
173
- return "unknown";
174
- }
175
-
176
- /**
177
- * Check if inferred type is compatible with schema type
178
- */
179
- export function typesAreCompatible(
180
- inferredType: string,
181
- schemaType: string,
182
- ): boolean {
183
- // Exact match
184
- if (inferredType === schemaType) {
185
- return true;
186
- }
187
-
188
- // Check if the inferred type is part of a union in the schema
189
- // For example: schemaType might be "number | \"default\"" and inferredType is "string"
190
- const schemaTypes = schemaType
191
- .split("|")
192
- .map((t) => t.trim().replaceAll(/^["']|["']$/g, ""));
193
-
194
- // If schema is a union, check if inferred type is compatible with any part
195
- if (schemaTypes.length > 1) {
196
- for (const st of schemaTypes) {
197
- // Handle quoted strings in unions (like "default")
198
- if (inferredType === "string" && st.startsWith('"') && st.endsWith('"')) {
199
- return true;
200
- }
201
- if (st === inferredType) {
202
- return true;
203
- }
204
- // Arrays
205
- if (inferredType === "array" && st.endsWith("[]")) {
206
- return true;
207
- }
208
- }
209
- }
210
-
211
- // Handle array types
212
- if (inferredType === "array" && schemaType.endsWith("[]")) {
213
- return true;
214
- }
215
-
216
- // Handle specific string literals - if schema expects specific strings and value is a string
217
- if (inferredType === "string" && schemaType.includes('"')) {
218
- return true;
219
- }
220
-
221
- // unknown is compatible with everything (schema might be less specific)
222
- if (schemaType === "unknown" || inferredType === "unknown") {
223
- return true;
224
- }
225
-
226
- return false;
227
- }
228
-
229
- /**
230
- * Convert Helm values to TypeScript interface
231
- */
232
- export function convertToTypeScriptInterface(options: {
233
- values: HelmValue;
234
- interfaceName: string;
235
- schema?: JSONSchemaProperty | null;
236
- yamlComments?: Map<string, string>;
237
- keyPrefix?: string;
238
- chartName?: string;
239
- }): TypeScriptInterface {
240
- const keyPrefix = options.keyPrefix ?? "";
241
- const properties: Record<string, TypeProperty> = {};
242
- const schemaProps = options.schema?.properties;
243
-
244
- for (const [key, value] of Object.entries(options.values)) {
245
- const sanitizedKey = sanitizePropertyName(key);
246
- const typeNameSuffix = sanitizeTypeName(key);
247
- const propertySchema = schemaProps?.[key];
248
- const fullKey = keyPrefix ? `${keyPrefix}.${key}` : key;
249
- const yamlComment = options.yamlComments?.get(fullKey);
250
-
251
- properties[sanitizedKey] = convertValueToProperty({
252
- value,
253
- nestedTypeName: `${options.interfaceName}${capitalizeFirst(typeNameSuffix)}`,
254
- schema: propertySchema,
255
- propertyName: key,
256
- yamlComment,
257
- yamlComments: options.yamlComments,
258
- fullKey,
259
- chartName: options.chartName,
260
- });
261
- }
262
-
263
- // Check if this interface should allow arbitrary properties
264
- const allowArbitraryProps =
265
- options.chartName != null && options.chartName !== ""
266
- ? shouldAllowArbitraryProps(
267
- keyPrefix,
268
- options.chartName,
269
- keyPrefix.split(".").pop() ?? "",
270
- options.yamlComments?.get(keyPrefix),
271
- )
272
- : false;
273
-
274
- return {
275
- name: options.interfaceName,
276
- properties,
277
- allowArbitraryProps,
278
- };
279
- }
280
-
281
- /**
282
- * Convert a value to a TypeProperty using JSON schema information
283
- */
284
- function convertWithSchema(
285
- ctx: PropertyConversionContext & { schema: JSONSchemaProperty },
286
- ): TypeProperty {
287
- const {
288
- value,
289
- nestedTypeName,
290
- schema,
291
- propertyName,
292
- yamlComment,
293
- yamlComments,
294
- fullKey,
295
- chartName,
296
- } = ctx;
297
-
298
- // Infer the type from the actual value for comparison
299
- const inferredType = inferTypeFromValue(value);
300
- const schemaType = jsonSchemaToTypeScript(schema);
301
-
302
- // Warn about type mismatches
303
- if (
304
- inferredType !== "" &&
305
- inferredType != null &&
306
- !typesAreCompatible(inferredType, schemaType)
307
- ) {
308
- const propName =
309
- propertyName !== "" && propertyName != null ? `'${propertyName}': ` : "";
310
- console.warn(
311
- ` ⚠️ Type mismatch for ${propName}Schema says '${schemaType}' but value suggests '${inferredType}' (value: ${String(value).slice(0, 50)})`,
312
- );
313
- }
314
-
315
- const description = mergeDescriptions(schema.description, yamlComment);
316
- const defaultValue = schema.default === undefined ? value : schema.default;
317
-
318
- // If schema defines it as an object with properties, recurse
319
- const helmValueCheckForProps = HelmValueSchema.safeParse(value);
320
- if (schema.properties && helmValueCheckForProps.success) {
321
- const nestedInterface = convertToTypeScriptInterface({
322
- values: helmValueCheckForProps.data,
323
- interfaceName: nestedTypeName,
324
- schema,
325
- yamlComments,
326
- keyPrefix: fullKey,
327
- chartName,
328
- });
329
- return {
330
- type: nestedTypeName,
331
- optional: true,
332
- nested: nestedInterface,
333
- description,
334
- default: defaultValue,
335
- };
336
- }
337
-
338
- // Handle object types without explicit properties
339
- const helmValueCheckForObject = HelmValueSchema.safeParse(value);
340
- if (schemaType === "object" && helmValueCheckForObject.success) {
341
- const nestedInterface = convertToTypeScriptInterface({
342
- values: helmValueCheckForObject.data,
343
- interfaceName: nestedTypeName,
344
- yamlComments,
345
- keyPrefix: fullKey,
346
- chartName,
347
- });
348
- return {
349
- type: nestedTypeName,
350
- optional: true,
351
- nested: nestedInterface,
352
- description,
353
- default: defaultValue,
354
- };
355
- }
356
-
357
- return {
358
- type: schemaType,
359
- optional: true,
360
- description,
361
- default: defaultValue,
362
- };
363
- }
364
-
365
- /**
366
- * Infer array element type from sampled elements
367
- */
368
- function inferArrayType(
369
- ctx: PropertyConversionContext,
370
- arrayValue: unknown[],
371
- ): TypeProperty {
372
- const { nestedTypeName } = ctx;
373
-
374
- if (arrayValue.length === 0) {
375
- return { type: "unknown[]", optional: true };
376
- }
377
-
378
- // Sample multiple elements for better type inference
379
- const elementTypes = new Set<string>();
380
- const elementTypeProps: TypeProperty[] = [];
381
- const sampleSize = Math.min(arrayValue.length, 3);
382
-
383
- for (let i = 0; i < sampleSize; i++) {
384
- const elementType = convertValueToProperty({
385
- value: arrayValue[i],
386
- nestedTypeName,
387
- });
388
- elementTypes.add(elementType.type);
389
- elementTypeProps.push(elementType);
390
- }
391
-
392
- // If all elements have the same type, use that
393
- if (elementTypes.size === 1) {
394
- return inferUniformArrayType(ctx, elementTypes, elementTypeProps);
395
- }
396
-
397
- // If mixed types, use union type for common cases
398
- const types = [...elementTypes].toSorted();
399
- if (
400
- types.length <= 3 &&
401
- types.every((t) => ["string", "number", "boolean"].includes(t))
402
- ) {
403
- return { type: `(${types.join(" | ")})[]`, optional: true };
404
- }
405
-
406
- return { type: "unknown[]", optional: true };
407
- }
408
-
409
- /**
410
- * Build TypeProperty for a uniform-type array
411
- */
412
- function inferUniformArrayType(
413
- ctx: PropertyConversionContext,
414
- elementTypes: Set<string>,
415
- elementTypeProps: TypeProperty[],
416
- ): TypeProperty {
417
- const { nestedTypeName, chartName, fullKey, propertyName, yamlComment } = ctx;
418
- const elementType = [...elementTypes][0];
419
- const elementProp = elementTypeProps[0];
420
- if (elementType === "" || !elementProp || elementType == null) {
421
- return { type: "unknown[]", optional: true };
422
- }
423
-
424
- if (elementProp.nested) {
425
- const arrayElementTypeName = `${nestedTypeName}Element`;
426
- const allowArbitraryProps =
427
- chartName !== "" && fullKey !== "" && chartName != null && fullKey != null
428
- ? shouldAllowArbitraryProps(
429
- fullKey,
430
- chartName,
431
- propertyName ?? "",
432
- yamlComment,
433
- )
434
- : false;
435
- const arrayElementInterface: TypeScriptInterface = {
436
- name: arrayElementTypeName,
437
- properties: elementProp.nested.properties,
438
- allowArbitraryProps,
439
- };
440
- return {
441
- type: `${arrayElementTypeName}[]`,
442
- optional: true,
443
- nested: arrayElementInterface,
444
- };
445
- }
446
-
447
- return { type: `${elementType}[]`, optional: true };
448
- }
449
-
450
- function convertValueToProperty(opts: PropertyConversionContext): TypeProperty {
451
- const {
452
- value,
453
- nestedTypeName,
454
- schema,
455
- propertyName,
456
- yamlComment,
457
- yamlComments,
458
- fullKey,
459
- chartName,
460
- } = opts;
461
-
462
- // If we have a JSON schema for this property, prefer it over inference
463
- if (schema) {
464
- return convertWithSchema({ ...opts, schema });
465
- }
466
-
467
- // Check for null/undefined first
468
- if (
469
- NullSchema.safeParse(value).success ||
470
- UndefinedSchema.safeParse(value).success
471
- ) {
472
- return { type: "unknown", optional: true };
473
- }
474
-
475
- // Well-known Kubernetes fields (resources, nodeSelector, tolerations,
476
- // affinity) have an API-defined shape. Inferring them from a chart's default
477
- // subset produces types that are too narrow (e.g. resources.requests that
478
- // only allow cpu). Emit the canonical permissive type instead — but only
479
- // when the default value's shape is compatible, so RBAC `resources:
480
- // ["secrets"]` arrays stay arrays.
481
- if (propertyName !== "" && propertyName != null) {
482
- const wellKnown = getWellKnownK8sFieldType(propertyName, value);
483
- if (wellKnown) {
484
- return {
485
- type: wellKnown.type,
486
- optional: true,
487
- description: yamlComment ?? wellKnown.description,
488
- };
489
- }
490
- }
491
-
492
- // Check for array (before coercion checks)
493
- const arrayResult = ArraySchema.safeParse(value);
494
- if (arrayResult.success) {
495
- return inferArrayType(opts, arrayResult.data);
496
- }
497
-
498
- // Check for object (before primitive coercion checks)
499
- const objectResult = HelmValueSchema.safeParse(value);
500
- if (objectResult.success) {
501
- const nestedInterface = convertToTypeScriptInterface({
502
- values: objectResult.data,
503
- interfaceName: nestedTypeName,
504
- yamlComments,
505
- keyPrefix: fullKey,
506
- chartName,
507
- });
508
-
509
- return {
510
- type: nestedTypeName,
511
- optional: true,
512
- nested: nestedInterface,
513
- description: yamlComment,
514
- default: value,
515
- };
516
- }
517
-
518
- // Infer primitive type from runtime value
519
- return inferPrimitiveType(value, yamlComment);
520
- }
package/src/types.ts DELETED
@@ -1,41 +0,0 @@
1
- // Core type definitions for Helm chart type generation
2
-
3
- export type ChartInfo = {
4
- name: string;
5
- repoUrl: string;
6
- version: string;
7
- chartName: string; // The actual chart name (may differ from versions.ts key)
8
- // When true, the chart is served from an OCI registry: fetch via
9
- // `helm pull oci://<repoUrl>/<chartName>` instead of `helm repo add` + pull.
10
- oci?: boolean;
11
- };
12
-
13
- export type JSONSchemaProperty = {
14
- type?: string | string[];
15
- enum?: unknown[];
16
- oneOf?: JSONSchemaProperty[];
17
- anyOf?: JSONSchemaProperty[];
18
- items?: JSONSchemaProperty;
19
- properties?: Record<string, JSONSchemaProperty>;
20
- required?: string[];
21
- description?: string;
22
- default?: unknown;
23
- };
24
-
25
- export type TypeScriptInterface = {
26
- name: string;
27
- properties: Record<string, TypeProperty>;
28
- /**
29
- * If true, add an index signature to allow arbitrary additional properties
30
- * Useful for config maps, arbitrary key-value stores, etc.
31
- */
32
- allowArbitraryProps?: boolean;
33
- };
34
-
35
- export type TypeProperty = {
36
- type: string;
37
- optional: boolean;
38
- description?: string;
39
- default?: unknown;
40
- nested?: TypeScriptInterface;
41
- };
package/src/utils.ts DELETED
@@ -1,76 +0,0 @@
1
- /**
2
- * Utility functions for Helm type generation
3
- */
4
-
5
- /**
6
- * Sanitize property names for TypeScript interfaces
7
- * Handles special characters, reserved keywords, and invalid syntax
8
- */
9
- export function sanitizePropertyName(key: string): string {
10
- // TypeScript reserved keywords that need quoting
11
- const reservedKeywords = new Set([
12
- "break",
13
- "case",
14
- "catch",
15
- "class",
16
- "const",
17
- "continue",
18
- "debugger",
19
- "default",
20
- "delete",
21
- "do",
22
- "else",
23
- "enum",
24
- "export",
25
- "extends",
26
- "false",
27
- "finally",
28
- "for",
29
- "function",
30
- "if",
31
- "import",
32
- "in",
33
- "instanceof",
34
- "new",
35
- "null",
36
- "return",
37
- "super",
38
- "switch",
39
- "this",
40
- "throw",
41
- "true",
42
- "try",
43
- "typeof",
44
- "var",
45
- "void",
46
- "while",
47
- "with",
48
- ]);
49
-
50
- // Check if key needs quoting
51
- const needsQuoting =
52
- reservedKeywords.has(key) ||
53
- /[^\w$]/.test(key) || // Contains special characters
54
- /^\d/.test(key); // Starts with digit
55
-
56
- return needsQuoting ? `"${key}"` : key;
57
- }
58
-
59
- /**
60
- * Sanitize type names by removing invalid characters and normalizing
61
- */
62
- export function sanitizeTypeName(key: string): string {
63
- return (
64
- key
65
- .replaceAll(/[^a-z0-9]/gi, "") // Remove all special characters
66
- .replace(/^\d+/, "") || // Remove leading digits
67
- "Property"
68
- ); // Fallback if empty
69
- }
70
-
71
- /**
72
- * Capitalize the first letter of a string
73
- */
74
- export function capitalizeFirst(str: string): string {
75
- return str.charAt(0).toUpperCase() + str.slice(1);
76
- }