kubernetes-fluent-client 1.5.1 → 1.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.d.ts ADDED
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env node
2
+ export {};
3
+ //# sourceMappingURL=cli.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":""}
package/dist/cli.js ADDED
@@ -0,0 +1,39 @@
1
+ #!/usr/bin/env node
2
+ "use strict";
3
+ // SPDX-License-Identifier: Apache-2.0
4
+ // SPDX-FileCopyrightText: 2023-Present The Kubernetes Fluent Client Authors
5
+ var __importDefault = (this && this.__importDefault) || function (mod) {
6
+ return (mod && mod.__esModule) ? mod : { "default": mod };
7
+ };
8
+ Object.defineProperty(exports, "__esModule", { value: true });
9
+ const helpers_1 = require("yargs/helpers");
10
+ const yargs_1 = __importDefault(require("yargs/yargs"));
11
+ const generate_1 = require("./generate");
12
+ void (0, yargs_1.default)((0, helpers_1.hideBin)(process.argv))
13
+ .version(false)
14
+ .command("crd [source] [directory]", "generate usable types from a K8s CRD", yargs => {
15
+ return yargs
16
+ .positional("source", {
17
+ describe: "the yaml file path, remote url, or K8s CRD name",
18
+ type: "string",
19
+ })
20
+ .positional("directory", {
21
+ describe: "the directory to output the generated types to",
22
+ type: "string",
23
+ })
24
+ .option("plain", {
25
+ alias: "p",
26
+ type: "boolean",
27
+ description: "generate plain types without binding to the fluent client, automatically enabled when an alternate language is specified",
28
+ })
29
+ .option("language", {
30
+ alias: "l",
31
+ type: "string",
32
+ default: "ts",
33
+ description: "the language to generate types in, see https://github.com/glideapps/quicktype#target-languages for a list of supported languages",
34
+ })
35
+ .demandOption(["source", "directory"]);
36
+ }, argv => {
37
+ void (0, generate_1.generate)(argv);
38
+ })
39
+ .parse();
package/dist/fetch.js CHANGED
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  // SPDX-License-Identifier: Apache-2.0
3
- // SPDX-FileCopyrightText: 2023-Present The Pepr Authors
3
+ // SPDX-FileCopyrightText: 2023-Present The Kubernetes Fluent Client Authors
4
4
  var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
5
5
  if (k2 === undefined) k2 = k;
6
6
  var desc = Object.getOwnPropertyDescriptor(m, k);
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  // SPDX-License-Identifier: Apache-2.0
3
- // SPDX-FileCopyrightText: 2023-Present The Pepr Authors
3
+ // SPDX-FileCopyrightText: 2023-Present The Kubernetes Fluent Client Authors
4
4
  var __importDefault = (this && this.__importDefault) || function (mod) {
5
5
  return (mod && mod.__esModule) ? mod : { "default": mod };
6
6
  };
@@ -1,4 +1,4 @@
1
1
  "use strict";
2
2
  // SPDX-License-Identifier: Apache-2.0
3
- // SPDX-FileCopyrightText: 2023-Present The Pepr Authors
3
+ // SPDX-FileCopyrightText: 2023-Present The Kubernetes Fluent Client Authors
4
4
  Object.defineProperty(exports, "__esModule", { value: true });
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  // SPDX-License-Identifier: Apache-2.0
3
- // SPDX-FileCopyrightText: 2023-Present The Pepr Authors
3
+ // SPDX-FileCopyrightText: 2023-Present The Kubernetes Fluent Client Authors
4
4
  Object.defineProperty(exports, "__esModule", { value: true });
5
5
  exports.K8s = void 0;
6
6
  const http_status_codes_1 = require("http-status-codes");
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  // SPDX-License-Identifier: Apache-2.0
3
- // SPDX-FileCopyrightText: 2023-Present The Pepr Authors
3
+ // SPDX-FileCopyrightText: 2023-Present The Kubernetes Fluent Client Authors
4
4
  Object.defineProperty(exports, "__esModule", { value: true });
5
5
  exports.WatchPhase = void 0;
6
6
  /**
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  // SPDX-License-Identifier: Apache-2.0
3
- // SPDX-FileCopyrightText: 2023-Present The Pepr Authors
3
+ // SPDX-FileCopyrightText: 2023-Present The Kubernetes Fluent Client Authors
4
4
  Object.defineProperty(exports, "__esModule", { value: true });
5
5
  exports.k8sExec = exports.k8sCfg = exports.pathBuilder = void 0;
6
6
  const client_node_1 = require("@kubernetes/client-node");
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  // SPDX-License-Identifier: Apache-2.0
3
- // SPDX-FileCopyrightText: 2023-Present The Pepr Authors
3
+ // SPDX-FileCopyrightText: 2023-Present The Kubernetes Fluent Client Authors
4
4
  Object.defineProperty(exports, "__esModule", { value: true });
5
5
  const globals_1 = require("@jest/globals");
6
6
  const fetch_1 = require("../fetch");
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  // SPDX-License-Identifier: Apache-2.0
3
- // SPDX-FileCopyrightText: 2023-Present The Pepr Authors
3
+ // SPDX-FileCopyrightText: 2023-Present The Kubernetes Fluent Client Authors
4
4
  var __importDefault = (this && this.__importDefault) || function (mod) {
5
5
  return (mod && mod.__esModule) ? mod : { "default": mod };
6
6
  };
@@ -0,0 +1,19 @@
1
+ import { TargetLanguage } from "quicktype-core";
2
+ export interface GenerateOptions {
3
+ /** The source URL, yaml file path or K8s CRD name */
4
+ source: string;
5
+ /** The output directory path */
6
+ directory?: string;
7
+ /** Disable kubernetes-fluent-client wrapping */
8
+ plain?: boolean;
9
+ /** The language to generate types in */
10
+ language?: string | TargetLanguage;
11
+ }
12
+ /**
13
+ * Generate TypeScript types from a K8s CRD
14
+ *
15
+ * @param opts The options to use when generating
16
+ * @returns A promise that resolves when the TypeScript types have been generated
17
+ */
18
+ export declare function generate(opts: GenerateOptions): Promise<Record<string, string[]>>;
19
+ //# sourceMappingURL=generate.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"generate.d.ts","sourceRoot":"","sources":["../src/generate.ts"],"names":[],"mappings":"AAMA,OAAO,EAIL,cAAc,EAEf,MAAM,gBAAgB,CAAC;AAMxB,MAAM,WAAW,eAAe;IAC9B,qDAAqD;IACrD,MAAM,EAAE,MAAM,CAAC;IACf,gCAAgC;IAChC,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,gDAAgD;IAChD,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,wCAAwC;IACxC,QAAQ,CAAC,EAAE,MAAM,GAAG,cAAc,CAAC;CACpC;AAyID;;;;;GAKG;AACH,wBAAsB,QAAQ,CAAC,IAAI,EAAE,eAAe,qCAkBnD"}
@@ -0,0 +1,165 @@
1
+ "use strict";
2
+ // SPDX-License-Identifier: Apache-2.0
3
+ // SPDX-FileCopyrightText: 2023-Present The Kubernetes Fluent Client Authors
4
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
5
+ if (k2 === undefined) k2 = k;
6
+ var desc = Object.getOwnPropertyDescriptor(m, k);
7
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
8
+ desc = { enumerable: true, get: function() { return m[k]; } };
9
+ }
10
+ Object.defineProperty(o, k2, desc);
11
+ }) : (function(o, m, k, k2) {
12
+ if (k2 === undefined) k2 = k;
13
+ o[k2] = m[k];
14
+ }));
15
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
16
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
17
+ }) : function(o, v) {
18
+ o["default"] = v;
19
+ });
20
+ var __importStar = (this && this.__importStar) || function (mod) {
21
+ if (mod && mod.__esModule) return mod;
22
+ var result = {};
23
+ if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
24
+ __setModuleDefault(result, mod);
25
+ return result;
26
+ };
27
+ Object.defineProperty(exports, "__esModule", { value: true });
28
+ exports.generate = void 0;
29
+ const client_node_1 = require("@kubernetes/client-node");
30
+ const fs = __importStar(require("fs"));
31
+ const path = __importStar(require("path"));
32
+ const quicktype_core_1 = require("quicktype-core");
33
+ const fetch_1 = require("./fetch");
34
+ const fluent_1 = require("./fluent");
35
+ const upstream_1 = require("./upstream");
36
+ /**
37
+ * Converts a CustomResourceDefinition to TypeScript types
38
+ *
39
+ * @param crd The CustomResourceDefinition to convert
40
+ * @param opts The options to use when converting
41
+ * @returns A promise that resolves when the CustomResourceDefinition has been converted
42
+ */
43
+ async function convertCRDtoTS(crd, opts) {
44
+ // Get the name of the kind
45
+ const name = crd.spec.names.kind;
46
+ const results = {};
47
+ for (const match of crd.spec.versions) {
48
+ // Get the schema from the matched version
49
+ const schema = JSON.stringify(match?.schema?.openAPIV3Schema);
50
+ // Create a new JSONSchemaInput
51
+ const schemaInput = new quicktype_core_1.JSONSchemaInput(new quicktype_core_1.FetchingJSONSchemaStore());
52
+ // Add the schema to the input
53
+ await schemaInput.addSource({ name, schema });
54
+ // Create a new InputData object
55
+ const inputData = new quicktype_core_1.InputData();
56
+ inputData.addInput(schemaInput);
57
+ // Generate the types
58
+ const out = await (0, quicktype_core_1.quicktype)({
59
+ inputData,
60
+ lang: opts.language || "ts",
61
+ rendererOptions: { "just-types": "true" },
62
+ });
63
+ let processedLines = out.lines;
64
+ // If using typescript, remove the line containing `[property: string]: any;`
65
+ if (opts.language === "ts" || opts.language === "typescript") {
66
+ processedLines = out.lines.filter(line => !line.includes("[property: string]: any;"));
67
+ }
68
+ // If the language is TypeScript and plain is not specified, wire up the fluent client
69
+ if (opts.language === "ts" && !opts.plain) {
70
+ processedLines.unshift(
71
+ // Add warning that the file is auto-generated
72
+ `// This file is auto-generated by kubernetes-fluent-client, do not edit manually\n`,
73
+ // Add the imports before any other lines
74
+ `import { GenericKind, RegisterKind } from "kubernetes-fluent-client";\n`);
75
+ // Replace the interface with a named class that extends GenericKind
76
+ const entryIdx = processedLines.findIndex(line => line.includes(`export interface ${name} {`));
77
+ // Replace the interface with a named class that extends GenericKind
78
+ processedLines[entryIdx] = `export class ${name} extends GenericKind {`;
79
+ // Add the RegisterKind call
80
+ processedLines.push(`RegisterKind(${name}, {`);
81
+ processedLines.push(` group: "${crd.spec.group}",`);
82
+ processedLines.push(` version: "${match.name}",`);
83
+ processedLines.push(` kind: "${name}",`);
84
+ processedLines.push(`});`);
85
+ }
86
+ const finalContents = processedLines.join("\n");
87
+ const fileName = `${name.toLowerCase()}-${match.name.toLowerCase()}`;
88
+ // If an output file is specified, write the output to the file
89
+ if (opts.directory) {
90
+ // Create the directory if it doesn't exist
91
+ fs.mkdirSync(opts.directory, { recursive: true });
92
+ // Write the file
93
+ const filePath = path.join(opts.directory, `${fileName}.${opts.language}`);
94
+ fs.writeFileSync(filePath, finalContents);
95
+ }
96
+ // Add the results to the array
97
+ results[fileName] = processedLines;
98
+ }
99
+ return results;
100
+ }
101
+ /**
102
+ * Reads a CustomResourceDefinition from a file, the cluster or the internet
103
+ *
104
+ * @param source The source to read from (file path, cluster or URL)
105
+ * @returns A promise that resolves when the CustomResourceDefinition has been read
106
+ */
107
+ async function readOrFetchCrd(source) {
108
+ const filePath = path.join(process.cwd(), source);
109
+ // First try to read the source as a file
110
+ try {
111
+ if (fs.existsSync(filePath)) {
112
+ const payload = fs.readFileSync(filePath, "utf8");
113
+ return (0, client_node_1.loadAllYaml)(payload);
114
+ }
115
+ }
116
+ catch (e) {
117
+ // Ignore errors
118
+ }
119
+ // Next try to parse the source as a URL
120
+ try {
121
+ const url = new URL(source);
122
+ // If the source is a URL, fetch it
123
+ if (url.protocol === "http:" || url.protocol === "https:") {
124
+ const { ok, data } = await (0, fetch_1.fetch)(source);
125
+ // If the request failed, throw an error
126
+ if (!ok) {
127
+ throw new Error(`Failed to fetch ${source}: ${data}`);
128
+ }
129
+ return (0, client_node_1.loadAllYaml)(data);
130
+ }
131
+ }
132
+ catch (e) {
133
+ // Ignore errors
134
+ }
135
+ // Finally, if the source is not a file or URL, try to read it as a CustomResourceDefinition from the cluster
136
+ try {
137
+ return [await (0, fluent_1.K8s)(upstream_1.CustomResourceDefinition).Get(source)];
138
+ }
139
+ catch (e) {
140
+ throw new Error(`Failed to read ${source} as a file, url or K8s CRD: ${e}`);
141
+ }
142
+ }
143
+ /**
144
+ * Generate TypeScript types from a K8s CRD
145
+ *
146
+ * @param opts The options to use when generating
147
+ * @returns A promise that resolves when the TypeScript types have been generated
148
+ */
149
+ async function generate(opts) {
150
+ const crds = await readOrFetchCrd(opts.source);
151
+ const results = {};
152
+ for (const crd of crds) {
153
+ if (!crd || crd.kind !== "CustomResourceDefinition" || !crd.spec?.versions?.length) {
154
+ // Ignore empty and non-CRD objects
155
+ continue;
156
+ }
157
+ // Add the results to the record
158
+ const out = await convertCRDtoTS(crd, opts);
159
+ for (const key of Object.keys(out)) {
160
+ results[key] = out[key];
161
+ }
162
+ }
163
+ return results;
164
+ }
165
+ exports.generate = generate;
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=generate.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"generate.test.d.ts","sourceRoot":"","sources":["../src/generate.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,256 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ const globals_1 = require("@jest/globals");
7
+ const generate_1 = require("./generate");
8
+ const fs_1 = __importDefault(require("fs"));
9
+ const sampleYaml = `
10
+ # non-crd should be ignored
11
+ apiVersion: v1
12
+ kind: ConfigMap
13
+ metadata:
14
+ name: test
15
+ namespace: default
16
+ data:
17
+ any: bleh
18
+ ---
19
+ apiVersion: apiextensions.k8s.io/v1
20
+ kind: CustomResourceDefinition
21
+ metadata:
22
+ name: movies.example.com
23
+ spec:
24
+ group: example.com
25
+ names:
26
+ kind: Movie
27
+ plural: movies
28
+ scope: Namespaced
29
+ versions:
30
+ - name: v1
31
+ schema:
32
+ openAPIV3Schema:
33
+ type: object
34
+ description: Movie nerd
35
+ properties:
36
+ spec:
37
+ properties:
38
+ title:
39
+ type: string
40
+ author:
41
+ type: string
42
+ type: object
43
+ ---
44
+ # duplicate entries should not break things
45
+ apiVersion: apiextensions.k8s.io/v1
46
+ kind: CustomResourceDefinition
47
+ metadata:
48
+ name: movies.example.com
49
+ spec:
50
+ group: example.com
51
+ names:
52
+ kind: Movie
53
+ plural: movies
54
+ scope: Namespaced
55
+ versions:
56
+ - name: v1
57
+ schema:
58
+ openAPIV3Schema:
59
+ type: object
60
+ description: Movie nerd
61
+ properties:
62
+ spec:
63
+ properties:
64
+ title:
65
+ type: string
66
+ author:
67
+ type: string
68
+ type: object
69
+ ---
70
+ # should support multiple versions
71
+ apiVersion: apiextensions.k8s.io/v1
72
+ kind: CustomResourceDefinition
73
+ metadata:
74
+ name: books.example.com
75
+ spec:
76
+ group: example.com
77
+ names:
78
+ kind: Book
79
+ plural: books
80
+ scope: Namespaced
81
+ versions:
82
+ - name: v1
83
+ schema:
84
+ openAPIV3Schema:
85
+ type: object
86
+ description: Book nerd
87
+ properties:
88
+ spec:
89
+ properties:
90
+ title:
91
+ type: string
92
+ author:
93
+ type: string
94
+ type: object
95
+ - name: v2
96
+ schema:
97
+ openAPIV3Schema:
98
+ type: object
99
+ description: Book nerd
100
+ properties:
101
+ spec:
102
+ properties:
103
+ author:
104
+ type: string
105
+ type: object
106
+ served: true
107
+ storage: true
108
+ `;
109
+ globals_1.jest.mock("./fetch", () => ({
110
+ fetch: globals_1.jest.fn(),
111
+ }));
112
+ globals_1.jest.mock("./fluent", () => ({
113
+ K8s: globals_1.jest.fn(),
114
+ }));
115
+ (0, globals_1.describe)("CRD to TypeScript Conversion", () => {
116
+ const originalReadFileSync = fs_1.default.readFileSync;
117
+ globals_1.jest.isolateModules(() => {
118
+ globals_1.jest.spyOn(fs_1.default, "existsSync").mockReturnValue(true);
119
+ globals_1.jest.spyOn(fs_1.default, "readFileSync").mockImplementation((...args) => {
120
+ // Super janky hack due ot source-map-support calling readFileSync internally
121
+ if (args[0].toString().includes("test-crd.yaml")) {
122
+ return sampleYaml;
123
+ }
124
+ return originalReadFileSync(...args);
125
+ });
126
+ globals_1.jest.spyOn(fs_1.default, "mkdirSync").mockReturnValue(undefined);
127
+ globals_1.jest.spyOn(fs_1.default, "writeFileSync").mockReturnValue(undefined);
128
+ });
129
+ (0, globals_1.beforeEach)(() => {
130
+ globals_1.jest.clearAllMocks();
131
+ });
132
+ (0, globals_1.test)("converts CRD to TypeScript", async () => {
133
+ const options = { source: "test-crd.yaml", language: "ts" }; // specify your options
134
+ const actual = await (0, generate_1.generate)(options);
135
+ const expectedMovie = [
136
+ "// This file is auto-generated by kubernetes-fluent-client, do not edit manually\n",
137
+ 'import { GenericKind, RegisterKind } from "kubernetes-fluent-client";\n',
138
+ "/**",
139
+ " * Movie nerd",
140
+ " */",
141
+ "export class Movie extends GenericKind {",
142
+ " spec?: Spec;",
143
+ "}",
144
+ "",
145
+ "export interface Spec {",
146
+ " author?: string;",
147
+ " title?: string;",
148
+ "}",
149
+ "",
150
+ "RegisterKind(Movie, {",
151
+ ' group: "example.com",',
152
+ ' version: "v1",',
153
+ ' kind: "Movie",',
154
+ "});",
155
+ ];
156
+ const expectedBookV1 = [
157
+ "// This file is auto-generated by kubernetes-fluent-client, do not edit manually\n",
158
+ 'import { GenericKind, RegisterKind } from "kubernetes-fluent-client";\n',
159
+ "/**",
160
+ " * Book nerd",
161
+ " */",
162
+ "export class Book extends GenericKind {",
163
+ " spec?: Spec;",
164
+ "}",
165
+ "",
166
+ "export interface Spec {",
167
+ " author?: string;",
168
+ " title?: string;",
169
+ "}",
170
+ "",
171
+ "RegisterKind(Book, {",
172
+ ' group: "example.com",',
173
+ ' version: "v1",',
174
+ ' kind: "Book",',
175
+ "});",
176
+ ];
177
+ const expectedBookV2 = expectedBookV1
178
+ .filter(line => !line.includes("title?"))
179
+ .map(line => line.replace("v1", "v2"));
180
+ (0, globals_1.expect)(actual["movie-v1"]).toEqual(expectedMovie);
181
+ (0, globals_1.expect)(actual["book-v1"]).toEqual(expectedBookV1);
182
+ (0, globals_1.expect)(actual["book-v2"]).toEqual(expectedBookV2);
183
+ });
184
+ (0, globals_1.test)("converts CRD to TypeScript with plain option", async () => {
185
+ const options = { source: "test-crd.yaml", language: "ts", plain: true }; // specify your options
186
+ const actual = await (0, generate_1.generate)(options);
187
+ const expectedMovie = [
188
+ "/**",
189
+ " * Movie nerd",
190
+ " */",
191
+ "export interface Movie {",
192
+ " spec?: Spec;",
193
+ "}",
194
+ "",
195
+ "export interface Spec {",
196
+ " author?: string;",
197
+ " title?: string;",
198
+ "}",
199
+ "",
200
+ ];
201
+ const expectedBookV1 = [
202
+ "/**",
203
+ " * Book nerd",
204
+ " */",
205
+ "export interface Book {",
206
+ " spec?: Spec;",
207
+ "}",
208
+ "",
209
+ "export interface Spec {",
210
+ " author?: string;",
211
+ " title?: string;",
212
+ "}",
213
+ "",
214
+ ];
215
+ const expectedBookV2 = expectedBookV1
216
+ .filter(line => !line.includes("title?"))
217
+ .map(line => line.replace("v1", "v2"));
218
+ (0, globals_1.expect)(actual["movie-v1"]).toEqual(expectedMovie);
219
+ (0, globals_1.expect)(actual["book-v1"]).toEqual(expectedBookV1);
220
+ (0, globals_1.expect)(actual["book-v2"]).toEqual(expectedBookV2);
221
+ });
222
+ (0, globals_1.test)("converts CRD to Go", async () => {
223
+ const options = { source: "test-crd.yaml", language: "go" }; // specify your options
224
+ const actual = await (0, generate_1.generate)(options);
225
+ const expectedMovie = [
226
+ "// Movie nerd",
227
+ "type Movie struct {",
228
+ '\tSpec *Spec `json:"spec,omitempty"`',
229
+ "}",
230
+ "",
231
+ "type Spec struct {",
232
+ '\tAuthor *string `json:"author,omitempty"`',
233
+ '\tTitle *string `json:"title,omitempty"`',
234
+ "}",
235
+ "",
236
+ ];
237
+ const expectedBookV1 = [
238
+ "// Book nerd",
239
+ "type Book struct {",
240
+ '\tSpec *Spec `json:"spec,omitempty"`',
241
+ "}",
242
+ "",
243
+ "type Spec struct {",
244
+ '\tAuthor *string `json:"author,omitempty"`',
245
+ '\tTitle *string `json:"title,omitempty"`',
246
+ "}",
247
+ "",
248
+ ];
249
+ const expectedBookV2 = expectedBookV1
250
+ .filter(line => !line.includes("Title"))
251
+ .map(line => line.replace("v1", "v2"));
252
+ (0, globals_1.expect)(actual["movie-v1"]).toEqual(expectedMovie);
253
+ (0, globals_1.expect)(actual["book-v1"]).toEqual(expectedBookV1);
254
+ (0, globals_1.expect)(actual["book-v2"]).toEqual(expectedBookV2);
255
+ });
256
+ });
package/dist/index.d.ts CHANGED
@@ -5,6 +5,7 @@ export { fetch } from "./fetch";
5
5
  export { StatusCodes as fetchStatus } from "http-status-codes";
6
6
  export { K8s } from "./fluent";
7
7
  export { RegisterKind, modelToGroupVersionKind } from "./kinds";
8
+ export { GenericKind } from "./types";
8
9
  export * from "./types";
9
10
  export * as K8sClientNode from "@kubernetes/client-node";
10
11
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,IAAI,MAAM,YAAY,CAAC;AAEnC,oGAAoG;AACpG,OAAO,EAAE,IAAI,EAAE,CAAC;AAGhB,OAAO,EAAE,KAAK,EAAE,MAAM,SAAS,CAAC;AAGhC,OAAO,EAAE,WAAW,IAAI,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAG/D,OAAO,EAAE,GAAG,EAAE,MAAM,UAAU,CAAC;AAG/B,OAAO,EAAE,YAAY,EAAE,uBAAuB,EAAE,MAAM,SAAS,CAAC;AAEhE,cAAc,SAAS,CAAC;AAExB,OAAO,KAAK,aAAa,MAAM,yBAAyB,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,IAAI,MAAM,YAAY,CAAC;AAEnC,oGAAoG;AACpG,OAAO,EAAE,IAAI,EAAE,CAAC;AAGhB,OAAO,EAAE,KAAK,EAAE,MAAM,SAAS,CAAC;AAGhC,OAAO,EAAE,WAAW,IAAI,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAG/D,OAAO,EAAE,GAAG,EAAE,MAAM,UAAU,CAAC;AAG/B,OAAO,EAAE,YAAY,EAAE,uBAAuB,EAAE,MAAM,SAAS,CAAC;AAGhE,OAAO,EAAE,WAAW,EAAE,MAAM,SAAS,CAAC;AAEtC,cAAc,SAAS,CAAC;AAExB,OAAO,KAAK,aAAa,MAAM,yBAAyB,CAAC"}
package/dist/index.js CHANGED
@@ -28,7 +28,7 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
28
28
  for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
29
29
  };
30
30
  Object.defineProperty(exports, "__esModule", { value: true });
31
- exports.K8sClientNode = exports.modelToGroupVersionKind = exports.RegisterKind = exports.K8s = exports.fetchStatus = exports.fetch = exports.kind = void 0;
31
+ exports.K8sClientNode = exports.GenericKind = exports.modelToGroupVersionKind = exports.RegisterKind = exports.K8s = exports.fetchStatus = exports.fetch = exports.kind = void 0;
32
32
  // Export kinds as a single object
33
33
  const kind = __importStar(require("./upstream"));
34
34
  exports.kind = kind;
@@ -45,5 +45,8 @@ Object.defineProperty(exports, "K8s", { enumerable: true, get: function () { ret
45
45
  var kinds_1 = require("./kinds");
46
46
  Object.defineProperty(exports, "RegisterKind", { enumerable: true, get: function () { return kinds_1.RegisterKind; } });
47
47
  Object.defineProperty(exports, "modelToGroupVersionKind", { enumerable: true, get: function () { return kinds_1.modelToGroupVersionKind; } });
48
+ // Export the GenericKind interface for CRD registration
49
+ var types_1 = require("./types");
50
+ Object.defineProperty(exports, "GenericKind", { enumerable: true, get: function () { return types_1.GenericKind; } });
48
51
  __exportStar(require("./types"), exports);
49
52
  exports.K8sClientNode = __importStar(require("@kubernetes/client-node"));
package/package.json CHANGED
@@ -1,7 +1,8 @@
1
1
  {
2
2
  "name": "kubernetes-fluent-client",
3
- "version": "1.5.1",
3
+ "version": "1.6.0",
4
4
  "description": "A @kubernetes/client-node fluent API wrapper that leverages K8s Server Side Apply",
5
+ "bin": "./dist/cli.js",
5
6
  "main": "dist/index.js",
6
7
  "types": "dist/index.d.ts",
7
8
  "scripts": {
@@ -39,18 +40,22 @@
39
40
  "fast-json-patch": "3.1.1",
40
41
  "http-status-codes": "2.3.0",
41
42
  "node-fetch": "2.7.0",
42
- "type-fest": "4.3.3"
43
+ "quicktype-core": "23.0.76",
44
+ "type-fest": "4.4.0",
45
+ "yargs": "17.7.2"
43
46
  },
44
47
  "devDependencies": {
45
48
  "@commitlint/cli": "17.7.2",
46
49
  "@commitlint/config-conventional": "17.7.0",
47
50
  "@jest/globals": "29.7.0",
48
51
  "@types/byline": "4.2.34",
49
- "@typescript-eslint/eslint-plugin": "6.7.4",
50
- "@typescript-eslint/parser": "6.7.4",
52
+ "@types/readable-stream": "4.0.3",
53
+ "@types/yargs": "17.0.28",
54
+ "@typescript-eslint/eslint-plugin": "6.7.5",
55
+ "@typescript-eslint/parser": "6.7.5",
51
56
  "eslint-plugin-jsdoc": "46.8.2",
52
57
  "jest": "29.7.0",
53
- "nock": "13.3.3",
58
+ "nock": "13.3.4",
54
59
  "prettier": "3.0.3",
55
60
  "semantic-release": "22.0.5",
56
61
  "ts-jest": "29.1.1",
package/src/cli.ts ADDED
@@ -0,0 +1,44 @@
1
+ #!/usr/bin/env node
2
+
3
+ // SPDX-License-Identifier: Apache-2.0
4
+ // SPDX-FileCopyrightText: 2023-Present The Kubernetes Fluent Client Authors
5
+
6
+ import { hideBin } from "yargs/helpers";
7
+ import yargs from "yargs/yargs";
8
+ import { GenerateOptions, generate } from "./generate";
9
+
10
+ void yargs(hideBin(process.argv))
11
+ .version(false)
12
+ .command(
13
+ "crd [source] [directory]",
14
+ "generate usable types from a K8s CRD",
15
+ yargs => {
16
+ return yargs
17
+ .positional("source", {
18
+ describe: "the yaml file path, remote url, or K8s CRD name",
19
+ type: "string",
20
+ })
21
+ .positional("directory", {
22
+ describe: "the directory to output the generated types to",
23
+ type: "string",
24
+ })
25
+ .option("plain", {
26
+ alias: "p",
27
+ type: "boolean",
28
+ description:
29
+ "generate plain types without binding to the fluent client, automatically enabled when an alternate language is specified",
30
+ })
31
+ .option("language", {
32
+ alias: "l",
33
+ type: "string",
34
+ default: "ts",
35
+ description:
36
+ "the language to generate types in, see https://github.com/glideapps/quicktype#target-languages for a list of supported languages",
37
+ })
38
+ .demandOption(["source", "directory"]);
39
+ },
40
+ argv => {
41
+ void generate(argv as GenerateOptions);
42
+ },
43
+ )
44
+ .parse();
package/src/fetch.test.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  // SPDX-License-Identifier: Apache-2.0
2
- // SPDX-FileCopyrightText: 2023-Present The Pepr Authors
2
+ // SPDX-FileCopyrightText: 2023-Present The Kubernetes Fluent Client Authors
3
3
 
4
4
  import { expect, test, beforeEach } from "@jest/globals";
5
5
 
package/src/fetch.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  // SPDX-License-Identifier: Apache-2.0
2
- // SPDX-FileCopyrightText: 2023-Present The Pepr Authors
2
+ // SPDX-FileCopyrightText: 2023-Present The Kubernetes Fluent Client Authors
3
3
 
4
4
  import { StatusCodes } from "http-status-codes";
5
5
  import fetchRaw, { FetchError, RequestInfo, RequestInit } from "node-fetch";
@@ -1,5 +1,5 @@
1
1
  // SPDX-License-Identifier: Apache-2.0
2
- // SPDX-FileCopyrightText: 2023-Present The Pepr Authors
2
+ // SPDX-FileCopyrightText: 2023-Present The Kubernetes Fluent Client Authors
3
3
 
4
4
  /**
5
5
  * Configuration for the apply function.
@@ -1,5 +1,5 @@
1
1
  // SPDX-License-Identifier: Apache-2.0
2
- // SPDX-FileCopyrightText: 2023-Present The Pepr Authors
2
+ // SPDX-FileCopyrightText: 2023-Present The Kubernetes Fluent Client Authors
3
3
 
4
4
  import { KubernetesListObject, KubernetesObject } from "@kubernetes/client-node";
5
5
  import { Operation } from "fast-json-patch";
@@ -1,5 +1,5 @@
1
1
  // SPDX-License-Identifier: Apache-2.0
2
- // SPDX-FileCopyrightText: 2023-Present The Pepr Authors
2
+ // SPDX-FileCopyrightText: 2023-Present The Kubernetes Fluent Client Authors
3
3
 
4
4
  import { KubernetesListObject, KubernetesObject } from "@kubernetes/client-node";
5
5
  import { Operation } from "fast-json-patch";
@@ -1,5 +1,5 @@
1
1
  // SPDX-License-Identifier: Apache-2.0
2
- // SPDX-FileCopyrightText: 2023-Present The Pepr Authors
2
+ // SPDX-FileCopyrightText: 2023-Present The Kubernetes Fluent Client Authors
3
3
 
4
4
  import { beforeEach, describe, expect, it, jest } from "@jest/globals";
5
5
 
@@ -1,5 +1,5 @@
1
1
  // SPDX-License-Identifier: Apache-2.0
2
- // SPDX-FileCopyrightText: 2023-Present The Pepr Authors
2
+ // SPDX-FileCopyrightText: 2023-Present The Kubernetes Fluent Client Authors
3
3
 
4
4
  import { KubeConfig, PatchStrategy } from "@kubernetes/client-node";
5
5
 
@@ -1,5 +1,5 @@
1
1
  // SPDX-License-Identifier: Apache-2.0
2
- // SPDX-FileCopyrightText: 2023-Present The Pepr Authors
2
+ // SPDX-FileCopyrightText: 2023-Present The Kubernetes Fluent Client Authors
3
3
 
4
4
  import byline from "byline";
5
5
  import fetch from "node-fetch";
@@ -0,0 +1,266 @@
1
+ import { beforeEach, describe, expect, jest, test } from "@jest/globals";
2
+ import { generate } from "./generate";
3
+ import fs from "fs";
4
+
5
+ const sampleYaml = `
6
+ # non-crd should be ignored
7
+ apiVersion: v1
8
+ kind: ConfigMap
9
+ metadata:
10
+ name: test
11
+ namespace: default
12
+ data:
13
+ any: bleh
14
+ ---
15
+ apiVersion: apiextensions.k8s.io/v1
16
+ kind: CustomResourceDefinition
17
+ metadata:
18
+ name: movies.example.com
19
+ spec:
20
+ group: example.com
21
+ names:
22
+ kind: Movie
23
+ plural: movies
24
+ scope: Namespaced
25
+ versions:
26
+ - name: v1
27
+ schema:
28
+ openAPIV3Schema:
29
+ type: object
30
+ description: Movie nerd
31
+ properties:
32
+ spec:
33
+ properties:
34
+ title:
35
+ type: string
36
+ author:
37
+ type: string
38
+ type: object
39
+ ---
40
+ # duplicate entries should not break things
41
+ apiVersion: apiextensions.k8s.io/v1
42
+ kind: CustomResourceDefinition
43
+ metadata:
44
+ name: movies.example.com
45
+ spec:
46
+ group: example.com
47
+ names:
48
+ kind: Movie
49
+ plural: movies
50
+ scope: Namespaced
51
+ versions:
52
+ - name: v1
53
+ schema:
54
+ openAPIV3Schema:
55
+ type: object
56
+ description: Movie nerd
57
+ properties:
58
+ spec:
59
+ properties:
60
+ title:
61
+ type: string
62
+ author:
63
+ type: string
64
+ type: object
65
+ ---
66
+ # should support multiple versions
67
+ apiVersion: apiextensions.k8s.io/v1
68
+ kind: CustomResourceDefinition
69
+ metadata:
70
+ name: books.example.com
71
+ spec:
72
+ group: example.com
73
+ names:
74
+ kind: Book
75
+ plural: books
76
+ scope: Namespaced
77
+ versions:
78
+ - name: v1
79
+ schema:
80
+ openAPIV3Schema:
81
+ type: object
82
+ description: Book nerd
83
+ properties:
84
+ spec:
85
+ properties:
86
+ title:
87
+ type: string
88
+ author:
89
+ type: string
90
+ type: object
91
+ - name: v2
92
+ schema:
93
+ openAPIV3Schema:
94
+ type: object
95
+ description: Book nerd
96
+ properties:
97
+ spec:
98
+ properties:
99
+ author:
100
+ type: string
101
+ type: object
102
+ served: true
103
+ storage: true
104
+ `;
105
+
106
+ jest.mock("./fetch", () => ({
107
+ fetch: jest.fn(),
108
+ }));
109
+
110
+ jest.mock("./fluent", () => ({
111
+ K8s: jest.fn(),
112
+ }));
113
+
114
+ describe("CRD to TypeScript Conversion", () => {
115
+ const originalReadFileSync = fs.readFileSync;
116
+
117
+ jest.isolateModules(() => {
118
+ jest.spyOn(fs, "existsSync").mockReturnValue(true);
119
+ jest.spyOn(fs, "readFileSync").mockImplementation((...args) => {
120
+ // Super janky hack due ot source-map-support calling readFileSync internally
121
+ if (args[0].toString().includes("test-crd.yaml")) {
122
+ return sampleYaml;
123
+ }
124
+ return originalReadFileSync(...args);
125
+ });
126
+ jest.spyOn(fs, "mkdirSync").mockReturnValue(undefined);
127
+ jest.spyOn(fs, "writeFileSync").mockReturnValue(undefined);
128
+ });
129
+
130
+ beforeEach(() => {
131
+ jest.clearAllMocks();
132
+ });
133
+
134
+ test("converts CRD to TypeScript", async () => {
135
+ const options = { source: "test-crd.yaml", language: "ts" }; // specify your options
136
+
137
+ const actual = await generate(options);
138
+ const expectedMovie = [
139
+ "// This file is auto-generated by kubernetes-fluent-client, do not edit manually\n",
140
+ 'import { GenericKind, RegisterKind } from "kubernetes-fluent-client";\n',
141
+ "/**",
142
+ " * Movie nerd",
143
+ " */",
144
+ "export class Movie extends GenericKind {",
145
+ " spec?: Spec;",
146
+ "}",
147
+ "",
148
+ "export interface Spec {",
149
+ " author?: string;",
150
+ " title?: string;",
151
+ "}",
152
+ "",
153
+ "RegisterKind(Movie, {",
154
+ ' group: "example.com",',
155
+ ' version: "v1",',
156
+ ' kind: "Movie",',
157
+ "});",
158
+ ];
159
+ const expectedBookV1 = [
160
+ "// This file is auto-generated by kubernetes-fluent-client, do not edit manually\n",
161
+ 'import { GenericKind, RegisterKind } from "kubernetes-fluent-client";\n',
162
+ "/**",
163
+ " * Book nerd",
164
+ " */",
165
+ "export class Book extends GenericKind {",
166
+ " spec?: Spec;",
167
+ "}",
168
+ "",
169
+ "export interface Spec {",
170
+ " author?: string;",
171
+ " title?: string;",
172
+ "}",
173
+ "",
174
+ "RegisterKind(Book, {",
175
+ ' group: "example.com",',
176
+ ' version: "v1",',
177
+ ' kind: "Book",',
178
+ "});",
179
+ ];
180
+ const expectedBookV2 = expectedBookV1
181
+ .filter(line => !line.includes("title?"))
182
+ .map(line => line.replace("v1", "v2"));
183
+
184
+ expect(actual["movie-v1"]).toEqual(expectedMovie);
185
+ expect(actual["book-v1"]).toEqual(expectedBookV1);
186
+ expect(actual["book-v2"]).toEqual(expectedBookV2);
187
+ });
188
+
189
+ test("converts CRD to TypeScript with plain option", async () => {
190
+ const options = { source: "test-crd.yaml", language: "ts", plain: true }; // specify your options
191
+
192
+ const actual = await generate(options);
193
+ const expectedMovie = [
194
+ "/**",
195
+ " * Movie nerd",
196
+ " */",
197
+ "export interface Movie {",
198
+ " spec?: Spec;",
199
+ "}",
200
+ "",
201
+ "export interface Spec {",
202
+ " author?: string;",
203
+ " title?: string;",
204
+ "}",
205
+ "",
206
+ ];
207
+ const expectedBookV1 = [
208
+ "/**",
209
+ " * Book nerd",
210
+ " */",
211
+ "export interface Book {",
212
+ " spec?: Spec;",
213
+ "}",
214
+ "",
215
+ "export interface Spec {",
216
+ " author?: string;",
217
+ " title?: string;",
218
+ "}",
219
+ "",
220
+ ];
221
+ const expectedBookV2 = expectedBookV1
222
+ .filter(line => !line.includes("title?"))
223
+ .map(line => line.replace("v1", "v2"));
224
+
225
+ expect(actual["movie-v1"]).toEqual(expectedMovie);
226
+ expect(actual["book-v1"]).toEqual(expectedBookV1);
227
+ expect(actual["book-v2"]).toEqual(expectedBookV2);
228
+ });
229
+
230
+ test("converts CRD to Go", async () => {
231
+ const options = { source: "test-crd.yaml", language: "go" }; // specify your options
232
+
233
+ const actual = await generate(options);
234
+ const expectedMovie = [
235
+ "// Movie nerd",
236
+ "type Movie struct {",
237
+ '\tSpec *Spec `json:"spec,omitempty"`',
238
+ "}",
239
+ "",
240
+ "type Spec struct {",
241
+ '\tAuthor *string `json:"author,omitempty"`',
242
+ '\tTitle *string `json:"title,omitempty"`',
243
+ "}",
244
+ "",
245
+ ];
246
+ const expectedBookV1 = [
247
+ "// Book nerd",
248
+ "type Book struct {",
249
+ '\tSpec *Spec `json:"spec,omitempty"`',
250
+ "}",
251
+ "",
252
+ "type Spec struct {",
253
+ '\tAuthor *string `json:"author,omitempty"`',
254
+ '\tTitle *string `json:"title,omitempty"`',
255
+ "}",
256
+ "",
257
+ ];
258
+ const expectedBookV2 = expectedBookV1
259
+ .filter(line => !line.includes("Title"))
260
+ .map(line => line.replace("v1", "v2"));
261
+
262
+ expect(actual["movie-v1"]).toEqual(expectedMovie);
263
+ expect(actual["book-v1"]).toEqual(expectedBookV1);
264
+ expect(actual["book-v2"]).toEqual(expectedBookV2);
265
+ });
266
+ });
@@ -0,0 +1,189 @@
1
+ // SPDX-License-Identifier: Apache-2.0
2
+ // SPDX-FileCopyrightText: 2023-Present The Kubernetes Fluent Client Authors
3
+
4
+ import { loadAllYaml } from "@kubernetes/client-node";
5
+ import * as fs from "fs";
6
+ import * as path from "path";
7
+ import {
8
+ FetchingJSONSchemaStore,
9
+ InputData,
10
+ JSONSchemaInput,
11
+ TargetLanguage,
12
+ quicktype,
13
+ } from "quicktype-core";
14
+
15
+ import { fetch } from "./fetch";
16
+ import { K8s } from "./fluent";
17
+ import { CustomResourceDefinition } from "./upstream";
18
+
19
+ export interface GenerateOptions {
20
+ /** The source URL, yaml file path or K8s CRD name */
21
+ source: string;
22
+ /** The output directory path */
23
+ directory?: string;
24
+ /** Disable kubernetes-fluent-client wrapping */
25
+ plain?: boolean;
26
+ /** The language to generate types in */
27
+ language?: string | TargetLanguage;
28
+ }
29
+
30
+ /**
31
+ * Converts a CustomResourceDefinition to TypeScript types
32
+ *
33
+ * @param crd The CustomResourceDefinition to convert
34
+ * @param opts The options to use when converting
35
+ * @returns A promise that resolves when the CustomResourceDefinition has been converted
36
+ */
37
+ async function convertCRDtoTS(
38
+ crd: CustomResourceDefinition,
39
+ opts: GenerateOptions,
40
+ ): Promise<Record<string, string[]>> {
41
+ // Get the name of the kind
42
+ const name = crd.spec.names.kind;
43
+
44
+ const results: Record<string, string[]> = {};
45
+
46
+ for (const match of crd.spec.versions) {
47
+ // Get the schema from the matched version
48
+ const schema = JSON.stringify(match?.schema?.openAPIV3Schema);
49
+
50
+ // Create a new JSONSchemaInput
51
+ const schemaInput = new JSONSchemaInput(new FetchingJSONSchemaStore());
52
+
53
+ // Add the schema to the input
54
+ await schemaInput.addSource({ name, schema });
55
+
56
+ // Create a new InputData object
57
+ const inputData = new InputData();
58
+ inputData.addInput(schemaInput);
59
+
60
+ // Generate the types
61
+ const out = await quicktype({
62
+ inputData,
63
+ lang: opts.language || "ts",
64
+ rendererOptions: { "just-types": "true" },
65
+ });
66
+
67
+ let processedLines = out.lines;
68
+
69
+ // If using typescript, remove the line containing `[property: string]: any;`
70
+ if (opts.language === "ts" || opts.language === "typescript") {
71
+ processedLines = out.lines.filter(line => !line.includes("[property: string]: any;"));
72
+ }
73
+
74
+ // If the language is TypeScript and plain is not specified, wire up the fluent client
75
+ if (opts.language === "ts" && !opts.plain) {
76
+ processedLines.unshift(
77
+ // Add warning that the file is auto-generated
78
+ `// This file is auto-generated by kubernetes-fluent-client, do not edit manually\n`,
79
+ // Add the imports before any other lines
80
+ `import { GenericKind, RegisterKind } from "kubernetes-fluent-client";\n`,
81
+ );
82
+
83
+ // Replace the interface with a named class that extends GenericKind
84
+ const entryIdx = processedLines.findIndex(line =>
85
+ line.includes(`export interface ${name} {`),
86
+ );
87
+
88
+ // Replace the interface with a named class that extends GenericKind
89
+ processedLines[entryIdx] = `export class ${name} extends GenericKind {`;
90
+
91
+ // Add the RegisterKind call
92
+ processedLines.push(`RegisterKind(${name}, {`);
93
+ processedLines.push(` group: "${crd.spec.group}",`);
94
+ processedLines.push(` version: "${match.name}",`);
95
+ processedLines.push(` kind: "${name}",`);
96
+ processedLines.push(`});`);
97
+ }
98
+
99
+ const finalContents = processedLines.join("\n");
100
+ const fileName = `${name.toLowerCase()}-${match.name.toLowerCase()}`;
101
+
102
+ // If an output file is specified, write the output to the file
103
+ if (opts.directory) {
104
+ // Create the directory if it doesn't exist
105
+ fs.mkdirSync(opts.directory, { recursive: true });
106
+
107
+ // Write the file
108
+ const filePath = path.join(opts.directory, `${fileName}.${opts.language}`);
109
+ fs.writeFileSync(filePath, finalContents);
110
+ }
111
+
112
+ // Add the results to the array
113
+ results[fileName] = processedLines;
114
+ }
115
+
116
+ return results;
117
+ }
118
+
119
+ /**
120
+ * Reads a CustomResourceDefinition from a file, the cluster or the internet
121
+ *
122
+ * @param source The source to read from (file path, cluster or URL)
123
+ * @returns A promise that resolves when the CustomResourceDefinition has been read
124
+ */
125
+ async function readOrFetchCrd(source: string): Promise<CustomResourceDefinition[]> {
126
+ const filePath = path.join(process.cwd(), source);
127
+
128
+ // First try to read the source as a file
129
+ try {
130
+ if (fs.existsSync(filePath)) {
131
+ const payload = fs.readFileSync(filePath, "utf8");
132
+ return loadAllYaml(payload) as CustomResourceDefinition[];
133
+ }
134
+ } catch (e) {
135
+ // Ignore errors
136
+ }
137
+
138
+ // Next try to parse the source as a URL
139
+ try {
140
+ const url = new URL(source);
141
+
142
+ // If the source is a URL, fetch it
143
+ if (url.protocol === "http:" || url.protocol === "https:") {
144
+ const { ok, data } = await fetch<string>(source);
145
+
146
+ // If the request failed, throw an error
147
+ if (!ok) {
148
+ throw new Error(`Failed to fetch ${source}: ${data}`);
149
+ }
150
+
151
+ return loadAllYaml(data) as CustomResourceDefinition[];
152
+ }
153
+ } catch (e) {
154
+ // Ignore errors
155
+ }
156
+
157
+ // Finally, if the source is not a file or URL, try to read it as a CustomResourceDefinition from the cluster
158
+ try {
159
+ return [await K8s(CustomResourceDefinition).Get(source)];
160
+ } catch (e) {
161
+ throw new Error(`Failed to read ${source} as a file, url or K8s CRD: ${e}`);
162
+ }
163
+ }
164
+
165
+ /**
166
+ * Generate TypeScript types from a K8s CRD
167
+ *
168
+ * @param opts The options to use when generating
169
+ * @returns A promise that resolves when the TypeScript types have been generated
170
+ */
171
+ export async function generate(opts: GenerateOptions) {
172
+ const crds = await readOrFetchCrd(opts.source);
173
+ const results: Record<string, string[]> = {};
174
+
175
+ for (const crd of crds) {
176
+ if (!crd || crd.kind !== "CustomResourceDefinition" || !crd.spec?.versions?.length) {
177
+ // Ignore empty and non-CRD objects
178
+ continue;
179
+ }
180
+
181
+ // Add the results to the record
182
+ const out = await convertCRDtoTS(crd, opts);
183
+ for (const key of Object.keys(out)) {
184
+ results[key] = out[key];
185
+ }
186
+ }
187
+
188
+ return results;
189
+ }
package/src/index.ts CHANGED
@@ -19,6 +19,9 @@ export { K8s } from "./fluent";
19
19
  // Export helpers for working with K8s types
20
20
  export { RegisterKind, modelToGroupVersionKind } from "./kinds";
21
21
 
22
+ // Export the GenericKind interface for CRD registration
23
+ export { GenericKind } from "./types";
24
+
22
25
  export * from "./types";
23
26
 
24
27
  export * as K8sClientNode from "@kubernetes/client-node";