kubernetes-fluent-client 3.0.3 → 4.0.0-rc-http2-watch

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 (42) hide show
  1. package/.prettierignore +4 -0
  2. package/README.md +24 -0
  3. package/dist/cli.js +21 -1
  4. package/dist/fileSystem.d.ts +11 -0
  5. package/dist/fileSystem.d.ts.map +1 -0
  6. package/dist/fileSystem.js +42 -0
  7. package/dist/fileSystem.test.d.ts +2 -0
  8. package/dist/fileSystem.test.d.ts.map +1 -0
  9. package/dist/fileSystem.test.js +75 -0
  10. package/dist/fluent/watch.d.ts +2 -0
  11. package/dist/fluent/watch.d.ts.map +1 -1
  12. package/dist/fluent/watch.js +147 -27
  13. package/dist/generate.d.ts +71 -11
  14. package/dist/generate.d.ts.map +1 -1
  15. package/dist/generate.js +130 -117
  16. package/dist/generate.test.js +293 -346
  17. package/dist/postProcessing.d.ts +246 -0
  18. package/dist/postProcessing.d.ts.map +1 -0
  19. package/dist/postProcessing.js +497 -0
  20. package/dist/postProcessing.test.d.ts +2 -0
  21. package/dist/postProcessing.test.d.ts.map +1 -0
  22. package/dist/postProcessing.test.js +550 -0
  23. package/e2e/cli.e2e.test.ts +127 -0
  24. package/e2e/crds/policyreports.default.expected/policyreport-v1alpha1.ts +332 -0
  25. package/e2e/crds/policyreports.default.expected/policyreport-v1alpha2.ts +360 -0
  26. package/e2e/crds/policyreports.default.expected/policyreport-v1beta1.ts +360 -0
  27. package/e2e/crds/policyreports.no.post.expected/policyreport-v1alpha1.ts +331 -0
  28. package/e2e/crds/policyreports.no.post.expected/policyreport-v1alpha2.ts +360 -0
  29. package/e2e/crds/policyreports.no.post.expected/policyreport-v1beta1.ts +360 -0
  30. package/e2e/crds/test.yaml/policyreports.test.yaml +1008 -0
  31. package/e2e/crds/test.yaml/uds-podmonitors.test.yaml +1245 -0
  32. package/e2e/crds/uds-podmonitors.default.expected/podmonitor-v1.ts +1333 -0
  33. package/e2e/crds/uds-podmonitors.no.post.expected/podmonitor-v1.ts +1360 -0
  34. package/package.json +6 -5
  35. package/src/cli.ts +25 -1
  36. package/src/fileSystem.test.ts +67 -0
  37. package/src/fileSystem.ts +25 -0
  38. package/src/fluent/watch.ts +174 -35
  39. package/src/generate.test.ts +368 -358
  40. package/src/generate.ts +173 -154
  41. package/src/postProcessing.test.ts +742 -0
  42. package/src/postProcessing.ts +568 -0
package/src/generate.ts CHANGED
@@ -18,197 +18,219 @@ import { CustomResourceDefinition } from "./upstream";
18
18
  import { LogFn } from "./types";
19
19
 
20
20
  export interface GenerateOptions {
21
- /** The source URL, yaml file path or K8s CRD name */
22
- source: string;
23
- /** The output directory path */
24
- directory?: string;
25
- /** Disable kubernetes-fluent-client wrapping */
26
- plain?: boolean;
27
- /** The language to generate types in */
21
+ source: string; // URL, file path, or K8s CRD name
22
+ directory?: string; // Output directory path
23
+ plain?: boolean; // Disable fluent client wrapping
28
24
  language?: string | TargetLanguage;
29
- /** Override the NPM package to import when generating formatted Typescript */
30
- npmPackage?: string;
31
- /** Log function callback */
32
- logFn: LogFn;
25
+ npmPackage?: string; // Override NPM package
26
+ logFn: LogFn; // Log function callback
27
+ noPost?: boolean; // Enable/disable post-processing
33
28
  }
34
29
 
35
30
  /**
36
31
  * Converts a CustomResourceDefinition to TypeScript types
37
32
  *
38
- * @param crd The CustomResourceDefinition to convert
39
- * @param opts The options to use when converting
40
- * @returns A promise that resolves when the CustomResourceDefinition has been converted
33
+ * @param crd - The CustomResourceDefinition object to convert.
34
+ * @param opts - The options for generating the TypeScript types.
35
+ * @returns A promise that resolves to a record of generated TypeScript types.
41
36
  */
42
- async function convertCRDtoTS(
37
+ export async function convertCRDtoTS(
43
38
  crd: CustomResourceDefinition,
44
39
  opts: GenerateOptions,
45
- ): Promise<Record<string, string[]>> {
46
- // Get the name of the kind
40
+ ): Promise<
41
+ {
42
+ results: Record<string, string[]>;
43
+ name: string;
44
+ crd: CustomResourceDefinition;
45
+ version: string;
46
+ }[]
47
+ > {
47
48
  const name = crd.spec.names.kind;
48
-
49
49
  const results: Record<string, string[]> = {};
50
+ const output: {
51
+ results: Record<string, string[]>;
52
+ name: string;
53
+ crd: CustomResourceDefinition;
54
+ version: string;
55
+ }[] = [];
56
+
57
+ // Check for missing versions or empty schema
58
+ if (!crd.spec.versions || crd.spec.versions.length === 0) {
59
+ opts.logFn(`Skipping ${crd.metadata?.name}, it does not appear to be a CRD`);
60
+ return [];
61
+ }
50
62
 
63
+ // Iterate through each version of the CRD
51
64
  for (const match of crd.spec.versions) {
52
- const version = match.name;
53
-
54
- // Get the schema from the matched version
55
- const schema = JSON.stringify(match?.schema?.openAPIV3Schema);
56
-
57
- // Create a new JSONSchemaInput
58
- const schemaInput = new JSONSchemaInput(new FetchingJSONSchemaStore());
59
-
60
- opts.logFn(`- Generating ${crd.spec.group}/${version} types for ${name}`);
61
-
62
- // Add the schema to the input
63
- await schemaInput.addSource({ name, schema });
64
-
65
- // Create a new InputData object
66
- const inputData = new InputData();
67
- inputData.addInput(schemaInput);
68
-
69
- // If the language is not specified, default to TypeScript
70
- if (!opts.language) {
71
- opts.language = "ts";
72
- }
73
-
74
- // Generate the types
75
- const out = await quicktype({
76
- inputData,
77
- lang: opts.language,
78
- rendererOptions: { "just-types": "true" },
79
- });
80
-
81
- let processedLines = out.lines;
82
-
83
- // If using typescript, remove the line containing `[property: string]: any;`
84
- if (opts.language === "ts" || opts.language === "typescript") {
85
- processedLines = out.lines.filter(line => !line.includes("[property: string]: any;"));
65
+ if (!match.schema?.openAPIV3Schema) {
66
+ opts.logFn(
67
+ `Skipping ${crd.metadata?.name ?? "unknown"}, it does not appear to have a valid schema`,
68
+ );
69
+ continue;
86
70
  }
87
71
 
88
- // If the language is TypeScript and plain is not specified, wire up the fluent client
89
- if (opts.language === "ts" && !opts.plain) {
90
- if (!opts.npmPackage) {
91
- opts.npmPackage = "kubernetes-fluent-client";
92
- }
93
-
94
- processedLines.unshift(
95
- // Add warning that the file is auto-generated
96
- `// This file is auto-generated by ${opts.npmPackage}, do not edit manually\n`,
97
- // Add the imports before any other lines
98
- `import { GenericKind, RegisterKind } from "${opts.npmPackage}";\n`,
99
- );
72
+ const schema = JSON.stringify(match.schema.openAPIV3Schema);
73
+ opts.logFn(`- Generating ${crd.spec.group}/${match.name} types for ${name}`);
100
74
 
101
- // Replace the interface with a named class that extends GenericKind
102
- const entryIdx = processedLines.findIndex(line =>
103
- line.includes(`export interface ${name} {`),
104
- );
75
+ const inputData = await prepareInputData(name, schema);
76
+ const generatedTypes = await generateTypes(inputData, opts);
105
77
 
106
- // Replace the interface with a named class that extends GenericKind
107
- processedLines[entryIdx] = `export class ${name} extends GenericKind {`;
78
+ const fileName = `${name.toLowerCase()}-${match.name.toLowerCase()}`;
79
+ writeGeneratedFile(fileName, opts.directory || "", generatedTypes, opts.language || "ts");
108
80
 
109
- // Add the RegisterKind call
110
- processedLines.push(`RegisterKind(${name}, {`);
111
- processedLines.push(` group: "${crd.spec.group}",`);
112
- processedLines.push(` version: "${version}",`);
113
- processedLines.push(` kind: "${name}",`);
114
- processedLines.push(` plural: "${crd.spec.names.plural}",`);
115
- processedLines.push(`});`);
116
- }
81
+ results[fileName] = generatedTypes;
82
+ output.push({ results, name, crd, version: match.name });
83
+ }
117
84
 
118
- const finalContents = processedLines.join("\n");
119
- const fileName = `${name.toLowerCase()}-${version.toLowerCase()}`;
85
+ return output;
86
+ }
120
87
 
121
- // If an output file is specified, write the output to the file
122
- if (opts.directory) {
123
- // Create the directory if it doesn't exist
124
- fs.mkdirSync(opts.directory, { recursive: true });
88
+ /**
89
+ * Prepares the input data for quicktype from the provided schema.
90
+ *
91
+ * @param name - The name of the schema.
92
+ * @param schema - The JSON schema as a string.
93
+ * @returns A promise that resolves to the input data for quicktype.
94
+ */
95
+ export async function prepareInputData(name: string, schema: string): Promise<InputData> {
96
+ // Create a new JSONSchemaInput
97
+ const schemaInput = new JSONSchemaInput(new FetchingJSONSchemaStore());
125
98
 
126
- // Write the file
127
- const filePath = path.join(opts.directory, `${fileName}.${opts.language}`);
128
- fs.writeFileSync(filePath, finalContents);
129
- }
99
+ // Add the schema to the input
100
+ await schemaInput.addSource({ name, schema });
130
101
 
131
- // Add the results to the array
132
- results[fileName] = processedLines;
133
- }
102
+ // Create a new InputData object
103
+ const inputData = new InputData();
104
+ inputData.addInput(schemaInput);
134
105
 
135
- return results;
106
+ return inputData;
136
107
  }
137
108
 
138
109
  /**
139
- * Reads a CustomResourceDefinition from a file, the cluster or the internet
110
+ * Generates TypeScript types using quicktype.
140
111
  *
141
- * @param opts The options to use when reading
142
- * @returns A promise that resolves when the CustomResourceDefinition has been read
112
+ * @param inputData - The input data for quicktype.
113
+ * @param opts - The options for generating the TypeScript types.
114
+ * @returns A promise that resolves to an array of generated TypeScript type lines.
143
115
  */
144
- async function readOrFetchCrd(opts: GenerateOptions): Promise<CustomResourceDefinition[]> {
145
- const { source, logFn } = opts;
146
- let filePath: string;
116
+ export async function generateTypes(
117
+ inputData: InputData,
118
+ opts: GenerateOptions,
119
+ ): Promise<string[]> {
120
+ // If the language is not specified, default to TypeScript
121
+ const language = opts.language || "ts";
122
+
123
+ // Generate the types
124
+ const out = await quicktype({
125
+ inputData,
126
+ lang: language,
127
+ rendererOptions: { "just-types": "true" },
128
+ });
129
+
130
+ return out.lines;
131
+ }
147
132
 
148
- if (source[0] === "/") {
149
- // If source is an absolute path
150
- filePath = source;
151
- } else {
152
- // If source is a relative path
153
- filePath = path.join(process.cwd(), source);
154
- }
133
+ /**
134
+ * Writes the processed lines to the output file.
135
+ *
136
+ * @param fileName - The name of the file to write.
137
+ * @param directory - The directory where the file will be written.
138
+ * @param content - The content to write to the file.
139
+ * @param language - The programming language of the file.
140
+ */
141
+ export function writeGeneratedFile(
142
+ fileName: string,
143
+ directory: string,
144
+ content: string[],
145
+ language: string | TargetLanguage,
146
+ ): void {
147
+ language = language || "ts";
148
+ if (!directory) return;
149
+
150
+ const filePath = path.join(directory, `${fileName}.${language}`);
151
+ fs.mkdirSync(directory, { recursive: true });
152
+ fs.writeFileSync(filePath, content.join("\n"));
153
+ }
155
154
 
156
- // First try to read the source as a file
155
+ /**
156
+ * Reads or fetches a CustomResourceDefinition from a file, URL, or the cluster.
157
+ *
158
+ * @param opts - The options for generating the TypeScript types.
159
+ * @returns A promise that resolves to an array of CustomResourceDefinition objects.
160
+ */
161
+ export async function readOrFetchCrd(opts: GenerateOptions): Promise<CustomResourceDefinition[]> {
157
162
  try {
163
+ const filePath = resolveFilePath(opts.source);
164
+
158
165
  if (fs.existsSync(filePath)) {
159
- logFn(`Attempting to load ${source} as a local file`);
160
- const payload = fs.readFileSync(filePath, "utf8");
161
- return loadAllYaml(payload) as CustomResourceDefinition[];
166
+ opts.logFn(`Attempting to load ${opts.source} as a local file`);
167
+ const content = fs.readFileSync(filePath, "utf8");
168
+ return loadAllYaml(content) as CustomResourceDefinition[];
162
169
  }
163
- } catch {
164
- // Ignore errors
165
- }
166
-
167
- // Next try to parse the source as a URL
168
- try {
169
- const url = new URL(source);
170
170
 
171
- // If the source is a URL, fetch it
172
- if (url.protocol === "http:" || url.protocol === "https:") {
173
- logFn(`Attempting to load ${source} as a URL`);
174
- const { ok, data } = await fetch<string>(source);
175
-
176
- // If the request failed, throw an error
177
- if (!ok) {
178
- throw new Error(`Failed to fetch ${source}: ${data}`);
171
+ const url = tryParseUrl(opts.source);
172
+ if (url) {
173
+ opts.logFn(`Attempting to load ${opts.source} as a URL`);
174
+ const { ok, data } = await fetch<string>(url.href);
175
+ if (ok) {
176
+ return loadAllYaml(data) as CustomResourceDefinition[];
179
177
  }
180
-
181
- return loadAllYaml(data) as CustomResourceDefinition[];
182
- }
183
- } catch (e) {
184
- // If invalid, ignore the error
185
- if (e.code !== "ERR_INVALID_URL") {
186
- throw new Error(`Error parsing URL ${source}`);
187
178
  }
179
+
180
+ // Fallback to Kubernetes cluster
181
+ opts.logFn(`Attempting to read ${opts.source} from the Kubernetes cluster`);
182
+ return [await K8s(CustomResourceDefinition).Get(opts.source)];
183
+ } catch (error) {
184
+ opts.logFn(`Error loading CRD: ${error.message}`);
185
+ throw new Error(`Failed to read ${opts.source} as a file, URL, or Kubernetes CRD`);
188
186
  }
187
+ }
188
+
189
+ /**
190
+ * Resolves the source file path, treating relative paths as local files.
191
+ *
192
+ * @param source - The source path to resolve.
193
+ * @returns The resolved file path.
194
+ */
195
+ export function resolveFilePath(source: string): string {
196
+ return source.startsWith("/") ? source : path.join(process.cwd(), source);
197
+ }
189
198
 
190
- // Finally, if the source is not a file or URL, try to read it as a CustomResourceDefinition from the cluster
199
+ /**
200
+ * Tries to parse the source as a URL.
201
+ *
202
+ * @param source - The source string to parse as a URL.
203
+ * @returns The parsed URL object or null if parsing fails.
204
+ */
205
+ export function tryParseUrl(source: string): URL | null {
191
206
  try {
192
- logFn(`Attempting to read ${source} from the current Kubernetes context`);
193
- return [await K8s(CustomResourceDefinition).Get(source)];
194
- } catch (e) {
195
- throw new Error(
196
- `Failed to read ${source} as a file, url or K8s CRD: ${
197
- e.data?.message || "Cluster not available"
198
- }`,
199
- );
207
+ return new URL(source);
208
+ } catch {
209
+ return null;
200
210
  }
201
211
  }
202
212
 
203
213
  /**
204
- * Generate TypeScript types from a K8s CRD
214
+ * Main generate function to convert CRDs to TypeScript types.
205
215
  *
206
- * @param opts The options to use when generating
207
- * @returns A promise that resolves when the TypeScript types have been generated
216
+ * @param opts - The options for generating the TypeScript types.
217
+ * @returns A promise that resolves to a record of generated TypeScript types.
208
218
  */
209
- export async function generate(opts: GenerateOptions) {
219
+ export async function generate(opts: GenerateOptions): Promise<
220
+ {
221
+ results: Record<string, string[]>;
222
+ name: string;
223
+ crd: CustomResourceDefinition;
224
+ version: string;
225
+ }[]
226
+ > {
210
227
  const crds = (await readOrFetchCrd(opts)).filter(crd => !!crd);
211
- const results: Record<string, string[]> = {};
228
+ const allResults: {
229
+ results: Record<string, string[]>;
230
+ name: string;
231
+ crd: CustomResourceDefinition;
232
+ version: string;
233
+ }[] = [];
212
234
 
213
235
  opts.logFn("");
214
236
 
@@ -219,19 +241,16 @@ export async function generate(opts: GenerateOptions) {
219
241
  continue;
220
242
  }
221
243
 
222
- // Add the results to the record
223
- const out = await convertCRDtoTS(crd, opts);
224
- for (const key of Object.keys(out)) {
225
- results[key] = out[key];
226
- }
244
+ allResults.push(...(await convertCRDtoTS(crd, opts)));
227
245
  }
228
246
 
229
247
  if (opts.directory) {
230
248
  // Notify the user that the files have been generated
231
- opts.logFn(
232
- `\n✅ Generated ${Object.keys(results).length} files in the ${opts.directory} directory`,
233
- );
249
+ opts.logFn(`\n✅ Generated ${allResults.length} files in the ${opts.directory} directory`);
250
+ } else {
251
+ // Log a message about the number of generated files even when no directory is provided
252
+ opts.logFn(`\n✅ Generated ${allResults.length} files`);
234
253
  }
235
254
 
236
- return results;
255
+ return allResults;
237
256
  }