@intentius/chant-lexicon-k8s 0.0.12

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 (123) hide show
  1. package/dist/integrity.json +32 -0
  2. package/dist/manifest.json +8 -0
  3. package/dist/meta.json +1413 -0
  4. package/dist/rules/hardcoded-namespace.ts +56 -0
  5. package/dist/rules/k8s-helpers.ts +149 -0
  6. package/dist/rules/wk8005.ts +59 -0
  7. package/dist/rules/wk8006.ts +68 -0
  8. package/dist/rules/wk8041.ts +73 -0
  9. package/dist/rules/wk8042.ts +48 -0
  10. package/dist/rules/wk8101.ts +65 -0
  11. package/dist/rules/wk8102.ts +42 -0
  12. package/dist/rules/wk8103.ts +45 -0
  13. package/dist/rules/wk8104.ts +69 -0
  14. package/dist/rules/wk8105.ts +45 -0
  15. package/dist/rules/wk8201.ts +55 -0
  16. package/dist/rules/wk8202.ts +46 -0
  17. package/dist/rules/wk8203.ts +46 -0
  18. package/dist/rules/wk8204.ts +54 -0
  19. package/dist/rules/wk8205.ts +56 -0
  20. package/dist/rules/wk8207.ts +45 -0
  21. package/dist/rules/wk8208.ts +45 -0
  22. package/dist/rules/wk8209.ts +45 -0
  23. package/dist/rules/wk8301.ts +51 -0
  24. package/dist/rules/wk8302.ts +46 -0
  25. package/dist/rules/wk8303.ts +96 -0
  26. package/dist/skills/chant-k8s.md +433 -0
  27. package/dist/types/index.d.ts +2934 -0
  28. package/package.json +30 -0
  29. package/src/actions/actions.test.ts +83 -0
  30. package/src/actions/apps.ts +23 -0
  31. package/src/actions/batch.ts +9 -0
  32. package/src/actions/core.ts +62 -0
  33. package/src/actions/index.ts +50 -0
  34. package/src/actions/networking.ts +15 -0
  35. package/src/actions/rbac.ts +13 -0
  36. package/src/codegen/docs-cli.ts +3 -0
  37. package/src/codegen/docs.ts +1147 -0
  38. package/src/codegen/generate-cli.ts +41 -0
  39. package/src/codegen/generate-lexicon.ts +69 -0
  40. package/src/codegen/generate-typescript.ts +97 -0
  41. package/src/codegen/generate.ts +144 -0
  42. package/src/codegen/naming.test.ts +63 -0
  43. package/src/codegen/naming.ts +187 -0
  44. package/src/codegen/package.ts +56 -0
  45. package/src/codegen/patches.ts +108 -0
  46. package/src/codegen/snapshot.test.ts +95 -0
  47. package/src/codegen/typecheck.test.ts +24 -0
  48. package/src/codegen/typecheck.ts +4 -0
  49. package/src/codegen/versions.ts +43 -0
  50. package/src/composites/autoscaled-service.ts +236 -0
  51. package/src/composites/composites.test.ts +1109 -0
  52. package/src/composites/cron-workload.ts +167 -0
  53. package/src/composites/index.ts +14 -0
  54. package/src/composites/namespace-env.ts +163 -0
  55. package/src/composites/node-agent.ts +224 -0
  56. package/src/composites/stateful-app.ts +134 -0
  57. package/src/composites/web-app.ts +180 -0
  58. package/src/composites/worker-pool.ts +230 -0
  59. package/src/coverage.test.ts +27 -0
  60. package/src/coverage.ts +35 -0
  61. package/src/crd/loader.ts +112 -0
  62. package/src/crd/parser.test.ts +217 -0
  63. package/src/crd/parser.ts +279 -0
  64. package/src/crd/types.ts +54 -0
  65. package/src/default-labels.test.ts +111 -0
  66. package/src/default-labels.ts +122 -0
  67. package/src/generated/index.d.ts +2934 -0
  68. package/src/generated/index.ts +203 -0
  69. package/src/generated/lexicon-k8s.json +1413 -0
  70. package/src/generated/runtime.ts +4 -0
  71. package/src/import/generator.test.ts +121 -0
  72. package/src/import/generator.ts +285 -0
  73. package/src/import/parser.test.ts +156 -0
  74. package/src/import/parser.ts +204 -0
  75. package/src/import/roundtrip.test.ts +86 -0
  76. package/src/index.ts +38 -0
  77. package/src/lint/post-synth/k8s-helpers.test.ts +219 -0
  78. package/src/lint/post-synth/k8s-helpers.ts +149 -0
  79. package/src/lint/post-synth/post-synth.test.ts +969 -0
  80. package/src/lint/post-synth/wk8005.ts +59 -0
  81. package/src/lint/post-synth/wk8006.ts +68 -0
  82. package/src/lint/post-synth/wk8041.ts +73 -0
  83. package/src/lint/post-synth/wk8042.ts +48 -0
  84. package/src/lint/post-synth/wk8101.ts +65 -0
  85. package/src/lint/post-synth/wk8102.ts +42 -0
  86. package/src/lint/post-synth/wk8103.ts +45 -0
  87. package/src/lint/post-synth/wk8104.ts +69 -0
  88. package/src/lint/post-synth/wk8105.ts +45 -0
  89. package/src/lint/post-synth/wk8201.ts +55 -0
  90. package/src/lint/post-synth/wk8202.ts +46 -0
  91. package/src/lint/post-synth/wk8203.ts +46 -0
  92. package/src/lint/post-synth/wk8204.ts +54 -0
  93. package/src/lint/post-synth/wk8205.ts +56 -0
  94. package/src/lint/post-synth/wk8207.ts +45 -0
  95. package/src/lint/post-synth/wk8208.ts +45 -0
  96. package/src/lint/post-synth/wk8209.ts +45 -0
  97. package/src/lint/post-synth/wk8301.ts +51 -0
  98. package/src/lint/post-synth/wk8302.ts +46 -0
  99. package/src/lint/post-synth/wk8303.ts +96 -0
  100. package/src/lint/rules/hardcoded-namespace.ts +56 -0
  101. package/src/lint/rules/rules.test.ts +69 -0
  102. package/src/lsp/completions.test.ts +64 -0
  103. package/src/lsp/completions.ts +20 -0
  104. package/src/lsp/hover.test.ts +69 -0
  105. package/src/lsp/hover.ts +68 -0
  106. package/src/package-cli.ts +28 -0
  107. package/src/plugin.test.ts +209 -0
  108. package/src/plugin.ts +915 -0
  109. package/src/serializer.test.ts +275 -0
  110. package/src/serializer.ts +278 -0
  111. package/src/spec/fetch.test.ts +24 -0
  112. package/src/spec/fetch.ts +68 -0
  113. package/src/spec/parse.test.ts +102 -0
  114. package/src/spec/parse.ts +477 -0
  115. package/src/testdata/manifests/configmap.yaml +7 -0
  116. package/src/testdata/manifests/deployment.yaml +22 -0
  117. package/src/testdata/manifests/full-app.yaml +61 -0
  118. package/src/testdata/manifests/secret.yaml +7 -0
  119. package/src/testdata/manifests/service.yaml +15 -0
  120. package/src/validate-cli.ts +21 -0
  121. package/src/validate.test.ts +29 -0
  122. package/src/validate.ts +46 -0
  123. package/src/variables.ts +36 -0
@@ -0,0 +1,102 @@
1
+ import { describe, test, expect } from "bun:test";
2
+ import {
3
+ gvkToTypeName,
4
+ gvkToApiVersion,
5
+ k8sShortName,
6
+ k8sServiceName,
7
+ } from "./parse";
8
+
9
+ describe("gvkToTypeName", () => {
10
+ test("core group maps to Core", () => {
11
+ expect(gvkToTypeName({ group: "", version: "v1", kind: "Pod" })).toBe(
12
+ "K8s::Core::Pod",
13
+ );
14
+ });
15
+
16
+ test("apps group", () => {
17
+ expect(
18
+ gvkToTypeName({ group: "apps", version: "v1", kind: "Deployment" }),
19
+ ).toBe("K8s::Apps::Deployment");
20
+ });
21
+
22
+ test("batch group", () => {
23
+ expect(
24
+ gvkToTypeName({ group: "batch", version: "v1", kind: "Job" }),
25
+ ).toBe("K8s::Batch::Job");
26
+ });
27
+
28
+ test("networking.k8s.io group", () => {
29
+ expect(
30
+ gvkToTypeName({
31
+ group: "networking.k8s.io",
32
+ version: "v1",
33
+ kind: "Ingress",
34
+ }),
35
+ ).toBe("K8s::Networking::Ingress");
36
+ });
37
+
38
+ test("rbac group normalised to Rbac", () => {
39
+ expect(
40
+ gvkToTypeName({
41
+ group: "rbac.authorization.k8s.io",
42
+ version: "v1",
43
+ kind: "Role",
44
+ }),
45
+ ).toBe("K8s::Rbac::Role");
46
+ });
47
+
48
+ test("autoscaling group", () => {
49
+ expect(
50
+ gvkToTypeName({
51
+ group: "autoscaling",
52
+ version: "v2",
53
+ kind: "HorizontalPodAutoscaler",
54
+ }),
55
+ ).toBe("K8s::Autoscaling::HorizontalPodAutoscaler");
56
+ });
57
+ });
58
+
59
+ describe("gvkToApiVersion", () => {
60
+ test("core group returns version only", () => {
61
+ expect(gvkToApiVersion({ group: "", version: "v1", kind: "Pod" })).toBe(
62
+ "v1",
63
+ );
64
+ });
65
+
66
+ test("empty string group returns version only", () => {
67
+ expect(
68
+ gvkToApiVersion({ group: "", version: "v1", kind: "Service" }),
69
+ ).toBe("v1");
70
+ });
71
+
72
+ test("non-core group returns group/version", () => {
73
+ expect(
74
+ gvkToApiVersion({ group: "apps", version: "v1", kind: "Deployment" }),
75
+ ).toBe("apps/v1");
76
+ });
77
+
78
+ test("networking group", () => {
79
+ expect(
80
+ gvkToApiVersion({
81
+ group: "networking.k8s.io",
82
+ version: "v1",
83
+ kind: "Ingress",
84
+ }),
85
+ ).toBe("networking.k8s.io/v1");
86
+ });
87
+ });
88
+
89
+ describe("k8sShortName", () => {
90
+ test("returns short name for known types", () => {
91
+ // k8sShortName should map well-known types to their abbreviations
92
+ const name = k8sShortName("Deployment");
93
+ expect(typeof name).toBe("string");
94
+ });
95
+ });
96
+
97
+ describe("k8sServiceName", () => {
98
+ test("returns service name for known types", () => {
99
+ const name = k8sServiceName("Deployment");
100
+ expect(typeof name).toBe("string");
101
+ });
102
+ });
@@ -0,0 +1,477 @@
1
+ /**
2
+ * Kubernetes OpenAPI Swagger 2.0 parser.
3
+ *
4
+ * Parses the single swagger.json into multiple K8sParseResult entries —
5
+ * one per resource identified by `x-kubernetes-group-version-kind`.
6
+ * Property types (Container, PodSpec, Volume, etc.) are extracted as
7
+ * nested property types within their parent resources.
8
+ */
9
+
10
+ import type { PropertyConstraints } from "@intentius/chant/codegen/json-schema";
11
+ import {
12
+ extractConstraints as coreExtractConstraints,
13
+ primaryType,
14
+ type JsonSchemaProperty,
15
+ } from "@intentius/chant/codegen/json-schema";
16
+
17
+ // ── Types ──────────────────────────────────────────────────────────
18
+
19
+ export type { PropertyConstraints };
20
+
21
+ export interface ParsedProperty {
22
+ name: string;
23
+ tsType: string;
24
+ required: boolean;
25
+ description?: string;
26
+ enum?: string[];
27
+ constraints: PropertyConstraints;
28
+ }
29
+
30
+ export interface ParsedPropertyType {
31
+ name: string;
32
+ /** The definition key in the original schema */
33
+ defType: string;
34
+ properties: ParsedProperty[];
35
+ }
36
+
37
+ export interface ParsedEnum {
38
+ name: string;
39
+ values: string[];
40
+ }
41
+
42
+ export interface ParsedResource {
43
+ typeName: string;
44
+ description?: string;
45
+ properties: ParsedProperty[];
46
+ attributes: Array<{ name: string; tsType: string }>;
47
+ deprecatedProperties: string[];
48
+ }
49
+
50
+ export interface GroupVersionKind {
51
+ group: string;
52
+ version: string;
53
+ kind: string;
54
+ }
55
+
56
+ export interface K8sParseResult {
57
+ resource: ParsedResource;
58
+ propertyTypes: ParsedPropertyType[];
59
+ enums: ParsedEnum[];
60
+ gvk: GroupVersionKind;
61
+ /** Whether this entity is a property type (nested inside resources) */
62
+ isProperty?: boolean;
63
+ }
64
+
65
+ // ── Swagger types ──────────────────────────────────────────────────
66
+
67
+ interface SwaggerDefinition {
68
+ type?: string | string[];
69
+ description?: string;
70
+ properties?: Record<string, SwaggerProperty>;
71
+ required?: string[];
72
+ enum?: string[];
73
+ $ref?: string;
74
+ items?: SwaggerProperty;
75
+ additionalProperties?: boolean | SwaggerProperty;
76
+ format?: string;
77
+ minimum?: number;
78
+ maximum?: number;
79
+ minLength?: number;
80
+ maxLength?: number;
81
+ pattern?: string;
82
+ "x-kubernetes-group-version-kind"?: GroupVersionKind[];
83
+ "x-kubernetes-int-or-string"?: boolean;
84
+ "x-kubernetes-preserve-unknown-fields"?: boolean;
85
+ }
86
+
87
+ interface SwaggerProperty extends SwaggerDefinition {
88
+ // Same shape as definition
89
+ }
90
+
91
+ interface SwaggerSpec {
92
+ definitions?: Record<string, SwaggerDefinition>;
93
+ [key: string]: unknown;
94
+ }
95
+
96
+ // ── Well-known property type definitions ───────────────────────────
97
+
98
+ /**
99
+ * Definitions that should be extracted as standalone property types
100
+ * even though they don't have GVK. Mapped to friendly names.
101
+ */
102
+ const PROPERTY_TYPE_DEFS: Record<string, { typeName: string; description: string }> = {
103
+ "io.k8s.api.core.v1.Container": { typeName: "K8s::Core::Container", description: "A container definition for a pod" },
104
+ "io.k8s.api.core.v1.ContainerPort": { typeName: "K8s::Core::ContainerPort", description: "A port to expose from a container" },
105
+ "io.k8s.api.core.v1.EnvVar": { typeName: "K8s::Core::EnvVar", description: "An environment variable for a container" },
106
+ "io.k8s.api.core.v1.EnvFromSource": { typeName: "K8s::Core::EnvFromSource", description: "Source for environment variables" },
107
+ "io.k8s.api.core.v1.Volume": { typeName: "K8s::Core::Volume", description: "A volume that can be mounted by containers" },
108
+ "io.k8s.api.core.v1.VolumeMount": { typeName: "K8s::Core::VolumeMount", description: "A volume mount in a container" },
109
+ "io.k8s.api.core.v1.PodSpec": { typeName: "K8s::Core::PodSpec", description: "Specification of a pod" },
110
+ "io.k8s.api.core.v1.PodTemplateSpec": { typeName: "K8s::Core::PodTemplateSpec", description: "Pod template specification" },
111
+ "io.k8s.api.core.v1.ServicePort": { typeName: "K8s::Core::ServicePort", description: "A port exposed by a service" },
112
+ "io.k8s.api.core.v1.Probe": { typeName: "K8s::Core::Probe", description: "A health check probe" },
113
+ "io.k8s.api.core.v1.ResourceRequirements": { typeName: "K8s::Core::ResourceRequirements", description: "CPU and memory resource requirements" },
114
+ "io.k8s.api.core.v1.SecurityContext": { typeName: "K8s::Core::SecurityContext", description: "Security options for a container" },
115
+ "io.k8s.api.core.v1.PodSecurityContext": { typeName: "K8s::Core::PodSecurityContext", description: "Security options for a pod" },
116
+ "io.k8s.api.core.v1.Capabilities": { typeName: "K8s::Core::Capabilities", description: "Linux capabilities to add or drop" },
117
+ "io.k8s.api.core.v1.ConfigMapKeySelector": { typeName: "K8s::Core::ConfigMapKeySelector", description: "Reference to a key in a ConfigMap" },
118
+ "io.k8s.api.core.v1.SecretKeySelector": { typeName: "K8s::Core::SecretKeySelector", description: "Reference to a key in a Secret" },
119
+ "io.k8s.api.core.v1.EnvVarSource": { typeName: "K8s::Core::EnvVarSource", description: "Source for an environment variable value" },
120
+ "io.k8s.api.core.v1.ObjectReference": { typeName: "K8s::Core::ObjectReference", description: "Reference to another Kubernetes object" },
121
+ "io.k8s.api.core.v1.LocalObjectReference": { typeName: "K8s::Core::LocalObjectReference", description: "Reference to a local object" },
122
+ "io.k8s.api.core.v1.Toleration": { typeName: "K8s::Core::Toleration", description: "A toleration for pod scheduling" },
123
+ "io.k8s.api.core.v1.Affinity": { typeName: "K8s::Core::Affinity", description: "Scheduling affinity rules" },
124
+ "io.k8s.api.core.v1.TopologySpreadConstraint": { typeName: "K8s::Core::TopologySpreadConstraint", description: "Pod topology spread constraint" },
125
+ "io.k8s.api.core.v1.PersistentVolumeClaimSpec": { typeName: "K8s::Core::PersistentVolumeClaimSpec", description: "PVC spec for StatefulSet volume templates" },
126
+ "io.k8s.api.core.v1.HTTPGetAction": { typeName: "K8s::Core::HTTPGetAction", description: "HTTP GET probe action" },
127
+ "io.k8s.api.core.v1.TCPSocketAction": { typeName: "K8s::Core::TCPSocketAction", description: "TCP socket probe action" },
128
+ "io.k8s.api.core.v1.ExecAction": { typeName: "K8s::Core::ExecAction", description: "Exec probe action" },
129
+ "io.k8s.api.core.v1.HostAlias": { typeName: "K8s::Core::HostAlias", description: "Host alias entry for /etc/hosts" },
130
+ "io.k8s.api.core.v1.EphemeralContainer": { typeName: "K8s::Core::EphemeralContainer", description: "An ephemeral container for debugging" },
131
+ "io.k8s.api.core.v1.KeyToPath": { typeName: "K8s::Core::KeyToPath", description: "Maps a key to a file path" },
132
+ "io.k8s.api.apps.v1.DeploymentStrategy": { typeName: "K8s::Apps::DeploymentStrategy", description: "Deployment rolling update strategy" },
133
+ "io.k8s.api.apps.v1.RollingUpdateDeployment": { typeName: "K8s::Apps::RollingUpdateDeployment", description: "Rolling update parameters" },
134
+ "io.k8s.api.networking.v1.IngressRule": { typeName: "K8s::Networking::IngressRule", description: "Ingress routing rule" },
135
+ "io.k8s.api.networking.v1.IngressTLS": { typeName: "K8s::Networking::IngressTLS", description: "Ingress TLS configuration" },
136
+ "io.k8s.api.networking.v1.HTTPIngressPath": { typeName: "K8s::Networking::HTTPIngressPath", description: "HTTP Ingress path" },
137
+ "io.k8s.api.networking.v1.IngressBackend": { typeName: "K8s::Networking::IngressBackend", description: "Ingress backend reference" },
138
+ "io.k8s.api.networking.v1.IngressServiceBackend": { typeName: "K8s::Networking::IngressServiceBackend", description: "Ingress service backend" },
139
+ "io.k8s.api.networking.v1.ServiceBackendPort": { typeName: "K8s::Networking::ServiceBackendPort", description: "Service port reference" },
140
+ "io.k8s.api.networking.v1.NetworkPolicyIngressRule": { typeName: "K8s::Networking::NetworkPolicyIngressRule", description: "NetworkPolicy ingress rule" },
141
+ "io.k8s.api.networking.v1.NetworkPolicyEgressRule": { typeName: "K8s::Networking::NetworkPolicyEgressRule", description: "NetworkPolicy egress rule" },
142
+ "io.k8s.api.networking.v1.NetworkPolicyPeer": { typeName: "K8s::Networking::NetworkPolicyPeer", description: "NetworkPolicy peer selector" },
143
+ "io.k8s.api.networking.v1.NetworkPolicyPort": { typeName: "K8s::Networking::NetworkPolicyPort", description: "NetworkPolicy port" },
144
+ "io.k8s.api.rbac.v1.PolicyRule": { typeName: "K8s::Rbac::PolicyRule", description: "RBAC policy rule" },
145
+ "io.k8s.api.rbac.v1.RoleRef": { typeName: "K8s::Rbac::RoleRef", description: "RBAC role reference" },
146
+ "io.k8s.api.rbac.v1.Subject": { typeName: "K8s::Rbac::Subject", description: "RBAC subject" },
147
+ "io.k8s.api.autoscaling.v2.MetricSpec": { typeName: "K8s::Autoscaling::MetricSpec", description: "HPA metric specification" },
148
+ "io.k8s.api.autoscaling.v2.HorizontalPodAutoscalerBehavior": { typeName: "K8s::Autoscaling::HorizontalPodAutoscalerBehavior", description: "HPA scaling behavior" },
149
+ "io.k8s.api.policy.v1.PodDisruptionBudgetSpec": { typeName: "K8s::Policy::PodDisruptionBudgetSpec", description: "PDB specification" },
150
+ "io.k8s.apimachinery.pkg.apis.meta.v1.ObjectMeta": { typeName: "K8s::Meta::ObjectMeta", description: "Standard object metadata" },
151
+ "io.k8s.apimachinery.pkg.apis.meta.v1.LabelSelector": { typeName: "K8s::Meta::LabelSelector", description: "Label selector" },
152
+ "io.k8s.apimachinery.pkg.apis.meta.v1.LabelSelectorRequirement": { typeName: "K8s::Meta::LabelSelectorRequirement", description: "Label selector requirement" },
153
+ };
154
+
155
+ // ── Parser ─────────────────────────────────────────────────────────
156
+
157
+ /**
158
+ * Parse the Kubernetes OpenAPI swagger.json into multiple resource results.
159
+ * Returns one result per top-level resource identified by x-kubernetes-group-version-kind.
160
+ */
161
+ export function parseK8sSwagger(data: string | Buffer): K8sParseResult[] {
162
+ const spec: SwaggerSpec = JSON.parse(typeof data === "string" ? data : data.toString("utf-8"));
163
+ const definitions = spec.definitions ?? {};
164
+ const results: K8sParseResult[] = [];
165
+
166
+ // Phase 1: Extract top-level resources (definitions with GVK)
167
+ for (const [defKey, def] of Object.entries(definitions)) {
168
+ const gvks = def["x-kubernetes-group-version-kind"];
169
+ if (!gvks || gvks.length === 0) continue;
170
+
171
+ // Take the first GVK (most definitions have exactly one)
172
+ const gvk = gvks[0];
173
+
174
+ // Skip internal/legacy API versions — only take the preferred version
175
+ if (!isPreferredVersion(defKey, gvk, definitions)) continue;
176
+
177
+ const typeName = gvkToTypeName(gvk);
178
+ const result = extractResource(defKey, def, typeName, gvk, definitions);
179
+ if (result) results.push(result);
180
+ }
181
+
182
+ // Phase 2: Extract well-known property types
183
+ for (const [defKey, config] of Object.entries(PROPERTY_TYPE_DEFS)) {
184
+ const def = definitions[defKey];
185
+ if (!def) continue;
186
+
187
+ const result = extractPropertyType(defKey, def, config.typeName, config.description, definitions);
188
+ if (result) results.push(result);
189
+ }
190
+
191
+ return results;
192
+ }
193
+
194
+ /**
195
+ * Convert GVK to our type name convention: K8s::{Group}::{Kind}
196
+ */
197
+ export function gvkToTypeName(gvk: GroupVersionKind): string {
198
+ const group = normalizeGroup(gvk.group);
199
+ return `K8s::${group}::${gvk.kind}`;
200
+ }
201
+
202
+ /**
203
+ * Convert GVK to apiVersion string for serialization.
204
+ */
205
+ export function gvkToApiVersion(gvk: GroupVersionKind): string {
206
+ if (!gvk.group || gvk.group === "") {
207
+ return gvk.version; // core group: "v1"
208
+ }
209
+ return `${gvk.group}/${gvk.version}`; // e.g. "apps/v1"
210
+ }
211
+
212
+ /**
213
+ * Normalize API group to a PascalCase segment.
214
+ * Empty group (core API) → "Core"
215
+ * "apps" → "Apps"
216
+ * "batch" → "Batch"
217
+ * "rbac.authorization.k8s.io" → "Rbac"
218
+ * "networking.k8s.io" → "Networking"
219
+ */
220
+ function normalizeGroup(group: string): string {
221
+ if (!group || group === "") return "Core";
222
+
223
+ // Take the first segment before any dots
224
+ const firstSegment = group.split(".")[0];
225
+ // Special-case RBAC
226
+ if (firstSegment === "rbac") return "Rbac";
227
+ // PascalCase the first segment
228
+ return firstSegment.charAt(0).toUpperCase() + firstSegment.slice(1);
229
+ }
230
+
231
+ /**
232
+ * Check if this definition key represents the preferred API version.
233
+ * Prefer stable (v1) over beta/alpha, and prefer the highest stable version.
234
+ */
235
+ function isPreferredVersion(defKey: string, gvk: GroupVersionKind, definitions: Record<string, SwaggerDefinition>): boolean {
236
+ // Skip alpha versions
237
+ if (gvk.version.includes("alpha")) return false;
238
+
239
+ // Find all definitions with the same GVK kind+group
240
+ const sameName: Array<{ key: string; version: string }> = [];
241
+ for (const [key, def] of Object.entries(definitions)) {
242
+ const gvks = def["x-kubernetes-group-version-kind"];
243
+ if (!gvks) continue;
244
+ for (const g of gvks) {
245
+ if (g.kind === gvk.kind && g.group === gvk.group) {
246
+ sameName.push({ key, version: g.version });
247
+ }
248
+ }
249
+ }
250
+
251
+ if (sameName.length <= 1) return true;
252
+
253
+ // Prefer stable (v1, v2) over beta
254
+ const stable = sameName.filter((s) => !s.version.includes("beta") && !s.version.includes("alpha"));
255
+ if (stable.length > 0) {
256
+ // Among stable, pick the highest version
257
+ stable.sort((a, b) => b.version.localeCompare(a.version));
258
+ return defKey === stable[0].key;
259
+ }
260
+
261
+ // All beta — pick highest
262
+ sameName.sort((a, b) => b.version.localeCompare(a.version));
263
+ return defKey === sameName[0].key;
264
+ }
265
+
266
+ /**
267
+ * Extract a top-level resource from a swagger definition.
268
+ */
269
+ function extractResource(
270
+ defKey: string,
271
+ def: SwaggerDefinition,
272
+ typeName: string,
273
+ gvk: GroupVersionKind,
274
+ definitions: Record<string, SwaggerDefinition>,
275
+ ): K8sParseResult | null {
276
+ if (!def.properties) return null;
277
+
278
+ const requiredSet = new Set<string>(def.required ?? []);
279
+
280
+ // K8s resources have standard fields (apiVersion, kind, metadata, spec, status)
281
+ // We only expose user-configurable properties — skip apiVersion, kind, status
282
+ const skipProps = new Set(["apiVersion", "kind", "status"]);
283
+ const filteredProps: Record<string, SwaggerProperty> = {};
284
+ for (const [name, prop] of Object.entries(def.properties)) {
285
+ if (!skipProps.has(name)) {
286
+ filteredProps[name] = prop;
287
+ }
288
+ }
289
+
290
+ const properties = parseProperties(filteredProps, requiredSet, definitions);
291
+
292
+ return {
293
+ resource: {
294
+ typeName,
295
+ description: def.description,
296
+ properties,
297
+ attributes: [
298
+ { name: "name", tsType: "string" },
299
+ { name: "namespace", tsType: "string" },
300
+ { name: "uid", tsType: "string" },
301
+ ],
302
+ deprecatedProperties: [],
303
+ },
304
+ propertyTypes: [],
305
+ enums: [],
306
+ gvk,
307
+ };
308
+ }
309
+
310
+ /**
311
+ * Extract a property type definition (not a top-level resource).
312
+ */
313
+ function extractPropertyType(
314
+ defKey: string,
315
+ def: SwaggerDefinition,
316
+ typeName: string,
317
+ description: string,
318
+ definitions: Record<string, SwaggerDefinition>,
319
+ ): K8sParseResult | null {
320
+ if (!def.properties) return null;
321
+
322
+ const requiredSet = new Set<string>(def.required ?? []);
323
+ const properties = parseProperties(def.properties, requiredSet, definitions);
324
+
325
+ const gvkParts = typeName.split("::");
326
+ return {
327
+ resource: {
328
+ typeName,
329
+ description,
330
+ properties,
331
+ attributes: [],
332
+ deprecatedProperties: [],
333
+ },
334
+ propertyTypes: [],
335
+ enums: [],
336
+ gvk: { group: gvkParts[1]?.toLowerCase() ?? "", version: "v1", kind: gvkParts[2] ?? "" },
337
+ isProperty: true,
338
+ };
339
+ }
340
+
341
+ /**
342
+ * Parse properties from a swagger definition into ParsedProperty[].
343
+ */
344
+ function parseProperties(
345
+ properties: Record<string, SwaggerProperty>,
346
+ requiredSet: Set<string>,
347
+ definitions: Record<string, SwaggerDefinition>,
348
+ ): ParsedProperty[] {
349
+ const result: ParsedProperty[] = [];
350
+
351
+ for (const [name, prop] of Object.entries(properties)) {
352
+ const tsType = resolvePropertyType(prop, definitions);
353
+ result.push({
354
+ name,
355
+ tsType,
356
+ required: requiredSet.has(name),
357
+ description: prop.description,
358
+ enum: prop.enum,
359
+ constraints: coreExtractConstraints(prop as JsonSchemaProperty),
360
+ });
361
+ }
362
+
363
+ return result;
364
+ }
365
+
366
+ /**
367
+ * Resolve a swagger property to its TypeScript type string.
368
+ */
369
+ function resolvePropertyType(prop: SwaggerProperty, definitions: Record<string, SwaggerDefinition>): string {
370
+ if (!prop) return "any";
371
+
372
+ // Handle $ref
373
+ if (prop.$ref) {
374
+ return resolveRefType(prop.$ref, definitions);
375
+ }
376
+
377
+ // x-kubernetes-int-or-string
378
+ if (prop["x-kubernetes-int-or-string"]) {
379
+ return "string | number";
380
+ }
381
+
382
+ // Inline enum
383
+ if (prop.enum && prop.enum.length > 0) {
384
+ return prop.enum.map((v) => JSON.stringify(v)).join(" | ");
385
+ }
386
+
387
+ const pt = primaryType(prop.type);
388
+ switch (pt) {
389
+ case "string":
390
+ return "string";
391
+ case "integer":
392
+ case "number":
393
+ return "number";
394
+ case "boolean":
395
+ return "boolean";
396
+ case "array":
397
+ if (prop.items) {
398
+ const itemType = resolvePropertyType(prop.items, definitions);
399
+ if (itemType.includes(" | ")) return `(${itemType})[]`;
400
+ return `${itemType}[]`;
401
+ }
402
+ return "any[]";
403
+ case "object":
404
+ if (prop.additionalProperties && typeof prop.additionalProperties === "object") {
405
+ const valueType = resolvePropertyType(prop.additionalProperties, definitions);
406
+ return `Record<string, ${valueType}>`;
407
+ }
408
+ return "Record<string, any>";
409
+ default:
410
+ return "any";
411
+ }
412
+ }
413
+
414
+ /**
415
+ * Resolve a $ref to a TypeScript type.
416
+ */
417
+ function resolveRefType(ref: string, definitions: Record<string, SwaggerDefinition>): string {
418
+ const prefix = "#/definitions/";
419
+ if (!ref.startsWith(prefix)) return "any";
420
+
421
+ const defKey = ref.slice(prefix.length);
422
+
423
+ // Well-known special types
424
+ if (defKey === "io.k8s.apimachinery.pkg.util.intstr.IntOrString") return "string | number";
425
+ if (defKey === "io.k8s.apimachinery.pkg.api.resource.Quantity") return "string";
426
+ if (defKey === "io.k8s.apimachinery.pkg.apis.meta.v1.Time") return "string";
427
+ if (defKey === "io.k8s.apimachinery.pkg.apis.meta.v1.MicroTime") return "string";
428
+ if (defKey === "io.k8s.apimachinery.pkg.runtime.RawExtension") return "Record<string, any>";
429
+
430
+ // Check if it's a known property type
431
+ const ptConfig = PROPERTY_TYPE_DEFS[defKey];
432
+ if (ptConfig) {
433
+ return k8sShortName(ptConfig.typeName);
434
+ }
435
+
436
+ // Resolve the definition
437
+ const def = definitions[defKey];
438
+ if (!def) return "any";
439
+
440
+ // Enum
441
+ if (def.enum && def.enum.length > 0 && !def.properties) {
442
+ return def.enum.map((v) => JSON.stringify(v)).join(" | ");
443
+ }
444
+
445
+ // Primitive type
446
+ if (def.type && !def.properties) {
447
+ const pt = primaryType(def.type);
448
+ switch (pt) {
449
+ case "string": return "string";
450
+ case "integer":
451
+ case "number": return "number";
452
+ case "boolean": return "boolean";
453
+ default: return "any";
454
+ }
455
+ }
456
+
457
+ // Object with properties — use Record
458
+ if (def.properties) return "Record<string, any>";
459
+
460
+ return "any";
461
+ }
462
+
463
+ /**
464
+ * Extract short name: "K8s::Apps::Deployment" → "Deployment"
465
+ */
466
+ export function k8sShortName(typeName: string): string {
467
+ const parts = typeName.split("::");
468
+ return parts[parts.length - 1];
469
+ }
470
+
471
+ /**
472
+ * Extract service/group name: "K8s::Apps::Deployment" → "Apps"
473
+ */
474
+ export function k8sServiceName(typeName: string): string {
475
+ const parts = typeName.split("::");
476
+ return parts.length >= 2 ? parts[1] : "Core";
477
+ }
@@ -0,0 +1,7 @@
1
+ apiVersion: v1
2
+ kind: ConfigMap
3
+ metadata:
4
+ name: app-config
5
+ data:
6
+ DATABASE_URL: "postgres://localhost:5432/mydb"
7
+ LOG_LEVEL: "info"
@@ -0,0 +1,22 @@
1
+ apiVersion: apps/v1
2
+ kind: Deployment
3
+ metadata:
4
+ name: nginx
5
+ labels:
6
+ app.kubernetes.io/name: nginx
7
+ spec:
8
+ replicas: 2
9
+ selector:
10
+ matchLabels:
11
+ app.kubernetes.io/name: nginx
12
+ template:
13
+ metadata:
14
+ labels:
15
+ app.kubernetes.io/name: nginx
16
+ spec:
17
+ containers:
18
+ - name: nginx
19
+ image: nginx:1.25
20
+ ports:
21
+ - containerPort: 80
22
+ name: http
@@ -0,0 +1,61 @@
1
+ apiVersion: apps/v1
2
+ kind: Deployment
3
+ metadata:
4
+ name: web-app
5
+ labels:
6
+ app.kubernetes.io/name: web-app
7
+ spec:
8
+ replicas: 3
9
+ selector:
10
+ matchLabels:
11
+ app.kubernetes.io/name: web-app
12
+ template:
13
+ metadata:
14
+ labels:
15
+ app.kubernetes.io/name: web-app
16
+ spec:
17
+ containers:
18
+ - name: web
19
+ image: web-app:1.0
20
+ ports:
21
+ - containerPort: 8080
22
+ name: http
23
+ env:
24
+ - name: DB_HOST
25
+ value: postgres
26
+ ---
27
+ apiVersion: v1
28
+ kind: Service
29
+ metadata:
30
+ name: web-app
31
+ spec:
32
+ selector:
33
+ app.kubernetes.io/name: web-app
34
+ ports:
35
+ - port: 80
36
+ targetPort: 8080
37
+ name: http
38
+ ---
39
+ apiVersion: networking.k8s.io/v1
40
+ kind: Ingress
41
+ metadata:
42
+ name: web-app
43
+ spec:
44
+ rules:
45
+ - host: web-app.example.com
46
+ http:
47
+ paths:
48
+ - path: /
49
+ pathType: Prefix
50
+ backend:
51
+ service:
52
+ name: web-app
53
+ port:
54
+ number: 80
55
+ ---
56
+ apiVersion: v1
57
+ kind: ConfigMap
58
+ metadata:
59
+ name: web-app-config
60
+ data:
61
+ APP_ENV: production
@@ -0,0 +1,7 @@
1
+ apiVersion: v1
2
+ kind: Secret
3
+ metadata:
4
+ name: app-secret
5
+ type: Opaque
6
+ stringData:
7
+ API_KEY: "changeme"
@@ -0,0 +1,15 @@
1
+ apiVersion: v1
2
+ kind: Service
3
+ metadata:
4
+ name: nginx
5
+ labels:
6
+ app.kubernetes.io/name: nginx
7
+ spec:
8
+ selector:
9
+ app.kubernetes.io/name: nginx
10
+ ports:
11
+ - port: 80
12
+ targetPort: 80
13
+ protocol: TCP
14
+ name: http
15
+ type: ClusterIP