@intentius/chant-lexicon-k3d 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +26 -0
- package/dist/codegen/docs.d.ts +7 -0
- package/dist/codegen/docs.d.ts.map +1 -0
- package/dist/codegen/generate-cli.d.ts +6 -0
- package/dist/codegen/generate-cli.d.ts.map +1 -0
- package/dist/codegen/generate-lexicon.d.ts +19 -0
- package/dist/codegen/generate-lexicon.d.ts.map +1 -0
- package/dist/codegen/generate-typescript.d.ts +11 -0
- package/dist/codegen/generate-typescript.d.ts.map +1 -0
- package/dist/codegen/generate.d.ts +15 -0
- package/dist/codegen/generate.d.ts.map +1 -0
- package/dist/codegen/naming.d.ts +13 -0
- package/dist/codegen/naming.d.ts.map +1 -0
- package/dist/codegen/package.d.ts +17 -0
- package/dist/codegen/package.d.ts.map +1 -0
- package/dist/codegen/parse.d.ts +57 -0
- package/dist/codegen/parse.d.ts.map +1 -0
- package/dist/describe-resources.d.ts +34 -0
- package/dist/describe-resources.d.ts.map +1 -0
- package/dist/generated/index.d.ts +22 -0
- package/dist/generated/index.d.ts.map +1 -0
- package/dist/generated/runtime.d.ts +5 -0
- package/dist/generated/runtime.d.ts.map +1 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/integrity.json +9 -0
- package/dist/lint/audit-catalog.d.ts +9 -0
- package/dist/lint/audit-catalog.d.ts.map +1 -0
- package/dist/lint/post-synth/index.d.ts +3 -0
- package/dist/lint/post-synth/index.d.ts.map +1 -0
- package/dist/lint/post-synth/k3d101.d.ts +19 -0
- package/dist/lint/post-synth/k3d101.d.ts.map +1 -0
- package/dist/lint/post-synth/k3d102.d.ts +12 -0
- package/dist/lint/post-synth/k3d102.d.ts.map +1 -0
- package/dist/lint/rules/index.d.ts +5 -0
- package/dist/lint/rules/index.d.ts.map +1 -0
- package/dist/lint/rules/registry-proxy-password.d.ts +17 -0
- package/dist/lint/rules/registry-proxy-password.d.ts.map +1 -0
- package/dist/lint/rules/sample.d.ts +8 -0
- package/dist/lint/rules/sample.d.ts.map +1 -0
- package/dist/lsp/completions.d.ts +7 -0
- package/dist/lsp/completions.d.ts.map +1 -0
- package/dist/lsp/hover.d.ts +6 -0
- package/dist/lsp/hover.d.ts.map +1 -0
- package/dist/manifest.json +6 -0
- package/dist/meta.json +184 -0
- package/dist/op/activities/index.d.ts +17 -0
- package/dist/op/activities/index.d.ts.map +1 -0
- package/dist/op/activities/k3d.d.ts +93 -0
- package/dist/op/activities/k3d.d.ts.map +1 -0
- package/dist/package-cli.d.ts +3 -0
- package/dist/package-cli.d.ts.map +1 -0
- package/dist/plugin.d.ts +8 -0
- package/dist/plugin.d.ts.map +1 -0
- package/dist/serializer.d.ts +35 -0
- package/dist/serializer.d.ts.map +1 -0
- package/dist/spec/fetch.d.ts +39 -0
- package/dist/spec/fetch.d.ts.map +1 -0
- package/dist/testdata/load-fixtures.d.ts +16 -0
- package/dist/testdata/load-fixtures.d.ts.map +1 -0
- package/dist/types/index.d.ts +189 -0
- package/dist/validate-cli.d.ts +3 -0
- package/dist/validate-cli.d.ts.map +1 -0
- package/dist/validate.d.ts +15 -0
- package/dist/validate.d.ts.map +1 -0
- package/package.json +58 -0
- package/src/codegen/docs.ts +25 -0
- package/src/codegen/generate-cli.ts +36 -0
- package/src/codegen/generate-lexicon.ts +58 -0
- package/src/codegen/generate-typescript.ts +102 -0
- package/src/codegen/generate.test.ts +154 -0
- package/src/codegen/generate.ts +139 -0
- package/src/codegen/naming.ts +56 -0
- package/src/codegen/package.ts +27 -0
- package/src/codegen/parse.ts +359 -0
- package/src/describe-resources.test.ts +142 -0
- package/src/describe-resources.ts +144 -0
- package/src/generated/.gitkeep +0 -0
- package/src/generated/index.d.ts +189 -0
- package/src/generated/index.ts +27 -0
- package/src/generated/lexicon-k3d.json +184 -0
- package/src/generated/runtime.ts +4 -0
- package/src/index.ts +8 -0
- package/src/lint/audit-catalog.ts +26 -0
- package/src/lint/post-synth/index.ts +9 -0
- package/src/lint/post-synth/k3d101.ts +87 -0
- package/src/lint/post-synth/k3d102.ts +40 -0
- package/src/lint/post-synth/post-synth.test.ts +76 -0
- package/src/lint/rules/index.ts +7 -0
- package/src/lint/rules/registry-proxy-password.test.ts +53 -0
- package/src/lint/rules/registry-proxy-password.ts +93 -0
- package/src/lsp/completions.test.ts +10 -0
- package/src/lsp/completions.ts +21 -0
- package/src/lsp/hover.test.ts +10 -0
- package/src/lsp/hover.ts +39 -0
- package/src/op/activities/index.ts +24 -0
- package/src/op/activities/k3d.test.ts +168 -0
- package/src/op/activities/k3d.ts +183 -0
- package/src/package-cli.ts +41 -0
- package/src/plugin.test.ts +17 -0
- package/src/plugin.ts +114 -0
- package/src/serializer.acceptance.test.ts +94 -0
- package/src/serializer.test.ts +189 -0
- package/src/serializer.ts +202 -0
- package/src/spec/fetch.ts +66 -0
- package/src/testdata/k3d-schema-fixture.json +431 -0
- package/src/testdata/load-fixtures.ts +27 -0
- package/src/validate-cli.ts +5 -0
- package/src/validate.ts +28 -0
package/package.json
ADDED
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@intentius/chant-lexicon-k3d",
|
|
3
|
+
"version": "0.0.1",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"files": [
|
|
6
|
+
"src/",
|
|
7
|
+
"dist/"
|
|
8
|
+
],
|
|
9
|
+
"exports": {
|
|
10
|
+
".": {
|
|
11
|
+
"development": "./src/index.ts",
|
|
12
|
+
"types": "./dist/index.d.ts",
|
|
13
|
+
"default": "./src/index.ts"
|
|
14
|
+
},
|
|
15
|
+
"./*": {
|
|
16
|
+
"development": "./src/*.ts",
|
|
17
|
+
"types": "./dist/*.d.ts",
|
|
18
|
+
"default": "./src/*.ts"
|
|
19
|
+
},
|
|
20
|
+
"./op/activities": {
|
|
21
|
+
"development": "./src/op/activities/index.ts",
|
|
22
|
+
"types": "./dist/op/activities/index.d.ts",
|
|
23
|
+
"default": "./src/op/activities/index.ts"
|
|
24
|
+
},
|
|
25
|
+
"./lint/post-synth": {
|
|
26
|
+
"development": "./src/lint/post-synth/index.ts",
|
|
27
|
+
"types": "./dist/lint/post-synth/index.d.ts",
|
|
28
|
+
"default": "./src/lint/post-synth/index.ts"
|
|
29
|
+
},
|
|
30
|
+
"./manifest": "./dist/manifest.json",
|
|
31
|
+
"./meta": "./dist/meta.json",
|
|
32
|
+
"./types": "./dist/types/index.d.ts"
|
|
33
|
+
},
|
|
34
|
+
"scripts": {
|
|
35
|
+
"generate": "npx tsx src/codegen/generate-cli.ts",
|
|
36
|
+
"validate": "npx tsx src/validate-cli.ts",
|
|
37
|
+
"docs": "npx tsx src/codegen/docs-cli.ts",
|
|
38
|
+
"build": "tsc -p tsconfig.build.json && tsc-alias -p tsconfig.build.json && find dist -type f \\( -name \"*.js\" -o -name \"*.js.map\" \\) -delete",
|
|
39
|
+
"prepack": "npm run generate && npm run bundle && npm run validate && npm run build",
|
|
40
|
+
"bundle": "tsx src/package-cli.ts"
|
|
41
|
+
},
|
|
42
|
+
"devDependencies": {
|
|
43
|
+
"@intentius/chant": "*",
|
|
44
|
+
"tsc-alias": "^1.8.17",
|
|
45
|
+
"typescript": "^5.9.3",
|
|
46
|
+
"@types/js-yaml": "^4.0.9"
|
|
47
|
+
},
|
|
48
|
+
"dependencies": {
|
|
49
|
+
"js-yaml": "^4.1.0"
|
|
50
|
+
},
|
|
51
|
+
"publishConfig": {
|
|
52
|
+
"access": "public"
|
|
53
|
+
},
|
|
54
|
+
"peerDependencies": {
|
|
55
|
+
"@intentius/chant": "^0.41.12",
|
|
56
|
+
"typescript": "^5.9.3"
|
|
57
|
+
}
|
|
58
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { docsPipeline, writeDocsSite } from "@intentius/chant/codegen/docs";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Generate documentation site for the k3d lexicon.
|
|
5
|
+
*/
|
|
6
|
+
export async function generateDocs(options?: { verbose?: boolean }): Promise<void> {
|
|
7
|
+
const config = {
|
|
8
|
+
name: "k3d",
|
|
9
|
+
displayName: "K3d",
|
|
10
|
+
description: "k3d lexicon documentation",
|
|
11
|
+
distDir: "./dist",
|
|
12
|
+
outDir: "./docs",
|
|
13
|
+
// TODO: Implement service grouping for your provider
|
|
14
|
+
serviceFromType: (type: string) => type.split("::")[1] ?? type,
|
|
15
|
+
// TODO: Implement resource documentation URLs
|
|
16
|
+
resourceTypeUrl: (type: string) => `#${type}`,
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
const result = docsPipeline(config);
|
|
20
|
+
writeDocsSite(config, result);
|
|
21
|
+
|
|
22
|
+
if (options?.verbose) {
|
|
23
|
+
console.error("Documentation generated");
|
|
24
|
+
}
|
|
25
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
#!/usr/bin/env tsx
|
|
2
|
+
/**
|
|
3
|
+
* CLI entry point for k3d lexicon generation.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { dirname } from "path";
|
|
7
|
+
import { fileURLToPath } from "url";
|
|
8
|
+
import { generate, writeGeneratedFiles } from "./generate";
|
|
9
|
+
|
|
10
|
+
const pkgDir = dirname(dirname(dirname(fileURLToPath(import.meta.url))));
|
|
11
|
+
|
|
12
|
+
async function main() {
|
|
13
|
+
const verbose = process.argv.includes("--verbose") || process.argv.includes("-v");
|
|
14
|
+
const force = process.argv.includes("--force") || process.argv.includes("-f");
|
|
15
|
+
|
|
16
|
+
console.error("Generating k3d lexicon...");
|
|
17
|
+
|
|
18
|
+
const result = await generate({ verbose, force });
|
|
19
|
+
writeGeneratedFiles(result, pkgDir);
|
|
20
|
+
|
|
21
|
+
console.error(
|
|
22
|
+
`Generated ${result.resources} entities, ${result.properties} property types, ${result.enums} enums`,
|
|
23
|
+
);
|
|
24
|
+
|
|
25
|
+
if (result.warnings.length > 0) {
|
|
26
|
+
console.error(`${result.warnings.length} warnings:`);
|
|
27
|
+
for (const w of result.warnings) {
|
|
28
|
+
console.error(` ${w.file}: ${w.error}`);
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
main().catch((err) => {
|
|
34
|
+
console.error("Generation failed:", err);
|
|
35
|
+
process.exit(1);
|
|
36
|
+
});
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Lexicon JSON generator — produces lexicon-k3d.json with metadata
|
|
3
|
+
* for all k3d entities.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import type { PropertyConstraints } from "@intentius/chant/codegen/json-schema";
|
|
7
|
+
import type { K3dParseResult } from "./parse";
|
|
8
|
+
import { k3dShortName } from "./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 LexiconEntry {
|
|
17
|
+
resourceType: string;
|
|
18
|
+
kind: "resource" | "property";
|
|
19
|
+
lexicon: "k3d";
|
|
20
|
+
deprecatedProperties?: string[];
|
|
21
|
+
constraints?: Record<string, PropertyConstraints>;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Generate the lexicon-k3d.json content.
|
|
26
|
+
*/
|
|
27
|
+
export function generateLexiconJSON(
|
|
28
|
+
results: K3dParseResult[],
|
|
29
|
+
naming: NamingStrategy,
|
|
30
|
+
): string {
|
|
31
|
+
const registryResources: RegistryResource[] = results.map((r) => ({
|
|
32
|
+
typeName: r.resource.typeName,
|
|
33
|
+
attributes: r.resource.attributes,
|
|
34
|
+
properties: r.resource.properties,
|
|
35
|
+
propertyTypes: r.propertyTypes.map((pt) => ({ name: pt.name, specType: pt.defType })),
|
|
36
|
+
}));
|
|
37
|
+
|
|
38
|
+
const entries = buildRegistry<LexiconEntry>(registryResources, naming, {
|
|
39
|
+
shortName: k3dShortName,
|
|
40
|
+
buildEntry: (resource, _tsName, _attrs, propConstraints) => {
|
|
41
|
+
const r = results.find((res) => res.resource.typeName === resource.typeName);
|
|
42
|
+
return {
|
|
43
|
+
resourceType: resource.typeName,
|
|
44
|
+
kind: (r?.isProperty ? "property" : "resource") as "resource" | "property",
|
|
45
|
+
lexicon: "k3d" as const,
|
|
46
|
+
...(r?.resource.deprecatedProperties?.length && { deprecatedProperties: r.resource.deprecatedProperties }),
|
|
47
|
+
...(propConstraints && Object.keys(propConstraints).length > 0 && { constraints: propConstraints }),
|
|
48
|
+
};
|
|
49
|
+
},
|
|
50
|
+
buildPropertyEntry: (resourceType, propertyType) => ({
|
|
51
|
+
resourceType: `${resourceType}.${propertyType}`,
|
|
52
|
+
kind: "property" as const,
|
|
53
|
+
lexicon: "k3d" as const,
|
|
54
|
+
}),
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
return serializeRegistry(entries);
|
|
58
|
+
}
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* TypeScript declaration file generator — produces index.d.ts with all
|
|
3
|
+
* k3d entity classes.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import type { K3dParseResult } from "./parse";
|
|
7
|
+
import { k3dShortName } from "./parse";
|
|
8
|
+
import type { NamingStrategy } from "./naming";
|
|
9
|
+
import { propertyTypeName, extractDefName } from "./naming";
|
|
10
|
+
import {
|
|
11
|
+
writeResourceClass,
|
|
12
|
+
writeEnumType,
|
|
13
|
+
type DtsProperty,
|
|
14
|
+
type DtsAttribute,
|
|
15
|
+
} from "@intentius/chant/codegen/generate-typescript";
|
|
16
|
+
|
|
17
|
+
interface ResourceEntry {
|
|
18
|
+
tsName: string;
|
|
19
|
+
properties: DtsProperty[];
|
|
20
|
+
attributes: DtsAttribute[];
|
|
21
|
+
remap: Map<string, string>;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
interface EnumEntry {
|
|
25
|
+
tsName: string;
|
|
26
|
+
values: string[];
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Generate the complete .d.ts file content.
|
|
31
|
+
*/
|
|
32
|
+
export function generateTypeScriptDeclarations(
|
|
33
|
+
results: K3dParseResult[],
|
|
34
|
+
naming: NamingStrategy,
|
|
35
|
+
): string {
|
|
36
|
+
const lines: string[] = [];
|
|
37
|
+
lines.push("// Code generated by chant k3d generate. DO NOT EDIT.");
|
|
38
|
+
|
|
39
|
+
const resources: ResourceEntry[] = [];
|
|
40
|
+
const enums: EnumEntry[] = [];
|
|
41
|
+
|
|
42
|
+
for (const r of results) {
|
|
43
|
+
const typeName = r.resource.typeName;
|
|
44
|
+
const tsName = naming.resolve(typeName);
|
|
45
|
+
if (!tsName) continue;
|
|
46
|
+
|
|
47
|
+
const shortName = k3dShortName(typeName);
|
|
48
|
+
const remap = new Map<string, string>();
|
|
49
|
+
for (const pt of r.propertyTypes) {
|
|
50
|
+
const defName = extractDefName(pt.name, shortName);
|
|
51
|
+
remap.set(pt.name, propertyTypeName(tsName, defName));
|
|
52
|
+
}
|
|
53
|
+
for (const e of r.enums) {
|
|
54
|
+
const defName = extractDefName(e.name, shortName);
|
|
55
|
+
remap.set(e.name, propertyTypeName(tsName, defName));
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
const dtsProps: DtsProperty[] = r.resource.properties.map((p) => ({
|
|
59
|
+
name: p.name,
|
|
60
|
+
type: p.tsType,
|
|
61
|
+
required: p.required,
|
|
62
|
+
description: p.description,
|
|
63
|
+
}));
|
|
64
|
+
const dtsAttrs: DtsAttribute[] = r.resource.attributes.map((a) => ({
|
|
65
|
+
name: a.name,
|
|
66
|
+
type: a.tsType,
|
|
67
|
+
}));
|
|
68
|
+
|
|
69
|
+
resources.push({ tsName, properties: dtsProps, attributes: dtsAttrs, remap });
|
|
70
|
+
|
|
71
|
+
for (const alias of naming.aliases(typeName)) {
|
|
72
|
+
resources.push({ tsName: alias, properties: dtsProps, attributes: dtsAttrs, remap });
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
for (const e of r.enums) {
|
|
76
|
+
const defName = extractDefName(e.name, shortName);
|
|
77
|
+
const eName = propertyTypeName(tsName, defName);
|
|
78
|
+
enums.push({ tsName: eName, values: e.values });
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
resources.sort((a, b) => a.tsName.localeCompare(b.tsName));
|
|
83
|
+
enums.sort((a, b) => a.tsName.localeCompare(b.tsName));
|
|
84
|
+
|
|
85
|
+
lines.push("");
|
|
86
|
+
lines.push("// --- k3d Entity classes ---");
|
|
87
|
+
for (const re of resources) {
|
|
88
|
+
writeResourceClass(lines, re.tsName, re.properties, re.attributes, re.remap);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
if (enums.length > 0) {
|
|
92
|
+
lines.push("");
|
|
93
|
+
lines.push("// --- Enum types ---");
|
|
94
|
+
for (const ee of enums) {
|
|
95
|
+
writeEnumType(lines, ee.tsName, ee.values);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
lines.push("");
|
|
100
|
+
|
|
101
|
+
return lines.join("\n");
|
|
102
|
+
}
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
import { describe, test, expect } from "vitest";
|
|
2
|
+
import { generate, writeGeneratedFiles } from "./generate";
|
|
3
|
+
import { parseConfigSchema } from "./parse";
|
|
4
|
+
import { loadSchemaFixture, loadSchemaFixtureMap } from "../testdata/load-fixtures";
|
|
5
|
+
|
|
6
|
+
const PROPERTY_ENTITY_NAMES = [
|
|
7
|
+
"Metadata",
|
|
8
|
+
"KubeAPI",
|
|
9
|
+
"Volume",
|
|
10
|
+
"Port",
|
|
11
|
+
"File",
|
|
12
|
+
"EnvVar",
|
|
13
|
+
"HostAlias",
|
|
14
|
+
"Registries",
|
|
15
|
+
"RegistryCreate",
|
|
16
|
+
"RegistryProxy",
|
|
17
|
+
"Options",
|
|
18
|
+
"K3dOptions",
|
|
19
|
+
"LoadbalancerOptions",
|
|
20
|
+
"K3sOptions",
|
|
21
|
+
"K3sExtraArg",
|
|
22
|
+
"NodeLabel",
|
|
23
|
+
"KubeconfigOptions",
|
|
24
|
+
"RuntimeOptions",
|
|
25
|
+
"RuntimeLabel",
|
|
26
|
+
"Ulimit",
|
|
27
|
+
];
|
|
28
|
+
|
|
29
|
+
describe("generate pipeline", () => {
|
|
30
|
+
test("generate function is exported and callable", () => {
|
|
31
|
+
expect(typeof generate).toBe("function");
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
test("writeGeneratedFiles function is exported", () => {
|
|
35
|
+
expect(typeof writeGeneratedFiles).toBe("function");
|
|
36
|
+
});
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
describe("offline fixture pipeline", () => {
|
|
40
|
+
test("registry parses and covers all entities", async () => {
|
|
41
|
+
const result = await generate({ schemaSource: loadSchemaFixtureMap() });
|
|
42
|
+
|
|
43
|
+
expect(result.resources).toBe(21);
|
|
44
|
+
expect(result.warnings).toHaveLength(0);
|
|
45
|
+
|
|
46
|
+
const lexicon = JSON.parse(result.lexiconJSON);
|
|
47
|
+
expect(Object.keys(lexicon)).toHaveLength(21);
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
test("K3d::Cluster is present with kind resource", async () => {
|
|
51
|
+
const result = await generate({ schemaSource: loadSchemaFixtureMap() });
|
|
52
|
+
const lexicon = JSON.parse(result.lexiconJSON);
|
|
53
|
+
|
|
54
|
+
expect(lexicon["Cluster"]).toBeDefined();
|
|
55
|
+
expect(lexicon["Cluster"].resourceType).toBe("K3d::Cluster");
|
|
56
|
+
expect(lexicon["Cluster"].kind).toBe("resource");
|
|
57
|
+
expect(lexicon["Cluster"].lexicon).toBe("k3d");
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
test("apiVersion and kind survive as declared Cluster properties", async () => {
|
|
61
|
+
// The serializer needs these to round-trip into the emitted config;
|
|
62
|
+
// they must not be stripped by codegen.
|
|
63
|
+
const result = await generate({ schemaSource: loadSchemaFixtureMap() });
|
|
64
|
+
|
|
65
|
+
expect(result.typesDTS).toContain('apiVersion: "k3d.io/v1alpha5";');
|
|
66
|
+
expect(result.typesDTS).toContain('kind: "Simple";');
|
|
67
|
+
|
|
68
|
+
const lexicon = JSON.parse(result.lexiconJSON);
|
|
69
|
+
expect(lexicon["Cluster"].constraints.apiVersion.enum).toEqual(["k3d.io/v1alpha5"]);
|
|
70
|
+
expect(lexicon["Cluster"].constraints.kind.enum).toEqual(["Simple"]);
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
test("all property entities are present with kind property", async () => {
|
|
74
|
+
const result = await generate({ schemaSource: loadSchemaFixtureMap() });
|
|
75
|
+
const lexicon = JSON.parse(result.lexiconJSON);
|
|
76
|
+
|
|
77
|
+
for (const name of PROPERTY_ENTITY_NAMES) {
|
|
78
|
+
expect(lexicon[name], name).toBeDefined();
|
|
79
|
+
expect(lexicon[name].kind, name).toBe("property");
|
|
80
|
+
expect(lexicon[name].resourceType, name).toBe(`K3d::${name}`);
|
|
81
|
+
}
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
test("the misplaced additionalProperties key is not emitted anywhere", async () => {
|
|
85
|
+
// Upstream bug: registries.properties contains a nested
|
|
86
|
+
// "additionalProperties": false — a naive walk would emit a property
|
|
87
|
+
// literally named additionalProperties. See parse.ts.
|
|
88
|
+
const result = await generate({ schemaSource: loadSchemaFixtureMap() });
|
|
89
|
+
|
|
90
|
+
expect(result.typesDTS).not.toContain("additionalProperties");
|
|
91
|
+
expect(result.lexiconJSON).not.toContain("additionalProperties");
|
|
92
|
+
expect(result.indexTS).not.toContain("additionalProperties");
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
test("type-less timeout with string examples is typed as string", async () => {
|
|
96
|
+
const result = await generate({ schemaSource: loadSchemaFixtureMap() });
|
|
97
|
+
expect(result.typesDTS).toContain("timeout?: string;");
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
test("Cluster cross-references property entity classes", async () => {
|
|
101
|
+
const result = await generate({ schemaSource: loadSchemaFixtureMap() });
|
|
102
|
+
|
|
103
|
+
expect(result.typesDTS).toContain("volumes?: Volume[];");
|
|
104
|
+
expect(result.typesDTS).toContain("ports?: Port[];");
|
|
105
|
+
expect(result.typesDTS).toContain("files?: File[];");
|
|
106
|
+
expect(result.typesDTS).toContain("env?: EnvVar[];");
|
|
107
|
+
expect(result.typesDTS).toContain("hostAliases?: HostAlias[];");
|
|
108
|
+
expect(result.typesDTS).toContain("registries?: Registries;");
|
|
109
|
+
expect(result.typesDTS).toContain("options?: Options;");
|
|
110
|
+
expect(result.typesDTS).toContain("kubeAPI?: KubeAPI;");
|
|
111
|
+
expect(result.typesDTS).toContain("metadata?: Metadata;");
|
|
112
|
+
// Nested cross-references
|
|
113
|
+
expect(result.typesDTS).toContain("k3d?: K3dOptions;");
|
|
114
|
+
expect(result.typesDTS).toContain("extraArgs?: K3sExtraArg[];");
|
|
115
|
+
expect(result.typesDTS).toContain("proxy?: RegistryProxy;");
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
test("KubeconfigOptions has its boolean flags", async () => {
|
|
119
|
+
const result = await generate({ schemaSource: loadSchemaFixtureMap() });
|
|
120
|
+
expect(result.typesDTS).toContain("updateDefaultKubeconfig?: boolean;");
|
|
121
|
+
expect(result.typesDTS).toContain("switchCurrentContext?: boolean;");
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
test("nodeFilters resolves through the definition to string[]", async () => {
|
|
125
|
+
const result = await generate({ schemaSource: loadSchemaFixtureMap() });
|
|
126
|
+
expect(result.typesDTS).toContain("nodeFilters?: string[];");
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
test("runtime index exports Cluster as resource and the rest as properties", async () => {
|
|
130
|
+
const result = await generate({ schemaSource: loadSchemaFixtureMap() });
|
|
131
|
+
|
|
132
|
+
expect(result.indexTS).toContain('export const Cluster = createResource("K3d::Cluster", "k3d", {});');
|
|
133
|
+
for (const name of PROPERTY_ENTITY_NAMES) {
|
|
134
|
+
expect(result.indexTS).toContain(`export const ${name} = createProperty("K3d::${name}", "k3d");`);
|
|
135
|
+
}
|
|
136
|
+
});
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
describe("parseConfigSchema", () => {
|
|
140
|
+
test("splits the single schema into 21 entities", () => {
|
|
141
|
+
const results = parseConfigSchema(loadSchemaFixture());
|
|
142
|
+
expect(results).toHaveLength(21);
|
|
143
|
+
expect(results[0].resource.typeName).toBe("K3d::Cluster");
|
|
144
|
+
expect(results[0].isProperty).toBeUndefined();
|
|
145
|
+
expect(results.filter((r) => r.isProperty)).toHaveLength(20);
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
test("files item keeps its required properties", () => {
|
|
149
|
+
const results = parseConfigSchema(loadSchemaFixture());
|
|
150
|
+
const file = results.find((r) => r.resource.typeName === "K3d::File");
|
|
151
|
+
const required = file!.resource.properties.filter((p) => p.required).map((p) => p.name);
|
|
152
|
+
expect(required.sort()).toEqual(["destination", "source"]);
|
|
153
|
+
});
|
|
154
|
+
});
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* k3d generation pipeline — uses core generatePipeline with k3d-specific
|
|
3
|
+
* fetch, parse, naming, and generation callbacks.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import {
|
|
7
|
+
generatePipeline,
|
|
8
|
+
writeGeneratedArtifacts,
|
|
9
|
+
type GenerateOptions,
|
|
10
|
+
type GenerateResult,
|
|
11
|
+
type GeneratePipelineConfig,
|
|
12
|
+
} from "@intentius/chant/codegen/generate";
|
|
13
|
+
import { dirname } from "path";
|
|
14
|
+
import { fileURLToPath } from "url";
|
|
15
|
+
import { fetchSchemas } from "../spec/fetch";
|
|
16
|
+
import { parseConfigSchema, k3dShortName, type K3dParseResult } from "./parse";
|
|
17
|
+
import { NamingStrategy, propertyTypeName, extractDefName } from "./naming";
|
|
18
|
+
import { generateLexiconJSON } from "./generate-lexicon";
|
|
19
|
+
import { generateTypeScriptDeclarations } from "./generate-typescript";
|
|
20
|
+
import {
|
|
21
|
+
generateRuntimeIndex as coreGenerateRuntimeIndex,
|
|
22
|
+
type RuntimeIndexEntry,
|
|
23
|
+
type RuntimeIndexPropertyEntry,
|
|
24
|
+
} from "@intentius/chant/codegen/generate-runtime-index";
|
|
25
|
+
|
|
26
|
+
export type { GenerateResult };
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Run the full k3d generation pipeline.
|
|
30
|
+
*/
|
|
31
|
+
export async function generate(opts: GenerateOptions = {}): Promise<GenerateResult> {
|
|
32
|
+
let pendingResults: K3dParseResult[] = [];
|
|
33
|
+
|
|
34
|
+
const config: GeneratePipelineConfig<K3dParseResult> = {
|
|
35
|
+
fetchSchemas: async (fetchOpts) => {
|
|
36
|
+
return fetchSchemas(fetchOpts.force);
|
|
37
|
+
},
|
|
38
|
+
|
|
39
|
+
parseSchema: (_typeName, data) => {
|
|
40
|
+
const results = parseConfigSchema(data);
|
|
41
|
+
if (results.length === 0) return null;
|
|
42
|
+
pendingResults = results.slice(1);
|
|
43
|
+
return results[0];
|
|
44
|
+
},
|
|
45
|
+
|
|
46
|
+
createNaming: (results) => new NamingStrategy(results),
|
|
47
|
+
|
|
48
|
+
augmentResults: (results, _opts, log) => {
|
|
49
|
+
if (pendingResults.length > 0) {
|
|
50
|
+
results.push(...pendingResults);
|
|
51
|
+
log(`Added ${pendingResults.length} additional entities from single schema`);
|
|
52
|
+
pendingResults = [];
|
|
53
|
+
}
|
|
54
|
+
log(`Total: ${results.length} k3d entity schemas`);
|
|
55
|
+
return { results };
|
|
56
|
+
},
|
|
57
|
+
|
|
58
|
+
generateRegistry: (results, naming) => {
|
|
59
|
+
return generateLexiconJSON(results, naming as NamingStrategy);
|
|
60
|
+
},
|
|
61
|
+
|
|
62
|
+
generateTypes: (results, naming) => {
|
|
63
|
+
return generateTypeScriptDeclarations(results, naming as NamingStrategy);
|
|
64
|
+
},
|
|
65
|
+
|
|
66
|
+
generateRuntimeIndex: (results, naming) => {
|
|
67
|
+
return generateRuntimeIndex(results, naming as NamingStrategy);
|
|
68
|
+
},
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
return generatePipeline(config, opts);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Write generated artifacts to disk.
|
|
76
|
+
*/
|
|
77
|
+
export function writeGeneratedFiles(result: GenerateResult, pkgDir?: string): void {
|
|
78
|
+
const baseDir = pkgDir ?? dirname(dirname(dirname(fileURLToPath(import.meta.url))));
|
|
79
|
+
writeGeneratedArtifacts({
|
|
80
|
+
baseDir,
|
|
81
|
+
files: {
|
|
82
|
+
"lexicon-k3d.json": result.lexiconJSON,
|
|
83
|
+
"index.d.ts": result.typesDTS,
|
|
84
|
+
"index.ts": result.indexTS,
|
|
85
|
+
"runtime.ts": `/**\n * Runtime factory constructors — re-exported from core.\n */\nexport { createResource, createProperty } from "@intentius/chant/runtime";\n`,
|
|
86
|
+
},
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Generate the runtime index.ts with factory constructor exports.
|
|
92
|
+
*/
|
|
93
|
+
function generateRuntimeIndex(
|
|
94
|
+
results: K3dParseResult[],
|
|
95
|
+
naming: NamingStrategy,
|
|
96
|
+
): string {
|
|
97
|
+
const resourceEntries: RuntimeIndexEntry[] = [];
|
|
98
|
+
const propertyEntries: RuntimeIndexPropertyEntry[] = [];
|
|
99
|
+
|
|
100
|
+
for (const r of results) {
|
|
101
|
+
const typeName = r.resource.typeName;
|
|
102
|
+
const tsName = naming.resolve(typeName);
|
|
103
|
+
if (!tsName) continue;
|
|
104
|
+
|
|
105
|
+
const attrs: Record<string, string> = {};
|
|
106
|
+
|
|
107
|
+
if (r.isProperty) {
|
|
108
|
+
propertyEntries.push({ tsName, resourceType: typeName });
|
|
109
|
+
for (const alias of naming.aliases(typeName)) {
|
|
110
|
+
propertyEntries.push({ tsName: alias, resourceType: typeName });
|
|
111
|
+
}
|
|
112
|
+
} else {
|
|
113
|
+
resourceEntries.push({ tsName, resourceType: typeName, attrs });
|
|
114
|
+
for (const alias of naming.aliases(typeName)) {
|
|
115
|
+
resourceEntries.push({ tsName: alias, resourceType: typeName, attrs });
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
const shortName = k3dShortName(typeName);
|
|
120
|
+
const ptAliases = naming.propertyTypeAliases(typeName);
|
|
121
|
+
for (const pt of r.propertyTypes) {
|
|
122
|
+
const defName = extractDefName(pt.name, shortName);
|
|
123
|
+
const ptName = propertyTypeName(tsName, defName);
|
|
124
|
+
const ptType = `${typeName}.${pt.defType}`;
|
|
125
|
+
propertyEntries.push({ tsName: ptName, resourceType: ptType });
|
|
126
|
+
|
|
127
|
+
if (ptAliases) {
|
|
128
|
+
const aliasName = ptAliases.get(defName);
|
|
129
|
+
if (aliasName) {
|
|
130
|
+
propertyEntries.push({ tsName: aliasName, resourceType: ptType });
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
return coreGenerateRuntimeIndex(resourceEntries, propertyEntries, {
|
|
137
|
+
lexiconName: "k3d",
|
|
138
|
+
});
|
|
139
|
+
}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* k3d naming strategy — flat namespace, one short name per entity.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import {
|
|
6
|
+
NamingStrategy as CoreNamingStrategy,
|
|
7
|
+
type NamingConfig,
|
|
8
|
+
type NamingInput,
|
|
9
|
+
} from "@intentius/chant/codegen/naming";
|
|
10
|
+
import { k3dShortName, k3dServiceName, type K3dParseResult } from "./parse";
|
|
11
|
+
|
|
12
|
+
export { propertyTypeName, extractDefName } from "@intentius/chant/codegen/naming";
|
|
13
|
+
|
|
14
|
+
const k3dNamingConfig: NamingConfig = {
|
|
15
|
+
priorityNames: {
|
|
16
|
+
"K3d::Cluster": "Cluster",
|
|
17
|
+
"K3d::Metadata": "Metadata",
|
|
18
|
+
"K3d::KubeAPI": "KubeAPI",
|
|
19
|
+
"K3d::Volume": "Volume",
|
|
20
|
+
"K3d::Port": "Port",
|
|
21
|
+
"K3d::File": "File",
|
|
22
|
+
"K3d::EnvVar": "EnvVar",
|
|
23
|
+
"K3d::HostAlias": "HostAlias",
|
|
24
|
+
"K3d::Registries": "Registries",
|
|
25
|
+
"K3d::RegistryCreate": "RegistryCreate",
|
|
26
|
+
"K3d::RegistryProxy": "RegistryProxy",
|
|
27
|
+
"K3d::Options": "Options",
|
|
28
|
+
"K3d::K3dOptions": "K3dOptions",
|
|
29
|
+
"K3d::LoadbalancerOptions": "LoadbalancerOptions",
|
|
30
|
+
"K3d::K3sOptions": "K3sOptions",
|
|
31
|
+
"K3d::K3sExtraArg": "K3sExtraArg",
|
|
32
|
+
"K3d::NodeLabel": "NodeLabel",
|
|
33
|
+
"K3d::KubeconfigOptions": "KubeconfigOptions",
|
|
34
|
+
"K3d::RuntimeOptions": "RuntimeOptions",
|
|
35
|
+
"K3d::RuntimeLabel": "RuntimeLabel",
|
|
36
|
+
"K3d::Ulimit": "Ulimit",
|
|
37
|
+
},
|
|
38
|
+
priorityAliases: {},
|
|
39
|
+
priorityPropertyAliases: {},
|
|
40
|
+
serviceAbbreviations: {},
|
|
41
|
+
shortName: k3dShortName,
|
|
42
|
+
serviceName: k3dServiceName,
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* k3d naming strategy.
|
|
47
|
+
*/
|
|
48
|
+
export class NamingStrategy extends CoreNamingStrategy {
|
|
49
|
+
constructor(results: K3dParseResult[]) {
|
|
50
|
+
const inputs: NamingInput[] = results.map((r) => ({
|
|
51
|
+
typeName: r.resource.typeName,
|
|
52
|
+
propertyTypes: r.propertyTypes,
|
|
53
|
+
}));
|
|
54
|
+
super(inputs, k3dNamingConfig);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { packagePipeline } from "@intentius/chant/codegen/package";
|
|
2
|
+
import type { PackagePipelineConfig } from "@intentius/chant/codegen/package";
|
|
3
|
+
import { generate } from "./generate";
|
|
4
|
+
import { dirname } from "path";
|
|
5
|
+
import { fileURLToPath } from "url";
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Package the k3d lexicon for distribution.
|
|
9
|
+
*/
|
|
10
|
+
export async function packageLexicon(options?: { verbose?: boolean; force?: boolean }) {
|
|
11
|
+
const srcDir = dirname(fileURLToPath(import.meta.url));
|
|
12
|
+
|
|
13
|
+
const { spec, stats } = await packagePipeline({
|
|
14
|
+
generate: (opts) => generate({ verbose: opts?.verbose, force: opts?.force }),
|
|
15
|
+
buildManifest: () => ({
|
|
16
|
+
name: "k3d",
|
|
17
|
+
version: "0.0.1",
|
|
18
|
+
chantVersion: ">=0.1.0",
|
|
19
|
+
namespace: "K3d",
|
|
20
|
+
}),
|
|
21
|
+
srcDir,
|
|
22
|
+
collectSkills: () => new Map(),
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
console.error(`Packaged ${stats.resources} resources, ${stats.ruleCount} rules`);
|
|
26
|
+
return { spec, stats };
|
|
27
|
+
}
|