wrekenfile-converter 2.0.1 → 2.0.4

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
@@ -96,6 +96,60 @@ const miniFiles: MiniWrekenfile[] = generateMiniWrekenfiles('./Wrekenfile.yaml')
96
96
  // Each miniFile contains { content, metadata }
97
97
  ```
98
98
 
99
+ ## CLI Tools
100
+
101
+ ### Convert OpenAPI to Wrekenfile
102
+
103
+ Generate a Wrekenfile YAML from an OpenAPI (YAML or JSON) spec:
104
+
105
+ ```bash
106
+ npx ts-node src/cli/cli-openapi-to-wrekenfile.ts --input <openapi.yaml|json> [--output <wrekenfile.yaml>] [--cwd <dir>]
107
+ ```
108
+
109
+ **Options:**
110
+ - `--input` or `-i`: Path to your OpenAPI YAML or JSON file (required)
111
+ - `--output` or `-o`: Path to output Wrekenfile YAML (optional, defaults to `output_wrekenfile.yaml`)
112
+ - `--cwd`: Working directory for resolving $refs (optional, defaults to the input file's directory)
113
+
114
+ **Example:**
115
+ ```bash
116
+ npx ts-node src/cli/cli-openapi-to-wrekenfile.ts --input examples/p3id_swagger.json --output wrekenfile.yaml --cwd .
117
+ ```
118
+
119
+ ### Convert Postman Collection to Wrekenfile
120
+
121
+ Convert a Postman collection JSON to a Wrekenfile YAML file:
122
+
123
+ ```bash
124
+ npx ts-node src/cli/cli-postman-to-wrekenfile.ts <postman_collection.json> <output_wrekenfile.yaml> [postman_environment.json]
125
+ ```
126
+
127
+ **Example:**
128
+ ```bash
129
+ npx ts-node src/cli/cli-postman-to-wrekenfile.ts examples/transact_bridge_postman.json wrekenfile.yaml
130
+ ```
131
+
132
+ **Note:** The third argument (environment file) is optional.
133
+
134
+ ### Generate Mini Wrekenfiles
135
+
136
+ Generate mini Wrekenfiles for each endpoint from a main Wrekenfile YAML:
137
+
138
+ ```bash
139
+ npx ts-node src/cli/cli-mini-wrekenfile-generator.ts --input <wrekenfile.yaml> [--output <dir>]
140
+ ```
141
+
142
+ **Options:**
143
+ - `--input` or `-i`: Path to your main Wrekenfile YAML (required)
144
+ - `--output` or `-o`: Output directory for mini Wrekenfiles (optional, defaults to `./mini-wrekenfiles`)
145
+
146
+ **Example:**
147
+ ```bash
148
+ npx ts-node src/cli/cli-mini-wrekenfile-generator.ts --input wrekenfile.yaml --output ./mini-wrekenfiles
149
+ ```
150
+
151
+ This will generate one mini Wrekenfile per endpoint in the specified output directory.
152
+
99
153
  ## API Reference
100
154
 
101
155
  ### Core Functions
@@ -167,6 +221,10 @@ src/
167
221
  ├── postman-to-wrekenfile.ts # Postman converter
168
222
  ├── wrekenfile-validator.ts # Validation logic
169
223
  ├── mini-wrekenfile-generator.ts # Mini chunk generator
224
+ ├── cli/ # CLI tools
225
+ │ ├── cli-openapi-to-wrekenfile.ts
226
+ │ ├── cli-postman-to-wrekenfile.ts
227
+ │ └── cli-mini-wrekenfile-generator.ts
170
228
  └── example-usage.ts # Usage examples
171
229
 
172
230
  dist/ # Compiled JavaScript + types
@@ -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,44 +251,46 @@ 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
  }
256
258
  // Extract inline schemas from operations
257
- for (const [pathStr, methods] of Object.entries(spec.paths)) {
258
- for (const [method, op] of Object.entries(methods)) {
259
- const operationId = op.operationId || `${method}-${pathStr.replace(/[\/{}]/g, '-')}`;
260
- // Extract request body schemas
261
- if ((_b = op.requestBody) === null || _b === void 0 ? void 0 : _b.content) {
262
- for (const [contentType, content] of Object.entries(op.requestBody.content)) {
263
- if (content.schema) {
264
- if (content.schema.$ref) {
265
- const refName = content.schema.$ref.split('/').pop();
266
- if (refName)
267
- collectAllReferencedSchemas(resolveRef(content.schema.$ref, spec, baseDir), refName);
268
- }
269
- else {
270
- const requestStructName = generateStructName(operationId, method, pathStr, 'Request');
271
- collectAllReferencedSchemas(content.schema, requestStructName);
259
+ if (spec.paths && typeof spec.paths === 'object') {
260
+ for (const [pathStr, methods] of Object.entries(spec.paths)) {
261
+ for (const [method, op] of Object.entries(methods)) {
262
+ const operationId = op.operationId || `${method}-${pathStr.replace(/[\/{}]/g, '-')}`;
263
+ // Extract request body schemas
264
+ if ((_b = op.requestBody) === null || _b === void 0 ? void 0 : _b.content) {
265
+ for (const [contentType, content] of Object.entries(op.requestBody.content)) {
266
+ if (content && content.schema) {
267
+ if (content.schema && content.schema.$ref) {
268
+ const refName = content.schema.$ref.split('/').pop();
269
+ if (refName)
270
+ collectAllReferencedSchemas(resolveRef(content.schema.$ref, spec, baseDir), refName);
271
+ }
272
+ else if (content.schema && typeof content.schema === 'object') {
273
+ const requestStructName = generateStructName(operationId, method, pathStr, 'Request');
274
+ collectAllReferencedSchemas(content.schema, requestStructName);
275
+ }
272
276
  }
273
277
  }
274
278
  }
275
- }
276
- // Extract response schemas
277
- if (op.responses) {
278
- for (const [code, response] of Object.entries(op.responses)) {
279
- if (response.content) {
280
- for (const [contentType, content] of Object.entries(response.content)) {
281
- if (content.schema) {
282
- if (content.schema.$ref) {
283
- const refName = content.schema.$ref.split('/').pop();
284
- if (refName)
285
- collectAllReferencedSchemas(resolveRef(content.schema.$ref, spec, baseDir), refName);
286
- }
287
- else {
288
- const responseStructName = generateStructName(operationId, method, pathStr, `Response${code}`);
289
- collectAllReferencedSchemas(content.schema, responseStructName);
279
+ // Extract response schemas
280
+ if (op.responses) {
281
+ for (const [code, response] of Object.entries(op.responses)) {
282
+ if (response && response.content) {
283
+ for (const [contentType, content] of Object.entries(response.content)) {
284
+ if (content && content.schema) {
285
+ if (content.schema && content.schema.$ref) {
286
+ const refName = content.schema.$ref.split('/').pop();
287
+ if (refName)
288
+ collectAllReferencedSchemas(resolveRef(content.schema.$ref, spec, baseDir), refName);
289
+ }
290
+ else if (content.schema && typeof content.schema === 'object') {
291
+ const responseStructName = generateStructName(operationId, method, pathStr, `Response${code}`);
292
+ collectAllReferencedSchemas(content.schema, responseStructName);
293
+ }
290
294
  }
291
295
  }
292
296
  }
@@ -416,13 +420,16 @@ function extractRequestBody(op, operationId, method, path, spec, baseDir) {
416
420
  if (contentType === 'application/json' && ((_a = requestBody.content[contentType]) === null || _a === void 0 ? void 0 : _a.schema)) {
417
421
  const bodySchema = requestBody.content[contentType].schema;
418
422
  let type;
419
- if (bodySchema.$ref) {
423
+ if (bodySchema && bodySchema.$ref) {
420
424
  type = getTypeFromSchema(bodySchema, spec, baseDir);
421
425
  }
422
- else {
426
+ else if (bodySchema) {
423
427
  const requestStructName = generateStructName(operationId, method, path, 'Request');
424
428
  type = `STRUCT(${requestStructName})`;
425
429
  }
430
+ else {
431
+ type = 'ANY';
432
+ }
426
433
  inputParams.push({
427
434
  name: 'body',
428
435
  type,
@@ -431,9 +438,9 @@ function extractRequestBody(op, operationId, method, path, spec, baseDir) {
431
438
  }
432
439
  else if (contentType === 'multipart/form-data' && ((_b = requestBody.content[contentType]) === null || _b === void 0 ? void 0 : _b.schema)) {
433
440
  const bodySchema = requestBody.content[contentType].schema;
434
- if (bodySchema.properties) {
441
+ if (bodySchema && bodySchema.properties) {
435
442
  for (const [key, prop] of Object.entries(bodySchema.properties)) {
436
- const type = prop.format === 'binary' ? 'FILE' : getTypeFromSchema(prop, spec, baseDir);
443
+ const type = prop && prop.format === 'binary' ? 'FILE' : getTypeFromSchema(prop, spec, baseDir);
437
444
  const required = (bodySchema.required || []).includes(key) ? 'TRUE' : 'FALSE';
438
445
  inputParams.push({
439
446
  name: key,
@@ -483,6 +490,11 @@ function extractInterfaces(spec, baseDir) {
483
490
  const interfaces = {};
484
491
  // Valid HTTP methods
485
492
  const validMethods = ['get', 'post', 'put', 'delete', 'patch', 'head', 'options', 'trace'];
493
+ // Check if paths exists and is an object
494
+ if (!spec.paths || typeof spec.paths !== 'object') {
495
+ console.warn('Warning: No paths found in OpenAPI specification');
496
+ return interfaces;
497
+ }
486
498
  for (const [pathStr, methods] of Object.entries(spec.paths)) {
487
499
  for (const [method, op] of Object.entries(methods)) {
488
500
  // Skip extension fields (x-*) and only process valid HTTP methods
@@ -558,6 +570,37 @@ function extractSecurityDefaults(spec) {
558
570
  }
559
571
  return defs;
560
572
  }
573
+ function cleanYaml(yamlString) {
574
+ return yamlString
575
+ .replace(/\t/g, ' ')
576
+ .replace(/[\u00A0]/g, ' ')
577
+ .replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, '')
578
+ .replace(/[ \t]+$/gm, '')
579
+ .replace(/\r\n/g, '\n');
580
+ }
581
+ function checkYamlForHiddenChars(yamlString) {
582
+ const lines = yamlString.split('\n');
583
+ for (let i = 0; i < lines.length; i++) {
584
+ const line = lines[i];
585
+ if (/\t/.test(line)) {
586
+ throw new Error(`YAML contains a TAB character at line ${i + 1}:\n${line}`);
587
+ }
588
+ if (/\u00A0/.test(line)) {
589
+ throw new Error(`YAML contains a non-breaking space (U+00A0) at line ${i + 1}:\n${line}`);
590
+ }
591
+ if (/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/.test(line)) {
592
+ throw new Error(`YAML contains a non-printable character at line ${i + 1}:\n${line}`);
593
+ }
594
+ }
595
+ }
596
+ function validateYaml(yamlString) {
597
+ try {
598
+ (0, js_yaml_2.load)(yamlString);
599
+ }
600
+ catch (e) {
601
+ throw new Error('Generated YAML is invalid: ' + e.message);
602
+ }
603
+ }
561
604
  function generateWrekenfile(spec, baseDir) {
562
605
  var _a, _b;
563
606
  if (!spec || typeof spec !== 'object') {
@@ -566,7 +609,7 @@ function generateWrekenfile(spec, baseDir) {
566
609
  if (!baseDir || typeof baseDir !== 'string') {
567
610
  throw new Error("Argument 'baseDir' is required and must be a string");
568
611
  }
569
- return (0, js_yaml_1.dump)({
612
+ let yamlString = (0, js_yaml_1.dump)({
570
613
  VERSION: '1.2',
571
614
  INIT: {
572
615
  DEFAULTS: [
@@ -577,4 +620,8 @@ function generateWrekenfile(spec, baseDir) {
577
620
  INTERFACES: extractInterfaces(spec, baseDir),
578
621
  STRUCTS: extractStructs(spec, baseDir),
579
622
  });
623
+ yamlString = cleanYaml(yamlString);
624
+ checkYamlForHiddenChars(yamlString);
625
+ validateYaml(yamlString);
626
+ return yamlString;
580
627
  }
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,13 +528,16 @@ 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`;
479
- wrekenfile += ` SUMMARY: ${operation.SUMMARY}\n`;
480
- wrekenfile += ` DESCRIPTION: ${operation.DESCRIPTION}\n`;
481
- wrekenfile += ` DESC: ${operation.DESC}\n`;
534
+ // Quote SUMMARY and DESCRIPTION if they contain special characters
535
+ const summary = operation.SUMMARY.includes(':') || operation.SUMMARY.includes('"') ? `"${operation.SUMMARY.replace(/"/g, '\\"')}"` : operation.SUMMARY;
536
+ const description = operation.DESCRIPTION.includes(':') || operation.DESCRIPTION.includes('"') ? `"${operation.DESCRIPTION.replace(/"/g, '\\"')}"` : operation.DESCRIPTION;
537
+ const desc = operation.DESC.includes(':') || operation.DESC.includes('"') ? `"${operation.DESC.replace(/"/g, '\\"')}"` : operation.DESC;
538
+ wrekenfile += ` SUMMARY: ${summary}\n`;
539
+ wrekenfile += ` DESCRIPTION: ${description}\n`;
540
+ wrekenfile += ` DESC: ${desc}\n`;
482
541
  wrekenfile += ` ENDPOINT: ${operation.ENDPOINT}\n`;
483
542
  wrekenfile += ` VISIBILITY: ${operation.VISIBILITY}\n`;
484
543
  wrekenfile += ` HTTP:\n`;
@@ -490,11 +549,12 @@ function generateWrekenfile(collection, variables) {
490
549
  }
491
550
  }
492
551
  wrekenfile += ` BODYTYPE: ${operation.HTTP.BODYTYPE}\n`;
493
- wrekenfile += ` INPUTS:\n`;
552
+ // INPUTS
494
553
  if (operation.INPUTS.length === 0) {
495
- wrekenfile += ` []\n`;
554
+ wrekenfile += ` INPUTS: []\n`;
496
555
  }
497
556
  else {
557
+ wrekenfile += ` INPUTS:\n`;
498
558
  for (const input of operation.INPUTS) {
499
559
  wrekenfile += ` - name: ${input.name}\n`;
500
560
  wrekenfile += ` type: ${input.type}\n`;
@@ -504,6 +564,7 @@ function generateWrekenfile(collection, variables) {
504
564
  }
505
565
  }
506
566
  }
567
+ // RETURNS
507
568
  wrekenfile += ` RETURNS:\n`;
508
569
  for (const ret of operation.RETURNS) {
509
570
  wrekenfile += ` - RETURNTYPE: ${ret.RETURNTYPE}\n`;
@@ -511,20 +572,33 @@ function generateWrekenfile(collection, variables) {
511
572
  wrekenfile += ` CODE: '${ret.CODE}'\n`;
512
573
  }
513
574
  }
514
- // Add STRUCTS section
515
575
  wrekenfile += `STRUCTS:\n`;
516
576
  for (const [structName, fields] of Object.entries(structs)) {
517
- wrekenfile += ` ${structName}:\n`;
518
577
  if (fields.length === 0) {
519
- wrekenfile += ` []\n`;
578
+ wrekenfile += ` ${structName}: []\n`;
520
579
  }
521
580
  else {
581
+ wrekenfile += ` ${structName}:\n`;
522
582
  for (const field of fields) {
523
- wrekenfile += ` - name: ${field.name}\n`;
524
- wrekenfile += ` type: ${field.type}\n`;
525
- wrekenfile += ` required: '${field.required}'\n`;
583
+ wrekenfile += ' - name: ' + field.name + '\n';
584
+ // Quote type if it contains non-alphanumeric characters (except underscore)
585
+ let typeValue = field.type;
586
+ if (/[^a-zA-Z0-9_]/.test(typeValue)) {
587
+ typeValue = '"' + typeValue.replace(/"/g, '\"') + '"';
588
+ }
589
+ wrekenfile += ' type: ' + typeValue + '\n';
590
+ wrekenfile += " required: '" + field.required + "'\n";
526
591
  }
527
592
  }
528
593
  }
594
+ // Debug print: show first 20 lines of STRUCTS block
595
+ const structsBlock = (_a = wrekenfile.split('STRUCTS:')[1]) === null || _a === void 0 ? void 0 : _a.split('\n').slice(0, 20).join('\n');
596
+ if (structsBlock) {
597
+ console.log('--- STRUCTS block preview ---');
598
+ console.log(structsBlock);
599
+ }
600
+ wrekenfile = cleanYaml(wrekenfile);
601
+ checkYamlForHiddenChars(wrekenfile);
602
+ validateYaml(wrekenfile);
529
603
  return wrekenfile;
530
604
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "wrekenfile-converter",
3
- "version": "2.0.1",
3
+ "version": "2.0.4",
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",