@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/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@shepherdjerred/helm-types",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.2.0-dev.891",
|
|
4
4
|
"description": "Generate TypeScript types from Helm chart values.yaml and values.schema.json",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -59,5 +59,9 @@
|
|
|
59
59
|
"dependencies": {
|
|
60
60
|
"yaml": "^2.8.1",
|
|
61
61
|
"zod": "^4.1.11"
|
|
62
|
+
},
|
|
63
|
+
"devDependencies": {
|
|
64
|
+
"@types/bun": "^1.3.11",
|
|
65
|
+
"jiti": "^2.6.1"
|
|
62
66
|
}
|
|
63
67
|
}
|
package/src/chart-fetcher.ts
CHANGED
|
@@ -1,14 +1,16 @@
|
|
|
1
1
|
// Using Bun.$ for path operations instead of node:path
|
|
2
2
|
import { parse as yamlParse } from "yaml";
|
|
3
|
-
import type { ChartInfo, JSONSchemaProperty } from "./types.
|
|
4
|
-
import { HelmValueSchema, RecordSchema, ErrorSchema } from "./schemas.
|
|
5
|
-
import type { HelmValue } from "./schemas.
|
|
6
|
-
import { parseYAMLComments } from "./yaml-comments.
|
|
3
|
+
import type { ChartInfo, JSONSchemaProperty } from "./types.ts";
|
|
4
|
+
import { HelmValueSchema, RecordSchema, ErrorSchema } from "./schemas.ts";
|
|
5
|
+
import type { HelmValue } from "./schemas.ts";
|
|
6
|
+
import { parseYAMLComments } from "./yaml-comments.ts";
|
|
7
7
|
|
|
8
8
|
/**
|
|
9
9
|
* Load JSON schema if it exists in the chart
|
|
10
10
|
*/
|
|
11
|
-
async function loadJSONSchema(
|
|
11
|
+
async function loadJSONSchema(
|
|
12
|
+
chartPath: string,
|
|
13
|
+
): Promise<JSONSchemaProperty | null> {
|
|
12
14
|
try {
|
|
13
15
|
const schemaPath = `${chartPath}/values.schema.json`;
|
|
14
16
|
const schemaContent = await Bun.file(schemaPath).text();
|
|
@@ -44,21 +46,29 @@ async function runCommand(command: string, args: string[]): Promise<string> {
|
|
|
44
46
|
if (exitCode === 0) {
|
|
45
47
|
return output;
|
|
46
48
|
} else {
|
|
47
|
-
throw new Error(
|
|
49
|
+
throw new Error(
|
|
50
|
+
`Command "${command} ${args.join(" ")}" failed with code ${exitCode.toString()}`,
|
|
51
|
+
);
|
|
48
52
|
}
|
|
49
53
|
} catch (error) {
|
|
50
54
|
const parseResult = ErrorSchema.safeParse(error);
|
|
51
|
-
const errorMessage = parseResult.success
|
|
52
|
-
|
|
55
|
+
const errorMessage = parseResult.success
|
|
56
|
+
? parseResult.data.message
|
|
57
|
+
: String(error);
|
|
58
|
+
throw new Error(
|
|
59
|
+
`Failed to spawn command "${command} ${args.join(" ")}": ${errorMessage}`,
|
|
60
|
+
);
|
|
53
61
|
}
|
|
54
62
|
}
|
|
55
63
|
|
|
56
64
|
/**
|
|
57
65
|
* Fetch a Helm chart and extract its values.yaml and optional schema
|
|
58
66
|
*/
|
|
59
|
-
export async function fetchHelmChart(
|
|
60
|
-
|
|
61
|
-
|
|
67
|
+
export async function fetchHelmChart(chart: ChartInfo): Promise<{
|
|
68
|
+
values: HelmValue;
|
|
69
|
+
schema: JSONSchemaProperty | null;
|
|
70
|
+
yamlComments: Map<string, string>;
|
|
71
|
+
}> {
|
|
62
72
|
const pwd = Bun.env["PWD"] ?? process.cwd();
|
|
63
73
|
const tempDir = `${pwd}/temp/helm-${chart.name}`;
|
|
64
74
|
const repoName = `temp-repo-${chart.name}-${String(Date.now())}`;
|
|
@@ -96,7 +106,9 @@ export async function fetchHelmChart(
|
|
|
96
106
|
|
|
97
107
|
// Parse YAML comments
|
|
98
108
|
const yamlComments = parseYAMLComments(valuesContent);
|
|
99
|
-
console.log(
|
|
109
|
+
console.log(
|
|
110
|
+
` 💬 Extracted ${String(yamlComments.size)} comments from values.yaml`,
|
|
111
|
+
);
|
|
100
112
|
|
|
101
113
|
// Parse YAML using yaml package
|
|
102
114
|
const parsedValues = yamlParse(valuesContent) as unknown;
|
|
@@ -106,13 +118,17 @@ export async function fetchHelmChart(
|
|
|
106
118
|
console.log(
|
|
107
119
|
` 🔍 Parsed values keys: ${Object.keys(recordParseResult.data)
|
|
108
120
|
.slice(0, 10)
|
|
109
|
-
.join(
|
|
121
|
+
.join(
|
|
122
|
+
", ",
|
|
123
|
+
)}${Object.keys(recordParseResult.data).length > 10 ? "..." : ""}`,
|
|
110
124
|
);
|
|
111
125
|
}
|
|
112
126
|
|
|
113
127
|
// Check if parsedValues is a valid object using Zod before validation
|
|
114
128
|
if (!recordParseResult.success) {
|
|
115
|
-
console.warn(
|
|
129
|
+
console.warn(
|
|
130
|
+
` ⚠️ Parsed values is not a valid record object: ${String(parsedValues)}`,
|
|
131
|
+
);
|
|
116
132
|
return { values: {}, schema: null, yamlComments: new Map() };
|
|
117
133
|
}
|
|
118
134
|
|
|
@@ -128,8 +144,13 @@ export async function fetchHelmChart(
|
|
|
128
144
|
return { values: parseResult.data, schema, yamlComments };
|
|
129
145
|
} else {
|
|
130
146
|
console.warn(` ⚠️ Zod validation failed for ${chart.name}:`);
|
|
131
|
-
console.warn(
|
|
132
|
-
|
|
147
|
+
console.warn(
|
|
148
|
+
` First few errors:`,
|
|
149
|
+
parseResult.error.issues.slice(0, 3),
|
|
150
|
+
);
|
|
151
|
+
console.warn(
|
|
152
|
+
` ⚠️ Falling back to unvalidated object for type generation`,
|
|
153
|
+
);
|
|
133
154
|
// Return the validated record data from the successful parse result
|
|
134
155
|
return { values: recordParseResult.data, schema, yamlComments };
|
|
135
156
|
}
|
package/src/chart-info-parser.ts
CHANGED
|
@@ -1,9 +1,11 @@
|
|
|
1
|
-
import type { ChartInfo } from "./types.
|
|
1
|
+
import type { ChartInfo } from "./types.ts";
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
4
|
* Parse chart information from versions.ts comments and values
|
|
5
5
|
*/
|
|
6
|
-
export async function parseChartInfoFromVersions(
|
|
6
|
+
export async function parseChartInfoFromVersions(
|
|
7
|
+
versionsPath = "src/versions.ts",
|
|
8
|
+
): Promise<ChartInfo[]> {
|
|
7
9
|
const content = await Bun.file(versionsPath).text();
|
|
8
10
|
const lines = content.split("\n");
|
|
9
11
|
const charts: ChartInfo[] = [];
|
|
@@ -13,39 +15,57 @@ export async function parseChartInfoFromVersions(versionsPath = "src/versions.ts
|
|
|
13
15
|
const nextLine = lines[i + 1];
|
|
14
16
|
|
|
15
17
|
// Look for renovate comments that indicate Helm charts
|
|
16
|
-
if (
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
if (!repoUrl || !versionKey) continue;
|
|
25
|
-
|
|
26
|
-
// Extract version value
|
|
27
|
-
const versionMatch = /:\s*"([^"]+)"/.exec(nextLine);
|
|
28
|
-
if (versionMatch) {
|
|
29
|
-
const version = versionMatch[1];
|
|
30
|
-
if (!version) continue;
|
|
31
|
-
|
|
32
|
-
// Try to determine chart name from the version key or URL
|
|
33
|
-
let chartName = versionKey;
|
|
34
|
-
|
|
35
|
-
// Handle special cases like "argo-cd" vs "argocd"
|
|
36
|
-
if (versionKey === "argo-cd") {
|
|
37
|
-
chartName = "argo-cd";
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
charts.push({
|
|
41
|
-
name: versionKey,
|
|
42
|
-
repoUrl: repoUrl.replace(/\/$/, ""), // Remove trailing slash
|
|
43
|
-
version,
|
|
44
|
-
chartName,
|
|
45
|
-
});
|
|
46
|
-
}
|
|
47
|
-
}
|
|
18
|
+
if (
|
|
19
|
+
line == null ||
|
|
20
|
+
!line.includes("renovate: datasource=helm") ||
|
|
21
|
+
nextLine == null ||
|
|
22
|
+
nextLine === ""
|
|
23
|
+
) {
|
|
24
|
+
continue;
|
|
48
25
|
}
|
|
26
|
+
|
|
27
|
+
const repoUrlMatch = /registryUrl=(\S+)/.exec(line);
|
|
28
|
+
const versionKeyMatch = /^\s*"?([^":\s]+)"?:/.exec(nextLine);
|
|
29
|
+
if (!repoUrlMatch || !versionKeyMatch) {
|
|
30
|
+
continue;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const repoUrl = repoUrlMatch[1];
|
|
34
|
+
const versionKey = versionKeyMatch[1];
|
|
35
|
+
if (
|
|
36
|
+
repoUrl == null ||
|
|
37
|
+
repoUrl === "" ||
|
|
38
|
+
versionKey == null ||
|
|
39
|
+
versionKey === ""
|
|
40
|
+
) {
|
|
41
|
+
continue;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// Extract version value
|
|
45
|
+
const versionMatch = /:\s*"([^"]+)"/.exec(nextLine);
|
|
46
|
+
if (!versionMatch) {
|
|
47
|
+
continue;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
const version = versionMatch[1];
|
|
51
|
+
if (version == null || version === "") {
|
|
52
|
+
continue;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// Try to determine chart name from the version key or URL
|
|
56
|
+
let chartName = versionKey;
|
|
57
|
+
|
|
58
|
+
// Handle special cases like "argo-cd" vs "argocd"
|
|
59
|
+
if (versionKey === "argo-cd") {
|
|
60
|
+
chartName = "argo-cd";
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
charts.push({
|
|
64
|
+
name: versionKey,
|
|
65
|
+
repoUrl: repoUrl.replace(/\/$/, ""), // Remove trailing slash
|
|
66
|
+
version,
|
|
67
|
+
chartName,
|
|
68
|
+
});
|
|
49
69
|
}
|
|
50
70
|
|
|
51
71
|
return charts;
|
package/src/cli.ts
CHANGED
|
@@ -5,14 +5,16 @@
|
|
|
5
5
|
* Generate TypeScript types from Helm charts
|
|
6
6
|
*/
|
|
7
7
|
import { z } from "zod";
|
|
8
|
-
import { fetchHelmChart
|
|
9
|
-
import
|
|
8
|
+
import { fetchHelmChart } from "./chart-fetcher.ts";
|
|
9
|
+
import { convertToTypeScriptInterface } from "./type-converter.ts";
|
|
10
|
+
import { generateTypeScriptCode } from "./interface-generator.ts";
|
|
11
|
+
import type { ChartInfo } from "./types.ts";
|
|
10
12
|
|
|
11
13
|
const ErrorSchema = z.object({
|
|
12
14
|
message: z.string(),
|
|
13
15
|
});
|
|
14
16
|
|
|
15
|
-
const HELP_TEXT = `
|
|
17
|
+
const HELP_TEXT = String.raw`
|
|
16
18
|
helm-types - Generate TypeScript types from Helm charts
|
|
17
19
|
|
|
18
20
|
USAGE:
|
|
@@ -29,25 +31,25 @@ OPTIONS:
|
|
|
29
31
|
|
|
30
32
|
EXAMPLES:
|
|
31
33
|
# Generate types for ArgoCD and print to stdout
|
|
32
|
-
bunx @homelab/helm-types
|
|
33
|
-
--name argo-cd
|
|
34
|
-
--repo https://argoproj.github.io/argo-helm
|
|
34
|
+
bunx @homelab/helm-types \
|
|
35
|
+
--name argo-cd \
|
|
36
|
+
--repo https://argoproj.github.io/argo-helm \
|
|
35
37
|
--version 8.3.1
|
|
36
38
|
|
|
37
39
|
# Generate types with custom output file
|
|
38
|
-
bunx @homelab/helm-types
|
|
39
|
-
--name argo-cd
|
|
40
|
-
--repo https://argoproj.github.io/argo-helm
|
|
41
|
-
--version 8.3.1
|
|
40
|
+
bunx @homelab/helm-types \
|
|
41
|
+
--name argo-cd \
|
|
42
|
+
--repo https://argoproj.github.io/argo-helm \
|
|
43
|
+
--version 8.3.1 \
|
|
42
44
|
--output argo-cd.types.ts
|
|
43
45
|
|
|
44
46
|
# Generate types with custom chart name and interface name
|
|
45
|
-
bunx @homelab/helm-types
|
|
46
|
-
--name argocd
|
|
47
|
-
--chart argo-cd
|
|
48
|
-
--repo https://argoproj.github.io/argo-helm
|
|
49
|
-
--version 8.3.1
|
|
50
|
-
--interface ArgocdHelmValues
|
|
47
|
+
bunx @homelab/helm-types \
|
|
48
|
+
--name argocd \
|
|
49
|
+
--chart argo-cd \
|
|
50
|
+
--repo https://argoproj.github.io/argo-helm \
|
|
51
|
+
--version 8.3.1 \
|
|
52
|
+
--interface ArgocdHelmValues \
|
|
51
53
|
--output argocd.types.ts
|
|
52
54
|
`;
|
|
53
55
|
|
|
@@ -61,6 +63,27 @@ type CliArgs = {
|
|
|
61
63
|
help?: boolean;
|
|
62
64
|
};
|
|
63
65
|
|
|
66
|
+
/** String-valued flag names (excludes boolean 'help') */
|
|
67
|
+
type StringCliArgsKey = Exclude<keyof CliArgs, "help">;
|
|
68
|
+
|
|
69
|
+
/** Map from flag name to CliArgs key */
|
|
70
|
+
const FLAG_MAP: Record<string, StringCliArgsKey> = {
|
|
71
|
+
"--name": "name",
|
|
72
|
+
"-n": "name",
|
|
73
|
+
"--chart": "chart",
|
|
74
|
+
"-c": "chart",
|
|
75
|
+
"--repo": "repo",
|
|
76
|
+
"-r": "repo",
|
|
77
|
+
"--version": "version",
|
|
78
|
+
"-v": "version",
|
|
79
|
+
"--output": "output",
|
|
80
|
+
"-o": "output",
|
|
81
|
+
"--interface": "interface",
|
|
82
|
+
"-i": "interface",
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
const HELP_FLAGS = new Set(["--help", "-h"]);
|
|
86
|
+
|
|
64
87
|
/**
|
|
65
88
|
* Simple argument parser for Bun CLI
|
|
66
89
|
*/
|
|
@@ -69,47 +92,26 @@ function parseCliArgs(args: string[]): CliArgs {
|
|
|
69
92
|
|
|
70
93
|
for (let i = 0; i < args.length; i++) {
|
|
71
94
|
const arg = args[i];
|
|
72
|
-
if (
|
|
95
|
+
if (arg == null || arg === "") {
|
|
96
|
+
continue;
|
|
97
|
+
}
|
|
73
98
|
|
|
74
|
-
if (arg
|
|
99
|
+
if (HELP_FLAGS.has(arg)) {
|
|
75
100
|
result.help = true;
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
}
|
|
82
|
-
} else if (arg === "--chart" || arg === "-c") {
|
|
83
|
-
const value = args[i + 1];
|
|
84
|
-
if (value) {
|
|
85
|
-
result.chart = value;
|
|
86
|
-
i += 1;
|
|
87
|
-
}
|
|
88
|
-
} else if (arg === "--repo" || arg === "-r") {
|
|
89
|
-
const value = args[i + 1];
|
|
90
|
-
if (value) {
|
|
91
|
-
result.repo = value;
|
|
92
|
-
i += 1;
|
|
93
|
-
}
|
|
94
|
-
} else if (arg === "--version" || arg === "-v") {
|
|
95
|
-
const value = args[i + 1];
|
|
96
|
-
if (value) {
|
|
97
|
-
result.version = value;
|
|
98
|
-
i += 1;
|
|
99
|
-
}
|
|
100
|
-
} else if (arg === "--output" || arg === "-o") {
|
|
101
|
-
const value = args[i + 1];
|
|
102
|
-
if (value) {
|
|
103
|
-
result.output = value;
|
|
104
|
-
i += 1;
|
|
105
|
-
}
|
|
106
|
-
} else if (arg === "--interface" || arg === "-i") {
|
|
101
|
+
continue;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
const key = FLAG_MAP[arg];
|
|
105
|
+
if (key != null) {
|
|
107
106
|
const value = args[i + 1];
|
|
108
|
-
if (value) {
|
|
109
|
-
result
|
|
107
|
+
if (value != null && value !== "") {
|
|
108
|
+
result[key] = value;
|
|
110
109
|
i += 1;
|
|
111
110
|
}
|
|
112
|
-
|
|
111
|
+
continue;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
if (arg.startsWith("-")) {
|
|
113
115
|
throw new Error(`Unknown argument: ${arg}`);
|
|
114
116
|
}
|
|
115
117
|
}
|
|
@@ -122,13 +124,20 @@ async function main() {
|
|
|
122
124
|
const args = parseCliArgs(Bun.argv.slice(2));
|
|
123
125
|
|
|
124
126
|
// Show help
|
|
125
|
-
if (args.help) {
|
|
127
|
+
if (args.help === true) {
|
|
126
128
|
console.log(HELP_TEXT);
|
|
127
129
|
process.exit(0);
|
|
128
130
|
}
|
|
129
131
|
|
|
130
132
|
// Validate required arguments
|
|
131
|
-
if (
|
|
133
|
+
if (
|
|
134
|
+
args.name == null ||
|
|
135
|
+
args.name === "" ||
|
|
136
|
+
args.repo == null ||
|
|
137
|
+
args.repo === "" ||
|
|
138
|
+
args.version == null ||
|
|
139
|
+
args.version === ""
|
|
140
|
+
) {
|
|
132
141
|
console.error("Error: Missing required arguments");
|
|
133
142
|
console.error("Required: --name, --repo, --version");
|
|
134
143
|
console.error("\nRun with --help for usage information");
|
|
@@ -144,9 +153,12 @@ async function main() {
|
|
|
144
153
|
};
|
|
145
154
|
|
|
146
155
|
// Generate interface name from chart name if not provided
|
|
147
|
-
const interfaceName =
|
|
156
|
+
const interfaceName =
|
|
157
|
+
args.interface ?? `${toPascalCase(args.name)}HelmValues`;
|
|
148
158
|
|
|
149
|
-
console.error(
|
|
159
|
+
console.error(
|
|
160
|
+
`Fetching chart: ${chartInfo.chartName}@${chartInfo.version}`,
|
|
161
|
+
);
|
|
150
162
|
console.error(`Repository: ${chartInfo.repoUrl}`);
|
|
151
163
|
console.error("");
|
|
152
164
|
|
|
@@ -157,13 +169,19 @@ async function main() {
|
|
|
157
169
|
console.error(`Converting to TypeScript interface: ${interfaceName}`);
|
|
158
170
|
|
|
159
171
|
// Convert to TypeScript interface
|
|
160
|
-
const tsInterface = convertToTypeScriptInterface(
|
|
172
|
+
const tsInterface = convertToTypeScriptInterface({
|
|
173
|
+
values,
|
|
174
|
+
interfaceName,
|
|
175
|
+
schema,
|
|
176
|
+
yamlComments,
|
|
177
|
+
chartName: args.name,
|
|
178
|
+
});
|
|
161
179
|
|
|
162
180
|
// Generate TypeScript code
|
|
163
181
|
const code = generateTypeScriptCode(tsInterface, args.name);
|
|
164
182
|
|
|
165
183
|
// Write to file or stdout
|
|
166
|
-
if (args.output) {
|
|
184
|
+
if (args.output != null && args.output !== "") {
|
|
167
185
|
await Bun.write(args.output, code);
|
|
168
186
|
console.error("");
|
|
169
187
|
console.error(`✅ Types written to: ${args.output}`);
|
package/src/code-generator.ts
CHANGED
|
@@ -1,11 +1,20 @@
|
|
|
1
|
-
import type { TypeScriptInterface } from "./types.
|
|
2
|
-
import {
|
|
3
|
-
|
|
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";
|
|
4
10
|
|
|
5
11
|
/**
|
|
6
12
|
* Generate TypeScript code from interface definition
|
|
7
13
|
*/
|
|
8
|
-
export function generateTypeScriptCode(
|
|
14
|
+
export function generateTypeScriptCode(
|
|
15
|
+
mainInterface: TypeScriptInterface,
|
|
16
|
+
chartName: string,
|
|
17
|
+
): string {
|
|
9
18
|
const interfaces: TypeScriptInterface[] = [];
|
|
10
19
|
|
|
11
20
|
// Collect all nested interfaces
|
|
@@ -22,18 +31,20 @@ export function generateTypeScriptCode(mainInterface: TypeScriptInterface, chart
|
|
|
22
31
|
// Generate parameter type (flattened dot notation)
|
|
23
32
|
code += generateParameterType(mainInterface, chartName);
|
|
24
33
|
|
|
25
|
-
//
|
|
34
|
+
// Add header comment for generated files
|
|
26
35
|
if (code.includes(": any")) {
|
|
27
36
|
code = `// Generated TypeScript types for ${chartName} Helm chart
|
|
28
|
-
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
29
37
|
|
|
30
|
-
${code.
|
|
38
|
+
${code.slice(Math.max(0, code.indexOf("\n\n") + 2))}`;
|
|
31
39
|
}
|
|
32
40
|
|
|
33
41
|
return code;
|
|
34
42
|
}
|
|
35
43
|
|
|
36
|
-
function collectNestedInterfaces(
|
|
44
|
+
function collectNestedInterfaces(
|
|
45
|
+
iface: TypeScriptInterface,
|
|
46
|
+
collected: TypeScriptInterface[],
|
|
47
|
+
): void {
|
|
37
48
|
for (const prop of Object.values(iface.properties)) {
|
|
38
49
|
if (prop.nested) {
|
|
39
50
|
collected.push(prop.nested);
|
|
@@ -61,13 +72,19 @@ function generateInterfaceCode(iface: TypeScriptInterface): string {
|
|
|
61
72
|
const optional = prop.optional ? "?" : "";
|
|
62
73
|
|
|
63
74
|
// Generate JSDoc comment if we have description or default
|
|
64
|
-
if (
|
|
75
|
+
if (
|
|
76
|
+
(prop.description != null && prop.description !== "") ||
|
|
77
|
+
prop.default !== undefined
|
|
78
|
+
) {
|
|
65
79
|
code += ` /**\n`;
|
|
66
80
|
|
|
67
|
-
if (prop.description) {
|
|
81
|
+
if (prop.description != null && prop.description !== "") {
|
|
68
82
|
// Format multi-line descriptions properly with " * " prefix
|
|
69
83
|
// Escape */ sequences to prevent premature comment closure
|
|
70
|
-
const escapedDescription = prop.description.
|
|
84
|
+
const escapedDescription = prop.description.replaceAll(
|
|
85
|
+
"*/",
|
|
86
|
+
String.raw`*\/`,
|
|
87
|
+
);
|
|
71
88
|
const descLines = escapedDescription.split("\n");
|
|
72
89
|
for (const line of descLines) {
|
|
73
90
|
code += ` * ${line}\n`;
|
|
@@ -76,8 +93,12 @@ function generateInterfaceCode(iface: TypeScriptInterface): string {
|
|
|
76
93
|
|
|
77
94
|
if (prop.default !== undefined) {
|
|
78
95
|
const defaultStr = formatDefaultValue(prop.default);
|
|
79
|
-
|
|
80
|
-
|
|
96
|
+
const hasDescription =
|
|
97
|
+
prop.description != null && prop.description !== "";
|
|
98
|
+
if (defaultStr != null && defaultStr !== "" && hasDescription) {
|
|
99
|
+
code += ` *\n`;
|
|
100
|
+
}
|
|
101
|
+
if (defaultStr != null && defaultStr !== "") {
|
|
81
102
|
code += ` * @default ${defaultStr}\n`;
|
|
82
103
|
}
|
|
83
104
|
}
|
|
@@ -96,13 +117,19 @@ function generateInterfaceCode(iface: TypeScriptInterface): string {
|
|
|
96
117
|
* Format a default value for display in JSDoc
|
|
97
118
|
*/
|
|
98
119
|
function formatDefaultValue(value: unknown): string | null {
|
|
99
|
-
if (value === null)
|
|
100
|
-
|
|
120
|
+
if (value === null) {
|
|
121
|
+
return "null";
|
|
122
|
+
}
|
|
123
|
+
if (value === undefined) {
|
|
124
|
+
return null;
|
|
125
|
+
}
|
|
101
126
|
|
|
102
127
|
// Handle arrays
|
|
103
128
|
const arrayCheck = ArraySchema.safeParse(value);
|
|
104
129
|
if (arrayCheck.success) {
|
|
105
|
-
if (arrayCheck.data.length === 0)
|
|
130
|
+
if (arrayCheck.data.length === 0) {
|
|
131
|
+
return "[]";
|
|
132
|
+
}
|
|
106
133
|
if (arrayCheck.data.length <= 3) {
|
|
107
134
|
try {
|
|
108
135
|
return JSON.stringify(arrayCheck.data);
|
|
@@ -117,7 +144,9 @@ function formatDefaultValue(value: unknown): string | null {
|
|
|
117
144
|
const recordCheck = RecordSchema.safeParse(value);
|
|
118
145
|
if (recordCheck.success) {
|
|
119
146
|
const keys = Object.keys(recordCheck.data);
|
|
120
|
-
if (keys.length === 0)
|
|
147
|
+
if (keys.length === 0) {
|
|
148
|
+
return "{}";
|
|
149
|
+
}
|
|
121
150
|
if (keys.length <= 3) {
|
|
122
151
|
try {
|
|
123
152
|
return JSON.stringify(recordCheck.data);
|
|
@@ -133,7 +162,7 @@ function formatDefaultValue(value: unknown): string | null {
|
|
|
133
162
|
if (stringCheck.success) {
|
|
134
163
|
// Truncate long strings
|
|
135
164
|
if (stringCheck.data.length > 50) {
|
|
136
|
-
return `"${stringCheck.data.
|
|
165
|
+
return `"${stringCheck.data.slice(0, 47)}..."`;
|
|
137
166
|
}
|
|
138
167
|
return `"${stringCheck.data}"`;
|
|
139
168
|
}
|
|
@@ -157,10 +186,13 @@ function formatDefaultValue(value: unknown): string | null {
|
|
|
157
186
|
}
|
|
158
187
|
}
|
|
159
188
|
|
|
160
|
-
function generateParameterType(
|
|
189
|
+
function generateParameterType(
|
|
190
|
+
iface: TypeScriptInterface,
|
|
191
|
+
chartName: string,
|
|
192
|
+
): string {
|
|
161
193
|
const parameterKeys = flattenInterfaceKeys(iface);
|
|
162
194
|
|
|
163
|
-
const normalizedChartName = capitalizeFirst(chartName).
|
|
195
|
+
const normalizedChartName = capitalizeFirst(chartName).replaceAll("-", "");
|
|
164
196
|
let code = `export type ${normalizedChartName}HelmParameters = {\n`;
|
|
165
197
|
|
|
166
198
|
for (const key of parameterKeys) {
|
|
@@ -172,12 +204,15 @@ function generateParameterType(iface: TypeScriptInterface, chartName: string): s
|
|
|
172
204
|
return code;
|
|
173
205
|
}
|
|
174
206
|
|
|
175
|
-
function flattenInterfaceKeys(
|
|
207
|
+
function flattenInterfaceKeys(
|
|
208
|
+
iface: TypeScriptInterface,
|
|
209
|
+
prefix = "",
|
|
210
|
+
): string[] {
|
|
176
211
|
const keys: string[] = [];
|
|
177
212
|
|
|
178
213
|
for (const [key, prop] of Object.entries(iface.properties)) {
|
|
179
214
|
// Remove quotes from key for parameter names
|
|
180
|
-
const cleanKey = key.
|
|
215
|
+
const cleanKey = key.replaceAll('"', "");
|
|
181
216
|
const fullKey = prefix ? `${prefix}.${cleanKey}` : cleanKey;
|
|
182
217
|
|
|
183
218
|
if (prop.nested) {
|