klasik 1.0.0 → 1.0.2
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/example.md +2 -2
- package/package.json +2 -2
- package/jest.config.js +0 -10
- package/src/cli.ts +0 -103
- package/src/index.ts +0 -2
- package/src/k8s-client-generator.ts +0 -145
- package/src/spec-downloader.ts +0 -233
- package/test/k8s-client-generator.test.ts +0 -257
- package/tsconfig.json +0 -20
package/example.md
CHANGED
|
@@ -90,11 +90,11 @@ To test the package locally with the linked `openapi-class-transformer`:
|
|
|
90
90
|
|
|
91
91
|
```bash
|
|
92
92
|
# In openapi-class-transformer directory
|
|
93
|
-
cd
|
|
93
|
+
cd folder
|
|
94
94
|
npm link
|
|
95
95
|
|
|
96
96
|
# In klasik directory
|
|
97
|
-
cd
|
|
97
|
+
cd folder
|
|
98
98
|
npm link openapi-class-transformer
|
|
99
99
|
|
|
100
100
|
# Build and test
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "klasik",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.2",
|
|
4
4
|
"description": "Download OpenAPI specs from remote URLs and generate TypeScript clients with class-transformer support",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
@@ -27,7 +27,7 @@
|
|
|
27
27
|
"dependencies": {
|
|
28
28
|
"axios": "^1.6.0",
|
|
29
29
|
"commander": "^11.0.0",
|
|
30
|
-
"openapi-class-transformer": "
|
|
30
|
+
"openapi-class-transformer": "1.0.1"
|
|
31
31
|
},
|
|
32
32
|
"devDependencies": {
|
|
33
33
|
"@types/jest": "^29.5.0",
|
package/jest.config.js
DELETED
package/src/cli.ts
DELETED
|
@@ -1,103 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
import { Command } from 'commander';
|
|
4
|
-
import { K8sClientGenerator } from './k8s-client-generator';
|
|
5
|
-
import * as path from 'path';
|
|
6
|
-
|
|
7
|
-
const program = new Command();
|
|
8
|
-
|
|
9
|
-
program
|
|
10
|
-
.name('klasik')
|
|
11
|
-
.description('Download OpenAPI specs from remote URLs and generate TypeScript clients with class-transformer support')
|
|
12
|
-
.version('1.0.0');
|
|
13
|
-
|
|
14
|
-
program
|
|
15
|
-
.command('generate')
|
|
16
|
-
.description('Generate TypeScript client from a remote OpenAPI spec')
|
|
17
|
-
.requiredOption('-u, --url <url>', 'Remote URL to download the OpenAPI spec from')
|
|
18
|
-
.requiredOption('-o, --output <dir>', 'Output directory for generated client code')
|
|
19
|
-
.option('-m, --mode <mode>', 'Generation mode: "full" (models + APIs + config) or "models-only"', 'full')
|
|
20
|
-
.option('-H, --header <header...>', 'Custom headers for the request (format: "Key: Value")')
|
|
21
|
-
.option('-t, --template <dir>', 'Custom template directory')
|
|
22
|
-
.option('-k, --keep-spec', 'Keep the downloaded spec file after generation', false)
|
|
23
|
-
.option('--timeout <ms>', 'Request timeout in milliseconds', '30000')
|
|
24
|
-
.action(async (options) => {
|
|
25
|
-
try {
|
|
26
|
-
// Validate mode
|
|
27
|
-
if (options.mode !== 'full' && options.mode !== 'models-only') {
|
|
28
|
-
console.error('Error: --mode must be either "full" or "models-only"');
|
|
29
|
-
process.exit(1);
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
// Parse headers if provided
|
|
33
|
-
const headers: Record<string, string> = {};
|
|
34
|
-
if (options.header) {
|
|
35
|
-
for (const header of options.header) {
|
|
36
|
-
const [key, ...valueParts] = header.split(':');
|
|
37
|
-
const value = valueParts.join(':').trim();
|
|
38
|
-
if (key && value) {
|
|
39
|
-
headers[key.trim()] = value;
|
|
40
|
-
}
|
|
41
|
-
}
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
// Resolve output directory to absolute path
|
|
45
|
-
const outputDir = path.resolve(options.output);
|
|
46
|
-
|
|
47
|
-
const generator = new K8sClientGenerator();
|
|
48
|
-
await generator.generate({
|
|
49
|
-
specUrl: options.url,
|
|
50
|
-
outputDir,
|
|
51
|
-
mode: options.mode,
|
|
52
|
-
headers: Object.keys(headers).length > 0 ? headers : undefined,
|
|
53
|
-
templateDir: options.template,
|
|
54
|
-
keepSpec: options.keepSpec,
|
|
55
|
-
timeout: parseInt(options.timeout, 10),
|
|
56
|
-
});
|
|
57
|
-
|
|
58
|
-
process.exit(0);
|
|
59
|
-
} catch (error) {
|
|
60
|
-
console.error('Error:', error instanceof Error ? error.message : error);
|
|
61
|
-
process.exit(1);
|
|
62
|
-
}
|
|
63
|
-
});
|
|
64
|
-
|
|
65
|
-
program
|
|
66
|
-
.command('download')
|
|
67
|
-
.description('Download an OpenAPI spec from a remote URL (without generating)')
|
|
68
|
-
.requiredOption('-u, --url <url>', 'Remote URL to download the OpenAPI spec from')
|
|
69
|
-
.requiredOption('-o, --output <file>', 'Output file path for the downloaded spec')
|
|
70
|
-
.option('-H, --header <header...>', 'Custom headers for the request (format: "Key: Value")')
|
|
71
|
-
.option('--timeout <ms>', 'Request timeout in milliseconds', '30000')
|
|
72
|
-
.action(async (options) => {
|
|
73
|
-
try {
|
|
74
|
-
const { SpecDownloader } = await import('./spec-downloader');
|
|
75
|
-
|
|
76
|
-
// Parse headers if provided
|
|
77
|
-
const headers: Record<string, string> = {};
|
|
78
|
-
if (options.header) {
|
|
79
|
-
for (const header of options.header) {
|
|
80
|
-
const [key, ...valueParts] = header.split(':');
|
|
81
|
-
const value = valueParts.join(':').trim();
|
|
82
|
-
if (key && value) {
|
|
83
|
-
headers[key.trim()] = value;
|
|
84
|
-
}
|
|
85
|
-
}
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
const downloader = new SpecDownloader();
|
|
89
|
-
await downloader.download({
|
|
90
|
-
url: options.url,
|
|
91
|
-
outputPath: path.resolve(options.output),
|
|
92
|
-
headers: Object.keys(headers).length > 0 ? headers : undefined,
|
|
93
|
-
timeout: parseInt(options.timeout, 10),
|
|
94
|
-
});
|
|
95
|
-
|
|
96
|
-
process.exit(0);
|
|
97
|
-
} catch (error) {
|
|
98
|
-
console.error('Error:', error instanceof Error ? error.message : error);
|
|
99
|
-
process.exit(1);
|
|
100
|
-
}
|
|
101
|
-
});
|
|
102
|
-
|
|
103
|
-
program.parse();
|
package/src/index.ts
DELETED
|
@@ -1,145 +0,0 @@
|
|
|
1
|
-
import { Generator, GeneratorOptions } from 'openapi-class-transformer';
|
|
2
|
-
import { SpecDownloader, DownloadOptions } from './spec-downloader';
|
|
3
|
-
import * as fs from 'fs';
|
|
4
|
-
|
|
5
|
-
export type GenerationMode = 'full' | 'models-only';
|
|
6
|
-
|
|
7
|
-
export interface K8sClientGeneratorOptions {
|
|
8
|
-
/**
|
|
9
|
-
* Remote URL to download the OpenAPI spec from
|
|
10
|
-
*/
|
|
11
|
-
specUrl: string;
|
|
12
|
-
|
|
13
|
-
/**
|
|
14
|
-
* Output directory for generated client code
|
|
15
|
-
*/
|
|
16
|
-
outputDir: string;
|
|
17
|
-
|
|
18
|
-
/**
|
|
19
|
-
* Generation mode
|
|
20
|
-
* - 'full': Generate models, APIs, and configuration (default)
|
|
21
|
-
* - 'models-only': Generate only model classes
|
|
22
|
-
* @default 'full'
|
|
23
|
-
*/
|
|
24
|
-
mode?: GenerationMode;
|
|
25
|
-
|
|
26
|
-
/**
|
|
27
|
-
* Optional headers to include in the spec download request
|
|
28
|
-
*/
|
|
29
|
-
headers?: Record<string, string>;
|
|
30
|
-
|
|
31
|
-
/**
|
|
32
|
-
* Optional custom template directory
|
|
33
|
-
*/
|
|
34
|
-
templateDir?: string;
|
|
35
|
-
|
|
36
|
-
/**
|
|
37
|
-
* Whether to keep the downloaded spec file after generation
|
|
38
|
-
* @default false
|
|
39
|
-
*/
|
|
40
|
-
keepSpec?: boolean;
|
|
41
|
-
|
|
42
|
-
/**
|
|
43
|
-
* Request timeout for downloading spec in milliseconds
|
|
44
|
-
* @default 30000
|
|
45
|
-
*/
|
|
46
|
-
timeout?: number;
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
export class K8sClientGenerator {
|
|
50
|
-
private downloader: SpecDownloader;
|
|
51
|
-
|
|
52
|
-
constructor() {
|
|
53
|
-
this.downloader = new SpecDownloader();
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
/**
|
|
57
|
-
* Generate TypeScript client from a remote OpenAPI spec URL
|
|
58
|
-
*/
|
|
59
|
-
async generate(options: K8sClientGeneratorOptions): Promise<void> {
|
|
60
|
-
const {
|
|
61
|
-
specUrl,
|
|
62
|
-
outputDir,
|
|
63
|
-
mode = 'full',
|
|
64
|
-
headers,
|
|
65
|
-
templateDir,
|
|
66
|
-
keepSpec = false,
|
|
67
|
-
timeout,
|
|
68
|
-
} = options;
|
|
69
|
-
|
|
70
|
-
let specPath: string | undefined;
|
|
71
|
-
|
|
72
|
-
try {
|
|
73
|
-
// Step 1: Download the OpenAPI spec
|
|
74
|
-
console.log('Step 1: Downloading OpenAPI specification...');
|
|
75
|
-
const downloadOptions: DownloadOptions = {
|
|
76
|
-
url: specUrl,
|
|
77
|
-
headers,
|
|
78
|
-
timeout,
|
|
79
|
-
};
|
|
80
|
-
|
|
81
|
-
specPath = await this.downloader.download(downloadOptions);
|
|
82
|
-
|
|
83
|
-
// Step 2: Validate output directory
|
|
84
|
-
console.log('Step 2: Preparing output directory...');
|
|
85
|
-
this.prepareOutputDirectory(outputDir);
|
|
86
|
-
|
|
87
|
-
// Step 3: Generate client using openapi-class-transformer
|
|
88
|
-
const modeLabel = mode === 'models-only' ? 'models only' : 'full client';
|
|
89
|
-
console.log(`Step 3: Generating TypeScript ${modeLabel}...`);
|
|
90
|
-
const generatorOptions: GeneratorOptions = {
|
|
91
|
-
inputSpec: specPath,
|
|
92
|
-
outputDir,
|
|
93
|
-
modelsOnly: mode === 'models-only',
|
|
94
|
-
};
|
|
95
|
-
|
|
96
|
-
// Only add templateDir if it's provided
|
|
97
|
-
if (templateDir) {
|
|
98
|
-
generatorOptions.templateDir = templateDir;
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
const generator = new Generator(generatorOptions);
|
|
102
|
-
await generator.generate();
|
|
103
|
-
|
|
104
|
-
console.log('✅ Client generation completed successfully!');
|
|
105
|
-
console.log(`📁 Generated files location: ${outputDir}`);
|
|
106
|
-
} catch (error) {
|
|
107
|
-
console.error('❌ Client generation failed:', error);
|
|
108
|
-
throw error;
|
|
109
|
-
} finally {
|
|
110
|
-
// Cleanup
|
|
111
|
-
if (!keepSpec && specPath) {
|
|
112
|
-
this.cleanupSpecFile(specPath);
|
|
113
|
-
}
|
|
114
|
-
}
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
/**
|
|
118
|
-
* Prepare the output directory
|
|
119
|
-
*/
|
|
120
|
-
private prepareOutputDirectory(outputDir: string): void {
|
|
121
|
-
if (!fs.existsSync(outputDir)) {
|
|
122
|
-
fs.mkdirSync(outputDir, { recursive: true });
|
|
123
|
-
console.log(`Created output directory: ${outputDir}`);
|
|
124
|
-
} else {
|
|
125
|
-
console.log(`Using existing output directory: ${outputDir}`);
|
|
126
|
-
}
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
/**
|
|
130
|
-
* Clean up the downloaded spec file
|
|
131
|
-
*/
|
|
132
|
-
private cleanupSpecFile(specPath: string): void {
|
|
133
|
-
try {
|
|
134
|
-
if (fs.existsSync(specPath)) {
|
|
135
|
-
fs.unlinkSync(specPath);
|
|
136
|
-
console.log(`Cleaned up downloaded spec file: ${specPath}`);
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
// Also cleanup temp directory if empty
|
|
140
|
-
this.downloader.cleanupTemp();
|
|
141
|
-
} catch (error) {
|
|
142
|
-
console.warn('Warning: Failed to cleanup spec file:', error);
|
|
143
|
-
}
|
|
144
|
-
}
|
|
145
|
-
}
|
package/src/spec-downloader.ts
DELETED
|
@@ -1,233 +0,0 @@
|
|
|
1
|
-
import axios from 'axios';
|
|
2
|
-
import * as fs from 'fs';
|
|
3
|
-
import * as path from 'path';
|
|
4
|
-
|
|
5
|
-
export interface DownloadOptions {
|
|
6
|
-
/**
|
|
7
|
-
* URL or file path to the OpenAPI spec
|
|
8
|
-
* Supports:
|
|
9
|
-
* - HTTP/HTTPS URLs: https://example.com/spec.json
|
|
10
|
-
* - File URLs: file:///path/to/spec.json
|
|
11
|
-
* - Absolute paths: /path/to/spec.json
|
|
12
|
-
* - Relative paths: ./spec.json, ../specs/api.json
|
|
13
|
-
*/
|
|
14
|
-
url: string;
|
|
15
|
-
|
|
16
|
-
/**
|
|
17
|
-
* Optional output path to save the downloaded spec
|
|
18
|
-
* If not provided, will save to a temp file (for HTTP) or use the source path (for files)
|
|
19
|
-
*/
|
|
20
|
-
outputPath?: string;
|
|
21
|
-
|
|
22
|
-
/**
|
|
23
|
-
* Optional headers to include in the request (only used for HTTP/HTTPS)
|
|
24
|
-
*/
|
|
25
|
-
headers?: Record<string, string>;
|
|
26
|
-
|
|
27
|
-
/**
|
|
28
|
-
* Request timeout in milliseconds (only used for HTTP/HTTPS)
|
|
29
|
-
* @default 30000
|
|
30
|
-
*/
|
|
31
|
-
timeout?: number;
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
export class SpecDownloader {
|
|
35
|
-
/**
|
|
36
|
-
* Download an OpenAPI specification from a URL or load from a local file
|
|
37
|
-
* @param options Download options
|
|
38
|
-
* @returns Path to the spec file
|
|
39
|
-
*/
|
|
40
|
-
async download(options: DownloadOptions): Promise<string> {
|
|
41
|
-
const { url, outputPath, headers, timeout = 30000 } = options;
|
|
42
|
-
|
|
43
|
-
// Check if it's a local file path
|
|
44
|
-
if (this.isLocalFile(url)) {
|
|
45
|
-
return this.loadLocalFile(url, outputPath);
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
// Otherwise, download from HTTP/HTTPS
|
|
49
|
-
console.log(`Downloading OpenAPI spec from ${url}...`);
|
|
50
|
-
|
|
51
|
-
try {
|
|
52
|
-
const response = await axios.get(url, {
|
|
53
|
-
headers,
|
|
54
|
-
timeout,
|
|
55
|
-
responseType: 'text',
|
|
56
|
-
});
|
|
57
|
-
|
|
58
|
-
// Determine output path
|
|
59
|
-
const specPath = outputPath || this.generateTempPath(url);
|
|
60
|
-
|
|
61
|
-
// Ensure directory exists
|
|
62
|
-
const dir = path.dirname(specPath);
|
|
63
|
-
if (!fs.existsSync(dir)) {
|
|
64
|
-
fs.mkdirSync(dir, { recursive: true });
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
// Parse and validate the spec
|
|
68
|
-
let specContent: string;
|
|
69
|
-
if (typeof response.data === 'string') {
|
|
70
|
-
// Try to parse as JSON to validate
|
|
71
|
-
try {
|
|
72
|
-
const parsed = JSON.parse(response.data);
|
|
73
|
-
specContent = JSON.stringify(parsed, null, 2);
|
|
74
|
-
} catch {
|
|
75
|
-
// If not JSON, might be YAML - save as-is
|
|
76
|
-
specContent = response.data;
|
|
77
|
-
}
|
|
78
|
-
} else {
|
|
79
|
-
specContent = JSON.stringify(response.data, null, 2);
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
// Validate it's an OpenAPI spec
|
|
83
|
-
this.validateSpec(specContent);
|
|
84
|
-
|
|
85
|
-
// Write to file
|
|
86
|
-
fs.writeFileSync(specPath, specContent, 'utf-8');
|
|
87
|
-
|
|
88
|
-
console.log(`OpenAPI spec downloaded successfully to ${specPath}`);
|
|
89
|
-
return specPath;
|
|
90
|
-
} catch (error) {
|
|
91
|
-
if (axios.isAxiosError(error)) {
|
|
92
|
-
throw new Error(
|
|
93
|
-
`Failed to download OpenAPI spec from ${url}: ${error.message}`
|
|
94
|
-
);
|
|
95
|
-
}
|
|
96
|
-
throw error;
|
|
97
|
-
}
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
/**
|
|
101
|
-
* Check if the URL is a local file path
|
|
102
|
-
*/
|
|
103
|
-
private isLocalFile(url: string): boolean {
|
|
104
|
-
// Check for file:// protocol
|
|
105
|
-
if (url.startsWith('file://')) {
|
|
106
|
-
return true;
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
// Check for HTTP/HTTPS protocols
|
|
110
|
-
if (url.startsWith('http://') || url.startsWith('https://')) {
|
|
111
|
-
return false;
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
// Check if it's an absolute or relative path
|
|
115
|
-
// Absolute paths start with / (Unix) or C:\ (Windows)
|
|
116
|
-
// Relative paths start with ./ or ../
|
|
117
|
-
return (
|
|
118
|
-
url.startsWith('/') ||
|
|
119
|
-
url.startsWith('./') ||
|
|
120
|
-
url.startsWith('../') ||
|
|
121
|
-
/^[a-zA-Z]:[\\\/]/.test(url) // Windows paths like C:\
|
|
122
|
-
);
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
/**
|
|
126
|
-
* Load and validate an OpenAPI spec from a local file
|
|
127
|
-
*/
|
|
128
|
-
private loadLocalFile(filePath: string, outputPath?: string): string {
|
|
129
|
-
// Handle file:// URLs
|
|
130
|
-
let resolvedPath = filePath;
|
|
131
|
-
if (filePath.startsWith('file://')) {
|
|
132
|
-
resolvedPath = filePath.replace('file://', '');
|
|
133
|
-
// On Windows, file URLs might be file:///C:/path
|
|
134
|
-
if (process.platform === 'win32' && resolvedPath.startsWith('/')) {
|
|
135
|
-
resolvedPath = resolvedPath.substring(1);
|
|
136
|
-
}
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
// Resolve to absolute path
|
|
140
|
-
resolvedPath = path.resolve(resolvedPath);
|
|
141
|
-
|
|
142
|
-
console.log(`Loading OpenAPI spec from local file: ${resolvedPath}...`);
|
|
143
|
-
|
|
144
|
-
// Check if file exists
|
|
145
|
-
if (!fs.existsSync(resolvedPath)) {
|
|
146
|
-
throw new Error(`File not found: ${resolvedPath}`);
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
// Read the file
|
|
150
|
-
const content = fs.readFileSync(resolvedPath, 'utf-8');
|
|
151
|
-
|
|
152
|
-
// Validate it's an OpenAPI spec
|
|
153
|
-
this.validateSpec(content);
|
|
154
|
-
|
|
155
|
-
// If outputPath is provided, copy to that location
|
|
156
|
-
if (outputPath) {
|
|
157
|
-
const dir = path.dirname(outputPath);
|
|
158
|
-
if (!fs.existsSync(dir)) {
|
|
159
|
-
fs.mkdirSync(dir, { recursive: true });
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
// Parse and format if it's JSON
|
|
163
|
-
let formattedContent = content;
|
|
164
|
-
try {
|
|
165
|
-
const parsed = JSON.parse(content);
|
|
166
|
-
formattedContent = JSON.stringify(parsed, null, 2);
|
|
167
|
-
} catch {
|
|
168
|
-
// Not JSON, keep original
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
fs.writeFileSync(outputPath, formattedContent, 'utf-8');
|
|
172
|
-
console.log(`OpenAPI spec copied to ${outputPath}`);
|
|
173
|
-
return outputPath;
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
console.log(`OpenAPI spec loaded successfully from ${resolvedPath}`);
|
|
177
|
-
return resolvedPath;
|
|
178
|
-
}
|
|
179
|
-
|
|
180
|
-
/**
|
|
181
|
-
* Generate a temporary file path based on the URL
|
|
182
|
-
*/
|
|
183
|
-
private generateTempPath(url: string): string {
|
|
184
|
-
const urlObj = new URL(url);
|
|
185
|
-
const hostname = urlObj.hostname.replace(/\./g, '-');
|
|
186
|
-
const timestamp = Date.now();
|
|
187
|
-
const filename = `openapi-${hostname}-${timestamp}.json`;
|
|
188
|
-
return path.join(process.cwd(), '.tmp', filename);
|
|
189
|
-
}
|
|
190
|
-
|
|
191
|
-
/**
|
|
192
|
-
* Validate that the downloaded content is an OpenAPI spec
|
|
193
|
-
*/
|
|
194
|
-
private validateSpec(content: string): void {
|
|
195
|
-
try {
|
|
196
|
-
const spec = JSON.parse(content);
|
|
197
|
-
|
|
198
|
-
// Check for OpenAPI version
|
|
199
|
-
if (!spec.openapi && !spec.swagger) {
|
|
200
|
-
throw new Error(
|
|
201
|
-
'Invalid OpenAPI spec: missing "openapi" or "swagger" field'
|
|
202
|
-
);
|
|
203
|
-
}
|
|
204
|
-
|
|
205
|
-
// Check for required fields
|
|
206
|
-
if (!spec.info) {
|
|
207
|
-
throw new Error('Invalid OpenAPI spec: missing "info" field');
|
|
208
|
-
}
|
|
209
|
-
|
|
210
|
-
if (!spec.paths && !spec.components) {
|
|
211
|
-
throw new Error(
|
|
212
|
-
'Invalid OpenAPI spec: must have either "paths" or "components"'
|
|
213
|
-
);
|
|
214
|
-
}
|
|
215
|
-
} catch (error) {
|
|
216
|
-
if (error instanceof SyntaxError) {
|
|
217
|
-
throw new Error('Invalid OpenAPI spec: not valid JSON');
|
|
218
|
-
}
|
|
219
|
-
throw error;
|
|
220
|
-
}
|
|
221
|
-
}
|
|
222
|
-
|
|
223
|
-
/**
|
|
224
|
-
* Clean up temporary files
|
|
225
|
-
*/
|
|
226
|
-
cleanupTemp(): void {
|
|
227
|
-
const tempDir = path.join(process.cwd(), '.tmp');
|
|
228
|
-
if (fs.existsSync(tempDir)) {
|
|
229
|
-
fs.rmSync(tempDir, { recursive: true, force: true });
|
|
230
|
-
console.log('Cleaned up temporary files');
|
|
231
|
-
}
|
|
232
|
-
}
|
|
233
|
-
}
|
|
@@ -1,257 +0,0 @@
|
|
|
1
|
-
import { K8sClientGenerator } from '../src/k8s-client-generator';
|
|
2
|
-
import { SpecDownloader } from '../src/spec-downloader';
|
|
3
|
-
import * as fs from 'fs';
|
|
4
|
-
import * as path from 'path';
|
|
5
|
-
|
|
6
|
-
describe('K8sClientGenerator', () => {
|
|
7
|
-
const testOutputDir = path.join(__dirname, '../test-output');
|
|
8
|
-
|
|
9
|
-
beforeEach(() => {
|
|
10
|
-
// Clean up test output directory
|
|
11
|
-
if (fs.existsSync(testOutputDir)) {
|
|
12
|
-
fs.rmSync(testOutputDir, { recursive: true, force: true });
|
|
13
|
-
}
|
|
14
|
-
});
|
|
15
|
-
|
|
16
|
-
afterEach(() => {
|
|
17
|
-
// Cleanup after each test
|
|
18
|
-
if (fs.existsSync(testOutputDir)) {
|
|
19
|
-
fs.rmSync(testOutputDir, { recursive: true, force: true });
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
// Clean up temp files
|
|
23
|
-
const tempDir = path.join(process.cwd(), '.tmp');
|
|
24
|
-
if (fs.existsSync(tempDir)) {
|
|
25
|
-
fs.rmSync(tempDir, { recursive: true, force: true });
|
|
26
|
-
}
|
|
27
|
-
});
|
|
28
|
-
|
|
29
|
-
describe('generate', () => {
|
|
30
|
-
it('should download spec and generate client from a valid URL', async () => {
|
|
31
|
-
const generator = new K8sClientGenerator();
|
|
32
|
-
|
|
33
|
-
// Use a simple test OpenAPI spec
|
|
34
|
-
const testSpec = {
|
|
35
|
-
openapi: '3.0.0',
|
|
36
|
-
info: { title: 'Test API', version: '1.0.0' },
|
|
37
|
-
paths: {
|
|
38
|
-
'/test': {
|
|
39
|
-
get: {
|
|
40
|
-
responses: {
|
|
41
|
-
'200': {
|
|
42
|
-
description: 'Success',
|
|
43
|
-
content: {
|
|
44
|
-
'application/json': {
|
|
45
|
-
schema: {
|
|
46
|
-
type: 'object',
|
|
47
|
-
properties: {
|
|
48
|
-
message: { type: 'string' }
|
|
49
|
-
}
|
|
50
|
-
}
|
|
51
|
-
}
|
|
52
|
-
}
|
|
53
|
-
}
|
|
54
|
-
}
|
|
55
|
-
}
|
|
56
|
-
}
|
|
57
|
-
}
|
|
58
|
-
};
|
|
59
|
-
|
|
60
|
-
// Create a mock server would be ideal, but for now we'll test with a file URL
|
|
61
|
-
// or skip this test in favor of integration tests
|
|
62
|
-
const specPath = path.join(testOutputDir, 'test-spec.json');
|
|
63
|
-
fs.mkdirSync(testOutputDir, { recursive: true });
|
|
64
|
-
fs.writeFileSync(specPath, JSON.stringify(testSpec, null, 2));
|
|
65
|
-
|
|
66
|
-
const outputDir = path.join(testOutputDir, 'generated');
|
|
67
|
-
|
|
68
|
-
// Note: This test would need a real HTTP server or mock
|
|
69
|
-
// For now, we'll test the structure
|
|
70
|
-
expect(generator).toBeDefined();
|
|
71
|
-
expect(typeof generator.generate).toBe('function');
|
|
72
|
-
}, 60000);
|
|
73
|
-
|
|
74
|
-
it('should create output directory if it does not exist', async () => {
|
|
75
|
-
const generator = new K8sClientGenerator();
|
|
76
|
-
expect(generator).toBeDefined();
|
|
77
|
-
});
|
|
78
|
-
|
|
79
|
-
it('should handle download errors gracefully', async () => {
|
|
80
|
-
const generator = new K8sClientGenerator();
|
|
81
|
-
|
|
82
|
-
await expect(
|
|
83
|
-
generator.generate({
|
|
84
|
-
specUrl: 'http://invalid-url-that-does-not-exist.com/spec.json',
|
|
85
|
-
outputDir: testOutputDir,
|
|
86
|
-
timeout: 1000,
|
|
87
|
-
})
|
|
88
|
-
).rejects.toThrow();
|
|
89
|
-
}, 60000);
|
|
90
|
-
});
|
|
91
|
-
});
|
|
92
|
-
|
|
93
|
-
describe('SpecDownloader', () => {
|
|
94
|
-
const testOutputDir = path.join(__dirname, '../test-output');
|
|
95
|
-
|
|
96
|
-
beforeEach(() => {
|
|
97
|
-
if (fs.existsSync(testOutputDir)) {
|
|
98
|
-
fs.rmSync(testOutputDir, { recursive: true, force: true });
|
|
99
|
-
}
|
|
100
|
-
});
|
|
101
|
-
|
|
102
|
-
afterEach(() => {
|
|
103
|
-
if (fs.existsSync(testOutputDir)) {
|
|
104
|
-
fs.rmSync(testOutputDir, { recursive: true, force: true });
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
const tempDir = path.join(process.cwd(), '.tmp');
|
|
108
|
-
if (fs.existsSync(tempDir)) {
|
|
109
|
-
fs.rmSync(tempDir, { recursive: true, force: true });
|
|
110
|
-
}
|
|
111
|
-
});
|
|
112
|
-
|
|
113
|
-
describe('download', () => {
|
|
114
|
-
it('should throw error for invalid URL', async () => {
|
|
115
|
-
const downloader = new SpecDownloader();
|
|
116
|
-
|
|
117
|
-
await expect(
|
|
118
|
-
downloader.download({
|
|
119
|
-
url: 'http://invalid-url-that-does-not-exist.com/spec.json',
|
|
120
|
-
timeout: 1000,
|
|
121
|
-
})
|
|
122
|
-
).rejects.toThrow();
|
|
123
|
-
}, 60000);
|
|
124
|
-
|
|
125
|
-
it('should validate OpenAPI spec format', async () => {
|
|
126
|
-
// This test validates the validation logic
|
|
127
|
-
const downloader = new SpecDownloader();
|
|
128
|
-
expect(downloader).toBeDefined();
|
|
129
|
-
expect(typeof downloader.download).toBe('function');
|
|
130
|
-
});
|
|
131
|
-
|
|
132
|
-
it('should load OpenAPI spec from local file path', async () => {
|
|
133
|
-
const downloader = new SpecDownloader();
|
|
134
|
-
|
|
135
|
-
// Create a test spec file
|
|
136
|
-
const testSpec = {
|
|
137
|
-
openapi: '3.0.0',
|
|
138
|
-
info: { title: 'Local Test API', version: '1.0.0' },
|
|
139
|
-
paths: {}
|
|
140
|
-
};
|
|
141
|
-
|
|
142
|
-
const testSpecPath = path.join(testOutputDir, 'local-spec.json');
|
|
143
|
-
fs.mkdirSync(testOutputDir, { recursive: true });
|
|
144
|
-
fs.writeFileSync(testSpecPath, JSON.stringify(testSpec, null, 2));
|
|
145
|
-
|
|
146
|
-
const result = await downloader.download({
|
|
147
|
-
url: testSpecPath,
|
|
148
|
-
});
|
|
149
|
-
|
|
150
|
-
expect(result).toBe(path.resolve(testSpecPath));
|
|
151
|
-
expect(fs.existsSync(result)).toBe(true);
|
|
152
|
-
});
|
|
153
|
-
|
|
154
|
-
it('should load OpenAPI spec from relative file path', async () => {
|
|
155
|
-
const downloader = new SpecDownloader();
|
|
156
|
-
|
|
157
|
-
// Create a test spec file
|
|
158
|
-
const testSpec = {
|
|
159
|
-
openapi: '3.0.0',
|
|
160
|
-
info: { title: 'Relative Test API', version: '1.0.0' },
|
|
161
|
-
paths: {}
|
|
162
|
-
};
|
|
163
|
-
|
|
164
|
-
const testSpecPath = path.join(testOutputDir, 'relative-spec.json');
|
|
165
|
-
fs.mkdirSync(testOutputDir, { recursive: true });
|
|
166
|
-
fs.writeFileSync(testSpecPath, JSON.stringify(testSpec, null, 2));
|
|
167
|
-
|
|
168
|
-
// Use relative path
|
|
169
|
-
const relativePath = path.relative(process.cwd(), testSpecPath);
|
|
170
|
-
|
|
171
|
-
const result = await downloader.download({
|
|
172
|
-
url: `./${relativePath}`,
|
|
173
|
-
});
|
|
174
|
-
|
|
175
|
-
expect(fs.existsSync(result)).toBe(true);
|
|
176
|
-
});
|
|
177
|
-
|
|
178
|
-
it('should load OpenAPI spec from file:// URL', async () => {
|
|
179
|
-
const downloader = new SpecDownloader();
|
|
180
|
-
|
|
181
|
-
// Create a test spec file
|
|
182
|
-
const testSpec = {
|
|
183
|
-
openapi: '3.0.0',
|
|
184
|
-
info: { title: 'File URL Test API', version: '1.0.0' },
|
|
185
|
-
paths: {}
|
|
186
|
-
};
|
|
187
|
-
|
|
188
|
-
const testSpecPath = path.join(testOutputDir, 'file-url-spec.json');
|
|
189
|
-
fs.mkdirSync(testOutputDir, { recursive: true });
|
|
190
|
-
fs.writeFileSync(testSpecPath, JSON.stringify(testSpec, null, 2));
|
|
191
|
-
|
|
192
|
-
// Use file:// URL
|
|
193
|
-
const fileUrl = `file://${path.resolve(testSpecPath)}`;
|
|
194
|
-
|
|
195
|
-
const result = await downloader.download({
|
|
196
|
-
url: fileUrl,
|
|
197
|
-
});
|
|
198
|
-
|
|
199
|
-
expect(fs.existsSync(result)).toBe(true);
|
|
200
|
-
const content = JSON.parse(fs.readFileSync(result, 'utf-8'));
|
|
201
|
-
expect(content.info.title).toBe('File URL Test API');
|
|
202
|
-
});
|
|
203
|
-
|
|
204
|
-
it('should copy local file to output path if specified', async () => {
|
|
205
|
-
const downloader = new SpecDownloader();
|
|
206
|
-
|
|
207
|
-
// Create a test spec file
|
|
208
|
-
const testSpec = {
|
|
209
|
-
openapi: '3.0.0',
|
|
210
|
-
info: { title: 'Copy Test API', version: '1.0.0' },
|
|
211
|
-
paths: {}
|
|
212
|
-
};
|
|
213
|
-
|
|
214
|
-
const sourceSpecPath = path.join(testOutputDir, 'source-spec.json');
|
|
215
|
-
const targetSpecPath = path.join(testOutputDir, 'target-spec.json');
|
|
216
|
-
fs.mkdirSync(testOutputDir, { recursive: true });
|
|
217
|
-
fs.writeFileSync(sourceSpecPath, JSON.stringify(testSpec, null, 2));
|
|
218
|
-
|
|
219
|
-
const result = await downloader.download({
|
|
220
|
-
url: sourceSpecPath,
|
|
221
|
-
outputPath: targetSpecPath,
|
|
222
|
-
});
|
|
223
|
-
|
|
224
|
-
expect(result).toBe(targetSpecPath);
|
|
225
|
-
expect(fs.existsSync(targetSpecPath)).toBe(true);
|
|
226
|
-
const content = JSON.parse(fs.readFileSync(targetSpecPath, 'utf-8'));
|
|
227
|
-
expect(content.info.title).toBe('Copy Test API');
|
|
228
|
-
});
|
|
229
|
-
|
|
230
|
-
it('should throw error for non-existent local file', async () => {
|
|
231
|
-
const downloader = new SpecDownloader();
|
|
232
|
-
|
|
233
|
-
await expect(
|
|
234
|
-
downloader.download({
|
|
235
|
-
url: '/non/existent/file.json',
|
|
236
|
-
})
|
|
237
|
-
).rejects.toThrow('File not found');
|
|
238
|
-
});
|
|
239
|
-
});
|
|
240
|
-
|
|
241
|
-
describe('cleanupTemp', () => {
|
|
242
|
-
it('should clean up temporary directory', () => {
|
|
243
|
-
const downloader = new SpecDownloader();
|
|
244
|
-
|
|
245
|
-
// Create temp directory
|
|
246
|
-
const tempDir = path.join(process.cwd(), '.tmp');
|
|
247
|
-
fs.mkdirSync(tempDir, { recursive: true });
|
|
248
|
-
fs.writeFileSync(path.join(tempDir, 'test.txt'), 'test');
|
|
249
|
-
|
|
250
|
-
expect(fs.existsSync(tempDir)).toBe(true);
|
|
251
|
-
|
|
252
|
-
downloader.cleanupTemp();
|
|
253
|
-
|
|
254
|
-
expect(fs.existsSync(tempDir)).toBe(false);
|
|
255
|
-
});
|
|
256
|
-
});
|
|
257
|
-
});
|
package/tsconfig.json
DELETED
|
@@ -1,20 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"compilerOptions": {
|
|
3
|
-
"target": "ES2020",
|
|
4
|
-
"module": "commonjs",
|
|
5
|
-
"lib": ["ES2020"],
|
|
6
|
-
"declaration": true,
|
|
7
|
-
"outDir": "./dist",
|
|
8
|
-
"rootDir": "./src",
|
|
9
|
-
"strict": true,
|
|
10
|
-
"esModuleInterop": true,
|
|
11
|
-
"skipLibCheck": true,
|
|
12
|
-
"forceConsistentCasingInFileNames": true,
|
|
13
|
-
"resolveJsonModule": true,
|
|
14
|
-
"moduleResolution": "node",
|
|
15
|
-
"experimentalDecorators": true,
|
|
16
|
-
"emitDecoratorMetadata": true
|
|
17
|
-
},
|
|
18
|
-
"include": ["src/**/*"],
|
|
19
|
-
"exclude": ["node_modules", "dist", "test"]
|
|
20
|
-
}
|