@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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@shepherdjerred/helm-types",
3
- "version": "1.1.0",
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
  }
@@ -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.js";
4
- import { HelmValueSchema, RecordSchema, ErrorSchema } from "./schemas.js";
5
- import type { HelmValue } from "./schemas.js";
6
- import { parseYAMLComments } from "./yaml-comments.js";
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(chartPath: string): Promise<JSONSchemaProperty | null> {
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(`Command "${command} ${args.join(" ")}" failed with code ${exitCode.toString()}`);
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 ? parseResult.data.message : String(error);
52
- throw new Error(`Failed to spawn command "${command} ${args.join(" ")}": ${errorMessage}`);
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
- chart: ChartInfo,
61
- ): Promise<{ values: HelmValue; schema: JSONSchemaProperty | null; yamlComments: Map<string, string> }> {
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(` 💬 Extracted ${String(yamlComments.size)} comments from values.yaml`);
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(", ")}${Object.keys(recordParseResult.data).length > 10 ? "..." : ""}`,
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(` ⚠️ Parsed values is not a valid record object: ${String(parsedValues)}`);
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(` First few errors:`, parseResult.error.issues.slice(0, 3));
132
- console.warn(` ⚠️ Falling back to unvalidated object for type generation`);
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
  }
@@ -1,9 +1,11 @@
1
- import type { ChartInfo } from "./types.js";
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(versionsPath = "src/versions.ts"): Promise<ChartInfo[]> {
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 (line?.includes("renovate: datasource=helm") && nextLine) {
17
- const repoUrlMatch = /registryUrl=([^\s]+)/.exec(line);
18
- const versionKeyMatch = /^\s*"?([^":]+)"?:/.exec(nextLine);
19
-
20
- if (repoUrlMatch && versionKeyMatch) {
21
- const repoUrl = repoUrlMatch[1];
22
- const versionKey = versionKeyMatch[1];
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, convertToTypeScriptInterface, generateTypeScriptCode } from "./helm-types.ts";
9
- import type { ChartInfo } from "./helm-types.ts";
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 (!arg) continue;
95
+ if (arg == null || arg === "") {
96
+ continue;
97
+ }
73
98
 
74
- if (arg === "--help" || arg === "-h") {
99
+ if (HELP_FLAGS.has(arg)) {
75
100
  result.help = true;
76
- } else if (arg === "--name" || arg === "-n") {
77
- const value = args[i + 1];
78
- if (value) {
79
- result.name = value;
80
- i += 1;
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.interface = value;
107
+ if (value != null && value !== "") {
108
+ result[key] = value;
110
109
  i += 1;
111
110
  }
112
- } else if (arg.startsWith("-")) {
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 (!args.name || !args.repo || !args.version) {
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 = args.interface ?? `${toPascalCase(args.name)}HelmValues`;
156
+ const interfaceName =
157
+ args.interface ?? `${toPascalCase(args.name)}HelmValues`;
148
158
 
149
- console.error(`Fetching chart: ${chartInfo.chartName}@${chartInfo.version}`);
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(values, interfaceName, schema, yamlComments, "", args.name);
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}`);
@@ -1,11 +1,20 @@
1
- import type { TypeScriptInterface } from "./types.js";
2
- import { StringSchema, ArraySchema, RecordSchema, ActualNumberSchema, ActualBooleanSchema } from "./schemas.js";
3
- import { capitalizeFirst } from "./utils.js";
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(mainInterface: TypeScriptInterface, chartName: string): string {
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
- // Check if any 'any' types were generated and add ESLint disable if needed
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.substring(code.indexOf("\n\n") + 2)}`;
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(iface: TypeScriptInterface, collected: TypeScriptInterface[]): void {
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 (prop.description || prop.default !== undefined) {
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.replace(/\*\//g, "*\\/");
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
- if (defaultStr) {
80
- if (prop.description) code += ` *\n`;
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) return "null";
100
- if (value === undefined) return null;
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) return "[]";
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) return "{}";
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.substring(0, 47)}..."`;
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(iface: TypeScriptInterface, chartName: string): string {
189
+ function generateParameterType(
190
+ iface: TypeScriptInterface,
191
+ chartName: string,
192
+ ): string {
161
193
  const parameterKeys = flattenInterfaceKeys(iface);
162
194
 
163
- const normalizedChartName = capitalizeFirst(chartName).replace(/-/g, "");
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(iface: TypeScriptInterface, prefix = ""): string[] {
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.replace(/"/g, "");
215
+ const cleanKey = key.replaceAll('"', "");
181
216
  const fullKey = prefix ? `${prefix}.${cleanKey}` : cleanKey;
182
217
 
183
218
  if (prop.nested) {