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 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,2 @@
1
+ #!/usr/bin/env node
2
+ export {};
@@ -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,2 @@
1
+ #!/usr/bin/env node
2
+ export {};
@@ -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,2 @@
1
+ #!/usr/bin/env node
2
+ export {};
@@ -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
- const t = type === null || type === void 0 ? void 0 : type.toLowerCase();
50
- if (t === 'string')
51
- return 'STRING';
52
- if (t === 'integer' || t === 'int')
53
- return 'INT';
54
- if (t === 'number')
55
- return 'FLOAT';
56
- if (t === 'boolean')
57
- return 'BOOL';
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, _b;
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
- // Check if the resolved schema is a simple type
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 ((_a = schema.items) === null || _a === void 0 ? void 0 : _a.$ref) {
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((_b = schema.items) === null || _b === void 0 ? void 0 : _b.type)}`;
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
- return (0, js_yaml_1.dump)({
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
- const t = type === null || type === void 0 ? void 0 : type.toLowerCase();
50
- if (t === 'string')
51
- return 'STRING';
52
- if (t === 'integer' || t === 'int')
53
- return 'INT';
54
- if (t === 'number')
55
- return 'FLOAT';
56
- if (t === 'boolean')
57
- return 'BOOL';
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, _c;
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
- if (schema.allOf)
115
+ }
116
+ if (schema && typeof schema === 'object' && schema.allOf) {
98
117
  return schema.allOf.flatMap((s) => parseSchema(name, s, spec, baseDir, depth + 1));
99
- if (schema.oneOf || schema.anyOf) {
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
- else if (prop.type === 'array') {
128
- if ((_b = prop.items) === null || _b === void 0 ? void 0 : _b.$ref) {
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((_c = prop.items) === null || _c === void 0 ? void 0 : _c.type)}`;
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
- return (0, js_yaml_1.dump)({
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}${key}Item)`;
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}${key})`;
135
+ type = `STRUCT(${prefix}${fieldName})`;
125
136
  }
126
137
  else {
127
138
  type = mapType(value);
128
139
  }
129
140
  fields.push({
130
- name: key,
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
- const requestStructName = generateStructName(itemName, method, path, 'Request');
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
- const responseStructName = generateStructName(itemName, method, path, `Response${response.code || '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
- const operationId = itemName.toLowerCase().replace(/[^a-z0-9]/g, '-');
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
- wrekenfile += ` INPUTS:\n`;
548
+ // INPUTS
494
549
  if (operation.INPUTS.length === 0) {
495
- wrekenfile += ` []\n`;
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 += ` []\n`;
574
+ wrekenfile += ` ${structName}: []\n`;
520
575
  }
521
576
  else {
577
+ wrekenfile += ` ${structName}:\n`;
522
578
  for (const field of fields) {
523
- wrekenfile += ` - name: ${field.name}\n`;
524
- wrekenfile += ` type: ${field.type}\n`;
525
- wrekenfile += ` required: '${field.required}'\n`;
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "wrekenfile-converter",
3
- "version": "2.0.0",
3
+ "version": "2.0.3",
4
4
  "description": "Convert OpenAPI and Postman specs to Wrekenfile format with mini-chunking for vector DB storage",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",