@shepherdjerred/helm-types 1.3.0-dev.3866 → 1.3.0-dev.3954

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/dist/cli.js CHANGED
@@ -21877,27 +21877,61 @@ async function runCommand(command, args) {
21877
21877
  throw new Error(`Failed to spawn command "${command} ${args.join(" ")}": ${errorMessage}`, { cause: error51 });
21878
21878
  }
21879
21879
  }
21880
+ async function resolveUntarredChartDir(tempDir, fallbackName) {
21881
+ const fallback = `${tempDir}/${fallbackName}`;
21882
+ if (await Bun.file(`${fallback}/Chart.yaml`).exists()) {
21883
+ return fallback;
21884
+ }
21885
+ const lsOutput = await runCommand("ls", ["-1", tempDir]);
21886
+ for (const entry of lsOutput.split(`
21887
+ `).map((s) => s.trim())) {
21888
+ if (entry === "") {
21889
+ continue;
21890
+ }
21891
+ if (await Bun.file(`${tempDir}/${entry}/Chart.yaml`).exists()) {
21892
+ return `${tempDir}/${entry}`;
21893
+ }
21894
+ }
21895
+ return fallback;
21896
+ }
21880
21897
  async function fetchHelmChart(chart) {
21881
21898
  const pwd = Bun.env["PWD"] ?? process.cwd();
21882
21899
  const tempDir = `${pwd}/temp/helm-${chart.name}`;
21883
21900
  const repoName = `temp-repo-${chart.name}-${String(Date.now())}`;
21884
21901
  try {
21885
21902
  await Bun.$`mkdir -p ${tempDir}`.quiet();
21886
- console.log(` \uD83D\uDCE6 Adding Helm repo: ${chart.repoUrl}`);
21887
- await runCommand("helm", ["repo", "add", repoName, chart.repoUrl]);
21888
- console.log(` \uD83D\uDD04 Updating Helm repos...`);
21889
- await runCommand("helm", ["repo", "update"]);
21890
- console.log(` ⬇️ Pulling chart ${chart.chartName}:${chart.version}...`);
21891
- await runCommand("helm", [
21892
- "pull",
21893
- `${repoName}/${chart.chartName}`,
21894
- "--version",
21895
- chart.version,
21896
- "--destination",
21897
- tempDir,
21898
- "--untar"
21899
- ]);
21900
- const valuesPath = `${tempDir}/${chart.chartName}/values.yaml`;
21903
+ let chartDir;
21904
+ if (chart.oci === true) {
21905
+ const ociRef = `oci://${chart.repoUrl}/${chart.chartName}`;
21906
+ console.log(` ⬇️ Pulling OCI chart ${ociRef}:${chart.version}...`);
21907
+ await runCommand("helm", [
21908
+ "pull",
21909
+ ociRef,
21910
+ "--version",
21911
+ chart.version,
21912
+ "--destination",
21913
+ tempDir,
21914
+ "--untar"
21915
+ ]);
21916
+ chartDir = await resolveUntarredChartDir(tempDir, chart.name);
21917
+ } else {
21918
+ console.log(` \uD83D\uDCE6 Adding Helm repo: ${chart.repoUrl}`);
21919
+ await runCommand("helm", ["repo", "add", repoName, chart.repoUrl]);
21920
+ console.log(` \uD83D\uDD04 Updating Helm repo ${repoName}...`);
21921
+ await runCommand("helm", ["repo", "update", repoName]);
21922
+ console.log(` ⬇️ Pulling chart ${chart.chartName}:${chart.version}...`);
21923
+ await runCommand("helm", [
21924
+ "pull",
21925
+ `${repoName}/${chart.chartName}`,
21926
+ "--version",
21927
+ chart.version,
21928
+ "--destination",
21929
+ tempDir,
21930
+ "--untar"
21931
+ ]);
21932
+ chartDir = `${tempDir}/${chart.chartName}`;
21933
+ }
21934
+ const valuesPath = `${chartDir}/values.yaml`;
21901
21935
  console.log(` \uD83D\uDCD6 Reading values.yaml from ${valuesPath}`);
21902
21936
  try {
21903
21937
  const valuesContent = await Bun.file(valuesPath).text();
@@ -21914,8 +21948,7 @@ async function fetchHelmChart(chart) {
21914
21948
  return { values: {}, schema: null, yamlComments: new Map };
21915
21949
  }
21916
21950
  const parseResult = HelmValueSchema.safeParse(recordParseResult.data);
21917
- const chartPath = `${tempDir}/${chart.chartName}`;
21918
- const schema = await loadJSONSchema(chartPath);
21951
+ const schema = await loadJSONSchema(chartDir);
21919
21952
  if (parseResult.success) {
21920
21953
  console.log(` ✅ Zod validation successful`);
21921
21954
  return { values: parseResult.data, schema, yamlComments };
@@ -21932,7 +21965,9 @@ async function fetchHelmChart(chart) {
21932
21965
  } finally {
21933
21966
  try {
21934
21967
  console.log(` \uD83E\uDDF9 Cleaning up...`);
21935
- await runCommand("helm", ["repo", "remove", repoName]);
21968
+ if (chart.oci !== true) {
21969
+ await runCommand("helm", ["repo", "remove", repoName]);
21970
+ }
21936
21971
  await Bun.$`rm -rf ${tempDir}`.quiet();
21937
21972
  } catch (cleanupError) {
21938
21973
  console.warn(`Cleanup failed for ${chart.name}:`, String(cleanupError));
@@ -21941,12 +21976,40 @@ async function fetchHelmChart(chart) {
21941
21976
  }
21942
21977
 
21943
21978
  // src/config.ts
21944
- var K8S_RESOURCE_SPEC_PATTERN = {
21945
- resourceSpecNames: ["resources"],
21946
- resourceSpecFields: ["requests", "limits"]
21979
+ var K8S_WELL_KNOWN_FIELDS = {
21980
+ resources: {
21981
+ type: "{ requests?: Record<string, string | number>; limits?: Record<string, string | number> }",
21982
+ allowedShapes: ["object"],
21983
+ description: "Kubernetes container resources (standard ResourceRequirements: arbitrary resource names, string or numeric quantities)"
21984
+ },
21985
+ nodeselector: {
21986
+ type: "Record<string, string>",
21987
+ allowedShapes: ["object"],
21988
+ description: "Kubernetes nodeSelector (arbitrary label key/value pairs)"
21989
+ },
21990
+ tolerations: {
21991
+ type: "unknown[]",
21992
+ allowedShapes: ["array"],
21993
+ description: "Kubernetes tolerations (standard Toleration objects)"
21994
+ },
21995
+ affinity: {
21996
+ type: "Record<string, unknown>",
21997
+ allowedShapes: ["object"],
21998
+ description: "Kubernetes affinity (standard Affinity object)"
21999
+ }
21947
22000
  };
21948
- function isK8sResourceSpec(propertyName) {
21949
- return K8S_RESOURCE_SPEC_PATTERN.resourceSpecNames.includes(propertyName.toLowerCase());
22001
+ function getWellKnownK8sFieldType(propertyName, value) {
22002
+ const field = K8S_WELL_KNOWN_FIELDS[propertyName.toLowerCase()];
22003
+ if (!field) {
22004
+ return;
22005
+ }
22006
+ if (value != null) {
22007
+ const shape = Array.isArray(value) ? "array" : typeof value === "object" ? "object" : "primitive";
22008
+ if (shape === "primitive" || !field.allowedShapes.includes(shape)) {
22009
+ return;
22010
+ }
22011
+ }
22012
+ return { type: field.type, description: field.description };
21950
22013
  }
21951
22014
  var EXTENSIBLE_TYPE_PATTERNS = {
21952
22015
  "argo-cd": [
@@ -21989,6 +22052,12 @@ var EXTENSIBLE_TYPE_PATTERNS = {
21989
22052
  ],
21990
22053
  seaweedfs: [
21991
22054
  "volume.dataDirs"
22055
+ ],
22056
+ "dagger-helm": [
22057
+ "engine"
22058
+ ],
22059
+ "agent-stack-k8s": [
22060
+ "config"
21992
22061
  ]
21993
22062
  };
21994
22063
  function shouldAllowArbitraryProps(keyPath, chartName, propertyName, yamlComment) {
@@ -22150,60 +22219,6 @@ function inferPrimitiveType(value, yamlComment) {
22150
22219
  console.warn(`Unrecognized value type for: ${String(value)}, using 'unknown'`);
22151
22220
  return { type: "unknown", optional: true, description: yamlComment };
22152
22221
  }
22153
- function augmentK8sResourceSpec(iface) {
22154
- const hasRequests = "requests" in iface.properties;
22155
- const hasLimits = "limits" in iface.properties;
22156
- if (hasRequests && !hasLimits) {
22157
- const requestsProp = iface.properties["requests"];
22158
- if (requestsProp) {
22159
- const limitsTypeName = requestsProp.type.replace("Requests", "Limits");
22160
- if (requestsProp.nested) {
22161
- const limitsNested = {
22162
- name: limitsTypeName,
22163
- properties: { ...requestsProp.nested.properties },
22164
- allowArbitraryProps: requestsProp.nested.allowArbitraryProps
22165
- };
22166
- iface.properties["limits"] = {
22167
- type: limitsTypeName,
22168
- optional: true,
22169
- nested: limitsNested,
22170
- description: "Kubernetes resource limits (memory, cpu, etc.)"
22171
- };
22172
- } else {
22173
- iface.properties["limits"] = {
22174
- type: requestsProp.type,
22175
- optional: true,
22176
- description: "Kubernetes resource limits (memory, cpu, etc.)"
22177
- };
22178
- }
22179
- }
22180
- }
22181
- if (hasLimits && !hasRequests) {
22182
- const limitsProp = iface.properties["limits"];
22183
- if (limitsProp) {
22184
- const requestsTypeName = limitsProp.type.replace("Limits", "Requests");
22185
- if (limitsProp.nested) {
22186
- const requestsNested = {
22187
- name: requestsTypeName,
22188
- properties: { ...limitsProp.nested.properties },
22189
- allowArbitraryProps: limitsProp.nested.allowArbitraryProps
22190
- };
22191
- iface.properties["requests"] = {
22192
- type: requestsTypeName,
22193
- optional: true,
22194
- nested: requestsNested,
22195
- description: "Kubernetes resource requests (memory, cpu, etc.)"
22196
- };
22197
- } else {
22198
- iface.properties["requests"] = {
22199
- type: limitsProp.type,
22200
- optional: true,
22201
- description: "Kubernetes resource requests (memory, cpu, etc.)"
22202
- };
22203
- }
22204
- }
22205
- }
22206
- }
22207
22222
 
22208
22223
  // src/type-converter.ts
22209
22224
  function jsonSchemaToTypeScript(schema) {
@@ -22484,6 +22499,16 @@ function convertValueToProperty(opts) {
22484
22499
  if (NullSchema.safeParse(value).success || UndefinedSchema.safeParse(value).success) {
22485
22500
  return { type: "unknown", optional: true };
22486
22501
  }
22502
+ if (propertyName != null && propertyName !== "") {
22503
+ const wellKnown = getWellKnownK8sFieldType(propertyName, value);
22504
+ if (wellKnown) {
22505
+ return {
22506
+ type: wellKnown.type,
22507
+ optional: true,
22508
+ description: yamlComment ?? wellKnown.description
22509
+ };
22510
+ }
22511
+ }
22487
22512
  const arrayResult = ArraySchema.safeParse(value);
22488
22513
  if (arrayResult.success) {
22489
22514
  return inferArrayType(opts, arrayResult.data);
@@ -22497,9 +22522,6 @@ function convertValueToProperty(opts) {
22497
22522
  keyPrefix: fullKey,
22498
22523
  chartName
22499
22524
  });
22500
- if (propertyName != null && propertyName !== "" && isK8sResourceSpec(propertyName)) {
22501
- augmentK8sResourceSpec(nestedInterface);
22502
- }
22503
22525
  return {
22504
22526
  type: nestedTypeName,
22505
22527
  optional: true,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@shepherdjerred/helm-types",
3
- "version": "1.3.0-dev.3866",
3
+ "version": "1.3.0-dev.3954",
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",
@@ -62,6 +62,32 @@ async function runCommand(command: string, args: string[]): Promise<string> {
62
62
  }
63
63
  }
64
64
 
65
+ /**
66
+ * Locate the directory `helm pull --untar` created. helm names it after the
67
+ * Chart.yaml `name`, which can differ from the OCI artifact path (e.g.
68
+ * `kueue/charts/kueue` untars to `kueue/`), so prefer the version-key fallback
69
+ * but fall back to scanning for the extracted Chart.yaml.
70
+ */
71
+ async function resolveUntarredChartDir(
72
+ tempDir: string,
73
+ fallbackName: string,
74
+ ): Promise<string> {
75
+ const fallback = `${tempDir}/${fallbackName}`;
76
+ if (await Bun.file(`${fallback}/Chart.yaml`).exists()) {
77
+ return fallback;
78
+ }
79
+ const lsOutput = await runCommand("ls", ["-1", tempDir]);
80
+ for (const entry of lsOutput.split("\n").map((s) => s.trim())) {
81
+ if (entry === "") {
82
+ continue;
83
+ }
84
+ if (await Bun.file(`${tempDir}/${entry}/Chart.yaml`).exists()) {
85
+ return `${tempDir}/${entry}`;
86
+ }
87
+ }
88
+ return fallback;
89
+ }
90
+
65
91
  /**
66
92
  * Fetch a Helm chart and extract its values.yaml and optional schema
67
93
  */
@@ -78,28 +104,49 @@ export async function fetchHelmChart(chart: ChartInfo): Promise<{
78
104
  // Ensure temp directory exists
79
105
  await Bun.$`mkdir -p ${tempDir}`.quiet();
80
106
 
81
- console.log(` 📦 Adding Helm repo: ${chart.repoUrl}`);
82
- // Add the helm repo
83
- await runCommand("helm", ["repo", "add", repoName, chart.repoUrl]);
84
-
85
- console.log(` 🔄 Updating Helm repos...`);
86
- // Update repo
87
- await runCommand("helm", ["repo", "update"]);
88
-
89
- console.log(` ⬇️ Pulling chart ${chart.chartName}:${chart.version}...`);
90
- // Pull the chart
91
- await runCommand("helm", [
92
- "pull",
93
- `${repoName}/${chart.chartName}`,
94
- "--version",
95
- chart.version,
96
- "--destination",
97
- tempDir,
98
- "--untar",
99
- ]);
107
+ let chartDir: string;
108
+ if (chart.oci === true) {
109
+ // OCI registry: pull directly, no `helm repo add` needed.
110
+ const ociRef = `oci://${chart.repoUrl}/${chart.chartName}`;
111
+ console.log(` ⬇️ Pulling OCI chart ${ociRef}:${chart.version}...`);
112
+ await runCommand("helm", [
113
+ "pull",
114
+ ociRef,
115
+ "--version",
116
+ chart.version,
117
+ "--destination",
118
+ tempDir,
119
+ "--untar",
120
+ ]);
121
+ chartDir = await resolveUntarredChartDir(tempDir, chart.name);
122
+ } else {
123
+ console.log(` 📦 Adding Helm repo: ${chart.repoUrl}`);
124
+ // Add the helm repo
125
+ await runCommand("helm", ["repo", "add", repoName, chart.repoUrl]);
126
+
127
+ console.log(` 🔄 Updating Helm repo ${repoName}...`);
128
+ // Update ONLY the repo we just added. `helm repo update` with no args
129
+ // refreshes every repo in the local helm config — including unrelated
130
+ // stale entries (e.g. the retired public bitnami repo) whose failure
131
+ // would abort an otherwise-fine fetch.
132
+ await runCommand("helm", ["repo", "update", repoName]);
133
+
134
+ console.log(` ⬇️ Pulling chart ${chart.chartName}:${chart.version}...`);
135
+ // Pull the chart
136
+ await runCommand("helm", [
137
+ "pull",
138
+ `${repoName}/${chart.chartName}`,
139
+ "--version",
140
+ chart.version,
141
+ "--destination",
142
+ tempDir,
143
+ "--untar",
144
+ ]);
145
+ chartDir = `${tempDir}/${chart.chartName}`;
146
+ }
100
147
 
101
148
  // Read values.yaml
102
- const valuesPath = `${tempDir}/${chart.chartName}/values.yaml`;
149
+ const valuesPath = `${chartDir}/values.yaml`;
103
150
  console.log(` 📖 Reading values.yaml from ${valuesPath}`);
104
151
 
105
152
  try {
@@ -137,8 +184,7 @@ export async function fetchHelmChart(chart: ChartInfo): Promise<{
137
184
  const parseResult = HelmValueSchema.safeParse(recordParseResult.data);
138
185
 
139
186
  // Try to load JSON schema
140
- const chartPath = `${tempDir}/${chart.chartName}`;
141
- const schema = await loadJSONSchema(chartPath);
187
+ const schema = await loadJSONSchema(chartDir);
142
188
 
143
189
  if (parseResult.success) {
144
190
  console.log(` ✅ Zod validation successful`);
@@ -163,7 +209,10 @@ export async function fetchHelmChart(chart: ChartInfo): Promise<{
163
209
  // Cleanup
164
210
  try {
165
211
  console.log(` 🧹 Cleaning up...`);
166
- await runCommand("helm", ["repo", "remove", repoName]);
212
+ // OCI charts never added a named repo, so only remove for HTTP repos.
213
+ if (chart.oci !== true) {
214
+ await runCommand("helm", ["repo", "remove", repoName]);
215
+ }
167
216
  await Bun.$`rm -rf ${tempDir}`.quiet();
168
217
  } catch (cleanupError) {
169
218
  console.warn(`Cleanup failed for ${chart.name}:`, String(cleanupError));
package/src/config.ts CHANGED
@@ -1,25 +1,68 @@
1
1
  /**
2
- * Well-known Kubernetes resource patterns.
3
- * When a property matches these patterns, we augment the type with standard K8s fields.
2
+ * Well-known Kubernetes fields whose shape is defined by the Kubernetes API,
3
+ * not by whatever subset a chart's values.yaml happens to set as defaults.
4
+ *
5
+ * Inferring these from defaults produces types that are too narrow — e.g. a
6
+ * chart defaulting `resources: {requests: {cpu: 0.2}}` would otherwise forbid
7
+ * setting a memory request at all. When a property matches one of these names
8
+ * AND its default value has a compatible shape, the canonical permissive type
9
+ * is emitted instead of a defaults-derived interface.
4
10
  */
5
- export const K8S_RESOURCE_SPEC_PATTERN = {
6
- /**
7
- * Property names that indicate a Kubernetes resource spec (requests/limits pattern)
8
- */
9
- resourceSpecNames: ["resources"],
10
- /**
11
- * Required sibling properties for a valid resource spec
12
- */
13
- resourceSpecFields: ["requests", "limits"] as const,
11
+ const K8S_WELL_KNOWN_FIELDS: Record<
12
+ string,
13
+ { type: string; allowedShapes: ("object" | "array")[]; description: string }
14
+ > = {
15
+ resources: {
16
+ type: "{ requests?: Record<string, string | number>; limits?: Record<string, string | number> }",
17
+ // RBAC rules also use a key named `resources`, but as an array of strings —
18
+ // the object guard keeps those out.
19
+ allowedShapes: ["object"],
20
+ description:
21
+ "Kubernetes container resources (standard ResourceRequirements: arbitrary resource names, string or numeric quantities)",
22
+ },
23
+ nodeselector: {
24
+ type: "Record<string, string>",
25
+ allowedShapes: ["object"],
26
+ description: "Kubernetes nodeSelector (arbitrary label key/value pairs)",
27
+ },
28
+ tolerations: {
29
+ type: "unknown[]",
30
+ allowedShapes: ["array"],
31
+ description: "Kubernetes tolerations (standard Toleration objects)",
32
+ },
33
+ affinity: {
34
+ type: "Record<string, unknown>",
35
+ allowedShapes: ["object"],
36
+ description: "Kubernetes affinity (standard Affinity object)",
37
+ },
14
38
  };
15
39
 
16
40
  /**
17
- * Check if a property name indicates a Kubernetes resource spec.
41
+ * Return the canonical type for a well-known Kubernetes field, or undefined if
42
+ * the property name doesn't match or the default value's shape is incompatible
43
+ * (e.g. an RBAC `resources: ["secrets"]` array, which must NOT become
44
+ * ResourceRequirements).
18
45
  */
19
- export function isK8sResourceSpec(propertyName: string): boolean {
20
- return K8S_RESOURCE_SPEC_PATTERN.resourceSpecNames.includes(
21
- propertyName.toLowerCase(),
22
- );
46
+ export function getWellKnownK8sFieldType(
47
+ propertyName: string,
48
+ value: unknown,
49
+ ): { type: string; description: string } | undefined {
50
+ const field = K8S_WELL_KNOWN_FIELDS[propertyName.toLowerCase()];
51
+ if (!field) {
52
+ return undefined;
53
+ }
54
+ // A null/undefined default carries no shape signal — trust the name.
55
+ if (value != null) {
56
+ const shape: "object" | "array" | "primitive" = Array.isArray(value)
57
+ ? "array"
58
+ : typeof value === "object"
59
+ ? "object"
60
+ : "primitive";
61
+ if (shape === "primitive" || !field.allowedShapes.includes(shape)) {
62
+ return undefined;
63
+ }
64
+ }
65
+ return { type: field.type, description: field.description };
23
66
  }
24
67
 
25
68
  /**
@@ -72,6 +115,14 @@ export const EXTENSIBLE_TYPE_PATTERNS: Record<string, string[]> = {
72
115
  seaweedfs: [
73
116
  "volume.dataDirs", // dataDirs elements support size, storageClass when type is persistentVolumeClaim
74
117
  ],
118
+ // OCI charts that document config keys only as commented-out examples in
119
+ // values.yaml, so inference from active defaults misses valid keys.
120
+ "dagger-helm": [
121
+ "engine", // engine.port / engine.configJson / engine.config are commented-out chart examples
122
+ ],
123
+ "agent-stack-k8s": [
124
+ "config", // config.queue / max-in-flight / empty-job-grace-period / default-checkout-params are valid but not defaulted
125
+ ],
75
126
  };
76
127
 
77
128
  /**
@@ -1,8 +1,4 @@
1
- import type {
2
- JSONSchemaProperty,
3
- TypeScriptInterface,
4
- TypeProperty,
5
- } from "./types.ts";
1
+ import type { JSONSchemaProperty, TypeProperty } from "./types.ts";
6
2
  import {
7
3
  StringSchema,
8
4
  ActualNumberSchema,
@@ -110,71 +106,3 @@ export function inferPrimitiveType(
110
106
  );
111
107
  return { type: "unknown", optional: true, description: yamlComment };
112
108
  }
113
-
114
- /**
115
- * Augment a Kubernetes resource spec interface with both requests and limits.
116
- * If only one is present, copy its type structure to the other.
117
- */
118
- export function augmentK8sResourceSpec(iface: TypeScriptInterface): void {
119
- const hasRequests = "requests" in iface.properties;
120
- const hasLimits = "limits" in iface.properties;
121
-
122
- // If we have requests but not limits, add limits with the same structure
123
- if (hasRequests && !hasLimits) {
124
- const requestsProp = iface.properties["requests"];
125
- if (requestsProp) {
126
- // Create limits property with the same type but different name for the nested interface
127
- const limitsTypeName = requestsProp.type.replace("Requests", "Limits");
128
-
129
- // If there's a nested interface, create a copy for limits
130
- if (requestsProp.nested) {
131
- const limitsNested: TypeScriptInterface = {
132
- name: limitsTypeName,
133
- properties: { ...requestsProp.nested.properties },
134
- allowArbitraryProps: requestsProp.nested.allowArbitraryProps,
135
- };
136
- iface.properties["limits"] = {
137
- type: limitsTypeName,
138
- optional: true,
139
- nested: limitsNested,
140
- description: "Kubernetes resource limits (memory, cpu, etc.)",
141
- };
142
- } else {
143
- // No nested interface, just copy the type
144
- iface.properties["limits"] = {
145
- type: requestsProp.type,
146
- optional: true,
147
- description: "Kubernetes resource limits (memory, cpu, etc.)",
148
- };
149
- }
150
- }
151
- }
152
-
153
- // If we have limits but not requests, add requests with the same structure
154
- if (hasLimits && !hasRequests) {
155
- const limitsProp = iface.properties["limits"];
156
- if (limitsProp) {
157
- const requestsTypeName = limitsProp.type.replace("Limits", "Requests");
158
-
159
- if (limitsProp.nested) {
160
- const requestsNested: TypeScriptInterface = {
161
- name: requestsTypeName,
162
- properties: { ...limitsProp.nested.properties },
163
- allowArbitraryProps: limitsProp.nested.allowArbitraryProps,
164
- };
165
- iface.properties["requests"] = {
166
- type: requestsTypeName,
167
- optional: true,
168
- nested: requestsNested,
169
- description: "Kubernetes resource requests (memory, cpu, etc.)",
170
- };
171
- } else {
172
- iface.properties["requests"] = {
173
- type: limitsProp.type,
174
- optional: true,
175
- description: "Kubernetes resource requests (memory, cpu, etc.)",
176
- };
177
- }
178
- }
179
- }
180
- }
@@ -14,7 +14,10 @@ import {
14
14
  ActualNumberSchema,
15
15
  StringBooleanSchema,
16
16
  } from "./schemas.ts";
17
- import { shouldAllowArbitraryProps, isK8sResourceSpec } from "./config.ts";
17
+ import {
18
+ shouldAllowArbitraryProps,
19
+ getWellKnownK8sFieldType,
20
+ } from "./config.ts";
18
21
  import {
19
22
  sanitizePropertyName,
20
23
  sanitizeTypeName,
@@ -24,7 +27,6 @@ import type { PropertyConversionContext } from "./type-converter-helpers.ts";
24
27
  import {
25
28
  mergeDescriptions,
26
29
  inferPrimitiveType,
27
- augmentK8sResourceSpec,
28
30
  } from "./type-converter-helpers.ts";
29
31
 
30
32
  /**
@@ -470,6 +472,23 @@ function convertValueToProperty(opts: PropertyConversionContext): TypeProperty {
470
472
  return { type: "unknown", optional: true };
471
473
  }
472
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 != null && propertyName !== "") {
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
+
473
492
  // Check for array (before coercion checks)
474
493
  const arrayResult = ArraySchema.safeParse(value);
475
494
  if (arrayResult.success) {
@@ -487,14 +506,6 @@ function convertValueToProperty(opts: PropertyConversionContext): TypeProperty {
487
506
  chartName,
488
507
  });
489
508
 
490
- if (
491
- propertyName != null &&
492
- propertyName !== "" &&
493
- isK8sResourceSpec(propertyName)
494
- ) {
495
- augmentK8sResourceSpec(nestedInterface);
496
- }
497
-
498
509
  return {
499
510
  type: nestedTypeName,
500
511
  optional: true,
package/src/types.ts CHANGED
@@ -5,6 +5,9 @@ export type ChartInfo = {
5
5
  repoUrl: string;
6
6
  version: string;
7
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;
8
11
  };
9
12
 
10
13
  export type JSONSchemaProperty = {