wrekenfile-converter 2.0.0 → 2.0.3
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 +64 -1
- package/dist/cli/cli-mini-wrekenfile-generator.d.ts +2 -0
- package/dist/cli/cli-mini-wrekenfile-generator.js +107 -0
- package/dist/cli/cli-openapi-to-wrekenfile.d.ts +2 -0
- package/dist/cli/cli-openapi-to-wrekenfile.js +115 -0
- package/dist/cli/cli-postman-to-wrekenfile.d.ts +2 -0
- package/dist/cli/cli-postman-to-wrekenfile.js +54 -0
- package/dist/openapi-to-wreken.js +99 -42
- package/dist/openapi-to-wrekenfile.d.ts +0 -0
- package/dist/openapi-to-wrekenfile.js +10 -0
- package/dist/openapi-v2-to-wrekenfile.js +112 -56
- package/dist/postman-to-wrekenfile.js +98 -28
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -77,6 +77,30 @@ const variables = {}; // Optionally provide Postman environment variables
|
|
|
77
77
|
const wrekenfileYaml = generateWrekenfileFromPostman(collection, variables);
|
|
78
78
|
```
|
|
79
79
|
|
|
80
|
+
---
|
|
81
|
+
|
|
82
|
+
## CLI: Convert Postman Collection to Wrekenfile (Local/Dev Only)
|
|
83
|
+
|
|
84
|
+
A standalone CLI script is provided for local development to convert a Postman collection JSON to a Wrekenfile YAML file. This script is **not included in the published library** and is safe to use only in your own repo.
|
|
85
|
+
|
|
86
|
+
**Usage:**
|
|
87
|
+
|
|
88
|
+
```bash
|
|
89
|
+
npx ts-node src/cli/cli-postman-to-wrekenfile.ts <postman_collection.json> <output_wrekenfile.yaml> [postman_environment.json]
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
Real example
|
|
93
|
+
|
|
94
|
+
```
|
|
95
|
+
npx ts-node src/cli/cli-postman-to-wrekenfile.ts examples/transact_bridge_postman.json wrekenfile.yaml
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
- The third argument (environment file) is optional.
|
|
99
|
+
- Prints helpful usage and error messages.
|
|
100
|
+
- Does not affect library builds or usage as a dependency.
|
|
101
|
+
|
|
102
|
+
---
|
|
103
|
+
|
|
80
104
|
### Validate a Wrekenfile
|
|
81
105
|
|
|
82
106
|
```typescript
|
|
@@ -182,4 +206,43 @@ mini-wrekenfiles/ # Generated mini chunks (if you save them)
|
|
|
182
206
|
|
|
183
207
|
## License
|
|
184
208
|
|
|
185
|
-
MIT
|
|
209
|
+
MIT
|
|
210
|
+
|
|
211
|
+
# Wrekenfile Tools
|
|
212
|
+
|
|
213
|
+
## Generate Wrekenfile from OpenAPI
|
|
214
|
+
|
|
215
|
+
You can generate a Wrekenfile YAML from an OpenAPI (YAML or JSON) spec using the CLI:
|
|
216
|
+
|
|
217
|
+
```
|
|
218
|
+
npx ts-node src/cli/cli-openapi-to-wrekenfile.ts --input <openapi.yaml|json> [--output <wrekenfile.yaml>] [--cwd <dir>]
|
|
219
|
+
```
|
|
220
|
+
|
|
221
|
+
- `--input` or `-i`: Path to your OpenAPI YAML or JSON file (required)
|
|
222
|
+
- `--output` or `-o`: Path to output Wrekenfile YAML (optional, defaults to `output_wrekenfile.yaml`)
|
|
223
|
+
- `--cwd`: Working directory for resolving $refs (optional, defaults to the input file's directory)
|
|
224
|
+
|
|
225
|
+
**Example:**
|
|
226
|
+
```
|
|
227
|
+
npx ts-node src/cli/cli-openapi-to-wrekenfile.ts --input examples/p3id_swagger.json --output wrekenfile.yaml --cwd .
|
|
228
|
+
```
|
|
229
|
+
|
|
230
|
+
---
|
|
231
|
+
|
|
232
|
+
## Generate Mini Wrekenfiles from a Wrekenfile
|
|
233
|
+
|
|
234
|
+
You can generate mini Wrekenfiles for each endpoint from a main Wrekenfile YAML using the CLI:
|
|
235
|
+
|
|
236
|
+
```
|
|
237
|
+
npx ts-node src/cli/cli-mini-wrekenfile-generator.ts --input <wrekenfile.yaml> [--output <dir>]
|
|
238
|
+
```
|
|
239
|
+
|
|
240
|
+
- `--input` or `-i`: Path to your main Wrekenfile YAML (required)
|
|
241
|
+
- `--output` or `-o`: Output directory for mini Wrekenfiles (optional, defaults to `./mini-wrekenfiles`)
|
|
242
|
+
|
|
243
|
+
**Example:**
|
|
244
|
+
```
|
|
245
|
+
npx ts-node src/cli/cli-mini-wrekenfile-generator.ts --input wrekenfile.yaml --output ./mini-wrekenfiles
|
|
246
|
+
```
|
|
247
|
+
|
|
248
|
+
This will generate one mini Wrekenfile per endpoint in the specified output directory.
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
"use strict";
|
|
3
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
4
|
+
if (k2 === undefined) k2 = k;
|
|
5
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
6
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
7
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
8
|
+
}
|
|
9
|
+
Object.defineProperty(o, k2, desc);
|
|
10
|
+
}) : (function(o, m, k, k2) {
|
|
11
|
+
if (k2 === undefined) k2 = k;
|
|
12
|
+
o[k2] = m[k];
|
|
13
|
+
}));
|
|
14
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
15
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
16
|
+
}) : function(o, v) {
|
|
17
|
+
o["default"] = v;
|
|
18
|
+
});
|
|
19
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
20
|
+
var ownKeys = function(o) {
|
|
21
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
22
|
+
var ar = [];
|
|
23
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
24
|
+
return ar;
|
|
25
|
+
};
|
|
26
|
+
return ownKeys(o);
|
|
27
|
+
};
|
|
28
|
+
return function (mod) {
|
|
29
|
+
if (mod && mod.__esModule) return mod;
|
|
30
|
+
var result = {};
|
|
31
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
32
|
+
__setModuleDefault(result, mod);
|
|
33
|
+
return result;
|
|
34
|
+
};
|
|
35
|
+
})();
|
|
36
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
37
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
38
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
39
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
40
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
41
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
42
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
43
|
+
});
|
|
44
|
+
};
|
|
45
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
46
|
+
const fs = __importStar(require("fs"));
|
|
47
|
+
const path = __importStar(require("path"));
|
|
48
|
+
const mini_wrekenfile_generator_1 = require("../mini-wrekenfile-generator");
|
|
49
|
+
function printUsage() {
|
|
50
|
+
console.log('Usage: npx ts-node src/cli/cli-mini-wrekenfile-generator.ts --input <wrekenfile.yaml> [--output <dir>]');
|
|
51
|
+
}
|
|
52
|
+
function parseArgs() {
|
|
53
|
+
const args = process.argv.slice(2);
|
|
54
|
+
const opts = {};
|
|
55
|
+
for (let i = 0; i < args.length; i++) {
|
|
56
|
+
if (args[i] === '--input' || args[i] === '-i') {
|
|
57
|
+
opts.input = args[++i];
|
|
58
|
+
}
|
|
59
|
+
else if (args[i] === '--output' || args[i] === '-o') {
|
|
60
|
+
opts.output = args[++i];
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
return opts;
|
|
64
|
+
}
|
|
65
|
+
function main() {
|
|
66
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
67
|
+
const opts = parseArgs();
|
|
68
|
+
if (!opts.input) {
|
|
69
|
+
printUsage();
|
|
70
|
+
process.exit(1);
|
|
71
|
+
}
|
|
72
|
+
const inputPath = path.resolve(opts.input);
|
|
73
|
+
const outputDir = opts.output ? path.resolve(opts.output) : path.resolve('./mini-wrekenfiles');
|
|
74
|
+
if (!fs.existsSync(inputPath)) {
|
|
75
|
+
console.error(`Input file not found: ${inputPath}`);
|
|
76
|
+
process.exit(1);
|
|
77
|
+
}
|
|
78
|
+
let wrekenfileContent;
|
|
79
|
+
try {
|
|
80
|
+
wrekenfileContent = fs.readFileSync(inputPath, 'utf8');
|
|
81
|
+
}
|
|
82
|
+
catch (err) {
|
|
83
|
+
console.error('Failed to read input file:', err);
|
|
84
|
+
process.exit(1);
|
|
85
|
+
}
|
|
86
|
+
let miniWrekenfiles;
|
|
87
|
+
try {
|
|
88
|
+
miniWrekenfiles = (0, mini_wrekenfile_generator_1.generateMiniWrekenfiles)(wrekenfileContent);
|
|
89
|
+
}
|
|
90
|
+
catch (err) {
|
|
91
|
+
console.error('Failed to generate mini Wrekenfiles:', err);
|
|
92
|
+
process.exit(1);
|
|
93
|
+
}
|
|
94
|
+
try {
|
|
95
|
+
(0, mini_wrekenfile_generator_1.saveMiniWrekenfiles)(miniWrekenfiles, outputDir);
|
|
96
|
+
console.log(`Generated ${miniWrekenfiles.length} mini Wrekenfiles in ${outputDir}`);
|
|
97
|
+
for (const mini of miniWrekenfiles) {
|
|
98
|
+
console.log(` - ${mini.metadata.filename}`);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
catch (err) {
|
|
102
|
+
console.error('Failed to save mini Wrekenfiles:', err);
|
|
103
|
+
process.exit(1);
|
|
104
|
+
}
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
main();
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
"use strict";
|
|
3
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
4
|
+
if (k2 === undefined) k2 = k;
|
|
5
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
6
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
7
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
8
|
+
}
|
|
9
|
+
Object.defineProperty(o, k2, desc);
|
|
10
|
+
}) : (function(o, m, k, k2) {
|
|
11
|
+
if (k2 === undefined) k2 = k;
|
|
12
|
+
o[k2] = m[k];
|
|
13
|
+
}));
|
|
14
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
15
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
16
|
+
}) : function(o, v) {
|
|
17
|
+
o["default"] = v;
|
|
18
|
+
});
|
|
19
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
20
|
+
var ownKeys = function(o) {
|
|
21
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
22
|
+
var ar = [];
|
|
23
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
24
|
+
return ar;
|
|
25
|
+
};
|
|
26
|
+
return ownKeys(o);
|
|
27
|
+
};
|
|
28
|
+
return function (mod) {
|
|
29
|
+
if (mod && mod.__esModule) return mod;
|
|
30
|
+
var result = {};
|
|
31
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
32
|
+
__setModuleDefault(result, mod);
|
|
33
|
+
return result;
|
|
34
|
+
};
|
|
35
|
+
})();
|
|
36
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
37
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
38
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
39
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
40
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
41
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
42
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
43
|
+
});
|
|
44
|
+
};
|
|
45
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
46
|
+
const fs = __importStar(require("fs"));
|
|
47
|
+
const path = __importStar(require("path"));
|
|
48
|
+
const js_yaml_1 = require("js-yaml");
|
|
49
|
+
const openapi_to_wreken_1 = require("../openapi-to-wreken");
|
|
50
|
+
function printUsage() {
|
|
51
|
+
console.log(`Usage: npx ts-node src/cli/cli-openapi-to-wrekenfile.ts --input <openapi.yaml|json> [--output <wrekenfile.yaml>] [--cwd <dir>]`);
|
|
52
|
+
}
|
|
53
|
+
function parseArgs() {
|
|
54
|
+
const args = process.argv.slice(2);
|
|
55
|
+
const opts = {};
|
|
56
|
+
for (let i = 0; i < args.length; i++) {
|
|
57
|
+
if (args[i] === '--input' || args[i] === '-i') {
|
|
58
|
+
opts.input = args[++i];
|
|
59
|
+
}
|
|
60
|
+
else if (args[i] === '--output' || args[i] === '-o') {
|
|
61
|
+
opts.output = args[++i];
|
|
62
|
+
}
|
|
63
|
+
else if (args[i] === '--cwd') {
|
|
64
|
+
opts.cwd = args[++i];
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
return opts;
|
|
68
|
+
}
|
|
69
|
+
function main() {
|
|
70
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
71
|
+
const opts = parseArgs();
|
|
72
|
+
if (!opts.input) {
|
|
73
|
+
printUsage();
|
|
74
|
+
process.exit(1);
|
|
75
|
+
}
|
|
76
|
+
const inputPath = path.resolve(opts.input);
|
|
77
|
+
const outputPath = path.resolve(opts.output || 'output_wrekenfile.yaml');
|
|
78
|
+
const baseDir = opts.cwd ? path.resolve(opts.cwd) : path.dirname(inputPath);
|
|
79
|
+
if (!fs.existsSync(inputPath)) {
|
|
80
|
+
console.error(`Input file not found: ${inputPath}`);
|
|
81
|
+
process.exit(1);
|
|
82
|
+
}
|
|
83
|
+
let openapiSpec;
|
|
84
|
+
try {
|
|
85
|
+
const raw = fs.readFileSync(inputPath, 'utf8');
|
|
86
|
+
if (inputPath.endsWith('.json')) {
|
|
87
|
+
openapiSpec = JSON.parse(raw);
|
|
88
|
+
}
|
|
89
|
+
else {
|
|
90
|
+
openapiSpec = (0, js_yaml_1.load)(raw);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
catch (err) {
|
|
94
|
+
console.error('Failed to load OpenAPI file:', err);
|
|
95
|
+
process.exit(1);
|
|
96
|
+
}
|
|
97
|
+
let wrekenfileYaml;
|
|
98
|
+
try {
|
|
99
|
+
wrekenfileYaml = (0, openapi_to_wreken_1.generateWrekenfile)(openapiSpec, baseDir);
|
|
100
|
+
}
|
|
101
|
+
catch (err) {
|
|
102
|
+
console.error('Failed to generate Wrekenfile:', err);
|
|
103
|
+
process.exit(1);
|
|
104
|
+
}
|
|
105
|
+
try {
|
|
106
|
+
fs.writeFileSync(outputPath, wrekenfileYaml, 'utf8');
|
|
107
|
+
console.log(`Wrekenfile written to ${outputPath}`);
|
|
108
|
+
}
|
|
109
|
+
catch (err) {
|
|
110
|
+
console.error('Failed to write output file:', err);
|
|
111
|
+
process.exit(1);
|
|
112
|
+
}
|
|
113
|
+
});
|
|
114
|
+
}
|
|
115
|
+
main();
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
"use strict";
|
|
3
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
4
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
5
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
6
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
7
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
8
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
9
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
10
|
+
});
|
|
11
|
+
};
|
|
12
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
13
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
14
|
+
};
|
|
15
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
16
|
+
const fs_1 = __importDefault(require("fs"));
|
|
17
|
+
const postman_to_wrekenfile_1 = require("../postman-to-wrekenfile");
|
|
18
|
+
function printUsage() {
|
|
19
|
+
console.log('Usage: npx ts-node src/cli-postman-to-wrekenfile.ts <postman_collection.json> <output_wrekenfile.yaml> [postman_environment.json]');
|
|
20
|
+
process.exit(1);
|
|
21
|
+
}
|
|
22
|
+
function main() {
|
|
23
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
24
|
+
const args = process.argv.slice(2);
|
|
25
|
+
if (args.length < 2) {
|
|
26
|
+
printUsage();
|
|
27
|
+
}
|
|
28
|
+
const [inputFile, outputFile, envFile] = args;
|
|
29
|
+
if (!fs_1.default.existsSync(inputFile)) {
|
|
30
|
+
console.error(`❌ Input file not found: ${inputFile}`);
|
|
31
|
+
process.exit(1);
|
|
32
|
+
}
|
|
33
|
+
let variables = {};
|
|
34
|
+
if (envFile) {
|
|
35
|
+
if (!fs_1.default.existsSync(envFile)) {
|
|
36
|
+
console.error(`❌ Environment file not found: ${envFile}`);
|
|
37
|
+
process.exit(1);
|
|
38
|
+
}
|
|
39
|
+
variables = (0, postman_to_wrekenfile_1.loadEnvironmentFile)(envFile);
|
|
40
|
+
}
|
|
41
|
+
try {
|
|
42
|
+
const postmanContent = fs_1.default.readFileSync(inputFile, 'utf8');
|
|
43
|
+
const postmanCollection = JSON.parse(postmanContent);
|
|
44
|
+
const wrekenfileYaml = (0, postman_to_wrekenfile_1.generateWrekenfile)(postmanCollection, variables);
|
|
45
|
+
fs_1.default.writeFileSync(outputFile, wrekenfileYaml);
|
|
46
|
+
console.log(`✅ Wrekenfile generated: ${outputFile}`);
|
|
47
|
+
}
|
|
48
|
+
catch (error) {
|
|
49
|
+
console.error(`❌ Error generating Wrekenfile: ${error.message}`);
|
|
50
|
+
process.exit(1);
|
|
51
|
+
}
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
main();
|
|
@@ -38,6 +38,7 @@ exports.generateWrekenfile = generateWrekenfile;
|
|
|
38
38
|
const fs = __importStar(require("fs"));
|
|
39
39
|
const path = __importStar(require("path"));
|
|
40
40
|
const js_yaml_1 = require("js-yaml");
|
|
41
|
+
const js_yaml_2 = require("js-yaml");
|
|
41
42
|
const externalRefCache = {};
|
|
42
43
|
function mapType(type, format) {
|
|
43
44
|
if (format === 'uuid')
|
|
@@ -46,15 +47,32 @@ function mapType(type, format) {
|
|
|
46
47
|
return 'TIMESTAMP';
|
|
47
48
|
if (format === 'binary')
|
|
48
49
|
return 'STRING'; // File uploads
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
50
|
+
if (typeof type === 'string') {
|
|
51
|
+
const t = type.toLowerCase();
|
|
52
|
+
if (t === 'string')
|
|
53
|
+
return 'STRING';
|
|
54
|
+
if (t === 'integer' || t === 'int')
|
|
55
|
+
return 'INT';
|
|
56
|
+
if (t === 'number')
|
|
57
|
+
return 'FLOAT';
|
|
58
|
+
if (t === 'boolean')
|
|
59
|
+
return 'BOOL';
|
|
60
|
+
return 'ANY';
|
|
61
|
+
}
|
|
62
|
+
// Handle array of types (OpenAPI allows type: ['string', 'null'])
|
|
63
|
+
if (Array.isArray(type) && type.length > 0 && typeof type[0] === 'string') {
|
|
64
|
+
const t = type[0].toLowerCase();
|
|
65
|
+
if (t === 'string')
|
|
66
|
+
return 'STRING';
|
|
67
|
+
if (t === 'integer' || t === 'int')
|
|
68
|
+
return 'INT';
|
|
69
|
+
if (t === 'number')
|
|
70
|
+
return 'FLOAT';
|
|
71
|
+
if (t === 'boolean')
|
|
72
|
+
return 'BOOL';
|
|
73
|
+
return 'ANY';
|
|
74
|
+
}
|
|
75
|
+
// Fallback for missing or unexpected type
|
|
58
76
|
return 'ANY';
|
|
59
77
|
}
|
|
60
78
|
function generateDesc(op, method, path) {
|
|
@@ -89,26 +107,27 @@ function resolveRef(ref, spec, baseDir) {
|
|
|
89
107
|
: externalRefCache[fullPath];
|
|
90
108
|
}
|
|
91
109
|
function getTypeFromSchema(schema, spec, baseDir) {
|
|
92
|
-
var _a
|
|
110
|
+
var _a;
|
|
111
|
+
if (!schema || typeof schema !== 'object') {
|
|
112
|
+
return 'ANY';
|
|
113
|
+
}
|
|
93
114
|
if (schema.$ref) {
|
|
94
115
|
const resolvedSchema = resolveRef(schema.$ref, spec, baseDir);
|
|
95
|
-
|
|
96
|
-
if (resolvedSchema.type && resolvedSchema.type !== 'object') {
|
|
116
|
+
if (resolvedSchema && resolvedSchema.type && resolvedSchema.type !== 'object') {
|
|
97
117
|
return mapType(resolvedSchema.type, resolvedSchema.format);
|
|
98
118
|
}
|
|
99
|
-
// It's a complex type, use STRUCT
|
|
100
119
|
return `STRUCT(${schema.$ref.split('/').pop()})`;
|
|
101
120
|
}
|
|
102
121
|
if (schema.type === 'array') {
|
|
103
|
-
if (
|
|
122
|
+
if (schema.items && schema.items.$ref) {
|
|
104
123
|
const resolvedItems = resolveRef(schema.items.$ref, spec, baseDir);
|
|
105
|
-
if (resolvedItems.type && resolvedItems.type !== 'object') {
|
|
124
|
+
if (resolvedItems && resolvedItems.type && resolvedItems.type !== 'object') {
|
|
106
125
|
return `[]${mapType(resolvedItems.type, resolvedItems.format)}`;
|
|
107
126
|
}
|
|
108
127
|
return `[]STRUCT(${schema.items.$ref.split('/').pop()})`;
|
|
109
128
|
}
|
|
110
129
|
else {
|
|
111
|
-
return `[]${mapType((
|
|
130
|
+
return `[]${mapType((_a = schema.items) === null || _a === void 0 ? void 0 : _a.type)}`;
|
|
112
131
|
}
|
|
113
132
|
}
|
|
114
133
|
if (schema.type && schema.type !== 'object') {
|
|
@@ -173,55 +192,55 @@ function extractStructs(spec, baseDir) {
|
|
|
173
192
|
const schemas = ((_a = spec.components) === null || _a === void 0 ? void 0 : _a.schemas) || {};
|
|
174
193
|
// Helper to recursively collect all referenced schemas, traversing all properties, arrays, and combiners
|
|
175
194
|
function collectAllReferencedSchemas(schema, name) {
|
|
176
|
-
if (!name || structs[name])
|
|
195
|
+
if (!schema || typeof schema !== 'object' || !name || structs[name])
|
|
177
196
|
return;
|
|
178
197
|
const resolved = schema.$ref ? resolveRef(schema.$ref, spec, baseDir) : schema;
|
|
179
198
|
const fields = parseSchema(name, resolved, spec, baseDir);
|
|
180
199
|
structs[name] = fields;
|
|
181
200
|
// Traverse all properties
|
|
182
|
-
if (resolved.type === 'object' && resolved.properties) {
|
|
201
|
+
if (resolved && resolved.type === 'object' && resolved.properties && typeof resolved.properties === 'object') {
|
|
183
202
|
for (const [propName, prop] of Object.entries(resolved.properties)) {
|
|
184
|
-
if (prop.$ref) {
|
|
203
|
+
if (prop && typeof prop === 'object' && prop.$ref) {
|
|
185
204
|
const refName = prop.$ref.split('/').pop();
|
|
186
205
|
if (refName)
|
|
187
206
|
collectAllReferencedSchemas(resolveRef(prop.$ref, spec, baseDir), refName);
|
|
188
207
|
}
|
|
189
|
-
else if (prop.type === 'array' && prop.items) {
|
|
190
|
-
if (prop.items.$ref) {
|
|
208
|
+
else if (prop && typeof prop === 'object' && prop.type === 'array' && prop.items) {
|
|
209
|
+
if (prop.items && typeof prop.items === 'object' && prop.items.$ref) {
|
|
191
210
|
const refName = prop.items.$ref.split('/').pop();
|
|
192
211
|
if (refName)
|
|
193
212
|
collectAllReferencedSchemas(resolveRef(prop.items.$ref, spec, baseDir), refName);
|
|
194
213
|
}
|
|
195
|
-
else if (prop.items.type === 'object' || prop.items.properties || prop.items.allOf || prop.items.oneOf || prop.items.anyOf) {
|
|
214
|
+
else if (prop.items && typeof prop.items === 'object' && (prop.items.type === 'object' || prop.items.properties || prop.items.allOf || prop.items.oneOf || prop.items.anyOf)) {
|
|
196
215
|
collectAllReferencedSchemas(prop.items, name + '_' + propName + '_Item');
|
|
197
216
|
}
|
|
198
217
|
}
|
|
199
|
-
else if (prop.type === 'object' || prop.properties || prop.allOf || prop.oneOf || prop.anyOf) {
|
|
218
|
+
else if (prop && typeof prop === 'object' && (prop.type === 'object' || prop.properties || prop.allOf || prop.oneOf || prop.anyOf)) {
|
|
200
219
|
collectAllReferencedSchemas(prop, name + '_' + propName);
|
|
201
220
|
}
|
|
202
221
|
}
|
|
203
222
|
}
|
|
204
223
|
// Traverse array items at root
|
|
205
|
-
if (resolved.type === 'array' && resolved.items) {
|
|
206
|
-
if (resolved.items.$ref) {
|
|
224
|
+
if (resolved && resolved.type === 'array' && resolved.items) {
|
|
225
|
+
if (resolved.items && typeof resolved.items === 'object' && resolved.items.$ref) {
|
|
207
226
|
const refName = resolved.items.$ref.split('/').pop();
|
|
208
227
|
if (refName)
|
|
209
228
|
collectAllReferencedSchemas(resolveRef(resolved.items.$ref, spec, baseDir), refName);
|
|
210
229
|
}
|
|
211
|
-
else if (resolved.items.type === 'object' || resolved.items.properties || resolved.items.allOf || resolved.items.oneOf || resolved.items.anyOf) {
|
|
230
|
+
else if (resolved.items && typeof resolved.items === 'object' && (resolved.items.type === 'object' || resolved.items.properties || resolved.items.allOf || resolved.items.oneOf || resolved.items.anyOf)) {
|
|
212
231
|
collectAllReferencedSchemas(resolved.items, name + '_Item');
|
|
213
232
|
}
|
|
214
233
|
}
|
|
215
234
|
// Traverse allOf/oneOf/anyOf
|
|
216
235
|
for (const combiner of ['allOf', 'oneOf', 'anyOf']) {
|
|
217
|
-
if (Array.isArray(resolved[combiner])) {
|
|
236
|
+
if (resolved && Array.isArray(resolved[combiner])) {
|
|
218
237
|
for (const subSchema of resolved[combiner]) {
|
|
219
|
-
if (subSchema.$ref) {
|
|
238
|
+
if (subSchema && typeof subSchema === 'object' && subSchema.$ref) {
|
|
220
239
|
const refName = subSchema.$ref.split('/').pop();
|
|
221
240
|
if (refName)
|
|
222
241
|
collectAllReferencedSchemas(resolveRef(subSchema.$ref, spec, baseDir), refName);
|
|
223
242
|
}
|
|
224
|
-
else {
|
|
243
|
+
else if (subSchema && typeof subSchema === 'object') {
|
|
225
244
|
collectAllReferencedSchemas(subSchema, name + '_' + combiner);
|
|
226
245
|
}
|
|
227
246
|
}
|
|
@@ -232,7 +251,7 @@ function extractStructs(spec, baseDir) {
|
|
|
232
251
|
for (const name in schemas) {
|
|
233
252
|
collectAllReferencedSchemas(schemas[name], name);
|
|
234
253
|
const schema = schemas[name];
|
|
235
|
-
if (schema.oneOf || schema.anyOf) {
|
|
254
|
+
if (schema && (schema.oneOf || schema.anyOf)) {
|
|
236
255
|
structs[`${name}_Union`] = [{ name: 'value', type: 'ANY', required: 'FALSE' }];
|
|
237
256
|
}
|
|
238
257
|
}
|
|
@@ -243,13 +262,13 @@ function extractStructs(spec, baseDir) {
|
|
|
243
262
|
// Extract request body schemas
|
|
244
263
|
if ((_b = op.requestBody) === null || _b === void 0 ? void 0 : _b.content) {
|
|
245
264
|
for (const [contentType, content] of Object.entries(op.requestBody.content)) {
|
|
246
|
-
if (content.schema) {
|
|
247
|
-
if (content.schema.$ref) {
|
|
265
|
+
if (content && content.schema) {
|
|
266
|
+
if (content.schema && content.schema.$ref) {
|
|
248
267
|
const refName = content.schema.$ref.split('/').pop();
|
|
249
268
|
if (refName)
|
|
250
269
|
collectAllReferencedSchemas(resolveRef(content.schema.$ref, spec, baseDir), refName);
|
|
251
270
|
}
|
|
252
|
-
else {
|
|
271
|
+
else if (content.schema && typeof content.schema === 'object') {
|
|
253
272
|
const requestStructName = generateStructName(operationId, method, pathStr, 'Request');
|
|
254
273
|
collectAllReferencedSchemas(content.schema, requestStructName);
|
|
255
274
|
}
|
|
@@ -259,15 +278,15 @@ function extractStructs(spec, baseDir) {
|
|
|
259
278
|
// Extract response schemas
|
|
260
279
|
if (op.responses) {
|
|
261
280
|
for (const [code, response] of Object.entries(op.responses)) {
|
|
262
|
-
if (response.content) {
|
|
281
|
+
if (response && response.content) {
|
|
263
282
|
for (const [contentType, content] of Object.entries(response.content)) {
|
|
264
|
-
if (content.schema) {
|
|
265
|
-
if (content.schema.$ref) {
|
|
283
|
+
if (content && content.schema) {
|
|
284
|
+
if (content.schema && content.schema.$ref) {
|
|
266
285
|
const refName = content.schema.$ref.split('/').pop();
|
|
267
286
|
if (refName)
|
|
268
287
|
collectAllReferencedSchemas(resolveRef(content.schema.$ref, spec, baseDir), refName);
|
|
269
288
|
}
|
|
270
|
-
else {
|
|
289
|
+
else if (content.schema && typeof content.schema === 'object') {
|
|
271
290
|
const responseStructName = generateStructName(operationId, method, pathStr, `Response${code}`);
|
|
272
291
|
collectAllReferencedSchemas(content.schema, responseStructName);
|
|
273
292
|
}
|
|
@@ -399,13 +418,16 @@ function extractRequestBody(op, operationId, method, path, spec, baseDir) {
|
|
|
399
418
|
if (contentType === 'application/json' && ((_a = requestBody.content[contentType]) === null || _a === void 0 ? void 0 : _a.schema)) {
|
|
400
419
|
const bodySchema = requestBody.content[contentType].schema;
|
|
401
420
|
let type;
|
|
402
|
-
if (bodySchema.$ref) {
|
|
421
|
+
if (bodySchema && bodySchema.$ref) {
|
|
403
422
|
type = getTypeFromSchema(bodySchema, spec, baseDir);
|
|
404
423
|
}
|
|
405
|
-
else {
|
|
424
|
+
else if (bodySchema) {
|
|
406
425
|
const requestStructName = generateStructName(operationId, method, path, 'Request');
|
|
407
426
|
type = `STRUCT(${requestStructName})`;
|
|
408
427
|
}
|
|
428
|
+
else {
|
|
429
|
+
type = 'ANY';
|
|
430
|
+
}
|
|
409
431
|
inputParams.push({
|
|
410
432
|
name: 'body',
|
|
411
433
|
type,
|
|
@@ -414,9 +436,9 @@ function extractRequestBody(op, operationId, method, path, spec, baseDir) {
|
|
|
414
436
|
}
|
|
415
437
|
else if (contentType === 'multipart/form-data' && ((_b = requestBody.content[contentType]) === null || _b === void 0 ? void 0 : _b.schema)) {
|
|
416
438
|
const bodySchema = requestBody.content[contentType].schema;
|
|
417
|
-
if (bodySchema.properties) {
|
|
439
|
+
if (bodySchema && bodySchema.properties) {
|
|
418
440
|
for (const [key, prop] of Object.entries(bodySchema.properties)) {
|
|
419
|
-
const type = prop.format === 'binary' ? 'FILE' : getTypeFromSchema(prop, spec, baseDir);
|
|
441
|
+
const type = prop && prop.format === 'binary' ? 'FILE' : getTypeFromSchema(prop, spec, baseDir);
|
|
420
442
|
const required = (bodySchema.required || []).includes(key) ? 'TRUE' : 'FALSE';
|
|
421
443
|
inputParams.push({
|
|
422
444
|
name: key,
|
|
@@ -541,6 +563,37 @@ function extractSecurityDefaults(spec) {
|
|
|
541
563
|
}
|
|
542
564
|
return defs;
|
|
543
565
|
}
|
|
566
|
+
function cleanYaml(yamlString) {
|
|
567
|
+
return yamlString
|
|
568
|
+
.replace(/\t/g, ' ')
|
|
569
|
+
.replace(/[\u00A0]/g, ' ')
|
|
570
|
+
.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, '')
|
|
571
|
+
.replace(/[ \t]+$/gm, '')
|
|
572
|
+
.replace(/\r\n/g, '\n');
|
|
573
|
+
}
|
|
574
|
+
function checkYamlForHiddenChars(yamlString) {
|
|
575
|
+
const lines = yamlString.split('\n');
|
|
576
|
+
for (let i = 0; i < lines.length; i++) {
|
|
577
|
+
const line = lines[i];
|
|
578
|
+
if (/\t/.test(line)) {
|
|
579
|
+
throw new Error(`YAML contains a TAB character at line ${i + 1}:\n${line}`);
|
|
580
|
+
}
|
|
581
|
+
if (/\u00A0/.test(line)) {
|
|
582
|
+
throw new Error(`YAML contains a non-breaking space (U+00A0) at line ${i + 1}:\n${line}`);
|
|
583
|
+
}
|
|
584
|
+
if (/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/.test(line)) {
|
|
585
|
+
throw new Error(`YAML contains a non-printable character at line ${i + 1}:\n${line}`);
|
|
586
|
+
}
|
|
587
|
+
}
|
|
588
|
+
}
|
|
589
|
+
function validateYaml(yamlString) {
|
|
590
|
+
try {
|
|
591
|
+
(0, js_yaml_2.load)(yamlString);
|
|
592
|
+
}
|
|
593
|
+
catch (e) {
|
|
594
|
+
throw new Error('Generated YAML is invalid: ' + e.message);
|
|
595
|
+
}
|
|
596
|
+
}
|
|
544
597
|
function generateWrekenfile(spec, baseDir) {
|
|
545
598
|
var _a, _b;
|
|
546
599
|
if (!spec || typeof spec !== 'object') {
|
|
@@ -549,7 +602,7 @@ function generateWrekenfile(spec, baseDir) {
|
|
|
549
602
|
if (!baseDir || typeof baseDir !== 'string') {
|
|
550
603
|
throw new Error("Argument 'baseDir' is required and must be a string");
|
|
551
604
|
}
|
|
552
|
-
|
|
605
|
+
let yamlString = (0, js_yaml_1.dump)({
|
|
553
606
|
VERSION: '1.2',
|
|
554
607
|
INIT: {
|
|
555
608
|
DEFAULTS: [
|
|
@@ -560,4 +613,8 @@ function generateWrekenfile(spec, baseDir) {
|
|
|
560
613
|
INTERFACES: extractInterfaces(spec, baseDir),
|
|
561
614
|
STRUCTS: extractStructs(spec, baseDir),
|
|
562
615
|
});
|
|
616
|
+
yamlString = cleanYaml(yamlString);
|
|
617
|
+
checkYamlForHiddenChars(yamlString);
|
|
618
|
+
validateYaml(yamlString);
|
|
619
|
+
return yamlString;
|
|
563
620
|
}
|
|
File without changes
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// Defensive fixes for OpenAPI v3 schema traversal
|
|
3
|
+
// 1. Guard all property accesses ($ref, type, properties, items, allOf, oneOf, anyOf) with checks for existence and correct type
|
|
4
|
+
// 2. Guard all calls to getTypeFromSchema or similar with checks for defined/object
|
|
5
|
+
// 3. Apply these checks in all recursive struct extraction and interface extraction logic
|
|
6
|
+
// Example for a recursive schema traversal:
|
|
7
|
+
// if (schema && typeof schema === 'object' && schema.$ref) { ... }
|
|
8
|
+
// if (prop && typeof prop === 'object' && prop.type === 'array' && prop.items) { ... }
|
|
9
|
+
// ...
|
|
10
|
+
// Please apply these patterns throughout the file, especially in struct extraction and interface extraction functions.
|
|
@@ -38,6 +38,7 @@ exports.generateWrekenfile = generateWrekenfile;
|
|
|
38
38
|
const fs = __importStar(require("fs"));
|
|
39
39
|
const path = __importStar(require("path"));
|
|
40
40
|
const js_yaml_1 = require("js-yaml");
|
|
41
|
+
const js_yaml_2 = require("js-yaml");
|
|
41
42
|
const externalRefCache = {};
|
|
42
43
|
function mapType(type, format) {
|
|
43
44
|
if (format === 'uuid')
|
|
@@ -46,15 +47,32 @@ function mapType(type, format) {
|
|
|
46
47
|
return 'TIMESTAMP';
|
|
47
48
|
if (format === 'binary')
|
|
48
49
|
return 'STRING'; // File uploads
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
50
|
+
if (typeof type === 'string') {
|
|
51
|
+
const t = type.toLowerCase();
|
|
52
|
+
if (t === 'string')
|
|
53
|
+
return 'STRING';
|
|
54
|
+
if (t === 'integer' || t === 'int')
|
|
55
|
+
return 'INT';
|
|
56
|
+
if (t === 'number')
|
|
57
|
+
return 'FLOAT';
|
|
58
|
+
if (t === 'boolean')
|
|
59
|
+
return 'BOOL';
|
|
60
|
+
return 'ANY';
|
|
61
|
+
}
|
|
62
|
+
// Handle array of types (OpenAPI allows type: ['string', 'null'])
|
|
63
|
+
if (Array.isArray(type) && type.length > 0 && typeof type[0] === 'string') {
|
|
64
|
+
const t = type[0].toLowerCase();
|
|
65
|
+
if (t === 'string')
|
|
66
|
+
return 'STRING';
|
|
67
|
+
if (t === 'integer' || t === 'int')
|
|
68
|
+
return 'INT';
|
|
69
|
+
if (t === 'number')
|
|
70
|
+
return 'FLOAT';
|
|
71
|
+
if (t === 'boolean')
|
|
72
|
+
return 'BOOL';
|
|
73
|
+
return 'ANY';
|
|
74
|
+
}
|
|
75
|
+
// Fallback for missing or unexpected type
|
|
58
76
|
return 'ANY';
|
|
59
77
|
}
|
|
60
78
|
function generateDesc(op, method, path) {
|
|
@@ -89,14 +107,16 @@ function resolveRef(ref, spec, baseDir) {
|
|
|
89
107
|
: externalRefCache[fullPath];
|
|
90
108
|
}
|
|
91
109
|
function parseSchema(name, schema, spec, baseDir, depth = 0) {
|
|
92
|
-
var _a, _b
|
|
110
|
+
var _a, _b;
|
|
93
111
|
if (depth > 3)
|
|
94
112
|
return [];
|
|
95
|
-
if (schema.$ref)
|
|
113
|
+
if (schema && typeof schema === 'object' && schema.$ref) {
|
|
96
114
|
return parseSchema(name, resolveRef(schema.$ref, spec, baseDir), spec, baseDir, depth + 1);
|
|
97
|
-
|
|
115
|
+
}
|
|
116
|
+
if (schema && typeof schema === 'object' && schema.allOf) {
|
|
98
117
|
return schema.allOf.flatMap((s) => parseSchema(name, s, spec, baseDir, depth + 1));
|
|
99
|
-
|
|
118
|
+
}
|
|
119
|
+
if (schema && typeof schema === 'object' && (schema.oneOf || schema.anyOf)) {
|
|
100
120
|
return [{
|
|
101
121
|
name: 'variant',
|
|
102
122
|
type: `STRUCT(${name}_Union)`,
|
|
@@ -104,32 +124,33 @@ function parseSchema(name, schema, spec, baseDir, depth = 0) {
|
|
|
104
124
|
}];
|
|
105
125
|
}
|
|
106
126
|
// Handle primitive types - return empty array (no struct needed)
|
|
107
|
-
if (schema.type && schema.type !== 'object' && schema.type !== 'array') {
|
|
127
|
+
if (schema && typeof schema === 'object' && schema.type && schema.type !== 'object' && schema.type !== 'array') {
|
|
108
128
|
return [];
|
|
109
129
|
}
|
|
110
130
|
// Handle empty objects (no properties) - return empty array
|
|
111
|
-
if (schema.type === 'object' && (!schema.properties || Object.keys(schema.properties).length === 0)) {
|
|
131
|
+
if (schema && typeof schema === 'object' && schema.type === 'object' && (!schema.properties || Object.keys(schema.properties).length === 0)) {
|
|
112
132
|
return [];
|
|
113
133
|
}
|
|
114
134
|
const fields = [];
|
|
115
|
-
if ((_a = schema.discriminator) === null || _a === void 0 ? void 0 : _a.propertyName) {
|
|
135
|
+
if (schema && typeof schema === 'object' && ((_a = schema.discriminator) === null || _a === void 0 ? void 0 : _a.propertyName)) {
|
|
116
136
|
fields.push({
|
|
117
137
|
name: schema.discriminator.propertyName,
|
|
118
138
|
type: 'STRING',
|
|
119
139
|
required: 'REQUIRED',
|
|
120
140
|
});
|
|
121
141
|
}
|
|
122
|
-
if (schema.type === 'object' && schema.properties) {
|
|
142
|
+
if (schema && typeof schema === 'object' && schema.type === 'object' && schema.properties) {
|
|
123
143
|
for (const [key, prop] of Object.entries(schema.properties)) {
|
|
124
144
|
let type = 'ANY';
|
|
125
|
-
if (prop.$ref)
|
|
145
|
+
if (prop && typeof prop === 'object' && prop.$ref) {
|
|
126
146
|
type = `STRUCT(${prop.$ref.split('/').pop()})`;
|
|
127
|
-
|
|
128
|
-
|
|
147
|
+
}
|
|
148
|
+
else if (prop && typeof prop === 'object' && prop.type === 'array') {
|
|
149
|
+
if (prop && typeof prop === 'object' && prop.items && prop.items.$ref) {
|
|
129
150
|
type = `[]STRUCT(${prop.items.$ref.split('/').pop()})`;
|
|
130
151
|
}
|
|
131
|
-
else {
|
|
132
|
-
type = `[]${mapType((
|
|
152
|
+
else if (prop && typeof prop === 'object' && prop.items) {
|
|
153
|
+
type = `[]${mapType((_b = prop.items) === null || _b === void 0 ? void 0 : _b.type)}`;
|
|
133
154
|
}
|
|
134
155
|
}
|
|
135
156
|
else {
|
|
@@ -162,7 +183,7 @@ function extractStructs(spec, baseDir) {
|
|
|
162
183
|
const fields = parseSchema(name, definitions[name], spec, baseDir);
|
|
163
184
|
// Always add the struct, even if empty
|
|
164
185
|
structs[name] = fields;
|
|
165
|
-
if (definitions[name].oneOf || definitions[name].anyOf) {
|
|
186
|
+
if (definitions[name] && typeof definitions[name] === 'object' && (definitions[name].oneOf || definitions[name].anyOf)) {
|
|
166
187
|
structs[`${name}_Union`] = [{ name: 'value', type: 'ANY', required: 'OPTIONAL' }];
|
|
167
188
|
}
|
|
168
189
|
}
|
|
@@ -173,7 +194,7 @@ function extractStructs(spec, baseDir) {
|
|
|
173
194
|
// Extract request body schemas (OpenAPI v2 uses parameters with in: body)
|
|
174
195
|
if (op.parameters) {
|
|
175
196
|
for (const param of op.parameters) {
|
|
176
|
-
if (param.in === 'body' && param.schema && !param.schema.$ref) {
|
|
197
|
+
if (param && typeof param === 'object' && param.in === 'body' && param.schema && !param.schema.$ref) {
|
|
177
198
|
// Inline schema - create a struct for it only if it has fields
|
|
178
199
|
const requestStructName = generateStructName(operationId, method, pathStr, 'Request');
|
|
179
200
|
const fields = parseSchema(requestStructName, param.schema, spec, baseDir);
|
|
@@ -186,7 +207,7 @@ function extractStructs(spec, baseDir) {
|
|
|
186
207
|
// Extract response schemas (OpenAPI v2 has schema directly in response)
|
|
187
208
|
if (op.responses) {
|
|
188
209
|
for (const [code, response] of Object.entries(op.responses)) {
|
|
189
|
-
if (response.schema && !response.schema.$ref) {
|
|
210
|
+
if (response && typeof response === 'object' && response.schema && !response.schema.$ref) {
|
|
190
211
|
// Inline schema - create a struct for it only if it has fields
|
|
191
212
|
const responseStructName = generateStructName(operationId, method, pathStr, `Response${code}`);
|
|
192
213
|
const fields = parseSchema(responseStructName, response.schema, spec, baseDir);
|
|
@@ -203,7 +224,7 @@ function extractStructs(spec, baseDir) {
|
|
|
203
224
|
function getContentTypeAndBodyType(op) {
|
|
204
225
|
var _a;
|
|
205
226
|
// Check if there are formData parameters
|
|
206
|
-
const hasFormData = (_a = op.parameters) === null || _a === void 0 ? void 0 : _a.some((param) => param.in === 'formData');
|
|
227
|
+
const hasFormData = (_a = op.parameters) === null || _a === void 0 ? void 0 : _a.some((param) => param && typeof param === 'object' && param.in === 'formData');
|
|
207
228
|
if (hasFormData) {
|
|
208
229
|
return { contentType: 'multipart/form-data', bodyType: 'FORM' };
|
|
209
230
|
}
|
|
@@ -259,7 +280,7 @@ function getHeadersForOperation(op, spec) {
|
|
|
259
280
|
// Check if Authorization is used as a parameter but not defined in securityDefinitions
|
|
260
281
|
if (op.parameters) {
|
|
261
282
|
for (const param of op.parameters) {
|
|
262
|
-
if (param.in === 'header' && param.name === 'Authorization' && !headerMap.has('Authorization')) {
|
|
283
|
+
if (param && typeof param === 'object' && param.in === 'header' && param.name === 'Authorization' && !headerMap.has('Authorization')) {
|
|
263
284
|
headerMap.set('Authorization', 'bearer_token');
|
|
264
285
|
}
|
|
265
286
|
}
|
|
@@ -280,25 +301,25 @@ function extractParameters(op, spec) {
|
|
|
280
301
|
if (op.parameters) {
|
|
281
302
|
for (let param of op.parameters) {
|
|
282
303
|
// Resolve parameter references
|
|
283
|
-
if (param.$ref) {
|
|
304
|
+
if (param && typeof param === 'object' && param.$ref) {
|
|
284
305
|
if (!spec.swaggerFile) {
|
|
285
306
|
throw new Error("spec.swaggerFile is undefined. Please provide a valid baseDir when calling generateWrekenfile, or ensure all refs are internal.");
|
|
286
307
|
}
|
|
287
308
|
param = resolveRef(param.$ref, spec, path.dirname(spec.swaggerFile));
|
|
288
309
|
}
|
|
289
310
|
// Skip body and formData parameters, they are handled in extractRequestBody
|
|
290
|
-
if (param.in === 'body' || param.in === 'formData') {
|
|
311
|
+
if (param && typeof param === 'object' && (param.in === 'body' || param.in === 'formData')) {
|
|
291
312
|
continue;
|
|
292
313
|
}
|
|
293
|
-
const paramType = param.in || 'query';
|
|
294
|
-
const paramName = param.name;
|
|
295
|
-
const paramSchema = param.schema || {}; // Schema is sometimes at root of param
|
|
296
|
-
const paramRequired = param.required ? 'REQUIRED' : 'OPTIONAL';
|
|
314
|
+
const paramType = param && typeof param === 'object' ? param.in || 'query' : 'query';
|
|
315
|
+
const paramName = param && typeof param === 'object' ? param.name : '';
|
|
316
|
+
const paramSchema = param && typeof param === 'object' ? param.schema || {} : {}; // Schema is sometimes at root of param
|
|
317
|
+
const paramRequired = param && typeof param === 'object' ? param.required ? 'REQUIRED' : 'OPTIONAL' : 'OPTIONAL';
|
|
297
318
|
let type = 'STRING';
|
|
298
|
-
if (param.type) {
|
|
319
|
+
if (param && typeof param === 'object' && param.type) {
|
|
299
320
|
type = mapType(param.type, param.format);
|
|
300
321
|
}
|
|
301
|
-
else if (paramSchema.type) {
|
|
322
|
+
else if (paramSchema && typeof paramSchema === 'object' && paramSchema.type) {
|
|
302
323
|
type = mapType(paramSchema.type, paramSchema.format);
|
|
303
324
|
}
|
|
304
325
|
inputParams.push({
|
|
@@ -314,10 +335,10 @@ function extractParameters(op, spec) {
|
|
|
314
335
|
function extractRequestBody(op, operationId, method, path) {
|
|
315
336
|
var _a;
|
|
316
337
|
const inputParams = [];
|
|
317
|
-
const bodyParam = (op.parameters || []).find((p) => p.in === 'body');
|
|
338
|
+
const bodyParam = (op.parameters || []).find((p) => p && typeof p === 'object' && p.in === 'body');
|
|
318
339
|
if (bodyParam) {
|
|
319
340
|
let type;
|
|
320
|
-
if ((_a = bodyParam.schema) === null || _a === void 0 ? void 0 : _a.$ref) {
|
|
341
|
+
if (bodyParam && typeof bodyParam === 'object' && ((_a = bodyParam.schema) === null || _a === void 0 ? void 0 : _a.$ref)) {
|
|
321
342
|
type = `STRUCT(${bodyParam.schema.$ref.split('/').pop()})`;
|
|
322
343
|
}
|
|
323
344
|
else {
|
|
@@ -328,17 +349,17 @@ function extractRequestBody(op, operationId, method, path) {
|
|
|
328
349
|
inputParams.push({
|
|
329
350
|
name: 'body',
|
|
330
351
|
type,
|
|
331
|
-
required: bodyParam.required ? 'REQUIRED' : 'OPTIONAL',
|
|
352
|
+
required: bodyParam && typeof bodyParam === 'object' ? bodyParam.required ? 'REQUIRED' : 'OPTIONAL' : 'OPTIONAL',
|
|
332
353
|
});
|
|
333
354
|
}
|
|
334
355
|
// Handle formData for multipart/form-data
|
|
335
356
|
if (op.parameters) {
|
|
336
357
|
for (const param of op.parameters) {
|
|
337
|
-
if (param.in === 'formData') {
|
|
358
|
+
if (param && typeof param === 'object' && param.in === 'formData') {
|
|
338
359
|
inputParams.push({
|
|
339
|
-
name: param.name,
|
|
340
|
-
type: param.type === 'file' ? 'FILE' : mapType(param.type, param.format),
|
|
341
|
-
required: param.required ? 'REQUIRED' : 'OPTIONAL',
|
|
360
|
+
name: param && typeof param === 'object' ? param.name : '',
|
|
361
|
+
type: param && typeof param === 'object' ? param.type === 'file' ? 'FILE' : mapType(param.type, param.format) : 'STRING',
|
|
362
|
+
required: param && typeof param === 'object' ? param.required ? 'REQUIRED' : 'OPTIONAL' : 'OPTIONAL',
|
|
342
363
|
});
|
|
343
364
|
}
|
|
344
365
|
}
|
|
@@ -351,25 +372,25 @@ function extractResponses(op, operationId, method, path) {
|
|
|
351
372
|
// Handle all response codes (success and error)
|
|
352
373
|
for (const [code, response] of Object.entries(op.responses || {})) {
|
|
353
374
|
let returnType = 'ANY';
|
|
354
|
-
if (response.schema) {
|
|
355
|
-
if (response.schema.$ref) {
|
|
375
|
+
if (response && typeof response === 'object' && response.schema) {
|
|
376
|
+
if (response && typeof response === 'object' && response.schema.$ref) {
|
|
356
377
|
returnType = `STRUCT(${response.schema.$ref.split('/').pop()})`;
|
|
357
378
|
}
|
|
358
|
-
else if (response.schema.type === 'array') {
|
|
359
|
-
if ((_a = response.schema.items) === null || _a === void 0 ? void 0 : _a.$ref) {
|
|
379
|
+
else if (response && typeof response === 'object' && response.schema.type === 'array') {
|
|
380
|
+
if (response && typeof response === 'object' && ((_a = response.schema.items) === null || _a === void 0 ? void 0 : _a.$ref)) {
|
|
360
381
|
returnType = `[]STRUCT(${response.schema.items.$ref.split('/').pop()})`;
|
|
361
382
|
}
|
|
362
|
-
else {
|
|
383
|
+
else if (response && typeof response === 'object' && response.schema.items) {
|
|
363
384
|
returnType = `[]${mapType((_b = response.schema.items) === null || _b === void 0 ? void 0 : _b.type)}`;
|
|
364
385
|
}
|
|
365
386
|
}
|
|
366
|
-
else if (response.schema.type === 'object') {
|
|
387
|
+
else if (response && typeof response === 'object' && response.schema.type === 'object') {
|
|
367
388
|
// Inline schema - use generated struct name
|
|
368
389
|
const responseStructName = generateStructName(operationId, method, path, `Response${code}`);
|
|
369
390
|
returnType = `STRUCT(${responseStructName})`;
|
|
370
391
|
}
|
|
371
392
|
}
|
|
372
|
-
else if (code === '204' || response.description === 'No Content') {
|
|
393
|
+
else if (code === '204' || response && typeof response === 'object' && response.description === 'No Content') {
|
|
373
394
|
returnType = 'VOID';
|
|
374
395
|
}
|
|
375
396
|
returns.push({
|
|
@@ -396,7 +417,7 @@ function extractInterfaces(spec) {
|
|
|
396
417
|
const alias = operationId;
|
|
397
418
|
const endpoint = pathStr.includes('{') ? `\`${base}${pathStr}\`` : `${base}${pathStr}`;
|
|
398
419
|
// Check if operation is hidden from docs
|
|
399
|
-
const isPrivate = op['x-hidden-from-docs'] === true;
|
|
420
|
+
const isPrivate = op && typeof op === 'object' && op['x-hidden-from-docs'] === true;
|
|
400
421
|
const visibility = isPrivate ? 'PRIVATE' : 'PUBLIC';
|
|
401
422
|
const { bodyType } = getContentTypeAndBodyType(op);
|
|
402
423
|
const headers = getHeadersForOperation(op, spec);
|
|
@@ -426,23 +447,54 @@ function extractSecurityDefaults(spec) {
|
|
|
426
447
|
const defs = [];
|
|
427
448
|
const securityDefinitions = spec.securityDefinitions || {};
|
|
428
449
|
for (const [name, scheme] of Object.entries(securityDefinitions)) {
|
|
429
|
-
if (scheme.type === 'basic') {
|
|
450
|
+
if (scheme && typeof scheme === 'object' && scheme.type === 'basic') {
|
|
430
451
|
defs.push({ basic_auth: 'Basic <BASE64>' });
|
|
431
452
|
}
|
|
432
|
-
else if (scheme.type === 'apiKey') {
|
|
433
|
-
if (scheme.in === 'header') {
|
|
453
|
+
else if (scheme && typeof scheme === 'object' && scheme.type === 'apiKey') {
|
|
454
|
+
if (scheme && typeof scheme === 'object' && scheme.in === 'header') {
|
|
434
455
|
defs.push({ [scheme.name.toLowerCase()]: `<${scheme.name.toUpperCase()}>` });
|
|
435
456
|
}
|
|
436
|
-
else if (scheme.in === 'query') {
|
|
457
|
+
else if (scheme && typeof scheme === 'object' && scheme.in === 'query') {
|
|
437
458
|
defs.push({ [`query_${scheme.name.toLowerCase()}`]: `<${scheme.name.toUpperCase()}>` });
|
|
438
459
|
}
|
|
439
460
|
}
|
|
440
|
-
else if (scheme.type === 'oauth2') {
|
|
461
|
+
else if (scheme && typeof scheme === 'object' && scheme.type === 'oauth2') {
|
|
441
462
|
defs.push({ bearer_token: 'BEARER <ACCESS_TOKEN>' });
|
|
442
463
|
}
|
|
443
464
|
}
|
|
444
465
|
return defs;
|
|
445
466
|
}
|
|
467
|
+
function cleanYaml(yamlString) {
|
|
468
|
+
return yamlString
|
|
469
|
+
.replace(/\t/g, ' ')
|
|
470
|
+
.replace(/[\u00A0]/g, ' ')
|
|
471
|
+
.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, '')
|
|
472
|
+
.replace(/[ \t]+$/gm, '')
|
|
473
|
+
.replace(/\r\n/g, '\n');
|
|
474
|
+
}
|
|
475
|
+
function checkYamlForHiddenChars(yamlString) {
|
|
476
|
+
const lines = yamlString.split('\n');
|
|
477
|
+
for (let i = 0; i < lines.length; i++) {
|
|
478
|
+
const line = lines[i];
|
|
479
|
+
if (/\t/.test(line)) {
|
|
480
|
+
throw new Error(`YAML contains a TAB character at line ${i + 1}:\n${line}`);
|
|
481
|
+
}
|
|
482
|
+
if (/\u00A0/.test(line)) {
|
|
483
|
+
throw new Error(`YAML contains a non-breaking space (U+00A0) at line ${i + 1}:\n${line}`);
|
|
484
|
+
}
|
|
485
|
+
if (/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/.test(line)) {
|
|
486
|
+
throw new Error(`YAML contains a non-printable character at line ${i + 1}:\n${line}`);
|
|
487
|
+
}
|
|
488
|
+
}
|
|
489
|
+
}
|
|
490
|
+
function validateYaml(yamlString) {
|
|
491
|
+
try {
|
|
492
|
+
(0, js_yaml_2.load)(yamlString);
|
|
493
|
+
}
|
|
494
|
+
catch (e) {
|
|
495
|
+
throw new Error('Generated YAML is invalid: ' + e.message);
|
|
496
|
+
}
|
|
497
|
+
}
|
|
446
498
|
function generateWrekenfile(spec, baseDir) {
|
|
447
499
|
var _a;
|
|
448
500
|
if (!spec || typeof spec !== 'object') {
|
|
@@ -453,7 +505,7 @@ function generateWrekenfile(spec, baseDir) {
|
|
|
453
505
|
}
|
|
454
506
|
// Add swaggerFile path to spec for ref resolution
|
|
455
507
|
spec.swaggerFile = baseDir;
|
|
456
|
-
|
|
508
|
+
let yamlString = (0, js_yaml_1.dump)({
|
|
457
509
|
VERSION: '1.2',
|
|
458
510
|
INIT: {
|
|
459
511
|
DEFAULTS: [
|
|
@@ -464,4 +516,8 @@ function generateWrekenfile(spec, baseDir) {
|
|
|
464
516
|
INTERFACES: extractInterfaces(spec),
|
|
465
517
|
STRUCTS: extractStructs(spec, baseDir),
|
|
466
518
|
}, { noArrayIndent: true });
|
|
519
|
+
yamlString = cleanYaml(yamlString);
|
|
520
|
+
checkYamlForHiddenChars(yamlString);
|
|
521
|
+
validateYaml(yamlString);
|
|
522
|
+
return yamlString;
|
|
467
523
|
}
|
|
@@ -43,6 +43,7 @@ exports.loadEnvironmentFile = loadEnvironmentFile;
|
|
|
43
43
|
exports.extractCollectionVariables = extractCollectionVariables;
|
|
44
44
|
exports.resolveVariables = resolveVariables;
|
|
45
45
|
const fs = __importStar(require("fs"));
|
|
46
|
+
const js_yaml_1 = require("js-yaml");
|
|
46
47
|
function mapType(value) {
|
|
47
48
|
if (typeof value === 'string') {
|
|
48
49
|
// Check for common patterns
|
|
@@ -103,14 +104,24 @@ function extractFieldsFromObject(obj, depth = 0, prefix = '') {
|
|
|
103
104
|
if (typeof obj !== 'object')
|
|
104
105
|
return [];
|
|
105
106
|
const fields = [];
|
|
107
|
+
const keyCount = {};
|
|
106
108
|
for (const [key, value] of Object.entries(obj)) {
|
|
107
109
|
let type = 'ANY';
|
|
108
110
|
let required = 'OPTIONAL';
|
|
111
|
+
// Handle duplicate keys
|
|
112
|
+
let fieldName = key;
|
|
113
|
+
if (keyCount[key] === undefined) {
|
|
114
|
+
keyCount[key] = 1;
|
|
115
|
+
}
|
|
116
|
+
else {
|
|
117
|
+
keyCount[key] += 1;
|
|
118
|
+
fieldName = `${key} ${keyCount[key]}`;
|
|
119
|
+
}
|
|
109
120
|
if (Array.isArray(value)) {
|
|
110
121
|
if (value.length > 0) {
|
|
111
122
|
const firstItem = value[0];
|
|
112
123
|
if (typeof firstItem === 'object' && firstItem !== null) {
|
|
113
|
-
type = `[]STRUCT(${prefix}${
|
|
124
|
+
type = `[]STRUCT(${prefix}${fieldName}Item)`;
|
|
114
125
|
}
|
|
115
126
|
else {
|
|
116
127
|
type = `[]${mapType(firstItem)}`;
|
|
@@ -121,13 +132,13 @@ function extractFieldsFromObject(obj, depth = 0, prefix = '') {
|
|
|
121
132
|
}
|
|
122
133
|
}
|
|
123
134
|
else if (typeof value === 'object' && value !== null) {
|
|
124
|
-
type = `STRUCT(${prefix}${
|
|
135
|
+
type = `STRUCT(${prefix}${fieldName})`;
|
|
125
136
|
}
|
|
126
137
|
else {
|
|
127
138
|
type = mapType(value);
|
|
128
139
|
}
|
|
129
140
|
fields.push({
|
|
130
|
-
name:
|
|
141
|
+
name: fieldName,
|
|
131
142
|
type,
|
|
132
143
|
required,
|
|
133
144
|
});
|
|
@@ -175,6 +186,17 @@ function resolveVariables(value, variables) {
|
|
|
175
186
|
}
|
|
176
187
|
function extractStructs(collection, variables) {
|
|
177
188
|
const structs = {};
|
|
189
|
+
const structNameCount = {};
|
|
190
|
+
function getUniqueStructName(name) {
|
|
191
|
+
if (structs[name] === undefined) {
|
|
192
|
+
structNameCount[name] = 1;
|
|
193
|
+
return name;
|
|
194
|
+
}
|
|
195
|
+
else {
|
|
196
|
+
structNameCount[name] = (structNameCount[name] || 1) + 1;
|
|
197
|
+
return `${name} ${structNameCount[name]}`;
|
|
198
|
+
}
|
|
199
|
+
}
|
|
178
200
|
function processItem(item) {
|
|
179
201
|
var _a;
|
|
180
202
|
if (item.request) {
|
|
@@ -186,47 +208,41 @@ function extractStructs(collection, variables) {
|
|
|
186
208
|
if (((_a = item.request.body) === null || _a === void 0 ? void 0 : _a.mode) === 'raw' && item.request.body.raw) {
|
|
187
209
|
const bodyData = parseJsonExample(item.request.body.raw);
|
|
188
210
|
if (bodyData) {
|
|
189
|
-
|
|
211
|
+
let requestStructName = generateStructName(itemName, method, path, 'Request');
|
|
212
|
+
requestStructName = getUniqueStructName(requestStructName);
|
|
190
213
|
const fields = extractFieldsFromObject(bodyData, 0, requestStructName);
|
|
191
|
-
// Always add the struct, even if empty
|
|
192
214
|
structs[requestStructName] = fields.length > 0 ? fields : [];
|
|
193
|
-
// Extract nested structs from request body
|
|
194
215
|
extractNestedStructs(bodyData, structs, requestStructName);
|
|
195
216
|
}
|
|
196
217
|
}
|
|
197
218
|
// Extract response structs from examples
|
|
198
219
|
if (item.response) {
|
|
199
220
|
for (const response of item.response) {
|
|
200
|
-
|
|
221
|
+
let responseStructName = generateStructName(itemName, method, path, `Response${response.code || '200'}`);
|
|
222
|
+
responseStructName = getUniqueStructName(responseStructName);
|
|
201
223
|
if (response.body) {
|
|
202
224
|
const responseData = parseJsonExample(response.body);
|
|
203
225
|
if (responseData) {
|
|
204
226
|
const fields = extractFieldsFromObject(responseData, 0, responseStructName);
|
|
205
|
-
// Always add the struct, even if empty
|
|
206
227
|
structs[responseStructName] = fields.length > 0 ? fields : [];
|
|
207
|
-
// Extract nested structs from response body
|
|
208
228
|
extractNestedStructs(responseData, structs, responseStructName);
|
|
209
229
|
}
|
|
210
230
|
else {
|
|
211
|
-
// If body is not valid JSON or empty, create an empty struct
|
|
212
231
|
structs[responseStructName] = [];
|
|
213
232
|
}
|
|
214
233
|
}
|
|
215
234
|
else {
|
|
216
|
-
// If no body at all, still create an empty struct
|
|
217
235
|
structs[responseStructName] = [];
|
|
218
236
|
}
|
|
219
237
|
}
|
|
220
238
|
}
|
|
221
239
|
}
|
|
222
|
-
// Recursively process nested items
|
|
223
240
|
if (item.item) {
|
|
224
241
|
for (const subItem of item.item) {
|
|
225
242
|
processItem(subItem);
|
|
226
243
|
}
|
|
227
244
|
}
|
|
228
245
|
}
|
|
229
|
-
// Process all items in the collection
|
|
230
246
|
for (const item of collection.item) {
|
|
231
247
|
processItem(item);
|
|
232
248
|
}
|
|
@@ -396,6 +412,17 @@ function extractResponses(item, itemName, method, path) {
|
|
|
396
412
|
}
|
|
397
413
|
function extractOperations(collection, variables) {
|
|
398
414
|
const operations = [];
|
|
415
|
+
const operationNameCount = {};
|
|
416
|
+
function getUniqueOperationName(name) {
|
|
417
|
+
if (operationNameCount[name] === undefined) {
|
|
418
|
+
operationNameCount[name] = 1;
|
|
419
|
+
return name;
|
|
420
|
+
}
|
|
421
|
+
else {
|
|
422
|
+
operationNameCount[name] += 1;
|
|
423
|
+
return `${name} ${operationNameCount[name]}`;
|
|
424
|
+
}
|
|
425
|
+
}
|
|
399
426
|
function processItem(item) {
|
|
400
427
|
if (item.request) {
|
|
401
428
|
const method = item.request.method || 'GET';
|
|
@@ -403,13 +430,13 @@ function extractOperations(collection, variables) {
|
|
|
403
430
|
const path = extractPathFromUrl(url, variables);
|
|
404
431
|
const itemName = item.name || 'unknown';
|
|
405
432
|
// Generate operation ID
|
|
406
|
-
|
|
433
|
+
let operationId = itemName.toLowerCase().replace(/[^a-z0-9]/g, '-');
|
|
434
|
+
operationId = getUniqueOperationName(operationId);
|
|
407
435
|
const { contentType, bodyType } = getContentTypeAndBodyType(item.request);
|
|
408
436
|
const headers = getHeadersForOperation(item.request, variables);
|
|
409
437
|
const inputs = extractParameters(item.request, variables);
|
|
410
438
|
const bodyInputs = extractRequestBody(item.request, itemName, method, path);
|
|
411
439
|
const returns = extractResponses(item, itemName, method, path);
|
|
412
|
-
// Combine all inputs (avoid duplicates)
|
|
413
440
|
const allInputs = [...inputs];
|
|
414
441
|
if (bodyInputs.length > 0) {
|
|
415
442
|
allInputs.push(...bodyInputs);
|
|
@@ -430,20 +457,51 @@ function extractOperations(collection, variables) {
|
|
|
430
457
|
RETURNS: returns,
|
|
431
458
|
});
|
|
432
459
|
}
|
|
433
|
-
// Recursively process nested items
|
|
434
460
|
if (item.item) {
|
|
435
461
|
for (const subItem of item.item) {
|
|
436
462
|
processItem(subItem);
|
|
437
463
|
}
|
|
438
464
|
}
|
|
439
465
|
}
|
|
440
|
-
// Process all items in the collection
|
|
441
466
|
for (const item of collection.item) {
|
|
442
467
|
processItem(item);
|
|
443
468
|
}
|
|
444
469
|
return operations;
|
|
445
470
|
}
|
|
471
|
+
function cleanYaml(yamlString) {
|
|
472
|
+
// Remove tabs, non-breaking spaces, and non-printable chars except standard whitespace and newlines
|
|
473
|
+
return yamlString
|
|
474
|
+
.replace(/\t/g, ' ')
|
|
475
|
+
.replace(/[\u00A0]/g, ' ')
|
|
476
|
+
.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, '')
|
|
477
|
+
.replace(/[ \t]+$/gm, '')
|
|
478
|
+
.replace(/\r\n/g, '\n');
|
|
479
|
+
}
|
|
480
|
+
function checkYamlForHiddenChars(yamlString) {
|
|
481
|
+
const lines = yamlString.split('\n');
|
|
482
|
+
for (let i = 0; i < lines.length; i++) {
|
|
483
|
+
const line = lines[i];
|
|
484
|
+
if (/\t/.test(line)) {
|
|
485
|
+
throw new Error(`YAML contains a TAB character at line ${i + 1}:\n${line}`);
|
|
486
|
+
}
|
|
487
|
+
if (/\u00A0/.test(line)) {
|
|
488
|
+
throw new Error(`YAML contains a non-breaking space (U+00A0) at line ${i + 1}:\n${line}`);
|
|
489
|
+
}
|
|
490
|
+
if (/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/.test(line)) {
|
|
491
|
+
throw new Error(`YAML contains a non-printable character at line ${i + 1}:\n${line}`);
|
|
492
|
+
}
|
|
493
|
+
}
|
|
494
|
+
}
|
|
495
|
+
function validateYaml(yamlString) {
|
|
496
|
+
try {
|
|
497
|
+
(0, js_yaml_1.load)(yamlString);
|
|
498
|
+
}
|
|
499
|
+
catch (e) {
|
|
500
|
+
throw new Error('Generated YAML is invalid: ' + e.message);
|
|
501
|
+
}
|
|
502
|
+
}
|
|
446
503
|
function generateWrekenfile(collection, variables) {
|
|
504
|
+
var _a;
|
|
447
505
|
if (!collection || typeof collection !== 'object') {
|
|
448
506
|
throw new Error("Argument 'collection' is required and must be an object");
|
|
449
507
|
}
|
|
@@ -453,7 +511,6 @@ function generateWrekenfile(collection, variables) {
|
|
|
453
511
|
const structs = extractStructs(collection, variables);
|
|
454
512
|
const operations = extractOperations(collection, variables);
|
|
455
513
|
let wrekenfile = `VERSION: '1.2'\n`;
|
|
456
|
-
// Add INIT section with defaults from environment variables
|
|
457
514
|
wrekenfile += `INIT:\n`;
|
|
458
515
|
wrekenfile += ` DEFAULTS:\n`;
|
|
459
516
|
if (Object.keys(variables).length === 0) {
|
|
@@ -461,7 +518,6 @@ function generateWrekenfile(collection, variables) {
|
|
|
461
518
|
}
|
|
462
519
|
else {
|
|
463
520
|
for (const [key, value] of Object.entries(variables)) {
|
|
464
|
-
// Skip sensitive values like API keys and signatures
|
|
465
521
|
const sensitiveKeys = ['api_key', 'api-key', 'x-api-key', 'signature', 'x-signature', 'authorization', 'token', 'password', 'secret'];
|
|
466
522
|
const isSensitive = sensitiveKeys.some(sensitiveKey => key.toLowerCase().includes(sensitiveKey));
|
|
467
523
|
if (isSensitive) {
|
|
@@ -472,7 +528,6 @@ function generateWrekenfile(collection, variables) {
|
|
|
472
528
|
}
|
|
473
529
|
}
|
|
474
530
|
}
|
|
475
|
-
// Add INTERFACES section
|
|
476
531
|
wrekenfile += `INTERFACES:\n`;
|
|
477
532
|
for (const operation of operations) {
|
|
478
533
|
wrekenfile += ` ${operation.name}:\n`;
|
|
@@ -490,11 +545,12 @@ function generateWrekenfile(collection, variables) {
|
|
|
490
545
|
}
|
|
491
546
|
}
|
|
492
547
|
wrekenfile += ` BODYTYPE: ${operation.HTTP.BODYTYPE}\n`;
|
|
493
|
-
|
|
548
|
+
// INPUTS
|
|
494
549
|
if (operation.INPUTS.length === 0) {
|
|
495
|
-
wrekenfile += `
|
|
550
|
+
wrekenfile += ` INPUTS: []\n`;
|
|
496
551
|
}
|
|
497
552
|
else {
|
|
553
|
+
wrekenfile += ` INPUTS:\n`;
|
|
498
554
|
for (const input of operation.INPUTS) {
|
|
499
555
|
wrekenfile += ` - name: ${input.name}\n`;
|
|
500
556
|
wrekenfile += ` type: ${input.type}\n`;
|
|
@@ -504,6 +560,7 @@ function generateWrekenfile(collection, variables) {
|
|
|
504
560
|
}
|
|
505
561
|
}
|
|
506
562
|
}
|
|
563
|
+
// RETURNS
|
|
507
564
|
wrekenfile += ` RETURNS:\n`;
|
|
508
565
|
for (const ret of operation.RETURNS) {
|
|
509
566
|
wrekenfile += ` - RETURNTYPE: ${ret.RETURNTYPE}\n`;
|
|
@@ -511,20 +568,33 @@ function generateWrekenfile(collection, variables) {
|
|
|
511
568
|
wrekenfile += ` CODE: '${ret.CODE}'\n`;
|
|
512
569
|
}
|
|
513
570
|
}
|
|
514
|
-
// Add STRUCTS section
|
|
515
571
|
wrekenfile += `STRUCTS:\n`;
|
|
516
572
|
for (const [structName, fields] of Object.entries(structs)) {
|
|
517
|
-
wrekenfile += ` ${structName}:\n`;
|
|
518
573
|
if (fields.length === 0) {
|
|
519
|
-
wrekenfile += `
|
|
574
|
+
wrekenfile += ` ${structName}: []\n`;
|
|
520
575
|
}
|
|
521
576
|
else {
|
|
577
|
+
wrekenfile += ` ${structName}:\n`;
|
|
522
578
|
for (const field of fields) {
|
|
523
|
-
wrekenfile +=
|
|
524
|
-
|
|
525
|
-
|
|
579
|
+
wrekenfile += ' - name: ' + field.name + '\n';
|
|
580
|
+
// Quote type if it contains non-alphanumeric characters (except underscore)
|
|
581
|
+
let typeValue = field.type;
|
|
582
|
+
if (/[^a-zA-Z0-9_]/.test(typeValue)) {
|
|
583
|
+
typeValue = '"' + typeValue.replace(/"/g, '\"') + '"';
|
|
584
|
+
}
|
|
585
|
+
wrekenfile += ' type: ' + typeValue + '\n';
|
|
586
|
+
wrekenfile += " required: '" + field.required + "'\n";
|
|
526
587
|
}
|
|
527
588
|
}
|
|
528
589
|
}
|
|
590
|
+
// Debug print: show first 20 lines of STRUCTS block
|
|
591
|
+
const structsBlock = (_a = wrekenfile.split('STRUCTS:')[1]) === null || _a === void 0 ? void 0 : _a.split('\n').slice(0, 20).join('\n');
|
|
592
|
+
if (structsBlock) {
|
|
593
|
+
console.log('--- STRUCTS block preview ---');
|
|
594
|
+
console.log(structsBlock);
|
|
595
|
+
}
|
|
596
|
+
wrekenfile = cleanYaml(wrekenfile);
|
|
597
|
+
checkYamlForHiddenChars(wrekenfile);
|
|
598
|
+
validateYaml(wrekenfile);
|
|
529
599
|
return wrekenfile;
|
|
530
600
|
}
|
package/package.json
CHANGED