klasik 1.0.17 → 1.0.20
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +126 -0
- package/dist/cli.js +2 -0
- package/dist/esm-fixer.d.ts +29 -0
- package/dist/esm-fixer.js +139 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +3 -1
- package/dist/k8s-client-generator.d.ts +5 -0
- package/dist/k8s-client-generator.js +8 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -9,6 +9,7 @@ Download OpenAPI specifications from remote URLs and generate TypeScript clients
|
|
|
9
9
|
- 📄 **JSON and YAML support** - Automatically parse and handle both formats (YAML specs are converted to JSON for code generation)
|
|
10
10
|
- 🔐 **Authentication support** - Custom headers including Bearer tokens and API keys
|
|
11
11
|
- 🔗 **External reference resolution** - Automatically download referenced schema files (`$ref`)
|
|
12
|
+
- 📦 **ESM compatibility** - Automatic `.js` extension fixing for Node.js ESM projects
|
|
12
13
|
- 🔄 **Automatic transformation** - Converts API responses to class instances using class-transformer
|
|
13
14
|
- 🎯 **Type-safe** - Full TypeScript support with decorators
|
|
14
15
|
- 🛠️ **Configurable** - Custom headers, timeouts, and templates
|
|
@@ -95,10 +96,14 @@ Generate a TypeScript client from an OpenAPI spec (remote URL or local file).
|
|
|
95
96
|
- `-u, --url <url>` - URL or file path to the OpenAPI spec (required)
|
|
96
97
|
- Supports: `https://...`, `http://...`, `file://...`, `/absolute/path`, `./relative/path`
|
|
97
98
|
- `-o, --output <dir>` - Output directory for generated client code (required)
|
|
99
|
+
- `-m, --mode <mode>` - Generation mode: `full` (models + APIs + config) or `models-only` (default: `full`)
|
|
100
|
+
- `full`: Generates complete axios client with APIs, models, and configuration
|
|
101
|
+
- `models-only`: Generates only model classes with class-transformer decorators (no API client code)
|
|
98
102
|
- `-H, --header <header...>` - Custom headers for HTTP requests (format: "Key: Value")
|
|
99
103
|
- Can be used multiple times for multiple headers
|
|
100
104
|
- Perfect for authorization: `--header "Authorization: Bearer token"`
|
|
101
105
|
- `-r, --resolve-refs` - Resolve and download external `$ref` references
|
|
106
|
+
- `--esm` - Add `.js` extensions to imports for ESM compatibility (Node.js modules)
|
|
102
107
|
- `-t, --template <dir>` - Custom template directory (klasik includes enhanced TSDoc templates by default)
|
|
103
108
|
- `-k, --keep-spec` - Keep the downloaded spec file after generation
|
|
104
109
|
- `--timeout <ms>` - Request timeout in milliseconds for HTTP requests (default: 30000)
|
|
@@ -417,6 +422,127 @@ await new K8sClientGenerator().generate({
|
|
|
417
422
|
});
|
|
418
423
|
```
|
|
419
424
|
|
|
425
|
+
## Best Practices for Existing Projects
|
|
426
|
+
|
|
427
|
+
When integrating klasik into an existing TypeScript project, follow these best practices to avoid conflicts:
|
|
428
|
+
|
|
429
|
+
### Generate into a Subdirectory
|
|
430
|
+
|
|
431
|
+
```bash
|
|
432
|
+
# Generate models into src/generated to avoid overwriting project files
|
|
433
|
+
npx klasik generate \
|
|
434
|
+
--url https://api.example.com/openapi.yaml \
|
|
435
|
+
--output ./src/generated \
|
|
436
|
+
--mode models-only \
|
|
437
|
+
--resolve-refs
|
|
438
|
+
```
|
|
439
|
+
|
|
440
|
+
### Clean Up Generated Config Files
|
|
441
|
+
|
|
442
|
+
klasik generates `package.json` and `tsconfig.json` files. Remove them after generation:
|
|
443
|
+
|
|
444
|
+
```json
|
|
445
|
+
// package.json
|
|
446
|
+
{
|
|
447
|
+
"scripts": {
|
|
448
|
+
"generate-client": "npx klasik generate --url $API_URL --output ./src/generated --mode models-only --resolve-refs && npm run cleanup-generated",
|
|
449
|
+
"cleanup-generated": "rm -f ./src/generated/package.json ./src/generated/tsconfig*.json ./src/generated/.openapi-generator*"
|
|
450
|
+
}
|
|
451
|
+
}
|
|
452
|
+
```
|
|
453
|
+
|
|
454
|
+
### TypeScript Configuration for class-transformer
|
|
455
|
+
|
|
456
|
+
Add these settings to your `tsconfig.json` for compatibility:
|
|
457
|
+
|
|
458
|
+
```json
|
|
459
|
+
{
|
|
460
|
+
"compilerOptions": {
|
|
461
|
+
"experimentalDecorators": true,
|
|
462
|
+
"emitDecoratorMetadata": true,
|
|
463
|
+
"skipLibCheck": true // Skip type checking in node_modules (recommended for class-transformer)
|
|
464
|
+
}
|
|
465
|
+
}
|
|
466
|
+
```
|
|
467
|
+
|
|
468
|
+
### Models-Only Mode
|
|
469
|
+
|
|
470
|
+
Use `--mode models-only` when you:
|
|
471
|
+
- Only need data models with class-transformer decorators
|
|
472
|
+
- Want to use your own API client (axios, fetch, etc.)
|
|
473
|
+
- Are integrating into an existing project with its own HTTP layer
|
|
474
|
+
- Want to avoid package.json/tsconfig.json conflicts
|
|
475
|
+
|
|
476
|
+
```bash
|
|
477
|
+
npx klasik generate \
|
|
478
|
+
--url https://api.example.com/openapi.yaml \
|
|
479
|
+
--output ./src/generated \
|
|
480
|
+
--mode models-only # ✅ Generates only models, no API client
|
|
481
|
+
```
|
|
482
|
+
|
|
483
|
+
### Full Mode
|
|
484
|
+
|
|
485
|
+
Use `--mode full` (default) when you:
|
|
486
|
+
- Want a complete ready-to-use axios client
|
|
487
|
+
- Are starting a new project from scratch
|
|
488
|
+
- Want automatic response transformation with class-transformer
|
|
489
|
+
|
|
490
|
+
```bash
|
|
491
|
+
npx klasik generate \
|
|
492
|
+
--url https://api.example.com/openapi.yaml \
|
|
493
|
+
--output ./api-client \
|
|
494
|
+
--mode full # Generates complete client with APIs
|
|
495
|
+
```
|
|
496
|
+
|
|
497
|
+
### ESM (ECMAScript Modules) Support
|
|
498
|
+
|
|
499
|
+
If your project uses ECMAScript Modules (`"type": "module"` in package.json), use the `--esm` flag to automatically add `.js` extensions to all relative imports:
|
|
500
|
+
|
|
501
|
+
```bash
|
|
502
|
+
# Generate ESM-compatible code
|
|
503
|
+
npx klasik generate \
|
|
504
|
+
--url https://api.example.com/openapi.yaml \
|
|
505
|
+
--output ./src/generated \
|
|
506
|
+
--mode models-only \
|
|
507
|
+
--esm # ✅ Adds .js extensions to imports
|
|
508
|
+
```
|
|
509
|
+
|
|
510
|
+
**What `--esm` does:**
|
|
511
|
+
- Adds `.js` extensions to all relative imports: `from './user'` → `from './user.js'`
|
|
512
|
+
- Adds `.js` extensions to all relative exports: `export * from './api'` → `export * from './api.js'`
|
|
513
|
+
- Handles directory imports: `from './models'` → `from './models/index.js'`
|
|
514
|
+
- Only affects relative imports (doesn't touch external packages like `axios`, `class-transformer`)
|
|
515
|
+
|
|
516
|
+
**Before `--esm`:**
|
|
517
|
+
```typescript
|
|
518
|
+
import { User } from './models/user';
|
|
519
|
+
import { Cluster } from '../models/cluster';
|
|
520
|
+
export * from './api/orgs-api';
|
|
521
|
+
```
|
|
522
|
+
|
|
523
|
+
**After `--esm`:**
|
|
524
|
+
```typescript
|
|
525
|
+
import { User } from './models/user.js';
|
|
526
|
+
import { Cluster } from '../models/cluster.js';
|
|
527
|
+
export * from './api/orgs-api.js';
|
|
528
|
+
```
|
|
529
|
+
|
|
530
|
+
**When to use `--esm`:**
|
|
531
|
+
- Your package.json has `"type": "module"`
|
|
532
|
+
- You're using Node.js native ESM
|
|
533
|
+
- You're targeting modern JavaScript environments
|
|
534
|
+
- You want to eliminate post-processing scripts
|
|
535
|
+
|
|
536
|
+
**package.json setup for ESM:**
|
|
537
|
+
```json
|
|
538
|
+
{
|
|
539
|
+
"type": "module",
|
|
540
|
+
"scripts": {
|
|
541
|
+
"generate-client": "npx klasik generate --url $API_URL --output src/generated --mode models-only --esm"
|
|
542
|
+
}
|
|
543
|
+
}
|
|
544
|
+
```
|
|
545
|
+
|
|
420
546
|
## Advanced Configuration
|
|
421
547
|
|
|
422
548
|
### Custom Error Handling
|
package/dist/cli.js
CHANGED
|
@@ -52,6 +52,7 @@ program
|
|
|
52
52
|
.option('-t, --template <dir>', 'Custom template directory')
|
|
53
53
|
.option('-k, --keep-spec', 'Keep the downloaded spec file after generation', false)
|
|
54
54
|
.option('-r, --resolve-refs', 'Resolve and download external $ref references', false)
|
|
55
|
+
.option('--esm', 'Add .js extensions to imports for ESM compatibility', false)
|
|
55
56
|
.option('--timeout <ms>', 'Request timeout in milliseconds', '30000')
|
|
56
57
|
.action(async (options) => {
|
|
57
58
|
try {
|
|
@@ -82,6 +83,7 @@ program
|
|
|
82
83
|
templateDir: options.template,
|
|
83
84
|
keepSpec: options.keepSpec,
|
|
84
85
|
resolveReferences: options.resolveRefs,
|
|
86
|
+
fixEsmImports: options.esm,
|
|
85
87
|
timeout: parseInt(options.timeout, 10),
|
|
86
88
|
});
|
|
87
89
|
process.exit(0);
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Fixes relative imports in TypeScript files to include .js extensions for ESM compatibility
|
|
3
|
+
*
|
|
4
|
+
* Node.js ESM requires explicit file extensions for relative imports.
|
|
5
|
+
* This utility adds .js extensions to all relative import/export statements.
|
|
6
|
+
*/
|
|
7
|
+
export declare class EsmFixer {
|
|
8
|
+
/**
|
|
9
|
+
* Fix all TypeScript files in a directory recursively
|
|
10
|
+
*/
|
|
11
|
+
fixDirectory(directory: string): Promise<void>;
|
|
12
|
+
/**
|
|
13
|
+
* Fix imports in a single file
|
|
14
|
+
* @returns true if file was modified, false otherwise
|
|
15
|
+
*/
|
|
16
|
+
private fixFile;
|
|
17
|
+
/**
|
|
18
|
+
* Fix import statements to add .js extensions
|
|
19
|
+
*/
|
|
20
|
+
private fixImports;
|
|
21
|
+
/**
|
|
22
|
+
* Fix export statements to add .js extensions
|
|
23
|
+
*/
|
|
24
|
+
private fixExports;
|
|
25
|
+
/**
|
|
26
|
+
* Get all TypeScript files in a directory recursively
|
|
27
|
+
*/
|
|
28
|
+
private getAllTsFiles;
|
|
29
|
+
}
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.EsmFixer = void 0;
|
|
37
|
+
const fs = __importStar(require("fs"));
|
|
38
|
+
const path = __importStar(require("path"));
|
|
39
|
+
/**
|
|
40
|
+
* Fixes relative imports in TypeScript files to include .js extensions for ESM compatibility
|
|
41
|
+
*
|
|
42
|
+
* Node.js ESM requires explicit file extensions for relative imports.
|
|
43
|
+
* This utility adds .js extensions to all relative import/export statements.
|
|
44
|
+
*/
|
|
45
|
+
class EsmFixer {
|
|
46
|
+
/**
|
|
47
|
+
* Fix all TypeScript files in a directory recursively
|
|
48
|
+
*/
|
|
49
|
+
async fixDirectory(directory) {
|
|
50
|
+
console.log('Fixing ESM imports to add .js extensions...');
|
|
51
|
+
const files = this.getAllTsFiles(directory);
|
|
52
|
+
let fixedCount = 0;
|
|
53
|
+
for (const file of files) {
|
|
54
|
+
const fixed = await this.fixFile(file);
|
|
55
|
+
if (fixed) {
|
|
56
|
+
fixedCount++;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
console.log(`✅ Fixed ${fixedCount} file(s) for ESM compatibility`);
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Fix imports in a single file
|
|
63
|
+
* @returns true if file was modified, false otherwise
|
|
64
|
+
*/
|
|
65
|
+
async fixFile(filePath) {
|
|
66
|
+
let content = fs.readFileSync(filePath, 'utf8');
|
|
67
|
+
const originalContent = content;
|
|
68
|
+
// Fix import statements
|
|
69
|
+
content = this.fixImports(content);
|
|
70
|
+
// Fix export statements
|
|
71
|
+
content = this.fixExports(content);
|
|
72
|
+
// Only write if content changed
|
|
73
|
+
if (content !== originalContent) {
|
|
74
|
+
fs.writeFileSync(filePath, content, 'utf8');
|
|
75
|
+
return true;
|
|
76
|
+
}
|
|
77
|
+
return false;
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* Fix import statements to add .js extensions
|
|
81
|
+
*/
|
|
82
|
+
fixImports(content) {
|
|
83
|
+
// Match: import ... from './relative/path'
|
|
84
|
+
// Don't match if already has extension (.js, .ts, .json)
|
|
85
|
+
// Don't match external packages (no . or ..)
|
|
86
|
+
const importRegex = /from\s+(['"])(\.|\.\.)(\/[^'"]*?)(?<!\.js|\.ts|\.json|\.mjs|\.cjs)\1/g;
|
|
87
|
+
return content.replace(importRegex, (match, quote, dots, importPath) => {
|
|
88
|
+
// Handle index imports: './models' -> './models/index.js'
|
|
89
|
+
// But only if the path doesn't already end with a filename
|
|
90
|
+
const hasFilename = /\/[^/]+$/.test(importPath);
|
|
91
|
+
if (!hasFilename && importPath.length > 0) {
|
|
92
|
+
// It's a directory import, add /index.js
|
|
93
|
+
return `from ${quote}${dots}${importPath}/index.js${quote}`;
|
|
94
|
+
}
|
|
95
|
+
// Normal file import, just add .js
|
|
96
|
+
return `from ${quote}${dots}${importPath}.js${quote}`;
|
|
97
|
+
});
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* Fix export statements to add .js extensions
|
|
101
|
+
*/
|
|
102
|
+
fixExports(content) {
|
|
103
|
+
// Match: export ... from './relative/path'
|
|
104
|
+
// Don't match if already has extension
|
|
105
|
+
const exportRegex = /from\s+(['"])(\.|\.\.)(\/[^'"]*?)(?<!\.js|\.ts|\.json|\.mjs|\.cjs)\1/g;
|
|
106
|
+
return content.replace(exportRegex, (match, quote, dots, exportPath) => {
|
|
107
|
+
// Handle index exports
|
|
108
|
+
const hasFilename = /\/[^/]+$/.test(exportPath);
|
|
109
|
+
if (!hasFilename && exportPath.length > 0) {
|
|
110
|
+
return `from ${quote}${dots}${exportPath}/index.js${quote}`;
|
|
111
|
+
}
|
|
112
|
+
return `from ${quote}${dots}${exportPath}.js${quote}`;
|
|
113
|
+
});
|
|
114
|
+
}
|
|
115
|
+
/**
|
|
116
|
+
* Get all TypeScript files in a directory recursively
|
|
117
|
+
*/
|
|
118
|
+
getAllTsFiles(directory) {
|
|
119
|
+
const files = [];
|
|
120
|
+
const walk = (dir) => {
|
|
121
|
+
const entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
122
|
+
for (const entry of entries) {
|
|
123
|
+
const fullPath = path.join(dir, entry.name);
|
|
124
|
+
if (entry.isDirectory()) {
|
|
125
|
+
// Skip node_modules and hidden directories
|
|
126
|
+
if (entry.name !== 'node_modules' && !entry.name.startsWith('.')) {
|
|
127
|
+
walk(fullPath);
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
else if (entry.isFile() && entry.name.endsWith('.ts')) {
|
|
131
|
+
files.push(fullPath);
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
};
|
|
135
|
+
walk(directory);
|
|
136
|
+
return files;
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
exports.EsmFixer = EsmFixer;
|
package/dist/index.d.ts
CHANGED
package/dist/index.js
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.SpecDownloader = exports.K8sClientGenerator = void 0;
|
|
3
|
+
exports.EsmFixer = exports.SpecDownloader = exports.K8sClientGenerator = void 0;
|
|
4
4
|
var k8s_client_generator_1 = require("./k8s-client-generator");
|
|
5
5
|
Object.defineProperty(exports, "K8sClientGenerator", { enumerable: true, get: function () { return k8s_client_generator_1.K8sClientGenerator; } });
|
|
6
6
|
var spec_downloader_1 = require("./spec-downloader");
|
|
7
7
|
Object.defineProperty(exports, "SpecDownloader", { enumerable: true, get: function () { return spec_downloader_1.SpecDownloader; } });
|
|
8
|
+
var esm_fixer_1 = require("./esm-fixer");
|
|
9
|
+
Object.defineProperty(exports, "EsmFixer", { enumerable: true, get: function () { return esm_fixer_1.EsmFixer; } });
|
|
@@ -33,6 +33,11 @@ export interface K8sClientGeneratorOptions {
|
|
|
33
33
|
* @default false
|
|
34
34
|
*/
|
|
35
35
|
resolveReferences?: boolean;
|
|
36
|
+
/**
|
|
37
|
+
* Whether to fix imports by adding .js extensions for ESM compatibility
|
|
38
|
+
* @default false
|
|
39
|
+
*/
|
|
40
|
+
fixEsmImports?: boolean;
|
|
36
41
|
/**
|
|
37
42
|
* Request timeout for downloading spec in milliseconds
|
|
38
43
|
* @default 30000
|
|
@@ -36,6 +36,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
36
36
|
exports.K8sClientGenerator = void 0;
|
|
37
37
|
const openapi_class_transformer_1 = require("openapi-class-transformer");
|
|
38
38
|
const spec_downloader_1 = require("./spec-downloader");
|
|
39
|
+
const esm_fixer_1 = require("./esm-fixer");
|
|
39
40
|
const fs = __importStar(require("fs"));
|
|
40
41
|
const path = __importStar(require("path"));
|
|
41
42
|
const yaml = __importStar(require("js-yaml"));
|
|
@@ -47,7 +48,7 @@ class K8sClientGenerator {
|
|
|
47
48
|
* Generate TypeScript client from a remote OpenAPI spec URL
|
|
48
49
|
*/
|
|
49
50
|
async generate(options) {
|
|
50
|
-
const { specUrl, outputDir, mode = 'full', headers, templateDir, keepSpec = false, resolveReferences = false, timeout, } = options;
|
|
51
|
+
const { specUrl, outputDir, mode = 'full', headers, templateDir, keepSpec = false, resolveReferences = false, fixEsmImports = false, timeout, } = options;
|
|
51
52
|
let specPath;
|
|
52
53
|
try {
|
|
53
54
|
// Step 1: Download the OpenAPI spec
|
|
@@ -80,6 +81,12 @@ class K8sClientGenerator {
|
|
|
80
81
|
}
|
|
81
82
|
const generator = new openapi_class_transformer_1.Generator(generatorOptions);
|
|
82
83
|
await generator.generate();
|
|
84
|
+
// Step 4: Fix ESM imports if requested
|
|
85
|
+
if (fixEsmImports) {
|
|
86
|
+
console.log('Step 4: Fixing ESM imports...');
|
|
87
|
+
const esmFixer = new esm_fixer_1.EsmFixer();
|
|
88
|
+
await esmFixer.fixDirectory(outputDir);
|
|
89
|
+
}
|
|
83
90
|
console.log('✅ Client generation completed successfully!');
|
|
84
91
|
console.log(`📁 Generated files location: ${outputDir}`);
|
|
85
92
|
}
|
package/package.json
CHANGED