@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.
- package/dist/integrity.json +32 -0
- package/dist/manifest.json +8 -0
- package/dist/meta.json +1413 -0
- package/dist/rules/hardcoded-namespace.ts +56 -0
- package/dist/rules/k8s-helpers.ts +149 -0
- package/dist/rules/wk8005.ts +59 -0
- package/dist/rules/wk8006.ts +68 -0
- package/dist/rules/wk8041.ts +73 -0
- package/dist/rules/wk8042.ts +48 -0
- package/dist/rules/wk8101.ts +65 -0
- package/dist/rules/wk8102.ts +42 -0
- package/dist/rules/wk8103.ts +45 -0
- package/dist/rules/wk8104.ts +69 -0
- package/dist/rules/wk8105.ts +45 -0
- package/dist/rules/wk8201.ts +55 -0
- package/dist/rules/wk8202.ts +46 -0
- package/dist/rules/wk8203.ts +46 -0
- package/dist/rules/wk8204.ts +54 -0
- package/dist/rules/wk8205.ts +56 -0
- package/dist/rules/wk8207.ts +45 -0
- package/dist/rules/wk8208.ts +45 -0
- package/dist/rules/wk8209.ts +45 -0
- package/dist/rules/wk8301.ts +51 -0
- package/dist/rules/wk8302.ts +46 -0
- package/dist/rules/wk8303.ts +96 -0
- package/dist/skills/chant-k8s.md +433 -0
- package/dist/types/index.d.ts +2934 -0
- package/package.json +30 -0
- package/src/actions/actions.test.ts +83 -0
- package/src/actions/apps.ts +23 -0
- package/src/actions/batch.ts +9 -0
- package/src/actions/core.ts +62 -0
- package/src/actions/index.ts +50 -0
- package/src/actions/networking.ts +15 -0
- package/src/actions/rbac.ts +13 -0
- package/src/codegen/docs-cli.ts +3 -0
- package/src/codegen/docs.ts +1147 -0
- package/src/codegen/generate-cli.ts +41 -0
- package/src/codegen/generate-lexicon.ts +69 -0
- package/src/codegen/generate-typescript.ts +97 -0
- package/src/codegen/generate.ts +144 -0
- package/src/codegen/naming.test.ts +63 -0
- package/src/codegen/naming.ts +187 -0
- package/src/codegen/package.ts +56 -0
- package/src/codegen/patches.ts +108 -0
- package/src/codegen/snapshot.test.ts +95 -0
- package/src/codegen/typecheck.test.ts +24 -0
- package/src/codegen/typecheck.ts +4 -0
- package/src/codegen/versions.ts +43 -0
- package/src/composites/autoscaled-service.ts +236 -0
- package/src/composites/composites.test.ts +1109 -0
- package/src/composites/cron-workload.ts +167 -0
- package/src/composites/index.ts +14 -0
- package/src/composites/namespace-env.ts +163 -0
- package/src/composites/node-agent.ts +224 -0
- package/src/composites/stateful-app.ts +134 -0
- package/src/composites/web-app.ts +180 -0
- package/src/composites/worker-pool.ts +230 -0
- package/src/coverage.test.ts +27 -0
- package/src/coverage.ts +35 -0
- package/src/crd/loader.ts +112 -0
- package/src/crd/parser.test.ts +217 -0
- package/src/crd/parser.ts +279 -0
- package/src/crd/types.ts +54 -0
- package/src/default-labels.test.ts +111 -0
- package/src/default-labels.ts +122 -0
- package/src/generated/index.d.ts +2934 -0
- package/src/generated/index.ts +203 -0
- package/src/generated/lexicon-k8s.json +1413 -0
- package/src/generated/runtime.ts +4 -0
- package/src/import/generator.test.ts +121 -0
- package/src/import/generator.ts +285 -0
- package/src/import/parser.test.ts +156 -0
- package/src/import/parser.ts +204 -0
- package/src/import/roundtrip.test.ts +86 -0
- package/src/index.ts +38 -0
- package/src/lint/post-synth/k8s-helpers.test.ts +219 -0
- package/src/lint/post-synth/k8s-helpers.ts +149 -0
- package/src/lint/post-synth/post-synth.test.ts +969 -0
- package/src/lint/post-synth/wk8005.ts +59 -0
- package/src/lint/post-synth/wk8006.ts +68 -0
- package/src/lint/post-synth/wk8041.ts +73 -0
- package/src/lint/post-synth/wk8042.ts +48 -0
- package/src/lint/post-synth/wk8101.ts +65 -0
- package/src/lint/post-synth/wk8102.ts +42 -0
- package/src/lint/post-synth/wk8103.ts +45 -0
- package/src/lint/post-synth/wk8104.ts +69 -0
- package/src/lint/post-synth/wk8105.ts +45 -0
- package/src/lint/post-synth/wk8201.ts +55 -0
- package/src/lint/post-synth/wk8202.ts +46 -0
- package/src/lint/post-synth/wk8203.ts +46 -0
- package/src/lint/post-synth/wk8204.ts +54 -0
- package/src/lint/post-synth/wk8205.ts +56 -0
- package/src/lint/post-synth/wk8207.ts +45 -0
- package/src/lint/post-synth/wk8208.ts +45 -0
- package/src/lint/post-synth/wk8209.ts +45 -0
- package/src/lint/post-synth/wk8301.ts +51 -0
- package/src/lint/post-synth/wk8302.ts +46 -0
- package/src/lint/post-synth/wk8303.ts +96 -0
- package/src/lint/rules/hardcoded-namespace.ts +56 -0
- package/src/lint/rules/rules.test.ts +69 -0
- package/src/lsp/completions.test.ts +64 -0
- package/src/lsp/completions.ts +20 -0
- package/src/lsp/hover.test.ts +69 -0
- package/src/lsp/hover.ts +68 -0
- package/src/package-cli.ts +28 -0
- package/src/plugin.test.ts +209 -0
- package/src/plugin.ts +915 -0
- package/src/serializer.test.ts +275 -0
- package/src/serializer.ts +278 -0
- package/src/spec/fetch.test.ts +24 -0
- package/src/spec/fetch.ts +68 -0
- package/src/spec/parse.test.ts +102 -0
- package/src/spec/parse.ts +477 -0
- package/src/testdata/manifests/configmap.yaml +7 -0
- package/src/testdata/manifests/deployment.yaml +22 -0
- package/src/testdata/manifests/full-app.yaml +61 -0
- package/src/testdata/manifests/secret.yaml +7 -0
- package/src/testdata/manifests/service.yaml +15 -0
- package/src/validate-cli.ts +21 -0
- package/src/validate.test.ts +29 -0
- package/src/validate.ts +46 -0
- package/src/variables.ts +36 -0
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
#!/usr/bin/env bun
|
|
2
|
+
/**
|
|
3
|
+
* CLI entry point for Kubernetes lexicon generation.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { dirname } from "path";
|
|
7
|
+
import { fileURLToPath } from "url";
|
|
8
|
+
import { generate, writeGeneratedFiles } from "./generate";
|
|
9
|
+
import { K8S_SCHEMA_VERSION } from "../spec/fetch";
|
|
10
|
+
|
|
11
|
+
const pkgDir = dirname(dirname(dirname(fileURLToPath(import.meta.url))));
|
|
12
|
+
|
|
13
|
+
async function main() {
|
|
14
|
+
const verbose = process.argv.includes("--verbose") || process.argv.includes("-v");
|
|
15
|
+
const force = process.argv.includes("--force") || process.argv.includes("-f");
|
|
16
|
+
|
|
17
|
+
// Parse --schema-version <ref>
|
|
18
|
+
const versionIdx = process.argv.indexOf("--schema-version");
|
|
19
|
+
const schemaVersion = versionIdx !== -1 ? process.argv[versionIdx + 1] : undefined;
|
|
20
|
+
|
|
21
|
+
console.error(`Generating Kubernetes lexicon (schema: ${schemaVersion ?? K8S_SCHEMA_VERSION})...`);
|
|
22
|
+
|
|
23
|
+
const result = await generate({ verbose, force, schemaVersion });
|
|
24
|
+
writeGeneratedFiles(result, pkgDir);
|
|
25
|
+
|
|
26
|
+
console.error(
|
|
27
|
+
`Generated ${result.resources} resources, ${result.properties} property types, ${result.enums} enums`,
|
|
28
|
+
);
|
|
29
|
+
|
|
30
|
+
if (result.warnings.length > 0) {
|
|
31
|
+
console.error(`${result.warnings.length} warnings:`);
|
|
32
|
+
for (const w of result.warnings) {
|
|
33
|
+
console.error(` ${w.file}: ${w.error}`);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
main().catch((err) => {
|
|
39
|
+
console.error("Generation failed:", err);
|
|
40
|
+
process.exit(1);
|
|
41
|
+
});
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Lexicon JSON generator — produces lexicon-k8s.json with metadata
|
|
3
|
+
* for all Kubernetes resource and property types.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import type { PropertyConstraints } from "@intentius/chant/codegen/json-schema";
|
|
7
|
+
import type { K8sParseResult } from "../spec/parse";
|
|
8
|
+
import { k8sShortName, gvkToApiVersion } from "../spec/parse";
|
|
9
|
+
import type { NamingStrategy } from "./naming";
|
|
10
|
+
import {
|
|
11
|
+
buildRegistry,
|
|
12
|
+
serializeRegistry,
|
|
13
|
+
type RegistryResource,
|
|
14
|
+
} from "@intentius/chant/codegen/generate-registry";
|
|
15
|
+
|
|
16
|
+
export interface K8sLexiconEntry {
|
|
17
|
+
resourceType: string;
|
|
18
|
+
kind: "resource" | "property";
|
|
19
|
+
lexicon: "k8s";
|
|
20
|
+
apiVersion?: string;
|
|
21
|
+
gvkKind?: string;
|
|
22
|
+
deprecatedProperties?: string[];
|
|
23
|
+
constraints?: Record<string, PropertyConstraints>;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Generate the lexicon-k8s.json content.
|
|
28
|
+
*/
|
|
29
|
+
export function generateLexiconJSON(
|
|
30
|
+
results: K8sParseResult[],
|
|
31
|
+
naming: NamingStrategy,
|
|
32
|
+
): string {
|
|
33
|
+
const registryResources: RegistryResource[] = results.map((r) => ({
|
|
34
|
+
typeName: r.resource.typeName,
|
|
35
|
+
attributes: r.resource.attributes,
|
|
36
|
+
properties: r.resource.properties,
|
|
37
|
+
propertyTypes: r.propertyTypes.map((pt) => ({ name: pt.name, specType: pt.defType })),
|
|
38
|
+
}));
|
|
39
|
+
|
|
40
|
+
const entries = buildRegistry<K8sLexiconEntry>(registryResources, naming, {
|
|
41
|
+
shortName: k8sShortName,
|
|
42
|
+
buildEntry: (resource, _tsName, _attrs, propConstraints) => {
|
|
43
|
+
const r = results.find((res) => res.resource.typeName === resource.typeName);
|
|
44
|
+
const entry: K8sLexiconEntry = {
|
|
45
|
+
resourceType: resource.typeName,
|
|
46
|
+
kind: (r?.isProperty ? "property" : "resource") as "resource" | "property",
|
|
47
|
+
lexicon: "k8s" as const,
|
|
48
|
+
};
|
|
49
|
+
if (r && !r.isProperty) {
|
|
50
|
+
entry.apiVersion = gvkToApiVersion(r.gvk);
|
|
51
|
+
entry.gvkKind = r.gvk.kind;
|
|
52
|
+
}
|
|
53
|
+
if (r?.resource.deprecatedProperties?.length) {
|
|
54
|
+
entry.deprecatedProperties = r.resource.deprecatedProperties;
|
|
55
|
+
}
|
|
56
|
+
if (propConstraints && Object.keys(propConstraints).length > 0) {
|
|
57
|
+
entry.constraints = propConstraints;
|
|
58
|
+
}
|
|
59
|
+
return entry;
|
|
60
|
+
},
|
|
61
|
+
buildPropertyEntry: (resourceType, propertyType) => ({
|
|
62
|
+
resourceType: `${resourceType}.${propertyType}`,
|
|
63
|
+
kind: "property" as const,
|
|
64
|
+
lexicon: "k8s" as const,
|
|
65
|
+
}),
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
return serializeRegistry(entries);
|
|
69
|
+
}
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* TypeScript declaration file generator — produces index.d.ts with all
|
|
3
|
+
* Kubernetes resource and property type classes.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import type { K8sParseResult } from "../spec/parse";
|
|
7
|
+
import { k8sShortName } from "../spec/parse";
|
|
8
|
+
import type { NamingStrategy } from "./naming";
|
|
9
|
+
import { propertyTypeName, extractDefName } from "./naming";
|
|
10
|
+
import {
|
|
11
|
+
writeResourceClass,
|
|
12
|
+
writePropertyClass,
|
|
13
|
+
writeEnumType,
|
|
14
|
+
type DtsProperty,
|
|
15
|
+
type DtsAttribute,
|
|
16
|
+
} from "@intentius/chant/codegen/generate-typescript";
|
|
17
|
+
|
|
18
|
+
interface ResourceEntry {
|
|
19
|
+
tsName: string;
|
|
20
|
+
isProperty: boolean;
|
|
21
|
+
properties: DtsProperty[];
|
|
22
|
+
attributes: DtsAttribute[];
|
|
23
|
+
remap: Map<string, string>;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Generate the complete .d.ts file content.
|
|
28
|
+
*/
|
|
29
|
+
export function generateTypeScriptDeclarations(
|
|
30
|
+
results: K8sParseResult[],
|
|
31
|
+
naming: NamingStrategy,
|
|
32
|
+
): string {
|
|
33
|
+
const lines: string[] = [];
|
|
34
|
+
lines.push("// Code generated by chant k8s generate. DO NOT EDIT.");
|
|
35
|
+
|
|
36
|
+
const resources: ResourceEntry[] = [];
|
|
37
|
+
|
|
38
|
+
for (const r of results) {
|
|
39
|
+
const typeName = r.resource.typeName;
|
|
40
|
+
const tsName = naming.resolve(typeName);
|
|
41
|
+
if (!tsName) continue;
|
|
42
|
+
|
|
43
|
+
const shortName = k8sShortName(typeName);
|
|
44
|
+
const remap = new Map<string, string>();
|
|
45
|
+
for (const pt of r.propertyTypes) {
|
|
46
|
+
const defName = extractDefName(pt.name, shortName);
|
|
47
|
+
remap.set(pt.name, propertyTypeName(tsName, defName));
|
|
48
|
+
}
|
|
49
|
+
for (const e of r.enums) {
|
|
50
|
+
const defName = extractDefName(e.name, shortName);
|
|
51
|
+
remap.set(e.name, propertyTypeName(tsName, defName));
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
const dtsProps: DtsProperty[] = r.resource.properties.map((p) => ({
|
|
55
|
+
name: p.name,
|
|
56
|
+
type: p.tsType,
|
|
57
|
+
required: p.required,
|
|
58
|
+
description: p.description,
|
|
59
|
+
}));
|
|
60
|
+
const dtsAttrs: DtsAttribute[] = r.resource.attributes.map((a) => ({
|
|
61
|
+
name: a.name,
|
|
62
|
+
type: a.tsType,
|
|
63
|
+
}));
|
|
64
|
+
|
|
65
|
+
resources.push({ tsName, isProperty: !!r.isProperty, properties: dtsProps, attributes: dtsAttrs, remap });
|
|
66
|
+
|
|
67
|
+
for (const alias of naming.aliases(typeName)) {
|
|
68
|
+
resources.push({ tsName: alias, isProperty: !!r.isProperty, properties: dtsProps, attributes: dtsAttrs, remap });
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// Sort alphabetically
|
|
73
|
+
resources.sort((a, b) => a.tsName.localeCompare(b.tsName));
|
|
74
|
+
|
|
75
|
+
// Resource classes
|
|
76
|
+
const resourceEntries = resources.filter((r) => !r.isProperty);
|
|
77
|
+
const propertyEntries = resources.filter((r) => r.isProperty);
|
|
78
|
+
|
|
79
|
+
if (resourceEntries.length > 0) {
|
|
80
|
+
lines.push("");
|
|
81
|
+
lines.push("// --- Kubernetes Resource classes ---");
|
|
82
|
+
for (const re of resourceEntries) {
|
|
83
|
+
writeResourceClass(lines, re.tsName, re.properties, re.attributes, re.remap);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
if (propertyEntries.length > 0) {
|
|
88
|
+
lines.push("");
|
|
89
|
+
lines.push("// --- Property type classes ---");
|
|
90
|
+
for (const pe of propertyEntries) {
|
|
91
|
+
writePropertyClass(lines, pe.tsName, pe.properties, pe.remap);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
lines.push("");
|
|
96
|
+
return lines.join("\n");
|
|
97
|
+
}
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Kubernetes generation pipeline — uses core generatePipeline
|
|
3
|
+
* with K8s-specific fetch, parse, naming, and generation callbacks.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { join } from "path";
|
|
7
|
+
import {
|
|
8
|
+
generatePipeline,
|
|
9
|
+
writeGeneratedArtifacts,
|
|
10
|
+
type GenerateOptions,
|
|
11
|
+
type GenerateResult,
|
|
12
|
+
type GeneratePipelineConfig,
|
|
13
|
+
} from "@intentius/chant/codegen/generate";
|
|
14
|
+
import { fetchSchemas } from "../spec/fetch";
|
|
15
|
+
import { parseK8sSwagger, k8sShortName, type K8sParseResult } from "../spec/parse";
|
|
16
|
+
import { NamingStrategy, propertyTypeName, extractDefName } from "./naming";
|
|
17
|
+
import { generateLexiconJSON } from "./generate-lexicon";
|
|
18
|
+
import { generateTypeScriptDeclarations } from "./generate-typescript";
|
|
19
|
+
import {
|
|
20
|
+
generateRuntimeIndex as coreGenerateRuntimeIndex,
|
|
21
|
+
type RuntimeIndexEntry,
|
|
22
|
+
type RuntimeIndexPropertyEntry,
|
|
23
|
+
} from "@intentius/chant/codegen/generate-runtime-index";
|
|
24
|
+
|
|
25
|
+
export type { GenerateResult };
|
|
26
|
+
|
|
27
|
+
export interface K8sGenerateOptions extends GenerateOptions {
|
|
28
|
+
/** Kubernetes version tag to fetch the schema from. */
|
|
29
|
+
schemaVersion?: string;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Run the full Kubernetes generation pipeline.
|
|
34
|
+
*/
|
|
35
|
+
export async function generate(opts: K8sGenerateOptions = {}): Promise<GenerateResult> {
|
|
36
|
+
// Pipeline state captured in closure — no module-level mutation
|
|
37
|
+
let pendingResults: K8sParseResult[] = [];
|
|
38
|
+
|
|
39
|
+
const config: GeneratePipelineConfig<K8sParseResult> = {
|
|
40
|
+
fetchSchemas: async (fetchOpts) => {
|
|
41
|
+
return fetchSchemas(fetchOpts.force, opts.schemaVersion);
|
|
42
|
+
},
|
|
43
|
+
|
|
44
|
+
parseSchema: (_typeName, data) => {
|
|
45
|
+
// The K8s schema is a single document — parseK8sSwagger returns multiple results.
|
|
46
|
+
// The pipeline calls this once per schema entry. We return the first result
|
|
47
|
+
// and use augmentResults to inject the rest.
|
|
48
|
+
const results = parseK8sSwagger(data);
|
|
49
|
+
if (results.length === 0) return null;
|
|
50
|
+
// Return the first result; stash the rest for augmentResults
|
|
51
|
+
pendingResults = results.slice(1);
|
|
52
|
+
return results[0];
|
|
53
|
+
},
|
|
54
|
+
|
|
55
|
+
createNaming: (results) => new NamingStrategy(results),
|
|
56
|
+
|
|
57
|
+
augmentResults: (results, _opts, log) => {
|
|
58
|
+
// Add the remaining results from the single-schema parse
|
|
59
|
+
if (pendingResults.length > 0) {
|
|
60
|
+
results.push(...pendingResults);
|
|
61
|
+
log(`Added ${pendingResults.length} additional K8s resources from OpenAPI spec`);
|
|
62
|
+
pendingResults = [];
|
|
63
|
+
}
|
|
64
|
+
log(`Total: ${results.length} K8s resource/property schemas`);
|
|
65
|
+
return { results };
|
|
66
|
+
},
|
|
67
|
+
|
|
68
|
+
generateRegistry: (results, naming) => {
|
|
69
|
+
return generateLexiconJSON(results, naming as NamingStrategy);
|
|
70
|
+
},
|
|
71
|
+
|
|
72
|
+
generateTypes: (results, naming) => {
|
|
73
|
+
return generateTypeScriptDeclarations(results, naming as NamingStrategy);
|
|
74
|
+
},
|
|
75
|
+
|
|
76
|
+
generateRuntimeIndex: (results, naming) => {
|
|
77
|
+
return generateRuntimeIndex(results, naming as NamingStrategy);
|
|
78
|
+
},
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
return generatePipeline(config, opts);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Write generated artifacts to disk.
|
|
86
|
+
*/
|
|
87
|
+
export function writeGeneratedFiles(result: GenerateResult, baseDir: string): void {
|
|
88
|
+
writeGeneratedArtifacts({
|
|
89
|
+
baseDir,
|
|
90
|
+
files: {
|
|
91
|
+
"lexicon-k8s.json": result.lexiconJSON,
|
|
92
|
+
"index.d.ts": result.typesDTS,
|
|
93
|
+
"index.ts": result.indexTS,
|
|
94
|
+
"runtime.ts": `/**\n * Runtime factory constructors — re-exported from core.\n */\nexport { createResource, createProperty } from "@intentius/chant/runtime";\n`,
|
|
95
|
+
},
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* Generate the runtime index.ts with factory constructor exports.
|
|
101
|
+
*/
|
|
102
|
+
function generateRuntimeIndex(
|
|
103
|
+
results: K8sParseResult[],
|
|
104
|
+
naming: NamingStrategy,
|
|
105
|
+
): string {
|
|
106
|
+
const resourceEntries: RuntimeIndexEntry[] = [];
|
|
107
|
+
const propertyEntries: RuntimeIndexPropertyEntry[] = [];
|
|
108
|
+
|
|
109
|
+
for (const r of results) {
|
|
110
|
+
const typeName = r.resource.typeName;
|
|
111
|
+
const tsName = naming.resolve(typeName);
|
|
112
|
+
if (!tsName) continue;
|
|
113
|
+
|
|
114
|
+
const attrs: Record<string, string> = {};
|
|
115
|
+
for (const attr of r.resource.attributes) {
|
|
116
|
+
attrs[attr.name] = attr.name;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
if (r.isProperty) {
|
|
120
|
+
propertyEntries.push({ tsName, resourceType: typeName });
|
|
121
|
+
for (const alias of naming.aliases(typeName)) {
|
|
122
|
+
propertyEntries.push({ tsName: alias, resourceType: typeName });
|
|
123
|
+
}
|
|
124
|
+
} else {
|
|
125
|
+
resourceEntries.push({ tsName, resourceType: typeName, attrs });
|
|
126
|
+
for (const alias of naming.aliases(typeName)) {
|
|
127
|
+
resourceEntries.push({ tsName: alias, resourceType: typeName, attrs });
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
// Nested property types
|
|
132
|
+
const shortName = k8sShortName(typeName);
|
|
133
|
+
for (const pt of r.propertyTypes) {
|
|
134
|
+
const defName = extractDefName(pt.name, shortName);
|
|
135
|
+
const ptName = propertyTypeName(tsName, defName);
|
|
136
|
+
const ptType = `${typeName}.${pt.defType}`;
|
|
137
|
+
propertyEntries.push({ tsName: ptName, resourceType: ptType });
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
return coreGenerateRuntimeIndex(resourceEntries, propertyEntries, {
|
|
142
|
+
lexiconName: "k8s",
|
|
143
|
+
});
|
|
144
|
+
}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { describe, test, expect } from "bun:test";
|
|
2
|
+
import { k8sShortName, type K8sParseResult } from "../spec/parse";
|
|
3
|
+
import { NamingStrategy } from "./naming";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Chant's naming philosophy: 1:1 with the K8s spec.
|
|
7
|
+
* Every priorityName must match the spec Kind exactly.
|
|
8
|
+
*
|
|
9
|
+
* This test guards against interpreted/abbreviated names creeping in
|
|
10
|
+
* (e.g. "RBACSubject" instead of "Subject", "HPABehavior" instead of
|
|
11
|
+
* "HorizontalPodAutoscalerBehavior").
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
/** Build a minimal K8sParseResult for a given typeName. */
|
|
15
|
+
function stubResult(typeName: string): K8sParseResult {
|
|
16
|
+
return {
|
|
17
|
+
resource: {
|
|
18
|
+
typeName,
|
|
19
|
+
properties: [],
|
|
20
|
+
attributes: [],
|
|
21
|
+
apiVersion: "v1",
|
|
22
|
+
kind: k8sShortName(typeName),
|
|
23
|
+
},
|
|
24
|
+
propertyTypes: [],
|
|
25
|
+
enums: [],
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
describe("naming spec fidelity", () => {
|
|
30
|
+
const typeNames = [
|
|
31
|
+
"K8s::Rbac::Subject",
|
|
32
|
+
"K8s::Autoscaling::HorizontalPodAutoscalerBehavior",
|
|
33
|
+
"K8s::Batch::Job",
|
|
34
|
+
"K8s::Core::Pod",
|
|
35
|
+
"K8s::Apps::Deployment",
|
|
36
|
+
"K8s::Core::Container",
|
|
37
|
+
"K8s::Networking::Ingress",
|
|
38
|
+
"K8s::Rbac::PolicyRule",
|
|
39
|
+
"K8s::Rbac::RoleRef",
|
|
40
|
+
];
|
|
41
|
+
|
|
42
|
+
const results = typeNames.map(stubResult);
|
|
43
|
+
const strategy = new NamingStrategy(results);
|
|
44
|
+
|
|
45
|
+
test("all priority names match spec Kind (short name)", () => {
|
|
46
|
+
for (const typeName of typeNames) {
|
|
47
|
+
const resolved = strategy.resolve(typeName);
|
|
48
|
+
const specKind = k8sShortName(typeName);
|
|
49
|
+
expect(resolved).toBe(specKind);
|
|
50
|
+
}
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
test("no priority name uses abbreviated prefixes", () => {
|
|
54
|
+
const badPrefixes = ["K8s", "RBAC", "HPA"];
|
|
55
|
+
|
|
56
|
+
for (const typeName of typeNames) {
|
|
57
|
+
const resolved = strategy.resolve(typeName)!;
|
|
58
|
+
for (const prefix of badPrefixes) {
|
|
59
|
+
expect(resolved.startsWith(prefix)).toBe(false);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
});
|
|
63
|
+
});
|
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Kubernetes naming strategy — maps K8s::{Group}::{Kind} type names
|
|
3
|
+
* to user-friendly TypeScript class names.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import {
|
|
7
|
+
NamingStrategy as CoreNamingStrategy,
|
|
8
|
+
type NamingConfig,
|
|
9
|
+
type NamingInput,
|
|
10
|
+
} from "@intentius/chant/codegen/naming";
|
|
11
|
+
import { k8sShortName, k8sServiceName, type K8sParseResult } from "../spec/parse";
|
|
12
|
+
|
|
13
|
+
export { propertyTypeName, extractDefName } from "@intentius/chant/codegen/naming";
|
|
14
|
+
|
|
15
|
+
const k8sNamingConfig: NamingConfig = {
|
|
16
|
+
priorityNames: {
|
|
17
|
+
// Core
|
|
18
|
+
"K8s::Core::Pod": "Pod",
|
|
19
|
+
"K8s::Core::Service": "Service",
|
|
20
|
+
"K8s::Core::ConfigMap": "ConfigMap",
|
|
21
|
+
"K8s::Core::Secret": "Secret",
|
|
22
|
+
"K8s::Core::Namespace": "Namespace",
|
|
23
|
+
"K8s::Core::ServiceAccount": "ServiceAccount",
|
|
24
|
+
"K8s::Core::PersistentVolume": "PersistentVolume",
|
|
25
|
+
"K8s::Core::PersistentVolumeClaim": "PersistentVolumeClaim",
|
|
26
|
+
"K8s::Core::Node": "Node",
|
|
27
|
+
"K8s::Core::Endpoints": "Endpoints",
|
|
28
|
+
"K8s::Core::ResourceQuota": "ResourceQuota",
|
|
29
|
+
"K8s::Core::LimitRange": "LimitRange",
|
|
30
|
+
"K8s::Core::ReplicationController": "ReplicationController",
|
|
31
|
+
|
|
32
|
+
// Apps
|
|
33
|
+
"K8s::Apps::Deployment": "Deployment",
|
|
34
|
+
"K8s::Apps::StatefulSet": "StatefulSet",
|
|
35
|
+
"K8s::Apps::DaemonSet": "DaemonSet",
|
|
36
|
+
"K8s::Apps::ReplicaSet": "ReplicaSet",
|
|
37
|
+
"K8s::Apps::ControllerRevision": "ControllerRevision",
|
|
38
|
+
|
|
39
|
+
// Batch
|
|
40
|
+
"K8s::Batch::Job": "Job",
|
|
41
|
+
"K8s::Batch::CronJob": "CronJob",
|
|
42
|
+
|
|
43
|
+
// Networking
|
|
44
|
+
"K8s::Networking::Ingress": "Ingress",
|
|
45
|
+
"K8s::Networking::IngressClass": "IngressClass",
|
|
46
|
+
"K8s::Networking::NetworkPolicy": "NetworkPolicy",
|
|
47
|
+
|
|
48
|
+
// RBAC
|
|
49
|
+
"K8s::Rbac::Role": "Role",
|
|
50
|
+
"K8s::Rbac::ClusterRole": "ClusterRole",
|
|
51
|
+
"K8s::Rbac::RoleBinding": "RoleBinding",
|
|
52
|
+
"K8s::Rbac::ClusterRoleBinding": "ClusterRoleBinding",
|
|
53
|
+
|
|
54
|
+
// Autoscaling
|
|
55
|
+
"K8s::Autoscaling::HorizontalPodAutoscaler": "HorizontalPodAutoscaler",
|
|
56
|
+
|
|
57
|
+
// Policy
|
|
58
|
+
"K8s::Policy::PodDisruptionBudget": "PodDisruptionBudget",
|
|
59
|
+
|
|
60
|
+
// Storage
|
|
61
|
+
"K8s::Storage::StorageClass": "StorageClass",
|
|
62
|
+
"K8s::Storage::CSIDriver": "CSIDriver",
|
|
63
|
+
|
|
64
|
+
// Scheduling
|
|
65
|
+
"K8s::Scheduling::PriorityClass": "PriorityClass",
|
|
66
|
+
|
|
67
|
+
// Coordination
|
|
68
|
+
"K8s::Coordination::Lease": "Lease",
|
|
69
|
+
|
|
70
|
+
// Discovery
|
|
71
|
+
"K8s::Discovery::EndpointSlice": "EndpointSlice",
|
|
72
|
+
|
|
73
|
+
// Certificates
|
|
74
|
+
"K8s::Certificates::CertificateSigningRequest": "CertificateSigningRequest",
|
|
75
|
+
|
|
76
|
+
// Admission
|
|
77
|
+
"K8s::Admissionregistration::ValidatingWebhookConfiguration": "ValidatingWebhookConfiguration",
|
|
78
|
+
"K8s::Admissionregistration::MutatingWebhookConfiguration": "MutatingWebhookConfiguration",
|
|
79
|
+
|
|
80
|
+
// Events (core vs events.k8s.io — the core version wins)
|
|
81
|
+
"K8s::Core::Event": "Event",
|
|
82
|
+
|
|
83
|
+
// Property types
|
|
84
|
+
"K8s::Core::Container": "Container",
|
|
85
|
+
"K8s::Core::ContainerPort": "ContainerPort",
|
|
86
|
+
"K8s::Core::EnvVar": "EnvVar",
|
|
87
|
+
"K8s::Core::EnvFromSource": "EnvFromSource",
|
|
88
|
+
"K8s::Core::Volume": "Volume",
|
|
89
|
+
"K8s::Core::VolumeMount": "VolumeMount",
|
|
90
|
+
"K8s::Core::PodSpec": "PodSpec",
|
|
91
|
+
"K8s::Core::PodTemplateSpec": "PodTemplateSpec",
|
|
92
|
+
"K8s::Core::ServicePort": "ServicePort",
|
|
93
|
+
"K8s::Core::Probe": "Probe",
|
|
94
|
+
"K8s::Core::ResourceRequirements": "ResourceRequirements",
|
|
95
|
+
"K8s::Core::SecurityContext": "SecurityContext",
|
|
96
|
+
"K8s::Core::PodSecurityContext": "PodSecurityContext",
|
|
97
|
+
"K8s::Core::Capabilities": "Capabilities",
|
|
98
|
+
"K8s::Core::ConfigMapKeySelector": "ConfigMapKeySelector",
|
|
99
|
+
"K8s::Core::SecretKeySelector": "SecretKeySelector",
|
|
100
|
+
"K8s::Core::EnvVarSource": "EnvVarSource",
|
|
101
|
+
"K8s::Core::ObjectReference": "ObjectReference",
|
|
102
|
+
"K8s::Core::LocalObjectReference": "LocalObjectReference",
|
|
103
|
+
"K8s::Core::Toleration": "Toleration",
|
|
104
|
+
"K8s::Core::Affinity": "Affinity",
|
|
105
|
+
"K8s::Core::TopologySpreadConstraint": "TopologySpreadConstraint",
|
|
106
|
+
"K8s::Core::PersistentVolumeClaimSpec": "PersistentVolumeClaimSpec",
|
|
107
|
+
"K8s::Core::HTTPGetAction": "HTTPGetAction",
|
|
108
|
+
"K8s::Core::TCPSocketAction": "TCPSocketAction",
|
|
109
|
+
"K8s::Core::ExecAction": "ExecAction",
|
|
110
|
+
"K8s::Core::HostAlias": "HostAlias",
|
|
111
|
+
"K8s::Core::EphemeralContainer": "EphemeralContainer",
|
|
112
|
+
"K8s::Core::KeyToPath": "KeyToPath",
|
|
113
|
+
"K8s::Apps::DeploymentStrategy": "DeploymentStrategy",
|
|
114
|
+
"K8s::Apps::RollingUpdateDeployment": "RollingUpdateDeployment",
|
|
115
|
+
"K8s::Networking::IngressRule": "IngressRule",
|
|
116
|
+
"K8s::Networking::IngressTLS": "IngressTLS",
|
|
117
|
+
"K8s::Networking::HTTPIngressPath": "HTTPIngressPath",
|
|
118
|
+
"K8s::Networking::IngressBackend": "IngressBackend",
|
|
119
|
+
"K8s::Networking::IngressServiceBackend": "IngressServiceBackend",
|
|
120
|
+
"K8s::Networking::ServiceBackendPort": "ServiceBackendPort",
|
|
121
|
+
"K8s::Networking::NetworkPolicyIngressRule": "NetworkPolicyIngressRule",
|
|
122
|
+
"K8s::Networking::NetworkPolicyEgressRule": "NetworkPolicyEgressRule",
|
|
123
|
+
"K8s::Networking::NetworkPolicyPeer": "NetworkPolicyPeer",
|
|
124
|
+
"K8s::Networking::NetworkPolicyPort": "NetworkPolicyPort",
|
|
125
|
+
"K8s::Rbac::PolicyRule": "PolicyRule",
|
|
126
|
+
"K8s::Rbac::RoleRef": "RoleRef",
|
|
127
|
+
"K8s::Rbac::Subject": "Subject",
|
|
128
|
+
"K8s::Autoscaling::MetricSpec": "MetricSpec",
|
|
129
|
+
"K8s::Autoscaling::HorizontalPodAutoscalerBehavior": "HorizontalPodAutoscalerBehavior",
|
|
130
|
+
"K8s::Policy::PodDisruptionBudgetSpec": "PodDisruptionBudgetSpec",
|
|
131
|
+
"K8s::Meta::ObjectMeta": "ObjectMeta",
|
|
132
|
+
"K8s::Meta::LabelSelector": "LabelSelector",
|
|
133
|
+
"K8s::Meta::LabelSelectorRequirement": "LabelSelectorRequirement",
|
|
134
|
+
},
|
|
135
|
+
|
|
136
|
+
priorityAliases: {
|
|
137
|
+
"K8s::Batch::Job": ["BatchJob"],
|
|
138
|
+
"K8s::Apps::Deployment": ["Deploy"],
|
|
139
|
+
"K8s::Core::ConfigMap": ["CM"],
|
|
140
|
+
"K8s::Core::ServiceAccount": ["SA"],
|
|
141
|
+
"K8s::Autoscaling::HorizontalPodAutoscaler": ["HPA"],
|
|
142
|
+
"K8s::Core::PersistentVolumeClaim": ["PVC"],
|
|
143
|
+
"K8s::Core::PersistentVolume": ["PV"],
|
|
144
|
+
"K8s::Policy::PodDisruptionBudget": ["PDB"],
|
|
145
|
+
"K8s::Core::Namespace": ["NS"],
|
|
146
|
+
"K8s::Apps::StatefulSet": ["STS"],
|
|
147
|
+
"K8s::Apps::DaemonSet": ["DS"],
|
|
148
|
+
"K8s::Core::Secret": ["Sec"],
|
|
149
|
+
"K8s::Networking::Ingress": ["Ing"],
|
|
150
|
+
"K8s::Networking::NetworkPolicy": ["NetPol"],
|
|
151
|
+
},
|
|
152
|
+
|
|
153
|
+
priorityPropertyAliases: {},
|
|
154
|
+
|
|
155
|
+
serviceAbbreviations: {
|
|
156
|
+
Core: "Core",
|
|
157
|
+
Apps: "Apps",
|
|
158
|
+
Batch: "Batch",
|
|
159
|
+
Networking: "Net",
|
|
160
|
+
Rbac: "Rbac",
|
|
161
|
+
Autoscaling: "AS",
|
|
162
|
+
Policy: "Pol",
|
|
163
|
+
Storage: "Stor",
|
|
164
|
+
Scheduling: "Sched",
|
|
165
|
+
Coordination: "Coord",
|
|
166
|
+
Discovery: "Disc",
|
|
167
|
+
Certificates: "Cert",
|
|
168
|
+
Admissionregistration: "Adm",
|
|
169
|
+
},
|
|
170
|
+
|
|
171
|
+
shortName: k8sShortName,
|
|
172
|
+
serviceName: k8sServiceName,
|
|
173
|
+
};
|
|
174
|
+
|
|
175
|
+
/**
|
|
176
|
+
* Kubernetes-specific naming strategy.
|
|
177
|
+
* Extends core NamingStrategy with K8s naming config.
|
|
178
|
+
*/
|
|
179
|
+
export class NamingStrategy extends CoreNamingStrategy {
|
|
180
|
+
constructor(results: K8sParseResult[]) {
|
|
181
|
+
const inputs: NamingInput[] = results.map((r) => ({
|
|
182
|
+
typeName: r.resource.typeName,
|
|
183
|
+
propertyTypes: r.propertyTypes,
|
|
184
|
+
}));
|
|
185
|
+
super(inputs, k8sNamingConfig);
|
|
186
|
+
}
|
|
187
|
+
}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Kubernetes lexicon packaging — delegates to core packagePipeline
|
|
3
|
+
* with K8s-specific manifest building and skill collection.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { createRequire } from "module";
|
|
7
|
+
import { readFileSync } from "fs";
|
|
8
|
+
const require = createRequire(import.meta.url);
|
|
9
|
+
import { join, dirname } from "path";
|
|
10
|
+
import { fileURLToPath } from "url";
|
|
11
|
+
import {
|
|
12
|
+
packagePipeline,
|
|
13
|
+
collectSkills,
|
|
14
|
+
type PackageOptions,
|
|
15
|
+
type PackageResult,
|
|
16
|
+
} from "@intentius/chant/codegen/package";
|
|
17
|
+
import { generate } from "./generate";
|
|
18
|
+
|
|
19
|
+
export type { PackageOptions, PackageResult };
|
|
20
|
+
|
|
21
|
+
const pkgDir = dirname(dirname(fileURLToPath(import.meta.url)));
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Package the Kubernetes lexicon into a distributable BundleSpec.
|
|
25
|
+
*/
|
|
26
|
+
export async function packageLexicon(opts: PackageOptions = {}): Promise<PackageResult> {
|
|
27
|
+
const pkgJson = JSON.parse(readFileSync(join(pkgDir, "..", "package.json"), "utf-8"));
|
|
28
|
+
|
|
29
|
+
return packagePipeline(
|
|
30
|
+
{
|
|
31
|
+
generate: (genOpts) => generate({ verbose: genOpts.verbose, force: genOpts.force }),
|
|
32
|
+
|
|
33
|
+
buildManifest: (_genResult) => {
|
|
34
|
+
return {
|
|
35
|
+
name: "k8s",
|
|
36
|
+
version: pkgJson.version ?? "0.0.0",
|
|
37
|
+
chantVersion: ">=0.1.0",
|
|
38
|
+
namespace: "K8s",
|
|
39
|
+
intrinsics: [],
|
|
40
|
+
pseudoParameters: {},
|
|
41
|
+
};
|
|
42
|
+
},
|
|
43
|
+
|
|
44
|
+
srcDir: pkgDir,
|
|
45
|
+
|
|
46
|
+
collectSkills: () => {
|
|
47
|
+
const { k8sPlugin } = require("../plugin");
|
|
48
|
+
const skillDefs = k8sPlugin.skills?.() ?? [];
|
|
49
|
+
return collectSkills(skillDefs);
|
|
50
|
+
},
|
|
51
|
+
|
|
52
|
+
version: pkgJson.version ?? "0.0.0",
|
|
53
|
+
},
|
|
54
|
+
opts,
|
|
55
|
+
);
|
|
56
|
+
}
|