@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.
Files changed (109) hide show
  1. package/README.md +26 -0
  2. package/dist/codegen/docs.d.ts +7 -0
  3. package/dist/codegen/docs.d.ts.map +1 -0
  4. package/dist/codegen/generate-cli.d.ts +6 -0
  5. package/dist/codegen/generate-cli.d.ts.map +1 -0
  6. package/dist/codegen/generate-lexicon.d.ts +19 -0
  7. package/dist/codegen/generate-lexicon.d.ts.map +1 -0
  8. package/dist/codegen/generate-typescript.d.ts +11 -0
  9. package/dist/codegen/generate-typescript.d.ts.map +1 -0
  10. package/dist/codegen/generate.d.ts +15 -0
  11. package/dist/codegen/generate.d.ts.map +1 -0
  12. package/dist/codegen/naming.d.ts +13 -0
  13. package/dist/codegen/naming.d.ts.map +1 -0
  14. package/dist/codegen/package.d.ts +17 -0
  15. package/dist/codegen/package.d.ts.map +1 -0
  16. package/dist/codegen/parse.d.ts +57 -0
  17. package/dist/codegen/parse.d.ts.map +1 -0
  18. package/dist/describe-resources.d.ts +34 -0
  19. package/dist/describe-resources.d.ts.map +1 -0
  20. package/dist/generated/index.d.ts +22 -0
  21. package/dist/generated/index.d.ts.map +1 -0
  22. package/dist/generated/runtime.d.ts +5 -0
  23. package/dist/generated/runtime.d.ts.map +1 -0
  24. package/dist/index.d.ts +4 -0
  25. package/dist/index.d.ts.map +1 -0
  26. package/dist/integrity.json +9 -0
  27. package/dist/lint/audit-catalog.d.ts +9 -0
  28. package/dist/lint/audit-catalog.d.ts.map +1 -0
  29. package/dist/lint/post-synth/index.d.ts +3 -0
  30. package/dist/lint/post-synth/index.d.ts.map +1 -0
  31. package/dist/lint/post-synth/k3d101.d.ts +19 -0
  32. package/dist/lint/post-synth/k3d101.d.ts.map +1 -0
  33. package/dist/lint/post-synth/k3d102.d.ts +12 -0
  34. package/dist/lint/post-synth/k3d102.d.ts.map +1 -0
  35. package/dist/lint/rules/index.d.ts +5 -0
  36. package/dist/lint/rules/index.d.ts.map +1 -0
  37. package/dist/lint/rules/registry-proxy-password.d.ts +17 -0
  38. package/dist/lint/rules/registry-proxy-password.d.ts.map +1 -0
  39. package/dist/lint/rules/sample.d.ts +8 -0
  40. package/dist/lint/rules/sample.d.ts.map +1 -0
  41. package/dist/lsp/completions.d.ts +7 -0
  42. package/dist/lsp/completions.d.ts.map +1 -0
  43. package/dist/lsp/hover.d.ts +6 -0
  44. package/dist/lsp/hover.d.ts.map +1 -0
  45. package/dist/manifest.json +6 -0
  46. package/dist/meta.json +184 -0
  47. package/dist/op/activities/index.d.ts +17 -0
  48. package/dist/op/activities/index.d.ts.map +1 -0
  49. package/dist/op/activities/k3d.d.ts +93 -0
  50. package/dist/op/activities/k3d.d.ts.map +1 -0
  51. package/dist/package-cli.d.ts +3 -0
  52. package/dist/package-cli.d.ts.map +1 -0
  53. package/dist/plugin.d.ts +8 -0
  54. package/dist/plugin.d.ts.map +1 -0
  55. package/dist/serializer.d.ts +35 -0
  56. package/dist/serializer.d.ts.map +1 -0
  57. package/dist/spec/fetch.d.ts +39 -0
  58. package/dist/spec/fetch.d.ts.map +1 -0
  59. package/dist/testdata/load-fixtures.d.ts +16 -0
  60. package/dist/testdata/load-fixtures.d.ts.map +1 -0
  61. package/dist/types/index.d.ts +189 -0
  62. package/dist/validate-cli.d.ts +3 -0
  63. package/dist/validate-cli.d.ts.map +1 -0
  64. package/dist/validate.d.ts +15 -0
  65. package/dist/validate.d.ts.map +1 -0
  66. package/package.json +58 -0
  67. package/src/codegen/docs.ts +25 -0
  68. package/src/codegen/generate-cli.ts +36 -0
  69. package/src/codegen/generate-lexicon.ts +58 -0
  70. package/src/codegen/generate-typescript.ts +102 -0
  71. package/src/codegen/generate.test.ts +154 -0
  72. package/src/codegen/generate.ts +139 -0
  73. package/src/codegen/naming.ts +56 -0
  74. package/src/codegen/package.ts +27 -0
  75. package/src/codegen/parse.ts +359 -0
  76. package/src/describe-resources.test.ts +142 -0
  77. package/src/describe-resources.ts +144 -0
  78. package/src/generated/.gitkeep +0 -0
  79. package/src/generated/index.d.ts +189 -0
  80. package/src/generated/index.ts +27 -0
  81. package/src/generated/lexicon-k3d.json +184 -0
  82. package/src/generated/runtime.ts +4 -0
  83. package/src/index.ts +8 -0
  84. package/src/lint/audit-catalog.ts +26 -0
  85. package/src/lint/post-synth/index.ts +9 -0
  86. package/src/lint/post-synth/k3d101.ts +87 -0
  87. package/src/lint/post-synth/k3d102.ts +40 -0
  88. package/src/lint/post-synth/post-synth.test.ts +76 -0
  89. package/src/lint/rules/index.ts +7 -0
  90. package/src/lint/rules/registry-proxy-password.test.ts +53 -0
  91. package/src/lint/rules/registry-proxy-password.ts +93 -0
  92. package/src/lsp/completions.test.ts +10 -0
  93. package/src/lsp/completions.ts +21 -0
  94. package/src/lsp/hover.test.ts +10 -0
  95. package/src/lsp/hover.ts +39 -0
  96. package/src/op/activities/index.ts +24 -0
  97. package/src/op/activities/k3d.test.ts +168 -0
  98. package/src/op/activities/k3d.ts +183 -0
  99. package/src/package-cli.ts +41 -0
  100. package/src/plugin.test.ts +17 -0
  101. package/src/plugin.ts +114 -0
  102. package/src/serializer.acceptance.test.ts +94 -0
  103. package/src/serializer.test.ts +189 -0
  104. package/src/serializer.ts +202 -0
  105. package/src/spec/fetch.ts +66 -0
  106. package/src/testdata/k3d-schema-fixture.json +431 -0
  107. package/src/testdata/load-fixtures.ts +27 -0
  108. package/src/validate-cli.ts +5 -0
  109. package/src/validate.ts +28 -0
@@ -0,0 +1,189 @@
1
+ // Code generated by chant k3d generate. DO NOT EDIT.
2
+
3
+ // --- k3d Entity classes ---
4
+
5
+ export declare class Cluster {
6
+ constructor(props: {
7
+ apiVersion: "k3d.io/v1alpha5";
8
+ kind: "Simple";
9
+ agents?: number;
10
+ env?: EnvVar[];
11
+ files?: File[];
12
+ /** Additional IP to multiple hostnames mappings */
13
+ hostAliases?: HostAlias[];
14
+ image?: string;
15
+ kubeAPI?: KubeAPI;
16
+ metadata?: Metadata;
17
+ network?: string;
18
+ options?: Options;
19
+ ports?: Port[];
20
+ registries?: Registries;
21
+ servers?: number;
22
+ subnet?: string;
23
+ token?: string;
24
+ volumes?: Volume[];
25
+ });
26
+ }
27
+
28
+ export declare class EnvVar {
29
+ constructor(props: {
30
+ envVar?: string;
31
+ nodeFilters?: string[];
32
+ });
33
+ }
34
+
35
+ export declare class File {
36
+ constructor(props: {
37
+ destination: string;
38
+ source: string;
39
+ description?: string;
40
+ nodeFilters?: string[];
41
+ });
42
+ }
43
+
44
+ export declare class HostAlias {
45
+ constructor(props: {
46
+ hostnames?: string[];
47
+ ip?: string;
48
+ });
49
+ }
50
+
51
+ export declare class K3dOptions {
52
+ constructor(props: {
53
+ disableImageVolume?: boolean;
54
+ disableLoadbalancer?: boolean;
55
+ disableRollback?: boolean;
56
+ loadbalancer?: LoadbalancerOptions;
57
+ timeout?: string;
58
+ wait?: boolean;
59
+ });
60
+ }
61
+
62
+ export declare class K3sExtraArg {
63
+ constructor(props: {
64
+ arg?: string;
65
+ nodeFilters?: string[];
66
+ });
67
+ }
68
+
69
+ export declare class K3sOptions {
70
+ constructor(props: {
71
+ extraArgs?: K3sExtraArg[];
72
+ nodeLabels?: NodeLabel[];
73
+ });
74
+ }
75
+
76
+ export declare class KubeAPI {
77
+ constructor(props: {
78
+ host?: string;
79
+ hostIP?: string;
80
+ hostPort?: string;
81
+ });
82
+ }
83
+
84
+ export declare class KubeconfigOptions {
85
+ constructor(props: {
86
+ switchCurrentContext?: boolean;
87
+ updateDefaultKubeconfig?: boolean;
88
+ });
89
+ }
90
+
91
+ export declare class LoadbalancerOptions {
92
+ constructor(props: {
93
+ configOverrides?: any[];
94
+ });
95
+ }
96
+
97
+ export declare class Metadata {
98
+ constructor(props: {
99
+ /** Name of the cluster (must be a valid hostname and will be prefixed with 'k3d-'). Example: 'mycluster'. */
100
+ name?: string;
101
+ });
102
+ }
103
+
104
+ export declare class NodeLabel {
105
+ constructor(props: {
106
+ label?: string;
107
+ nodeFilters?: string[];
108
+ });
109
+ }
110
+
111
+ export declare class Options {
112
+ constructor(props: {
113
+ k3d?: K3dOptions;
114
+ k3s?: K3sOptions;
115
+ kubeconfig?: KubeconfigOptions;
116
+ runtime?: RuntimeOptions;
117
+ });
118
+ }
119
+
120
+ export declare class Port {
121
+ constructor(props: {
122
+ nodeFilters?: string[];
123
+ port?: string;
124
+ });
125
+ }
126
+
127
+ export declare class Registries {
128
+ constructor(props: {
129
+ /** Reference a K3s registry configuration file or at it's contents here. */
130
+ config?: string;
131
+ /** Create a new container image registry alongside the cluster. */
132
+ create?: RegistryCreate;
133
+ /** Connect another container image registry to the cluster. */
134
+ use?: string[];
135
+ });
136
+ }
137
+
138
+ export declare class RegistryCreate {
139
+ constructor(props: {
140
+ enforcePortMatch?: boolean;
141
+ host?: string;
142
+ hostPort?: string;
143
+ image?: string;
144
+ name?: string;
145
+ proxy?: RegistryProxy;
146
+ volumes?: string[];
147
+ });
148
+ }
149
+
150
+ export declare class RegistryProxy {
151
+ constructor(props: {
152
+ password?: string;
153
+ remoteURL?: string;
154
+ username?: string;
155
+ });
156
+ }
157
+
158
+ export declare class RuntimeLabel {
159
+ constructor(props: {
160
+ label?: string;
161
+ nodeFilters?: string[];
162
+ });
163
+ }
164
+
165
+ export declare class RuntimeOptions {
166
+ constructor(props: {
167
+ agentsMemory?: string;
168
+ gpuRequest?: string;
169
+ hostPidMode?: boolean;
170
+ labels?: RuntimeLabel[];
171
+ serversMemory?: string;
172
+ ulimits?: Ulimit[];
173
+ });
174
+ }
175
+
176
+ export declare class Ulimit {
177
+ constructor(props: {
178
+ hard?: number;
179
+ name?: string;
180
+ soft?: number;
181
+ });
182
+ }
183
+
184
+ export declare class Volume {
185
+ constructor(props: {
186
+ nodeFilters?: string[];
187
+ volume?: string;
188
+ });
189
+ }
@@ -0,0 +1,27 @@
1
+ // Code generated by chant generate. DO NOT EDIT.
2
+ import { createResource, createProperty } from "./runtime";
3
+
4
+ export const Cluster = createResource("K3d::Cluster", "k3d", {});
5
+
6
+ export const EnvVar = createProperty("K3d::EnvVar", "k3d");
7
+ export const File = createProperty("K3d::File", "k3d");
8
+ export const HostAlias = createProperty("K3d::HostAlias", "k3d");
9
+ export const K3dOptions = createProperty("K3d::K3dOptions", "k3d");
10
+ export const K3sExtraArg = createProperty("K3d::K3sExtraArg", "k3d");
11
+ export const K3sOptions = createProperty("K3d::K3sOptions", "k3d");
12
+ export const KubeAPI = createProperty("K3d::KubeAPI", "k3d");
13
+ export const KubeconfigOptions = createProperty("K3d::KubeconfigOptions", "k3d");
14
+ export const LoadbalancerOptions = createProperty("K3d::LoadbalancerOptions", "k3d");
15
+ export const Metadata = createProperty("K3d::Metadata", "k3d");
16
+ export const NodeLabel = createProperty("K3d::NodeLabel", "k3d");
17
+ export const Options = createProperty("K3d::Options", "k3d");
18
+ export const Port = createProperty("K3d::Port", "k3d");
19
+ export const Registries = createProperty("K3d::Registries", "k3d");
20
+ export const RegistryCreate = createProperty("K3d::RegistryCreate", "k3d");
21
+ export const RegistryProxy = createProperty("K3d::RegistryProxy", "k3d");
22
+ export const RuntimeLabel = createProperty("K3d::RuntimeLabel", "k3d");
23
+ export const RuntimeOptions = createProperty("K3d::RuntimeOptions", "k3d");
24
+ export const Ulimit = createProperty("K3d::Ulimit", "k3d");
25
+ export const Volume = createProperty("K3d::Volume", "k3d");
26
+
27
+ // Re-exports for convenience
@@ -0,0 +1,184 @@
1
+ {
2
+ "Cluster": {
3
+ "resourceType": "K3d::Cluster",
4
+ "kind": "resource",
5
+ "lexicon": "k3d",
6
+ "constraints": {
7
+ "apiVersion": {
8
+ "default": "k3d.io/v1alpha5",
9
+ "enum": [
10
+ "k3d.io/v1alpha5"
11
+ ]
12
+ },
13
+ "kind": {
14
+ "default": "Simple",
15
+ "enum": [
16
+ "Simple"
17
+ ]
18
+ },
19
+ "servers": {
20
+ "minimum": 1
21
+ },
22
+ "agents": {
23
+ "minimum": 0
24
+ },
25
+ "subnet": {
26
+ "default": "auto"
27
+ }
28
+ }
29
+ },
30
+ "EnvVar": {
31
+ "resourceType": "K3d::EnvVar",
32
+ "kind": "property",
33
+ "lexicon": "k3d"
34
+ },
35
+ "File": {
36
+ "resourceType": "K3d::File",
37
+ "kind": "property",
38
+ "lexicon": "k3d"
39
+ },
40
+ "HostAlias": {
41
+ "resourceType": "K3d::HostAlias",
42
+ "kind": "property",
43
+ "lexicon": "k3d"
44
+ },
45
+ "K3dOptions": {
46
+ "resourceType": "K3d::K3dOptions",
47
+ "kind": "property",
48
+ "lexicon": "k3d",
49
+ "constraints": {
50
+ "wait": {
51
+ "default": true
52
+ },
53
+ "disableLoadbalancer": {
54
+ "default": false
55
+ },
56
+ "disableImageVolume": {
57
+ "default": false
58
+ },
59
+ "disableRollback": {
60
+ "default": false
61
+ }
62
+ }
63
+ },
64
+ "K3sExtraArg": {
65
+ "resourceType": "K3d::K3sExtraArg",
66
+ "kind": "property",
67
+ "lexicon": "k3d"
68
+ },
69
+ "K3sOptions": {
70
+ "resourceType": "K3d::K3sOptions",
71
+ "kind": "property",
72
+ "lexicon": "k3d"
73
+ },
74
+ "KubeAPI": {
75
+ "resourceType": "K3d::KubeAPI",
76
+ "kind": "property",
77
+ "lexicon": "k3d",
78
+ "constraints": {
79
+ "host": {
80
+ "format": "hostname"
81
+ },
82
+ "hostIP": {
83
+ "format": "ipv4"
84
+ }
85
+ }
86
+ },
87
+ "KubeconfigOptions": {
88
+ "resourceType": "K3d::KubeconfigOptions",
89
+ "kind": "property",
90
+ "lexicon": "k3d",
91
+ "constraints": {
92
+ "updateDefaultKubeconfig": {
93
+ "default": true
94
+ },
95
+ "switchCurrentContext": {
96
+ "default": true
97
+ }
98
+ }
99
+ },
100
+ "LoadbalancerOptions": {
101
+ "resourceType": "K3d::LoadbalancerOptions",
102
+ "kind": "property",
103
+ "lexicon": "k3d"
104
+ },
105
+ "Metadata": {
106
+ "resourceType": "K3d::Metadata",
107
+ "kind": "property",
108
+ "lexicon": "k3d",
109
+ "constraints": {
110
+ "name": {
111
+ "format": "hostname"
112
+ }
113
+ }
114
+ },
115
+ "NodeLabel": {
116
+ "resourceType": "K3d::NodeLabel",
117
+ "kind": "property",
118
+ "lexicon": "k3d"
119
+ },
120
+ "Options": {
121
+ "resourceType": "K3d::Options",
122
+ "kind": "property",
123
+ "lexicon": "k3d"
124
+ },
125
+ "Port": {
126
+ "resourceType": "K3d::Port",
127
+ "kind": "property",
128
+ "lexicon": "k3d"
129
+ },
130
+ "Registries": {
131
+ "resourceType": "K3d::Registries",
132
+ "kind": "property",
133
+ "lexicon": "k3d"
134
+ },
135
+ "RegistryCreate": {
136
+ "resourceType": "K3d::RegistryCreate",
137
+ "kind": "property",
138
+ "lexicon": "k3d",
139
+ "constraints": {
140
+ "host": {
141
+ "default": "0.0.0.0"
142
+ },
143
+ "hostPort": {
144
+ "default": "random"
145
+ },
146
+ "image": {
147
+ "default": "docker.io/library/registry:2"
148
+ },
149
+ "enforcePortMatch": {
150
+ "default": false
151
+ }
152
+ }
153
+ },
154
+ "RegistryProxy": {
155
+ "resourceType": "K3d::RegistryProxy",
156
+ "kind": "property",
157
+ "lexicon": "k3d"
158
+ },
159
+ "RuntimeLabel": {
160
+ "resourceType": "K3d::RuntimeLabel",
161
+ "kind": "property",
162
+ "lexicon": "k3d"
163
+ },
164
+ "RuntimeOptions": {
165
+ "resourceType": "K3d::RuntimeOptions",
166
+ "kind": "property",
167
+ "lexicon": "k3d",
168
+ "constraints": {
169
+ "hostPidMode": {
170
+ "default": false
171
+ }
172
+ }
173
+ },
174
+ "Ulimit": {
175
+ "resourceType": "K3d::Ulimit",
176
+ "kind": "property",
177
+ "lexicon": "k3d"
178
+ },
179
+ "Volume": {
180
+ "resourceType": "K3d::Volume",
181
+ "kind": "property",
182
+ "lexicon": "k3d"
183
+ }
184
+ }
@@ -0,0 +1,4 @@
1
+ /**
2
+ * Runtime factory constructors — re-exported from core.
3
+ */
4
+ export { createResource, createProperty } from "@intentius/chant/runtime";
package/src/index.ts ADDED
@@ -0,0 +1,8 @@
1
+ // Plugin
2
+ export { k3dPlugin } from "./plugin";
3
+
4
+ // Serializer
5
+ export { k3dSerializer } from "./serializer";
6
+
7
+ // Generated resources — export everything from generated index
8
+ export * from "./generated/index";
@@ -0,0 +1,26 @@
1
+ /**
2
+ * The k3d lexicon's chant audit catalog — metadata for its post-synth
3
+ * checks, contributed via k3dPlugin.auditCatalog() (#687, #1346). Every
4
+ * post-synth check has an entry, or the check contributes nothing to
5
+ * `chant audit`, silently.
6
+ */
7
+ import { auditRule, type RuleMeta } from "@intentius/chant/audit/catalog";
8
+
9
+ export const k3dAuditCatalog: Record<string, RuleMeta> = {
10
+ K3D101: auditRule(
11
+ "K3D101",
12
+ "merge-worthy",
13
+ "guidance",
14
+ "nodeFilter matches no node",
15
+ 'Use a real k3d selector: "all", "loadbalancer", or "server"/"agent" with ":<index>" or ":*".',
16
+ { category: "correctness" },
17
+ ),
18
+ K3D102: auditRule(
19
+ "K3D102",
20
+ "merge-worthy",
21
+ "guidance",
22
+ "Registry proxy password in the emitted cluster config",
23
+ "Remove the credential from the declaration; connect the registry via registries.use or configure the proxy outside chant.",
24
+ { category: "security" },
25
+ ),
26
+ };
@@ -0,0 +1,9 @@
1
+ // Code generated by chant generate. DO NOT EDIT.
2
+ import type { PostSynthCheck } from "@intentius/chant/lint/post-synth";
3
+ import { k3d101 } from "./k3d101";
4
+ import { k3d102 } from "./k3d102";
5
+
6
+ export const postSynthChecks: PostSynthCheck[] = [
7
+ k3d101,
8
+ k3d102,
9
+ ];
@@ -0,0 +1,87 @@
1
+ /**
2
+ * K3D101: malformed nodeFilters.
3
+ *
4
+ * `nodeFilters` are k3d's own selector syntax and the schema types them as
5
+ * bare strings, so a typo validates, applies to nothing, and reports
6
+ * nothing — the port mapping or volume silently lands nowhere. This is the
7
+ * silent-at-create failure worth an error.
8
+ *
9
+ * Valid forms (k3d v5): `all`, `loadbalancer`, or `server`/`agent` with an
10
+ * optional `:<index>` / `:*`, and for port mappings an optional trailing
11
+ * `:direct` / `:proxy`.
12
+ */
13
+
14
+ import { load, loadAll } from "js-yaml";
15
+ import type { PostSynthCheck, PostSynthContext, PostSynthDiagnostic } from "@intentius/chant/lint/post-synth";
16
+ import type { SerializerResult } from "@intentius/chant/serializer";
17
+
18
+ const VALID_FILTER = /^(all|loadbalancer|server|agent)(:(\d+|\*))?(:(direct|proxy))?$/;
19
+
20
+ export function k3dDocuments(ctx: PostSynthContext): Array<{ source: string; doc: unknown }> {
21
+ const docs: Array<{ source: string; doc: unknown }> = [];
22
+ for (const [outputName, output] of ctx.outputs) {
23
+ const texts: Array<[string, string]> =
24
+ typeof output === "string"
25
+ ? [[outputName, output]]
26
+ : [
27
+ [outputName, (output as SerializerResult).primary],
28
+ ...Object.entries((output as SerializerResult).files ?? {}),
29
+ ];
30
+ for (const [source, text] of texts) {
31
+ if (!text || !text.includes("k3d.io/")) continue;
32
+ try {
33
+ for (const doc of loadAll(text)) docs.push({ source, doc });
34
+ } catch {
35
+ try {
36
+ docs.push({ source, doc: load(text) });
37
+ } catch {
38
+ // not YAML — someone else's output
39
+ }
40
+ }
41
+ }
42
+ }
43
+ return docs;
44
+ }
45
+
46
+ function collectFilters(value: unknown, path: string, out: Array<{ path: string; filter: string }>): void {
47
+ if (Array.isArray(value)) {
48
+ value.forEach((v, i) => collectFilters(v, `${path}[${i}]`, out));
49
+ return;
50
+ }
51
+ if (typeof value !== "object" || value === null) return;
52
+ for (const [key, v] of Object.entries(value as Record<string, unknown>)) {
53
+ if (key === "nodeFilters" && Array.isArray(v)) {
54
+ v.forEach((f, i) => {
55
+ if (typeof f === "string") out.push({ path: `${path}.nodeFilters[${i}]`, filter: f });
56
+ });
57
+ continue;
58
+ }
59
+ collectFilters(v, path ? `${path}.${key}` : key, out);
60
+ }
61
+ }
62
+
63
+ export const k3d101: PostSynthCheck = {
64
+ id: "K3D101",
65
+ description: "A nodeFilter that matches no node applies to nothing, silently",
66
+
67
+ check(ctx: PostSynthContext): PostSynthDiagnostic[] {
68
+ const diagnostics: PostSynthDiagnostic[] = [];
69
+ for (const { source, doc } of k3dDocuments(ctx)) {
70
+ const filters: Array<{ path: string; filter: string }> = [];
71
+ collectFilters(doc, "", filters);
72
+ for (const { path, filter } of filters) {
73
+ if (VALID_FILTER.test(filter)) continue;
74
+ diagnostics.push({
75
+ checkId: "K3D101",
76
+ severity: "error",
77
+ message:
78
+ `${source}: nodeFilter "${filter}" at ${path} is not a k3d selector — valid forms are ` +
79
+ `"all", "loadbalancer", "server"/"agent" with optional ":<index>" or ":*", plus ` +
80
+ `":direct"/":proxy" on port mappings. k3d applies an unmatched filter to nothing and says nothing.`,
81
+ lexicon: "k3d",
82
+ });
83
+ }
84
+ }
85
+ return diagnostics;
86
+ },
87
+ };
@@ -0,0 +1,40 @@
1
+ /**
2
+ * K3D102: registry proxy credential in the emitted config.
3
+ *
4
+ * The other half of K3D001: the lint rule sees literals in source, this
5
+ * sees what actually landed in the artifact — a value that arrived through
6
+ * a variable, a helper, or anything else the AST rule cannot follow. The
7
+ * emitted config is the file people commit, attach to issues and copy to
8
+ * other machines, which is exactly where a registry credential must not be.
9
+ */
10
+
11
+ import type { PostSynthCheck, PostSynthContext, PostSynthDiagnostic } from "@intentius/chant/lint/post-synth";
12
+ import { k3dDocuments } from "./k3d101";
13
+
14
+ export const k3d102: PostSynthCheck = {
15
+ id: "K3D102",
16
+ description: "The emitted cluster config carries a registry proxy password",
17
+
18
+ check(ctx: PostSynthContext): PostSynthDiagnostic[] {
19
+ const diagnostics: PostSynthDiagnostic[] = [];
20
+ for (const { source, doc } of k3dDocuments(ctx)) {
21
+ if (typeof doc !== "object" || doc === null) continue;
22
+ const registries = (doc as Record<string, unknown>).registries as Record<string, unknown> | undefined;
23
+ const create = registries?.create as Record<string, unknown> | undefined;
24
+ const proxy = create?.proxy as Record<string, unknown> | undefined;
25
+ const password = proxy?.password;
26
+ if (typeof password === "string" && password.length > 0) {
27
+ diagnostics.push({
28
+ checkId: "K3D102",
29
+ severity: "error",
30
+ message:
31
+ `${source}: registries.create.proxy.password is present in the emitted config — ` +
32
+ `this file is the walk-away artifact and a credential inside it is a credential in source. ` +
33
+ `Connect a pre-existing registry via registries.use, or supply the proxy credential outside chant.`,
34
+ lexicon: "k3d",
35
+ });
36
+ }
37
+ }
38
+ return diagnostics;
39
+ },
40
+ };
@@ -0,0 +1,76 @@
1
+ import { describe, expect, test } from "vitest";
2
+ import type { PostSynthContext } from "@intentius/chant/lint/post-synth";
3
+ import { k3d101 } from "./k3d101";
4
+ import { k3d102 } from "./k3d102";
5
+
6
+ function ctx(yaml: string): PostSynthContext {
7
+ const outputs = new Map<string, string>([["k3d", yaml]]);
8
+ return {
9
+ outputs,
10
+ entities: new Map(),
11
+ buildResult: { outputs, entities: new Map(), warnings: [] },
12
+ } as unknown as PostSynthContext;
13
+ }
14
+
15
+ const HEADER = "apiVersion: k3d.io/v1alpha5\nkind: Simple\n";
16
+
17
+ describe("K3D101: malformed nodeFilters", () => {
18
+ test("accepts every documented form", () => {
19
+ const yaml =
20
+ HEADER +
21
+ `ports:
22
+ - port: "8080:80"
23
+ nodeFilters: ["loadbalancer", "server:0", "server:*", "agent:1", "all", "loadbalancer:proxy", "server:0:direct"]
24
+ `;
25
+ expect(k3d101.check(ctx(yaml))).toHaveLength(0);
26
+ });
27
+
28
+ test("flags a typo'd role", () => {
29
+ const yaml = HEADER + `volumes:\n - volume: /tmp/x:/x\n nodeFilters: ["sevrer:0"]\n`;
30
+ const diags = k3d101.check(ctx(yaml));
31
+ expect(diags).toHaveLength(1);
32
+ expect(diags[0].checkId).toBe("K3D101");
33
+ expect(diags[0].severity).toBe("error");
34
+ expect(diags[0].message).toContain("sevrer:0");
35
+ });
36
+
37
+ test("flags a malformed index", () => {
38
+ const diags = k3d101.check(ctx(HEADER + `ports:\n - port: "1:1"\n nodeFilters: ["server:one"]\n`));
39
+ expect(diags).toHaveLength(1);
40
+ });
41
+
42
+ test("ignores output that is not a k3d config", () => {
43
+ const outputs = new Map<string, string>([["k8s", "kind: Deployment\nmetadata:\n name: x\n"]]);
44
+ const context = {
45
+ outputs,
46
+ entities: new Map(),
47
+ buildResult: { outputs, entities: new Map(), warnings: [] },
48
+ } as unknown as PostSynthContext;
49
+ expect(k3d101.check(context)).toHaveLength(0);
50
+ });
51
+ });
52
+
53
+ describe("K3D102: proxy password in the emitted config", () => {
54
+ test("flags a password that landed in the artifact", () => {
55
+ const yaml =
56
+ HEADER +
57
+ `registries:
58
+ create:
59
+ name: myregistry
60
+ proxy:
61
+ remoteURL: https://registry-1.docker.io
62
+ password: hunter2
63
+ `;
64
+ const diags = k3d102.check(ctx(yaml));
65
+ expect(diags).toHaveLength(1);
66
+ expect(diags[0].checkId).toBe("K3D102");
67
+ expect(diags[0].severity).toBe("error");
68
+ });
69
+
70
+ test("silent when there is no proxy or no password", () => {
71
+ expect(k3d102.check(ctx(HEADER + "servers: 1\n"))).toHaveLength(0);
72
+ const noPass =
73
+ HEADER + `registries:\n create:\n name: r\n proxy:\n remoteURL: https://x\n`;
74
+ expect(k3d102.check(ctx(noPass))).toHaveLength(0);
75
+ });
76
+ });
@@ -0,0 +1,7 @@
1
+ import type { LintRule } from "@intentius/chant/lint/rule";
2
+ import { registryProxyPasswordRule } from "./registry-proxy-password";
3
+
4
+ export { registryProxyPasswordRule } from "./registry-proxy-password";
5
+
6
+ /** All lint rules provided by this lexicon (imported by plugin.ts's lintRules()). */
7
+ export const rules: LintRule[] = [registryProxyPasswordRule];