wrekenfile-converter 2.0.1 → 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')
@@ -106,26 +107,27 @@ function resolveRef(ref, spec, baseDir) {
106
107
  : externalRefCache[fullPath];
107
108
  }
108
109
  function getTypeFromSchema(schema, spec, baseDir) {
109
- var _a, _b;
110
+ var _a;
111
+ if (!schema || typeof schema !== 'object') {
112
+ return 'ANY';
113
+ }
110
114
  if (schema.$ref) {
111
115
  const resolvedSchema = resolveRef(schema.$ref, spec, baseDir);
112
- // Check if the resolved schema is a simple type
113
- if (resolvedSchema.type && resolvedSchema.type !== 'object') {
116
+ if (resolvedSchema && resolvedSchema.type && resolvedSchema.type !== 'object') {
114
117
  return mapType(resolvedSchema.type, resolvedSchema.format);
115
118
  }
116
- // It's a complex type, use STRUCT
117
119
  return `STRUCT(${schema.$ref.split('/').pop()})`;
118
120
  }
119
121
  if (schema.type === 'array') {
120
- if ((_a = schema.items) === null || _a === void 0 ? void 0 : _a.$ref) {
122
+ if (schema.items && schema.items.$ref) {
121
123
  const resolvedItems = resolveRef(schema.items.$ref, spec, baseDir);
122
- if (resolvedItems.type && resolvedItems.type !== 'object') {
124
+ if (resolvedItems && resolvedItems.type && resolvedItems.type !== 'object') {
123
125
  return `[]${mapType(resolvedItems.type, resolvedItems.format)}`;
124
126
  }
125
127
  return `[]STRUCT(${schema.items.$ref.split('/').pop()})`;
126
128
  }
127
129
  else {
128
- 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)}`;
129
131
  }
130
132
  }
131
133
  if (schema.type && schema.type !== 'object') {
@@ -190,55 +192,55 @@ function extractStructs(spec, baseDir) {
190
192
  const schemas = ((_a = spec.components) === null || _a === void 0 ? void 0 : _a.schemas) || {};
191
193
  // Helper to recursively collect all referenced schemas, traversing all properties, arrays, and combiners
192
194
  function collectAllReferencedSchemas(schema, name) {
193
- if (!name || structs[name])
195
+ if (!schema || typeof schema !== 'object' || !name || structs[name])
194
196
  return;
195
197
  const resolved = schema.$ref ? resolveRef(schema.$ref, spec, baseDir) : schema;
196
198
  const fields = parseSchema(name, resolved, spec, baseDir);
197
199
  structs[name] = fields;
198
200
  // Traverse all properties
199
- if (resolved.type === 'object' && resolved.properties) {
201
+ if (resolved && resolved.type === 'object' && resolved.properties && typeof resolved.properties === 'object') {
200
202
  for (const [propName, prop] of Object.entries(resolved.properties)) {
201
- if (prop.$ref) {
203
+ if (prop && typeof prop === 'object' && prop.$ref) {
202
204
  const refName = prop.$ref.split('/').pop();
203
205
  if (refName)
204
206
  collectAllReferencedSchemas(resolveRef(prop.$ref, spec, baseDir), refName);
205
207
  }
206
- else if (prop.type === 'array' && prop.items) {
207
- 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) {
208
210
  const refName = prop.items.$ref.split('/').pop();
209
211
  if (refName)
210
212
  collectAllReferencedSchemas(resolveRef(prop.items.$ref, spec, baseDir), refName);
211
213
  }
212
- 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)) {
213
215
  collectAllReferencedSchemas(prop.items, name + '_' + propName + '_Item');
214
216
  }
215
217
  }
216
- 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)) {
217
219
  collectAllReferencedSchemas(prop, name + '_' + propName);
218
220
  }
219
221
  }
220
222
  }
221
223
  // Traverse array items at root
222
- if (resolved.type === 'array' && resolved.items) {
223
- if (resolved.items.$ref) {
224
+ if (resolved && resolved.type === 'array' && resolved.items) {
225
+ if (resolved.items && typeof resolved.items === 'object' && resolved.items.$ref) {
224
226
  const refName = resolved.items.$ref.split('/').pop();
225
227
  if (refName)
226
228
  collectAllReferencedSchemas(resolveRef(resolved.items.$ref, spec, baseDir), refName);
227
229
  }
228
- 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)) {
229
231
  collectAllReferencedSchemas(resolved.items, name + '_Item');
230
232
  }
231
233
  }
232
234
  // Traverse allOf/oneOf/anyOf
233
235
  for (const combiner of ['allOf', 'oneOf', 'anyOf']) {
234
- if (Array.isArray(resolved[combiner])) {
236
+ if (resolved && Array.isArray(resolved[combiner])) {
235
237
  for (const subSchema of resolved[combiner]) {
236
- if (subSchema.$ref) {
238
+ if (subSchema && typeof subSchema === 'object' && subSchema.$ref) {
237
239
  const refName = subSchema.$ref.split('/').pop();
238
240
  if (refName)
239
241
  collectAllReferencedSchemas(resolveRef(subSchema.$ref, spec, baseDir), refName);
240
242
  }
241
- else {
243
+ else if (subSchema && typeof subSchema === 'object') {
242
244
  collectAllReferencedSchemas(subSchema, name + '_' + combiner);
243
245
  }
244
246
  }
@@ -249,7 +251,7 @@ function extractStructs(spec, baseDir) {
249
251
  for (const name in schemas) {
250
252
  collectAllReferencedSchemas(schemas[name], name);
251
253
  const schema = schemas[name];
252
- if (schema.oneOf || schema.anyOf) {
254
+ if (schema && (schema.oneOf || schema.anyOf)) {
253
255
  structs[`${name}_Union`] = [{ name: 'value', type: 'ANY', required: 'FALSE' }];
254
256
  }
255
257
  }
@@ -260,13 +262,13 @@ function extractStructs(spec, baseDir) {
260
262
  // Extract request body schemas
261
263
  if ((_b = op.requestBody) === null || _b === void 0 ? void 0 : _b.content) {
262
264
  for (const [contentType, content] of Object.entries(op.requestBody.content)) {
263
- if (content.schema) {
264
- if (content.schema.$ref) {
265
+ if (content && content.schema) {
266
+ if (content.schema && content.schema.$ref) {
265
267
  const refName = content.schema.$ref.split('/').pop();
266
268
  if (refName)
267
269
  collectAllReferencedSchemas(resolveRef(content.schema.$ref, spec, baseDir), refName);
268
270
  }
269
- else {
271
+ else if (content.schema && typeof content.schema === 'object') {
270
272
  const requestStructName = generateStructName(operationId, method, pathStr, 'Request');
271
273
  collectAllReferencedSchemas(content.schema, requestStructName);
272
274
  }
@@ -276,15 +278,15 @@ function extractStructs(spec, baseDir) {
276
278
  // Extract response schemas
277
279
  if (op.responses) {
278
280
  for (const [code, response] of Object.entries(op.responses)) {
279
- if (response.content) {
281
+ if (response && response.content) {
280
282
  for (const [contentType, content] of Object.entries(response.content)) {
281
- if (content.schema) {
282
- if (content.schema.$ref) {
283
+ if (content && content.schema) {
284
+ if (content.schema && content.schema.$ref) {
283
285
  const refName = content.schema.$ref.split('/').pop();
284
286
  if (refName)
285
287
  collectAllReferencedSchemas(resolveRef(content.schema.$ref, spec, baseDir), refName);
286
288
  }
287
- else {
289
+ else if (content.schema && typeof content.schema === 'object') {
288
290
  const responseStructName = generateStructName(operationId, method, pathStr, `Response${code}`);
289
291
  collectAllReferencedSchemas(content.schema, responseStructName);
290
292
  }
@@ -416,13 +418,16 @@ function extractRequestBody(op, operationId, method, path, spec, baseDir) {
416
418
  if (contentType === 'application/json' && ((_a = requestBody.content[contentType]) === null || _a === void 0 ? void 0 : _a.schema)) {
417
419
  const bodySchema = requestBody.content[contentType].schema;
418
420
  let type;
419
- if (bodySchema.$ref) {
421
+ if (bodySchema && bodySchema.$ref) {
420
422
  type = getTypeFromSchema(bodySchema, spec, baseDir);
421
423
  }
422
- else {
424
+ else if (bodySchema) {
423
425
  const requestStructName = generateStructName(operationId, method, path, 'Request');
424
426
  type = `STRUCT(${requestStructName})`;
425
427
  }
428
+ else {
429
+ type = 'ANY';
430
+ }
426
431
  inputParams.push({
427
432
  name: 'body',
428
433
  type,
@@ -431,9 +436,9 @@ function extractRequestBody(op, operationId, method, path, spec, baseDir) {
431
436
  }
432
437
  else if (contentType === 'multipart/form-data' && ((_b = requestBody.content[contentType]) === null || _b === void 0 ? void 0 : _b.schema)) {
433
438
  const bodySchema = requestBody.content[contentType].schema;
434
- if (bodySchema.properties) {
439
+ if (bodySchema && bodySchema.properties) {
435
440
  for (const [key, prop] of Object.entries(bodySchema.properties)) {
436
- const type = prop.format === 'binary' ? 'FILE' : getTypeFromSchema(prop, spec, baseDir);
441
+ const type = prop && prop.format === 'binary' ? 'FILE' : getTypeFromSchema(prop, spec, baseDir);
437
442
  const required = (bodySchema.required || []).includes(key) ? 'TRUE' : 'FALSE';
438
443
  inputParams.push({
439
444
  name: key,
@@ -558,6 +563,37 @@ function extractSecurityDefaults(spec) {
558
563
  }
559
564
  return defs;
560
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
+ }
561
597
  function generateWrekenfile(spec, baseDir) {
562
598
  var _a, _b;
563
599
  if (!spec || typeof spec !== 'object') {
@@ -566,7 +602,7 @@ function generateWrekenfile(spec, baseDir) {
566
602
  if (!baseDir || typeof baseDir !== 'string') {
567
603
  throw new Error("Argument 'baseDir' is required and must be a string");
568
604
  }
569
- return (0, js_yaml_1.dump)({
605
+ let yamlString = (0, js_yaml_1.dump)({
570
606
  VERSION: '1.2',
571
607
  INIT: {
572
608
  DEFAULTS: [
@@ -577,4 +613,8 @@ function generateWrekenfile(spec, baseDir) {
577
613
  INTERFACES: extractInterfaces(spec, baseDir),
578
614
  STRUCTS: extractStructs(spec, baseDir),
579
615
  });
616
+ yamlString = cleanYaml(yamlString);
617
+ checkYamlForHiddenChars(yamlString);
618
+ validateYaml(yamlString);
619
+ return yamlString;
580
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')
@@ -106,14 +107,16 @@ function resolveRef(ref, spec, baseDir) {
106
107
  : externalRefCache[fullPath];
107
108
  }
108
109
  function parseSchema(name, schema, spec, baseDir, depth = 0) {
109
- var _a, _b, _c;
110
+ var _a, _b;
110
111
  if (depth > 3)
111
112
  return [];
112
- if (schema.$ref)
113
+ if (schema && typeof schema === 'object' && schema.$ref) {
113
114
  return parseSchema(name, resolveRef(schema.$ref, spec, baseDir), spec, baseDir, depth + 1);
114
- if (schema.allOf)
115
+ }
116
+ if (schema && typeof schema === 'object' && schema.allOf) {
115
117
  return schema.allOf.flatMap((s) => parseSchema(name, s, spec, baseDir, depth + 1));
116
- if (schema.oneOf || schema.anyOf) {
118
+ }
119
+ if (schema && typeof schema === 'object' && (schema.oneOf || schema.anyOf)) {
117
120
  return [{
118
121
  name: 'variant',
119
122
  type: `STRUCT(${name}_Union)`,
@@ -121,32 +124,33 @@ function parseSchema(name, schema, spec, baseDir, depth = 0) {
121
124
  }];
122
125
  }
123
126
  // Handle primitive types - return empty array (no struct needed)
124
- if (schema.type && schema.type !== 'object' && schema.type !== 'array') {
127
+ if (schema && typeof schema === 'object' && schema.type && schema.type !== 'object' && schema.type !== 'array') {
125
128
  return [];
126
129
  }
127
130
  // Handle empty objects (no properties) - return empty array
128
- 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)) {
129
132
  return [];
130
133
  }
131
134
  const fields = [];
132
- 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)) {
133
136
  fields.push({
134
137
  name: schema.discriminator.propertyName,
135
138
  type: 'STRING',
136
139
  required: 'REQUIRED',
137
140
  });
138
141
  }
139
- if (schema.type === 'object' && schema.properties) {
142
+ if (schema && typeof schema === 'object' && schema.type === 'object' && schema.properties) {
140
143
  for (const [key, prop] of Object.entries(schema.properties)) {
141
144
  let type = 'ANY';
142
- if (prop.$ref)
145
+ if (prop && typeof prop === 'object' && prop.$ref) {
143
146
  type = `STRUCT(${prop.$ref.split('/').pop()})`;
144
- else if (prop.type === 'array') {
145
- 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) {
146
150
  type = `[]STRUCT(${prop.items.$ref.split('/').pop()})`;
147
151
  }
148
- else {
149
- 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)}`;
150
154
  }
151
155
  }
152
156
  else {
@@ -179,7 +183,7 @@ function extractStructs(spec, baseDir) {
179
183
  const fields = parseSchema(name, definitions[name], spec, baseDir);
180
184
  // Always add the struct, even if empty
181
185
  structs[name] = fields;
182
- if (definitions[name].oneOf || definitions[name].anyOf) {
186
+ if (definitions[name] && typeof definitions[name] === 'object' && (definitions[name].oneOf || definitions[name].anyOf)) {
183
187
  structs[`${name}_Union`] = [{ name: 'value', type: 'ANY', required: 'OPTIONAL' }];
184
188
  }
185
189
  }
@@ -190,7 +194,7 @@ function extractStructs(spec, baseDir) {
190
194
  // Extract request body schemas (OpenAPI v2 uses parameters with in: body)
191
195
  if (op.parameters) {
192
196
  for (const param of op.parameters) {
193
- if (param.in === 'body' && param.schema && !param.schema.$ref) {
197
+ if (param && typeof param === 'object' && param.in === 'body' && param.schema && !param.schema.$ref) {
194
198
  // Inline schema - create a struct for it only if it has fields
195
199
  const requestStructName = generateStructName(operationId, method, pathStr, 'Request');
196
200
  const fields = parseSchema(requestStructName, param.schema, spec, baseDir);
@@ -203,7 +207,7 @@ function extractStructs(spec, baseDir) {
203
207
  // Extract response schemas (OpenAPI v2 has schema directly in response)
204
208
  if (op.responses) {
205
209
  for (const [code, response] of Object.entries(op.responses)) {
206
- if (response.schema && !response.schema.$ref) {
210
+ if (response && typeof response === 'object' && response.schema && !response.schema.$ref) {
207
211
  // Inline schema - create a struct for it only if it has fields
208
212
  const responseStructName = generateStructName(operationId, method, pathStr, `Response${code}`);
209
213
  const fields = parseSchema(responseStructName, response.schema, spec, baseDir);
@@ -220,7 +224,7 @@ function extractStructs(spec, baseDir) {
220
224
  function getContentTypeAndBodyType(op) {
221
225
  var _a;
222
226
  // Check if there are formData parameters
223
- 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');
224
228
  if (hasFormData) {
225
229
  return { contentType: 'multipart/form-data', bodyType: 'FORM' };
226
230
  }
@@ -276,7 +280,7 @@ function getHeadersForOperation(op, spec) {
276
280
  // Check if Authorization is used as a parameter but not defined in securityDefinitions
277
281
  if (op.parameters) {
278
282
  for (const param of op.parameters) {
279
- 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')) {
280
284
  headerMap.set('Authorization', 'bearer_token');
281
285
  }
282
286
  }
@@ -297,25 +301,25 @@ function extractParameters(op, spec) {
297
301
  if (op.parameters) {
298
302
  for (let param of op.parameters) {
299
303
  // Resolve parameter references
300
- if (param.$ref) {
304
+ if (param && typeof param === 'object' && param.$ref) {
301
305
  if (!spec.swaggerFile) {
302
306
  throw new Error("spec.swaggerFile is undefined. Please provide a valid baseDir when calling generateWrekenfile, or ensure all refs are internal.");
303
307
  }
304
308
  param = resolveRef(param.$ref, spec, path.dirname(spec.swaggerFile));
305
309
  }
306
310
  // Skip body and formData parameters, they are handled in extractRequestBody
307
- if (param.in === 'body' || param.in === 'formData') {
311
+ if (param && typeof param === 'object' && (param.in === 'body' || param.in === 'formData')) {
308
312
  continue;
309
313
  }
310
- const paramType = param.in || 'query';
311
- const paramName = param.name;
312
- const paramSchema = param.schema || {}; // Schema is sometimes at root of param
313
- 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';
314
318
  let type = 'STRING';
315
- if (param.type) {
319
+ if (param && typeof param === 'object' && param.type) {
316
320
  type = mapType(param.type, param.format);
317
321
  }
318
- else if (paramSchema.type) {
322
+ else if (paramSchema && typeof paramSchema === 'object' && paramSchema.type) {
319
323
  type = mapType(paramSchema.type, paramSchema.format);
320
324
  }
321
325
  inputParams.push({
@@ -331,10 +335,10 @@ function extractParameters(op, spec) {
331
335
  function extractRequestBody(op, operationId, method, path) {
332
336
  var _a;
333
337
  const inputParams = [];
334
- const bodyParam = (op.parameters || []).find((p) => p.in === 'body');
338
+ const bodyParam = (op.parameters || []).find((p) => p && typeof p === 'object' && p.in === 'body');
335
339
  if (bodyParam) {
336
340
  let type;
337
- 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)) {
338
342
  type = `STRUCT(${bodyParam.schema.$ref.split('/').pop()})`;
339
343
  }
340
344
  else {
@@ -345,17 +349,17 @@ function extractRequestBody(op, operationId, method, path) {
345
349
  inputParams.push({
346
350
  name: 'body',
347
351
  type,
348
- required: bodyParam.required ? 'REQUIRED' : 'OPTIONAL',
352
+ required: bodyParam && typeof bodyParam === 'object' ? bodyParam.required ? 'REQUIRED' : 'OPTIONAL' : 'OPTIONAL',
349
353
  });
350
354
  }
351
355
  // Handle formData for multipart/form-data
352
356
  if (op.parameters) {
353
357
  for (const param of op.parameters) {
354
- if (param.in === 'formData') {
358
+ if (param && typeof param === 'object' && param.in === 'formData') {
355
359
  inputParams.push({
356
- name: param.name,
357
- type: param.type === 'file' ? 'FILE' : mapType(param.type, param.format),
358
- 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',
359
363
  });
360
364
  }
361
365
  }
@@ -368,25 +372,25 @@ function extractResponses(op, operationId, method, path) {
368
372
  // Handle all response codes (success and error)
369
373
  for (const [code, response] of Object.entries(op.responses || {})) {
370
374
  let returnType = 'ANY';
371
- if (response.schema) {
372
- if (response.schema.$ref) {
375
+ if (response && typeof response === 'object' && response.schema) {
376
+ if (response && typeof response === 'object' && response.schema.$ref) {
373
377
  returnType = `STRUCT(${response.schema.$ref.split('/').pop()})`;
374
378
  }
375
- else if (response.schema.type === 'array') {
376
- 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)) {
377
381
  returnType = `[]STRUCT(${response.schema.items.$ref.split('/').pop()})`;
378
382
  }
379
- else {
383
+ else if (response && typeof response === 'object' && response.schema.items) {
380
384
  returnType = `[]${mapType((_b = response.schema.items) === null || _b === void 0 ? void 0 : _b.type)}`;
381
385
  }
382
386
  }
383
- else if (response.schema.type === 'object') {
387
+ else if (response && typeof response === 'object' && response.schema.type === 'object') {
384
388
  // Inline schema - use generated struct name
385
389
  const responseStructName = generateStructName(operationId, method, path, `Response${code}`);
386
390
  returnType = `STRUCT(${responseStructName})`;
387
391
  }
388
392
  }
389
- else if (code === '204' || response.description === 'No Content') {
393
+ else if (code === '204' || response && typeof response === 'object' && response.description === 'No Content') {
390
394
  returnType = 'VOID';
391
395
  }
392
396
  returns.push({
@@ -413,7 +417,7 @@ function extractInterfaces(spec) {
413
417
  const alias = operationId;
414
418
  const endpoint = pathStr.includes('{') ? `\`${base}${pathStr}\`` : `${base}${pathStr}`;
415
419
  // Check if operation is hidden from docs
416
- const isPrivate = op['x-hidden-from-docs'] === true;
420
+ const isPrivate = op && typeof op === 'object' && op['x-hidden-from-docs'] === true;
417
421
  const visibility = isPrivate ? 'PRIVATE' : 'PUBLIC';
418
422
  const { bodyType } = getContentTypeAndBodyType(op);
419
423
  const headers = getHeadersForOperation(op, spec);
@@ -443,23 +447,54 @@ function extractSecurityDefaults(spec) {
443
447
  const defs = [];
444
448
  const securityDefinitions = spec.securityDefinitions || {};
445
449
  for (const [name, scheme] of Object.entries(securityDefinitions)) {
446
- if (scheme.type === 'basic') {
450
+ if (scheme && typeof scheme === 'object' && scheme.type === 'basic') {
447
451
  defs.push({ basic_auth: 'Basic <BASE64>' });
448
452
  }
449
- else if (scheme.type === 'apiKey') {
450
- if (scheme.in === 'header') {
453
+ else if (scheme && typeof scheme === 'object' && scheme.type === 'apiKey') {
454
+ if (scheme && typeof scheme === 'object' && scheme.in === 'header') {
451
455
  defs.push({ [scheme.name.toLowerCase()]: `<${scheme.name.toUpperCase()}>` });
452
456
  }
453
- else if (scheme.in === 'query') {
457
+ else if (scheme && typeof scheme === 'object' && scheme.in === 'query') {
454
458
  defs.push({ [`query_${scheme.name.toLowerCase()}`]: `<${scheme.name.toUpperCase()}>` });
455
459
  }
456
460
  }
457
- else if (scheme.type === 'oauth2') {
461
+ else if (scheme && typeof scheme === 'object' && scheme.type === 'oauth2') {
458
462
  defs.push({ bearer_token: 'BEARER <ACCESS_TOKEN>' });
459
463
  }
460
464
  }
461
465
  return defs;
462
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
+ }
463
498
  function generateWrekenfile(spec, baseDir) {
464
499
  var _a;
465
500
  if (!spec || typeof spec !== 'object') {
@@ -470,7 +505,7 @@ function generateWrekenfile(spec, baseDir) {
470
505
  }
471
506
  // Add swaggerFile path to spec for ref resolution
472
507
  spec.swaggerFile = baseDir;
473
- return (0, js_yaml_1.dump)({
508
+ let yamlString = (0, js_yaml_1.dump)({
474
509
  VERSION: '1.2',
475
510
  INIT: {
476
511
  DEFAULTS: [
@@ -481,4 +516,8 @@ function generateWrekenfile(spec, baseDir) {
481
516
  INTERFACES: extractInterfaces(spec),
482
517
  STRUCTS: extractStructs(spec, baseDir),
483
518
  }, { noArrayIndent: true });
519
+ yamlString = cleanYaml(yamlString);
520
+ checkYamlForHiddenChars(yamlString);
521
+ validateYaml(yamlString);
522
+ return yamlString;
484
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.1",
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",