@rexeus/typeweaver 0.0.2 → 0.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.
@@ -1,9 +1,12 @@
1
1
  import * as path from 'path';
2
2
  import path__default, { dirname } from 'path';
3
+ import url, { fileURLToPath, pathToFileURL, URL as URL$1 } from 'url';
4
+ import { Command } from 'commander';
3
5
  import fs, { realpathSync, statSync } from 'fs';
4
- import { HttpResponseDefinition, HttpOperationDefinition, HttpStatusCodeNameMap } from '@rexeus/typeweaver-core';
6
+ import { PluginRegistry, PluginContextBuilder } from '@rexeus/typeweaver-gen';
7
+ import TypesPlugin from '@rexeus/typeweaver-types';
8
+ import { render } from 'ejs';
5
9
  import { createRequire, builtinModules } from 'module';
6
- import url, { fileURLToPath, pathToFileURL, URL as URL$1 } from 'url';
7
10
  import process3 from 'process';
8
11
  import os from 'os';
9
12
  import tty from 'tty';
@@ -11,202 +14,140 @@ import fs$1 from 'fs/promises';
11
14
  import assert2 from 'assert';
12
15
  import v8 from 'v8';
13
16
  import { format, inspect } from 'util';
14
- import { PluginRegistry, PluginContextBuilder } from '@rexeus/typeweaver-gen';
15
- import TypesPlugin from '@rexeus/typeweaver-types';
16
- import { Command } from 'commander';
17
+ import { HttpMethod, HttpStatusCode, HttpResponseDefinition, HttpOperationDefinition, HttpStatusCodeNameMap } from '@rexeus/typeweaver-core';
18
+ import { z } from 'zod/v4';
17
19
 
18
- class InvalidSharedDirError extends Error {
19
- constructor(explanation) {
20
- super("Invalid shared dir");
21
- this.explanation = explanation;
20
+ var version = "0.0.4";
21
+ var packageJson = {
22
+ version: version};
23
+
24
+ class IndexFileGenerator {
25
+ constructor(templateDir) {
26
+ this.templateDir = templateDir;
27
+ }
28
+ generate(context) {
29
+ const templateFilePath = path__default.join(this.templateDir, "Index.ejs");
30
+ const template = fs.readFileSync(templateFilePath, "utf8");
31
+ const indexPaths = /* @__PURE__ */ new Set();
32
+ for (const generatedFile of context.getGeneratedFiles()) {
33
+ indexPaths.add(`./${generatedFile.replace(/\.ts$/, "")}`);
34
+ }
35
+ const content = render(template, {
36
+ indexPaths: Array.from(indexPaths).sort()
37
+ });
38
+ fs.writeFileSync(path__default.join(context.outputDir, "index.ts"), content);
22
39
  }
23
40
  }
24
41
 
25
- class InvalidSharedResponseDefinitionError extends Error {
26
- constructor(fileName, dir, file, explanation) {
27
- super("Invalid shared response definition");
28
- this.fileName = fileName;
29
- this.dir = dir;
30
- this.file = file;
31
- this.explanation = explanation;
42
+ class PluginLoadingFailure extends Error {
43
+ constructor(pluginName, attempts) {
44
+ super(`Failed to load plugin '${pluginName}'`);
45
+ this.pluginName = pluginName;
46
+ this.attempts = attempts;
47
+ Object.setPrototypeOf(this, PluginLoadingFailure.prototype);
32
48
  }
33
49
  }
34
50
 
35
- class ResourceReader {
36
- constructor(config) {
37
- this.config = config;
51
+ class PluginLoader {
52
+ constructor(registry, requiredPlugins, strategies = ["npm", "local"]) {
53
+ this.registry = registry;
54
+ this.requiredPlugins = requiredPlugins;
55
+ this.strategies = strategies;
38
56
  }
39
- async getResources() {
40
- const contents = fs.readdirSync(this.config.sourceDir, {
41
- withFileTypes: true
42
- });
43
- const result = {
44
- entityResources: {},
45
- sharedResponseResources: []
46
- };
47
- const sharedDefinitions = contents.find(
48
- (content) => content.name === "shared"
49
- );
50
- if (sharedDefinitions) {
51
- if (!sharedDefinitions.isDirectory()) {
52
- throw new InvalidSharedDirError("'shared' is a file, not a directory");
53
- }
54
- result.sharedResponseResources = await this.getSharedResponseResources();
55
- console.info(
56
- `Found '${result.sharedResponseResources.length}' shared responses`
57
- );
58
- } else {
59
- console.info("No 'shared' directory found");
57
+ /**
58
+ * Load all plugins from configuration
59
+ */
60
+ async loadPlugins(config) {
61
+ for (const requiredPlugin of this.requiredPlugins) {
62
+ this.registry.register(requiredPlugin);
60
63
  }
61
- for (const content of contents) {
62
- if (!content.isDirectory()) {
63
- console.info(`Skipping '${content.name}' as it is not a directory`);
64
- continue;
65
- }
66
- if (content.name === "shared") {
67
- continue;
68
- }
69
- const entityName = content.name;
70
- const entitySourceDir = path__default.join(this.config.sourceDir, entityName);
71
- const operationResources = await this.getEntityOperationResources(
72
- entitySourceDir,
73
- entityName
74
- );
75
- result.entityResources[entityName] = operationResources;
76
- console.info(
77
- `Found '${operationResources.length}' operation definitions for entity '${entityName}'`
78
- );
64
+ if (!config?.plugins) {
65
+ return;
79
66
  }
80
- return result;
81
- }
82
- async getSharedResponseResources() {
83
- const sharedContents = fs.readdirSync(this.config.sharedSourceDir, {
84
- withFileTypes: true
85
- });
86
- const sharedResponseResources = [];
87
- for (const content of sharedContents) {
88
- if (!content.isFile()) {
89
- console.info(`Skipping '${content.name}' as it is not a file`);
90
- continue;
91
- }
92
- const sourceFileName = content.name;
93
- const sourceFile = path__default.join(this.config.sharedSourceDir, sourceFileName);
94
- const definition = await import(sourceFile);
95
- if (!definition.default) {
96
- console.info(
97
- `Skipping '${sourceFile}' as it does not have a default export`
98
- );
99
- continue;
100
- }
101
- if (!(definition.default instanceof HttpResponseDefinition)) {
102
- console.info(
103
- `Skipping '${sourceFile}' as it is not an instance of HttpResponseDefinition`
104
- );
105
- continue;
67
+ const successful = [];
68
+ for (const plugin of config.plugins) {
69
+ let result;
70
+ if (typeof plugin === "string") {
71
+ result = await this.loadPlugin(plugin);
72
+ } else {
73
+ result = await this.loadPlugin(plugin[0]);
106
74
  }
107
- if (!definition.default.isShared) {
108
- throw new InvalidSharedResponseDefinitionError(
109
- sourceFileName,
110
- this.config.sharedSourceDir,
111
- sourceFile,
112
- "'isShared' property is not set to 'true'"
75
+ if (result.success === false) {
76
+ throw new PluginLoadingFailure(
77
+ result.error.pluginName,
78
+ result.error.attempts
113
79
  );
114
80
  }
115
- const outputDir = this.config.sharedOutputDir;
116
- const outputFileName = `${definition.default.name}Response.ts`;
117
- const outputFile = path__default.join(outputDir, outputFileName);
118
- sharedResponseResources.push({
119
- ...definition.default,
120
- isShared: true,
121
- sourceDir: this.config.sharedSourceDir,
122
- sourceFile,
123
- sourceFileName,
124
- outputFile,
125
- outputFileName,
126
- outputDir
127
- });
81
+ successful.push(result.value);
82
+ this.registry.register(result.value.plugin);
128
83
  }
129
- return sharedResponseResources;
84
+ this.reportSuccessfulLoads(successful);
130
85
  }
131
- async getEntityOperationResources(sourceDir, entityName) {
132
- const contents = fs.readdirSync(sourceDir, {
133
- withFileTypes: true
134
- });
135
- const definitions = [];
136
- for (const content of contents) {
137
- if (!content.isFile()) {
138
- console.info(`Skipping '${content.name}' as it is not a file`);
139
- continue;
140
- }
141
- const sourceFileName = content.name;
142
- const sourceFile = path__default.join(sourceDir, sourceFileName);
143
- const definition = await import(sourceFile);
144
- if (!definition.default) {
145
- console.info(
146
- `Skipping '${sourceFile}' as it does not have a default export`
147
- );
148
- continue;
86
+ /**
87
+ * Load a plugin from a string identifier
88
+ */
89
+ async loadPlugin(pluginName) {
90
+ const possiblePaths = this.generatePluginPaths(pluginName);
91
+ const attempts = [];
92
+ for (const possiblePath of possiblePaths) {
93
+ try {
94
+ const pluginPackage = await import(possiblePath);
95
+ if (pluginPackage.default) {
96
+ return {
97
+ success: true,
98
+ value: {
99
+ plugin: new pluginPackage.default(),
100
+ source: possiblePath
101
+ }
102
+ };
103
+ }
104
+ attempts.push({
105
+ path: possiblePath,
106
+ error: "No default export found"
107
+ });
108
+ } catch (error) {
109
+ attempts.push({
110
+ path: possiblePath,
111
+ error: error instanceof Error ? error.message : String(error)
112
+ });
149
113
  }
150
- if (!(definition.default instanceof HttpOperationDefinition)) {
151
- console.info(
152
- `Skipping '${sourceFile}' as it is not an instance of HttpOperationDefinition`
153
- );
154
- continue;
114
+ }
115
+ return {
116
+ success: false,
117
+ error: {
118
+ pluginName,
119
+ attempts
155
120
  }
156
- const { operationId } = definition.default;
157
- const outputDir = path__default.join(this.config.outputDir, entityName);
158
- const outputRequestFileName = `${operationId}Request.ts`;
159
- const outputRequestFile = path__default.join(outputDir, outputRequestFileName);
160
- const outputResponseFileName = `${operationId}Response.ts`;
161
- const outputResponseFile = path__default.join(outputDir, outputResponseFileName);
162
- const outputRequestValidationFileName = `${operationId}RequestValidator.ts`;
163
- const outputRequestValidationFile = path__default.join(
164
- outputDir,
165
- outputRequestValidationFileName
166
- );
167
- const outputResponseValidationFileName = `${operationId}ResponseValidator.ts`;
168
- const outputResponseValidationFile = path__default.join(
169
- outputDir,
170
- outputResponseValidationFileName
171
- );
172
- const outputClientFileName = `${operationId}Client.ts`;
173
- const outputClientFile = path__default.join(outputDir, outputClientFileName);
174
- const operationResource = {
175
- sourceDir,
176
- sourceFile,
177
- sourceFileName,
178
- definition: {
179
- ...definition.default,
180
- responses: []
181
- },
182
- outputDir,
183
- entityName,
184
- outputRequestFile,
185
- outputResponseFile,
186
- outputResponseValidationFile,
187
- outputRequestValidationFile,
188
- outputRequestFileName,
189
- outputRequestValidationFileName,
190
- outputResponseFileName,
191
- outputResponseValidationFileName,
192
- outputClientFile,
193
- outputClientFileName
194
- };
195
- if (!definition.default.responses) {
196
- throw new Error(
197
- `Operation '${operationId}' does not have any responses`
198
- );
121
+ };
122
+ }
123
+ /**
124
+ * Generate possible plugin paths based on configured strategies
125
+ */
126
+ generatePluginPaths(pluginName) {
127
+ const paths = [];
128
+ for (const strategy of this.strategies) {
129
+ switch (strategy) {
130
+ case "npm":
131
+ paths.push(`@rexeus/typeweaver-${pluginName}`);
132
+ paths.push(`@rexeus/${pluginName}`);
133
+ break;
134
+ case "local":
135
+ paths.push(pluginName);
136
+ break;
199
137
  }
200
- for (const response of definition.default.responses) {
201
- const extendedResponse = {
202
- ...response,
203
- statusCodeName: HttpStatusCodeNameMap[response.statusCode]
204
- };
205
- operationResource.definition.responses.push(extendedResponse);
138
+ }
139
+ return paths;
140
+ }
141
+ /**
142
+ * Report successful plugin loads
143
+ */
144
+ reportSuccessfulLoads(successful) {
145
+ if (successful.length > 0) {
146
+ console.info(`Successfully loaded ${successful.length} plugin(s):`);
147
+ for (const result of successful) {
148
+ console.info(` - ${result.plugin.name} (from ${result.source})`);
206
149
  }
207
- definitions.push(operationResource);
208
150
  }
209
- return definitions;
210
151
  }
211
152
  }
212
153
 
@@ -219,7 +160,7 @@ var public_exports$1 = {};
219
160
  __export$1(public_exports$1, {
220
161
  builders: () => builders,
221
162
  printer: () => printer,
222
- utils: () => utils$1
163
+ utils: () => utils
223
164
  });
224
165
  var DOC_TYPE_STRING$1 = "string";
225
166
  var DOC_TYPE_ARRAY$1 = "array";
@@ -1326,7 +1267,7 @@ var builders = {
1326
1267
  concat: (parts) => parts
1327
1268
  };
1328
1269
  var printer = { printDocToString: printDocToString$1 };
1329
- var utils$1 = {
1270
+ var utils = {
1330
1271
  willBreak,
1331
1272
  traverseDoc: traverse_doc_default$1,
1332
1273
  findInDoc,
@@ -1343,7 +1284,7 @@ var doc = /*#__PURE__*/Object.freeze({
1343
1284
  builders: builders,
1344
1285
  default: public_default,
1345
1286
  printer: printer,
1346
- utils: utils$1
1287
+ utils: utils
1347
1288
  });
1348
1289
 
1349
1290
  const require = createRequire(import.meta.url);
@@ -22452,11 +22393,11 @@ var { parsers, printers } = createParsersAndPrinters([
22452
22393
  printers: ["estree", "estree-json"]
22453
22394
  },
22454
22395
  {
22455
- importPlugin: () => import('./flow--vV0j3Y-.js'),
22396
+ importPlugin: () => import('./flow-q2wMXrDa.js'),
22456
22397
  parsers: ["flow"]
22457
22398
  },
22458
22399
  {
22459
- importPlugin: () => import('./glimmer-B-ODUU1A.js'),
22400
+ importPlugin: () => import('./glimmer-wgjvri6H.js'),
22460
22401
  parsers: ["glimmer"],
22461
22402
  printers: ["glimmer"]
22462
22403
  },
@@ -22471,7 +22412,7 @@ var { parsers, printers } = createParsersAndPrinters([
22471
22412
  printers: ["html"]
22472
22413
  },
22473
22414
  {
22474
- importPlugin: () => import('./markdown-BsYj_q7V.js'),
22415
+ importPlugin: () => import('./markdown-Nz6Lc3gB.js'),
22475
22416
  parsers: ["markdown", "mdx", "remark"],
22476
22417
  printers: ["mdast"]
22477
22418
  },
@@ -22480,16 +22421,16 @@ var { parsers, printers } = createParsersAndPrinters([
22480
22421
  parsers: ["meriyah"]
22481
22422
  },
22482
22423
  {
22483
- importPlugin: () => import('./postcss-DdgOJBTx.js'),
22424
+ importPlugin: () => import('./postcss-yEOijaXJ.js'),
22484
22425
  parsers: ["css", "less", "scss"],
22485
22426
  printers: ["postcss"]
22486
22427
  },
22487
22428
  {
22488
- importPlugin: () => import('./typescript-C4gnKzhB.js'),
22429
+ importPlugin: () => import('./typescript-Cv79a1Qz.js'),
22489
22430
  parsers: ["typescript"]
22490
22431
  },
22491
22432
  {
22492
- importPlugin: () => import('./yaml-B0tq6Ttj.js'),
22433
+ importPlugin: () => import('./yaml-DT3qlFoE.js'),
22493
22434
  parsers: ["yaml"],
22494
22435
  printers: ["yaml"]
22495
22436
  }
@@ -22809,7 +22750,6 @@ var debugApis = {
22809
22750
  printDocToString: withPlugins(printDocToString2),
22810
22751
  mockable: mockable_default
22811
22752
  };
22812
- var src_default = index_exports;
22813
22753
 
22814
22754
  class Prettier {
22815
22755
  constructor(outputDir) {
@@ -22824,7 +22764,7 @@ class Prettier {
22824
22764
  if (content.isFile()) {
22825
22765
  const filePath = path__default.join(targetDir, content.name);
22826
22766
  const unformatted = fs.readFileSync(filePath, "utf8");
22827
- const formatted = await src_default.format(unformatted, {
22767
+ const formatted = await format2(unformatted, {
22828
22768
  parser: "typescript"
22829
22769
  });
22830
22770
  fs.writeFileSync(filePath, formatted);
@@ -22835,845 +22775,823 @@ class Prettier {
22835
22775
  }
22836
22776
  }
22837
22777
 
22838
- function getDefaultExportFromCjs (x) {
22839
- return x && x.__esModule && Object.prototype.hasOwnProperty.call(x, 'default') ? x['default'] : x;
22778
+ class DuplicateOperationIdError extends Error {
22779
+ constructor(operationId, firstFile, duplicateFile) {
22780
+ super(
22781
+ `Duplicate operation ID '${operationId}' found.
22782
+ First defined in: \`${firstFile}\`
22783
+ Duplicate found in: \`${duplicateFile}\``
22784
+ );
22785
+ this.operationId = operationId;
22786
+ this.firstFile = firstFile;
22787
+ this.duplicateFile = duplicateFile;
22788
+ }
22840
22789
  }
22841
22790
 
22842
- var ejs$1 = {};
22791
+ class DuplicateResponseNameError extends Error {
22792
+ constructor(responseName, firstFile, duplicateFile) {
22793
+ super(
22794
+ `Duplicate response name '${responseName}' found.
22795
+ First defined in: \`${firstFile}\`
22796
+ Duplicate found in: \`${duplicateFile}\``
22797
+ );
22798
+ this.responseName = responseName;
22799
+ this.firstFile = firstFile;
22800
+ this.duplicateFile = duplicateFile;
22801
+ }
22802
+ }
22843
22803
 
22844
- var utils = {};
22845
-
22846
- var hasRequiredUtils;
22804
+ class DuplicateRouteError extends Error {
22805
+ constructor(path, method, firstOperationId, firstFile, duplicateOperationId, duplicateFile) {
22806
+ super(
22807
+ `Duplicate route '${method} ${path}' found.
22808
+ First defined by operation '${firstOperationId}' in: \`${firstFile}\`
22809
+ Duplicate defined by operation '${duplicateOperationId}' in: \`${duplicateFile}\``
22810
+ );
22811
+ this.path = path;
22812
+ this.method = method;
22813
+ this.firstOperationId = firstOperationId;
22814
+ this.firstFile = firstFile;
22815
+ this.duplicateOperationId = duplicateOperationId;
22816
+ this.duplicateFile = duplicateFile;
22817
+ }
22818
+ }
22847
22819
 
22848
- function requireUtils () {
22849
- if (hasRequiredUtils) return utils;
22850
- hasRequiredUtils = 1;
22851
- (function (exports) {
22852
- var regExpChars = /[|\\{}()[\]^$+*?.]/g;
22853
- var hasOwnProperty = Object.prototype.hasOwnProperty;
22854
- var hasOwn = function(obj, key) {
22855
- return hasOwnProperty.apply(obj, [key]);
22856
- };
22857
- exports.escapeRegExpChars = function(string) {
22858
- if (!string) {
22859
- return "";
22860
- }
22861
- return String(string).replace(regExpChars, "\\$&");
22862
- };
22863
- var _ENCODE_HTML_RULES = {
22864
- "&": "&",
22865
- "<": "&lt;",
22866
- ">": "&gt;",
22867
- '"': "&#34;",
22868
- "'": "&#39;"
22869
- };
22870
- var _MATCH_HTML = /[&<>'"]/g;
22871
- function encode_char(c) {
22872
- return _ENCODE_HTML_RULES[c] || c;
22873
- }
22874
- var escapeFuncStr = `var _ENCODE_HTML_RULES = {
22875
- "&": "&amp;"
22876
- , "<": "&lt;"
22877
- , ">": "&gt;"
22878
- , '"': "&#34;"
22879
- , "'": "&#39;"
22880
- }
22881
- , _MATCH_HTML = /[&<>'"]/g;
22882
- function encode_char(c) {
22883
- return _ENCODE_HTML_RULES[c] || c;
22884
- };
22885
- `;
22886
- exports.escapeXML = function(markup) {
22887
- return markup == void 0 ? "" : String(markup).replace(_MATCH_HTML, encode_char);
22888
- };
22889
- function escapeXMLToString() {
22890
- return Function.prototype.toString.call(this) + ";\n" + escapeFuncStr;
22891
- }
22892
- try {
22893
- if (typeof Object.defineProperty === "function") {
22894
- Object.defineProperty(exports.escapeXML, "toString", { value: escapeXMLToString });
22895
- } else {
22896
- exports.escapeXML.toString = escapeXMLToString;
22897
- }
22898
- } catch (err) {
22899
- console.warn("Unable to set escapeXML.toString (is the Function prototype frozen?)");
22900
- }
22901
- exports.shallowCopy = function(to, from) {
22902
- from = from || {};
22903
- if (to !== null && to !== void 0) {
22904
- for (var p in from) {
22905
- if (!hasOwn(from, p)) {
22906
- continue;
22907
- }
22908
- if (p === "__proto__" || p === "constructor") {
22909
- continue;
22910
- }
22911
- to[p] = from[p];
22912
- }
22913
- }
22914
- return to;
22915
- };
22916
- exports.shallowCopyFromList = function(to, from, list) {
22917
- list = list || [];
22918
- from = from || {};
22919
- if (to !== null && to !== void 0) {
22920
- for (var i = 0; i < list.length; i++) {
22921
- var p = list[i];
22922
- if (typeof from[p] != "undefined") {
22923
- if (!hasOwn(from, p)) {
22924
- continue;
22925
- }
22926
- if (p === "__proto__" || p === "constructor") {
22927
- continue;
22928
- }
22929
- to[p] = from[p];
22930
- }
22931
- }
22932
- }
22933
- return to;
22934
- };
22935
- exports.cache = {
22936
- _data: {},
22937
- set: function(key, val) {
22938
- this._data[key] = val;
22939
- },
22940
- get: function(key) {
22941
- return this._data[key];
22942
- },
22943
- remove: function(key) {
22944
- delete this._data[key];
22945
- },
22946
- reset: function() {
22947
- this._data = {};
22948
- }
22949
- };
22950
- exports.hyphenToCamel = function(str) {
22951
- return str.replace(/-[a-z]/g, function(match) {
22952
- return match[1].toUpperCase();
22953
- });
22954
- };
22955
- exports.createNullProtoObjWherePossible = function() {
22956
- if (typeof Object.create == "function") {
22957
- return function() {
22958
- return /* @__PURE__ */ Object.create(null);
22959
- };
22960
- }
22961
- if (!({ __proto__: null } instanceof Object)) {
22962
- return function() {
22963
- return { __proto__: null };
22964
- };
22965
- }
22966
- return function() {
22967
- return {};
22968
- };
22969
- }();
22970
- exports.hasOwnOnlyObject = function(obj) {
22971
- var o = exports.createNullProtoObjWherePossible();
22972
- for (var p in obj) {
22973
- if (hasOwn(obj, p)) {
22974
- o[p] = obj[p];
22975
- }
22976
- }
22977
- return o;
22978
- };
22979
- } (utils));
22980
- return utils;
22820
+ class DefinitionRegistry {
22821
+ operationIds = /* @__PURE__ */ new Map();
22822
+ responseNames = /* @__PURE__ */ new Map();
22823
+ routes = /* @__PURE__ */ new Map();
22824
+ registerOperation(operation, sourceFile) {
22825
+ const existingFile = this.operationIds.get(operation.operationId);
22826
+ if (existingFile) {
22827
+ throw new DuplicateOperationIdError(
22828
+ operation.operationId,
22829
+ existingFile,
22830
+ sourceFile
22831
+ );
22832
+ }
22833
+ this.operationIds.set(operation.operationId, sourceFile);
22834
+ const normalizedPath = this.normalizePath(operation.path);
22835
+ const routeKey = `${operation.method} ${normalizedPath}`;
22836
+ const existingRoute = this.routes.get(routeKey);
22837
+ if (existingRoute) {
22838
+ throw new DuplicateRouteError(
22839
+ operation.path,
22840
+ operation.method,
22841
+ existingRoute.operationId,
22842
+ existingRoute.file,
22843
+ operation.operationId,
22844
+ sourceFile
22845
+ );
22846
+ }
22847
+ this.routes.set(routeKey, {
22848
+ operationId: operation.operationId,
22849
+ file: sourceFile
22850
+ });
22851
+ }
22852
+ registerResponse(response, sourceFile) {
22853
+ const existingFile = this.responseNames.get(response.name);
22854
+ if (existingFile) {
22855
+ throw new DuplicateResponseNameError(
22856
+ response.name,
22857
+ existingFile,
22858
+ sourceFile
22859
+ );
22860
+ }
22861
+ this.responseNames.set(response.name, sourceFile);
22862
+ }
22863
+ hasOperationId(operationId) {
22864
+ return this.operationIds.has(operationId);
22865
+ }
22866
+ hasResponseName(name) {
22867
+ return this.responseNames.has(name);
22868
+ }
22869
+ hasRoute(method, path) {
22870
+ const normalizedPath = this.normalizePath(path);
22871
+ const routeKey = `${method} ${normalizedPath}`;
22872
+ return this.routes.has(routeKey);
22873
+ }
22874
+ getOperationFile(operationId) {
22875
+ return this.operationIds.get(operationId);
22876
+ }
22877
+ getResponseFile(name) {
22878
+ return this.responseNames.get(name);
22879
+ }
22880
+ getRouteInfo(method, path) {
22881
+ const normalizedPath = this.normalizePath(path);
22882
+ const routeKey = `${method} ${normalizedPath}`;
22883
+ return this.routes.get(routeKey);
22884
+ }
22885
+ normalizePath(path) {
22886
+ let paramIndex = 1;
22887
+ return path.replace(/:([a-zA-Z0-9_]+)/g, () => {
22888
+ return `:param${paramIndex++}`;
22889
+ });
22890
+ }
22981
22891
  }
22982
22892
 
22983
- var version$1 = "3.1.10";
22984
- var require$$3 = {
22985
- version: version$1};
22893
+ class EmptyResponseArrayError extends Error {
22894
+ constructor(operationId, file) {
22895
+ super(
22896
+ `Operation '${operationId}' has no responses defined at \`${file}\`
22897
+ Operations must have at least one response definition`
22898
+ );
22899
+ this.operationId = operationId;
22900
+ this.file = file;
22901
+ }
22902
+ }
22986
22903
 
22987
- var hasRequiredEjs;
22904
+ class InvalidHttpMethodError extends Error {
22905
+ constructor(operationId, method, file) {
22906
+ const validMethods = Object.values(HttpMethod).join(", ");
22907
+ super(
22908
+ `Invalid HTTP method '${method}' in operation '${operationId}' at \`${file}\`
22909
+ Valid methods: ${validMethods}`
22910
+ );
22911
+ this.operationId = operationId;
22912
+ this.method = method;
22913
+ this.file = file;
22914
+ }
22915
+ }
22988
22916
 
22989
- function requireEjs () {
22990
- if (hasRequiredEjs) return ejs$1;
22991
- hasRequiredEjs = 1;
22992
- (function (exports) {
22993
- /**
22994
- * @file Embedded JavaScript templating engine. {@link http://ejs.co}
22995
- * @author Matthew Eernisse <mde@fleegix.org>
22996
- * @author Tiancheng "Timothy" Gu <timothygu99@gmail.com>
22997
- * @project EJS
22998
- * @license {@link http://www.apache.org/licenses/LICENSE-2.0 Apache License, Version 2.0}
22999
- */
23000
- var fs$1 = fs;
23001
- var path = path__default;
23002
- var utils = requireUtils();
23003
- var scopeOptionWarned = false;
23004
- var _VERSION_STRING = require$$3.version;
23005
- var _DEFAULT_OPEN_DELIMITER = "<";
23006
- var _DEFAULT_CLOSE_DELIMITER = ">";
23007
- var _DEFAULT_DELIMITER = "%";
23008
- var _DEFAULT_LOCALS_NAME = "locals";
23009
- var _NAME = "ejs";
23010
- var _REGEX_STRING = "(<%%|%%>|<%=|<%-|<%_|<%#|<%|%>|-%>|_%>)";
23011
- var _OPTS_PASSABLE_WITH_DATA = [
23012
- "delimiter",
23013
- "scope",
23014
- "context",
23015
- "debug",
23016
- "compileDebug",
23017
- "client",
23018
- "_with",
23019
- "rmWhitespace",
23020
- "strict",
23021
- "filename",
23022
- "async"
23023
- ];
23024
- var _OPTS_PASSABLE_WITH_DATA_EXPRESS = _OPTS_PASSABLE_WITH_DATA.concat("cache");
23025
- var _BOM = /^\uFEFF/;
23026
- var _JS_IDENTIFIER = /^[a-zA-Z_$][0-9a-zA-Z_$]*$/;
23027
- exports.cache = utils.cache;
23028
- exports.fileLoader = fs$1.readFileSync;
23029
- exports.localsName = _DEFAULT_LOCALS_NAME;
23030
- exports.promiseImpl = new Function("return this;")().Promise;
23031
- exports.resolveInclude = function(name, filename, isDir) {
23032
- var dirname = path.dirname;
23033
- var extname = path.extname;
23034
- var resolve = path.resolve;
23035
- var includePath = resolve(isDir ? filename : dirname(filename), name);
23036
- var ext = extname(name);
23037
- if (!ext) {
23038
- includePath += ".ejs";
23039
- }
23040
- return includePath;
23041
- };
23042
- function resolvePaths(name, paths) {
23043
- var filePath;
23044
- if (paths.some(function(v) {
23045
- filePath = exports.resolveInclude(name, v, true);
23046
- return fs$1.existsSync(filePath);
23047
- })) {
23048
- return filePath;
23049
- }
23050
- }
23051
- function getIncludePath(path2, options) {
23052
- var includePath;
23053
- var filePath;
23054
- var views = options.views;
23055
- var match = /^[A-Za-z]+:\\|^\//.exec(path2);
23056
- if (match && match.length) {
23057
- path2 = path2.replace(/^\/*/, "");
23058
- if (Array.isArray(options.root)) {
23059
- includePath = resolvePaths(path2, options.root);
23060
- } else {
23061
- includePath = exports.resolveInclude(path2, options.root || "/", true);
23062
- }
23063
- } else {
23064
- if (options.filename) {
23065
- filePath = exports.resolveInclude(path2, options.filename);
23066
- if (fs$1.existsSync(filePath)) {
23067
- includePath = filePath;
23068
- }
23069
- }
23070
- if (!includePath && Array.isArray(views)) {
23071
- includePath = resolvePaths(path2, views);
23072
- }
23073
- if (!includePath && typeof options.includer !== "function") {
23074
- throw new Error('Could not find the include file "' + options.escapeFunction(path2) + '"');
23075
- }
23076
- }
23077
- return includePath;
23078
- }
23079
- function handleCache(options, template) {
23080
- var func;
23081
- var filename = options.filename;
23082
- var hasTemplate = arguments.length > 1;
23083
- if (options.cache) {
23084
- if (!filename) {
23085
- throw new Error("cache option requires a filename");
23086
- }
23087
- func = exports.cache.get(filename);
23088
- if (func) {
23089
- return func;
23090
- }
23091
- if (!hasTemplate) {
23092
- template = fileLoader(filename).toString().replace(_BOM, "");
23093
- }
23094
- } else if (!hasTemplate) {
23095
- if (!filename) {
23096
- throw new Error("Internal EJS error: no file name or template provided");
23097
- }
23098
- template = fileLoader(filename).toString().replace(_BOM, "");
23099
- }
23100
- func = exports.compile(template, options);
23101
- if (options.cache) {
23102
- exports.cache.set(filename, func);
23103
- }
23104
- return func;
23105
- }
23106
- function tryHandleCache(options, data, cb) {
23107
- var result;
23108
- if (!cb) {
23109
- if (typeof exports.promiseImpl == "function") {
23110
- return new exports.promiseImpl(function(resolve, reject) {
23111
- try {
23112
- result = handleCache(options)(data);
23113
- resolve(result);
23114
- } catch (err) {
23115
- reject(err);
23116
- }
23117
- });
23118
- } else {
23119
- throw new Error("Please provide a callback function");
23120
- }
23121
- } else {
23122
- try {
23123
- result = handleCache(options)(data);
23124
- } catch (err) {
23125
- return cb(err);
23126
- }
23127
- cb(null, result);
23128
- }
23129
- }
23130
- function fileLoader(filePath) {
23131
- return exports.fileLoader(filePath);
23132
- }
23133
- function includeFile(path2, options) {
23134
- var opts = utils.shallowCopy(utils.createNullProtoObjWherePossible(), options);
23135
- opts.filename = getIncludePath(path2, opts);
23136
- if (typeof options.includer === "function") {
23137
- var includerResult = options.includer(path2, opts.filename);
23138
- if (includerResult) {
23139
- if (includerResult.filename) {
23140
- opts.filename = includerResult.filename;
23141
- }
23142
- if (includerResult.template) {
23143
- return handleCache(opts, includerResult.template);
23144
- }
23145
- }
23146
- }
23147
- return handleCache(opts);
23148
- }
23149
- function rethrow(err, str, flnm, lineno, esc) {
23150
- var lines = str.split("\n");
23151
- var start = Math.max(lineno - 3, 0);
23152
- var end = Math.min(lines.length, lineno + 3);
23153
- var filename = esc(flnm);
23154
- var context = lines.slice(start, end).map(function(line, i) {
23155
- var curr = i + start + 1;
23156
- return (curr == lineno ? " >> " : " ") + curr + "| " + line;
23157
- }).join("\n");
23158
- err.path = filename;
23159
- err.message = (filename || "ejs") + ":" + lineno + "\n" + context + "\n\n" + err.message;
23160
- throw err;
23161
- }
23162
- function stripSemi(str) {
23163
- return str.replace(/;(\s*$)/, "$1");
23164
- }
23165
- exports.compile = function compile(template, opts) {
23166
- var templ;
23167
- if (opts && opts.scope) {
23168
- if (!scopeOptionWarned) {
23169
- console.warn("`scope` option is deprecated and will be removed in EJS 3");
23170
- scopeOptionWarned = true;
23171
- }
23172
- if (!opts.context) {
23173
- opts.context = opts.scope;
23174
- }
23175
- delete opts.scope;
23176
- }
23177
- templ = new Template(template, opts);
23178
- return templ.compile();
23179
- };
23180
- exports.render = function(template, d, o) {
23181
- var data = d || utils.createNullProtoObjWherePossible();
23182
- var opts = o || utils.createNullProtoObjWherePossible();
23183
- if (arguments.length == 2) {
23184
- utils.shallowCopyFromList(opts, data, _OPTS_PASSABLE_WITH_DATA);
23185
- }
23186
- return handleCache(opts, template)(data);
23187
- };
23188
- exports.renderFile = function() {
23189
- var args = Array.prototype.slice.call(arguments);
23190
- var filename = args.shift();
23191
- var cb;
23192
- var opts = { filename };
23193
- var data;
23194
- var viewOpts;
23195
- if (typeof arguments[arguments.length - 1] == "function") {
23196
- cb = args.pop();
23197
- }
23198
- if (args.length) {
23199
- data = args.shift();
23200
- if (args.length) {
23201
- utils.shallowCopy(opts, args.pop());
23202
- } else {
23203
- if (data.settings) {
23204
- if (data.settings.views) {
23205
- opts.views = data.settings.views;
23206
- }
23207
- if (data.settings["view cache"]) {
23208
- opts.cache = true;
23209
- }
23210
- viewOpts = data.settings["view options"];
23211
- if (viewOpts) {
23212
- utils.shallowCopy(opts, viewOpts);
23213
- }
23214
- }
23215
- utils.shallowCopyFromList(opts, data, _OPTS_PASSABLE_WITH_DATA_EXPRESS);
23216
- }
23217
- opts.filename = filename;
23218
- } else {
23219
- data = utils.createNullProtoObjWherePossible();
23220
- }
23221
- return tryHandleCache(opts, data, cb);
23222
- };
23223
- exports.Template = Template;
23224
- exports.clearCache = function() {
23225
- exports.cache.reset();
23226
- };
23227
- function Template(text, optsParam) {
23228
- var opts = utils.hasOwnOnlyObject(optsParam);
23229
- var options = utils.createNullProtoObjWherePossible();
23230
- this.templateText = text;
23231
- this.mode = null;
23232
- this.truncate = false;
23233
- this.currentLine = 1;
23234
- this.source = "";
23235
- options.client = opts.client || false;
23236
- options.escapeFunction = opts.escape || opts.escapeFunction || utils.escapeXML;
23237
- options.compileDebug = opts.compileDebug !== false;
23238
- options.debug = !!opts.debug;
23239
- options.filename = opts.filename;
23240
- options.openDelimiter = opts.openDelimiter || exports.openDelimiter || _DEFAULT_OPEN_DELIMITER;
23241
- options.closeDelimiter = opts.closeDelimiter || exports.closeDelimiter || _DEFAULT_CLOSE_DELIMITER;
23242
- options.delimiter = opts.delimiter || exports.delimiter || _DEFAULT_DELIMITER;
23243
- options.strict = opts.strict || false;
23244
- options.context = opts.context;
23245
- options.cache = opts.cache || false;
23246
- options.rmWhitespace = opts.rmWhitespace;
23247
- options.root = opts.root;
23248
- options.includer = opts.includer;
23249
- options.outputFunctionName = opts.outputFunctionName;
23250
- options.localsName = opts.localsName || exports.localsName || _DEFAULT_LOCALS_NAME;
23251
- options.views = opts.views;
23252
- options.async = opts.async;
23253
- options.destructuredLocals = opts.destructuredLocals;
23254
- options.legacyInclude = typeof opts.legacyInclude != "undefined" ? !!opts.legacyInclude : true;
23255
- if (options.strict) {
23256
- options._with = false;
23257
- } else {
23258
- options._with = typeof opts._with != "undefined" ? opts._with : true;
23259
- }
23260
- this.opts = options;
23261
- this.regex = this.createRegex();
23262
- }
23263
- Template.modes = {
23264
- EVAL: "eval",
23265
- ESCAPED: "escaped",
23266
- RAW: "raw",
23267
- COMMENT: "comment",
23268
- LITERAL: "literal"
23269
- };
23270
- Template.prototype = {
23271
- createRegex: function() {
23272
- var str = _REGEX_STRING;
23273
- var delim = utils.escapeRegExpChars(this.opts.delimiter);
23274
- var open = utils.escapeRegExpChars(this.opts.openDelimiter);
23275
- var close = utils.escapeRegExpChars(this.opts.closeDelimiter);
23276
- str = str.replace(/%/g, delim).replace(/</g, open).replace(/>/g, close);
23277
- return new RegExp(str);
23278
- },
23279
- compile: function() {
23280
- var src;
23281
- var fn;
23282
- var opts = this.opts;
23283
- var prepended = "";
23284
- var appended = "";
23285
- var escapeFn = opts.escapeFunction;
23286
- var ctor;
23287
- var sanitizedFilename = opts.filename ? JSON.stringify(opts.filename) : "undefined";
23288
- if (!this.source) {
23289
- this.generateSource();
23290
- prepended += ' var __output = "";\n function __append(s) { if (s !== undefined && s !== null) __output += s }\n';
23291
- if (opts.outputFunctionName) {
23292
- if (!_JS_IDENTIFIER.test(opts.outputFunctionName)) {
23293
- throw new Error("outputFunctionName is not a valid JS identifier.");
23294
- }
23295
- prepended += " var " + opts.outputFunctionName + " = __append;\n";
23296
- }
23297
- if (opts.localsName && !_JS_IDENTIFIER.test(opts.localsName)) {
23298
- throw new Error("localsName is not a valid JS identifier.");
23299
- }
23300
- if (opts.destructuredLocals && opts.destructuredLocals.length) {
23301
- var destructuring = " var __locals = (" + opts.localsName + " || {}),\n";
23302
- for (var i = 0; i < opts.destructuredLocals.length; i++) {
23303
- var name = opts.destructuredLocals[i];
23304
- if (!_JS_IDENTIFIER.test(name)) {
23305
- throw new Error("destructuredLocals[" + i + "] is not a valid JS identifier.");
23306
- }
23307
- if (i > 0) {
23308
- destructuring += ",\n ";
23309
- }
23310
- destructuring += name + " = __locals." + name;
23311
- }
23312
- prepended += destructuring + ";\n";
23313
- }
23314
- if (opts._with !== false) {
23315
- prepended += " with (" + opts.localsName + " || {}) {\n";
23316
- appended += " }\n";
23317
- }
23318
- appended += " return __output;\n";
23319
- this.source = prepended + this.source + appended;
23320
- }
23321
- if (opts.compileDebug) {
23322
- src = "var __line = 1\n , __lines = " + JSON.stringify(this.templateText) + "\n , __filename = " + sanitizedFilename + ";\ntry {\n" + this.source + "} catch (e) {\n rethrow(e, __lines, __filename, __line, escapeFn);\n}\n";
23323
- } else {
23324
- src = this.source;
23325
- }
23326
- if (opts.client) {
23327
- src = "escapeFn = escapeFn || " + escapeFn.toString() + ";\n" + src;
23328
- if (opts.compileDebug) {
23329
- src = "rethrow = rethrow || " + rethrow.toString() + ";\n" + src;
23330
- }
23331
- }
23332
- if (opts.strict) {
23333
- src = '"use strict";\n' + src;
23334
- }
23335
- if (opts.debug) {
23336
- console.log(src);
23337
- }
23338
- if (opts.compileDebug && opts.filename) {
23339
- src = src + "\n//# sourceURL=" + sanitizedFilename + "\n";
23340
- }
23341
- try {
23342
- if (opts.async) {
23343
- try {
23344
- ctor = new Function("return (async function(){}).constructor;")();
23345
- } catch (e) {
23346
- if (e instanceof SyntaxError) {
23347
- throw new Error("This environment does not support async/await");
23348
- } else {
23349
- throw e;
23350
- }
23351
- }
23352
- } else {
23353
- ctor = Function;
23354
- }
23355
- fn = new ctor(opts.localsName + ", escapeFn, include, rethrow", src);
23356
- } catch (e) {
23357
- if (e instanceof SyntaxError) {
23358
- if (opts.filename) {
23359
- e.message += " in " + opts.filename;
23360
- }
23361
- e.message += " while compiling ejs\n\n";
23362
- e.message += "If the above error is not helpful, you may want to try EJS-Lint:\n";
23363
- e.message += "https://github.com/RyanZim/EJS-Lint";
23364
- if (!opts.async) {
23365
- e.message += "\n";
23366
- e.message += "Or, if you meant to create an async function, pass `async: true` as an option.";
23367
- }
23368
- }
23369
- throw e;
23370
- }
23371
- var returnedFn = opts.client ? fn : function anonymous(data) {
23372
- var include = function(path2, includeData) {
23373
- var d = utils.shallowCopy(utils.createNullProtoObjWherePossible(), data);
23374
- if (includeData) {
23375
- d = utils.shallowCopy(d, includeData);
23376
- }
23377
- return includeFile(path2, opts)(d);
23378
- };
23379
- return fn.apply(
23380
- opts.context,
23381
- [data || utils.createNullProtoObjWherePossible(), escapeFn, include, rethrow]
23382
- );
23383
- };
23384
- if (opts.filename && typeof Object.defineProperty === "function") {
23385
- var filename = opts.filename;
23386
- var basename = path.basename(filename, path.extname(filename));
23387
- try {
23388
- Object.defineProperty(returnedFn, "name", {
23389
- value: basename,
23390
- writable: false,
23391
- enumerable: false,
23392
- configurable: true
23393
- });
23394
- } catch (e) {
23395
- }
23396
- }
23397
- return returnedFn;
23398
- },
23399
- generateSource: function() {
23400
- var opts = this.opts;
23401
- if (opts.rmWhitespace) {
23402
- this.templateText = this.templateText.replace(/[\r\n]+/g, "\n").replace(/^\s+|\s+$/gm, "");
23403
- }
23404
- this.templateText = this.templateText.replace(/[ \t]*<%_/gm, "<%_").replace(/_%>[ \t]*/gm, "_%>");
23405
- var self = this;
23406
- var matches = this.parseTemplateText();
23407
- var d = this.opts.delimiter;
23408
- var o = this.opts.openDelimiter;
23409
- var c = this.opts.closeDelimiter;
23410
- if (matches && matches.length) {
23411
- matches.forEach(function(line, index) {
23412
- var closing;
23413
- if (line.indexOf(o + d) === 0 && line.indexOf(o + d + d) !== 0) {
23414
- closing = matches[index + 2];
23415
- if (!(closing == d + c || closing == "-" + d + c || closing == "_" + d + c)) {
23416
- throw new Error('Could not find matching close tag for "' + line + '".');
23417
- }
23418
- }
23419
- self.scanLine(line);
23420
- });
23421
- }
23422
- },
23423
- parseTemplateText: function() {
23424
- var str = this.templateText;
23425
- var pat = this.regex;
23426
- var result = pat.exec(str);
23427
- var arr = [];
23428
- var firstPos;
23429
- while (result) {
23430
- firstPos = result.index;
23431
- if (firstPos !== 0) {
23432
- arr.push(str.substring(0, firstPos));
23433
- str = str.slice(firstPos);
23434
- }
23435
- arr.push(result[0]);
23436
- str = str.slice(result[0].length);
23437
- result = pat.exec(str);
23438
- }
23439
- if (str) {
23440
- arr.push(str);
23441
- }
23442
- return arr;
23443
- },
23444
- _addOutput: function(line) {
23445
- if (this.truncate) {
23446
- line = line.replace(/^(?:\r\n|\r|\n)/, "");
23447
- this.truncate = false;
23448
- }
23449
- if (!line) {
23450
- return line;
23451
- }
23452
- line = line.replace(/\\/g, "\\\\");
23453
- line = line.replace(/\n/g, "\\n");
23454
- line = line.replace(/\r/g, "\\r");
23455
- line = line.replace(/"/g, '\\"');
23456
- this.source += ' ; __append("' + line + '")\n';
23457
- },
23458
- scanLine: function(line) {
23459
- var self = this;
23460
- var d = this.opts.delimiter;
23461
- var o = this.opts.openDelimiter;
23462
- var c = this.opts.closeDelimiter;
23463
- var newLineCount = 0;
23464
- newLineCount = line.split("\n").length - 1;
23465
- switch (line) {
23466
- case o + d:
23467
- case o + d + "_":
23468
- this.mode = Template.modes.EVAL;
23469
- break;
23470
- case o + d + "=":
23471
- this.mode = Template.modes.ESCAPED;
23472
- break;
23473
- case o + d + "-":
23474
- this.mode = Template.modes.RAW;
23475
- break;
23476
- case o + d + "#":
23477
- this.mode = Template.modes.COMMENT;
23478
- break;
23479
- case o + d + d:
23480
- this.mode = Template.modes.LITERAL;
23481
- this.source += ' ; __append("' + line.replace(o + d + d, o + d) + '")\n';
23482
- break;
23483
- case d + d + c:
23484
- this.mode = Template.modes.LITERAL;
23485
- this.source += ' ; __append("' + line.replace(d + d + c, d + c) + '")\n';
23486
- break;
23487
- case d + c:
23488
- case "-" + d + c:
23489
- case "_" + d + c:
23490
- if (this.mode == Template.modes.LITERAL) {
23491
- this._addOutput(line);
23492
- }
23493
- this.mode = null;
23494
- this.truncate = line.indexOf("-") === 0 || line.indexOf("_") === 0;
23495
- break;
23496
- default:
23497
- if (this.mode) {
23498
- switch (this.mode) {
23499
- case Template.modes.EVAL:
23500
- case Template.modes.ESCAPED:
23501
- case Template.modes.RAW:
23502
- if (line.lastIndexOf("//") > line.lastIndexOf("\n")) {
23503
- line += "\n";
23504
- }
23505
- }
23506
- switch (this.mode) {
23507
- // Just executing code
23508
- case Template.modes.EVAL:
23509
- this.source += " ; " + line + "\n";
23510
- break;
23511
- // Exec, esc, and output
23512
- case Template.modes.ESCAPED:
23513
- this.source += " ; __append(escapeFn(" + stripSemi(line) + "))\n";
23514
- break;
23515
- // Exec and output
23516
- case Template.modes.RAW:
23517
- this.source += " ; __append(" + stripSemi(line) + ")\n";
23518
- break;
23519
- case Template.modes.COMMENT:
23520
- break;
23521
- // Literal <%% mode, append as raw output
23522
- case Template.modes.LITERAL:
23523
- this._addOutput(line);
23524
- break;
23525
- }
23526
- } else {
23527
- this._addOutput(line);
23528
- }
23529
- }
23530
- if (self.opts.compileDebug && newLineCount) {
23531
- this.currentLine += newLineCount;
23532
- this.source += " ; __line = " + this.currentLine + "\n";
23533
- }
23534
- }
23535
- };
23536
- exports.escapeXML = utils.escapeXML;
23537
- exports.__express = exports.renderFile;
23538
- exports.VERSION = _VERSION_STRING;
23539
- exports.name = _NAME;
23540
- if (typeof window != "undefined") {
23541
- window.ejs = exports;
23542
- }
23543
- } (ejs$1));
23544
- return ejs$1;
22917
+ class InvalidPathParameterError extends Error {
22918
+ constructor(operationId, path, issue, file) {
22919
+ super(
22920
+ `Invalid path parameters in operation '${operationId}' at \`${file}\`
22921
+ Path: ${path}
22922
+ Issue: ${issue}`
22923
+ );
22924
+ this.operationId = operationId;
22925
+ this.path = path;
22926
+ this.issue = issue;
22927
+ this.file = file;
22928
+ }
23545
22929
  }
23546
22930
 
23547
- var ejsExports = requireEjs();
23548
- var ejs = /*@__PURE__*/getDefaultExportFromCjs(ejsExports);
22931
+ class InvalidSchemaError extends Error {
22932
+ constructor(schemaType, definitionName, context, file) {
22933
+ const schemaRequirement = schemaType === "body" ? "Must be a Zod schema" : "Must be a Zod object schema";
22934
+ super(
22935
+ `Invalid ${schemaType} schema in ${context} '${definitionName}' at \`${file}\`. ${schemaRequirement}.`
22936
+ );
22937
+ this.schemaType = schemaType;
22938
+ this.definitionName = definitionName;
22939
+ this.context = context;
22940
+ this.file = file;
22941
+ }
22942
+ }
23549
22943
 
23550
- class IndexFileGenerator {
23551
- constructor(templateDir) {
23552
- this.templateDir = templateDir;
22944
+ class InvalidSchemaShapeError extends Error {
22945
+ constructor(schemaType, definitionName, context, propertyName, invalidType, file) {
22946
+ const allowedTypes = schemaType === "param" ? "string-based types (ZodString, ZodLiteral<string>, ZodEnum) or ZodOptional of these types" : "string-based types (ZodString, ZodLiteral<string>, ZodEnum), ZodOptional, or ZodArray of these types";
22947
+ super(
22948
+ `Invalid ${schemaType} schema shape in ${context} '${definitionName}' at \`${file}\`
22949
+ Property '${propertyName}' has invalid type: ${invalidType}
22950
+ Allowed types: ${allowedTypes}`
22951
+ );
22952
+ this.schemaType = schemaType;
22953
+ this.definitionName = definitionName;
22954
+ this.context = context;
22955
+ this.propertyName = propertyName;
22956
+ this.invalidType = invalidType;
22957
+ this.file = file;
23553
22958
  }
23554
- generate(context) {
23555
- const templateFilePath = path__default.join(this.templateDir, "Index.ejs");
23556
- const template = fs.readFileSync(templateFilePath, "utf8");
23557
- const indexPaths = /* @__PURE__ */ new Set();
23558
- for (const generatedFile of context.getGeneratedFiles()) {
23559
- indexPaths.add(`./${generatedFile.replace(/\.ts$/, "")}`);
23560
- }
23561
- const content = ejs.render(template, {
23562
- indexPaths: Array.from(indexPaths).sort()
23563
- });
23564
- fs.writeFileSync(path__default.join(context.outputDir, "index.ts"), content);
22959
+ }
22960
+
22961
+ class InvalidStatusCodeError extends Error {
22962
+ constructor(statusCode, responseName, file) {
22963
+ const validStatusCodes = Object.values(HttpStatusCode).join(", ");
22964
+ super(
22965
+ `Invalid status code '${statusCode}' in response '${responseName}' at \`${file}\`
22966
+ Valid status codes: ${validStatusCodes}`
22967
+ );
22968
+ this.statusCode = statusCode;
22969
+ this.responseName = responseName;
22970
+ this.file = file;
23565
22971
  }
23566
22972
  }
23567
22973
 
23568
- class PluginLoadingFailure extends Error {
23569
- constructor(pluginName, attempts) {
23570
- super(`Failed to load plugin '${pluginName}'`);
23571
- this.pluginName = pluginName;
23572
- this.attempts = attempts;
23573
- Object.setPrototypeOf(this, PluginLoadingFailure.prototype);
22974
+ class MissingRequiredFieldError extends Error {
22975
+ constructor(entityType, entityName, missingField, file) {
22976
+ super(
22977
+ `Missing required field '${missingField}' in ${entityType} '${entityName}' at \`${file}\``
22978
+ );
22979
+ this.entityType = entityType;
22980
+ this.entityName = entityName;
22981
+ this.missingField = missingField;
22982
+ this.file = file;
23574
22983
  }
23575
22984
  }
23576
22985
 
23577
- class PluginLoader {
23578
- constructor(registry, requiredPlugins, strategies = ["npm", "local"]) {
23579
- this.registry = registry;
23580
- this.requiredPlugins = requiredPlugins;
23581
- this.strategies = strategies;
22986
+ class DefinitionValidator {
22987
+ registry;
22988
+ constructor(registry) {
22989
+ this.registry = registry ?? new DefinitionRegistry();
22990
+ }
22991
+ validateOperation(operation, sourceFile) {
22992
+ this.registry.registerOperation(operation, sourceFile);
22993
+ this.validateOperationRequiredFields(operation, sourceFile);
22994
+ this.validateHttpMethod(operation, sourceFile);
22995
+ if (operation.request) {
22996
+ this.validateRequestSchemas(operation, sourceFile);
22997
+ }
22998
+ this.validateOperationResponses(operation, sourceFile);
22999
+ this.validatePathParameters(operation, sourceFile);
23000
+ }
23001
+ validateResponse(response, sourceFile) {
23002
+ this.registry.registerResponse(response, sourceFile);
23003
+ this.validateResponseRequiredFields(response, sourceFile);
23004
+ this.validateStatusCode(response, sourceFile);
23005
+ this.validateResponseSchemas(response, sourceFile);
23006
+ }
23007
+ validateOperationRequiredFields(operation, sourceFile) {
23008
+ if (!operation.operationId) {
23009
+ throw new MissingRequiredFieldError(
23010
+ "operation",
23011
+ "unknown",
23012
+ "operationId",
23013
+ sourceFile
23014
+ );
23015
+ }
23016
+ if (!operation.path) {
23017
+ throw new MissingRequiredFieldError(
23018
+ "operation",
23019
+ operation.operationId,
23020
+ "path",
23021
+ sourceFile
23022
+ );
23023
+ }
23024
+ if (!operation.method) {
23025
+ throw new MissingRequiredFieldError(
23026
+ "operation",
23027
+ operation.operationId,
23028
+ "method",
23029
+ sourceFile
23030
+ );
23031
+ }
23032
+ if (!operation.summary) {
23033
+ throw new MissingRequiredFieldError(
23034
+ "operation",
23035
+ operation.operationId,
23036
+ "summary",
23037
+ sourceFile
23038
+ );
23039
+ }
23582
23040
  }
23583
- /**
23584
- * Load all plugins from configuration
23585
- */
23586
- async loadPlugins(config) {
23587
- for (const requiredPlugin of this.requiredPlugins) {
23588
- this.registry.register(requiredPlugin);
23041
+ validateHttpMethod(operation, sourceFile) {
23042
+ const validMethods = Object.values(HttpMethod);
23043
+ if (!validMethods.includes(operation.method)) {
23044
+ throw new InvalidHttpMethodError(
23045
+ operation.operationId,
23046
+ operation.method,
23047
+ sourceFile
23048
+ );
23589
23049
  }
23590
- if (!config?.plugins) {
23591
- return;
23050
+ }
23051
+ validateRequestSchemas(operation, sourceFile) {
23052
+ const request = operation.request;
23053
+ if (request.header) {
23054
+ this.validateSchema(
23055
+ request.header,
23056
+ "header",
23057
+ operation.operationId,
23058
+ "request",
23059
+ sourceFile
23060
+ );
23592
23061
  }
23593
- const successful = [];
23594
- for (const plugin of config.plugins) {
23595
- let result;
23596
- if (typeof plugin === "string") {
23597
- result = await this.loadPlugin(plugin);
23598
- } else {
23599
- result = await this.loadPlugin(plugin[0]);
23062
+ if (request.query) {
23063
+ this.validateSchema(
23064
+ request.query,
23065
+ "query",
23066
+ operation.operationId,
23067
+ "request",
23068
+ sourceFile
23069
+ );
23070
+ }
23071
+ if (request.body) {
23072
+ this.validateSchema(
23073
+ request.body,
23074
+ "body",
23075
+ operation.operationId,
23076
+ "request",
23077
+ sourceFile
23078
+ );
23079
+ }
23080
+ if (request.param) {
23081
+ this.validateSchema(
23082
+ request.param,
23083
+ "param",
23084
+ operation.operationId,
23085
+ "request",
23086
+ sourceFile
23087
+ );
23088
+ }
23089
+ }
23090
+ validateOperationResponses(operation, sourceFile) {
23091
+ if (!operation.responses || operation.responses.length === 0) {
23092
+ throw new EmptyResponseArrayError(operation.operationId, sourceFile);
23093
+ }
23094
+ for (const response of operation.responses) {
23095
+ if (response instanceof HttpResponseDefinition) {
23096
+ continue;
23600
23097
  }
23601
- if (result.success === false) {
23602
- throw new PluginLoadingFailure(
23603
- result.error.pluginName,
23604
- result.error.attempts
23098
+ this.validateResponse(response, sourceFile);
23099
+ }
23100
+ }
23101
+ validateResponseRequiredFields(response, sourceFile) {
23102
+ if (!response.name) {
23103
+ throw new MissingRequiredFieldError(
23104
+ "response",
23105
+ "unknown",
23106
+ "name",
23107
+ sourceFile
23108
+ );
23109
+ }
23110
+ if (response.statusCode === void 0 || response.statusCode === null) {
23111
+ throw new MissingRequiredFieldError(
23112
+ "response",
23113
+ response.name,
23114
+ "statusCode",
23115
+ sourceFile
23116
+ );
23117
+ }
23118
+ if (!response.description) {
23119
+ throw new MissingRequiredFieldError(
23120
+ "response",
23121
+ response.name,
23122
+ "description",
23123
+ sourceFile
23124
+ );
23125
+ }
23126
+ }
23127
+ validateStatusCode(response, sourceFile) {
23128
+ const validStatusCodes = Object.values(HttpStatusCode);
23129
+ if (!validStatusCodes.includes(response.statusCode)) {
23130
+ throw new InvalidStatusCodeError(
23131
+ response.statusCode,
23132
+ response.name,
23133
+ sourceFile
23134
+ );
23135
+ }
23136
+ }
23137
+ validateResponseSchemas(response, sourceFile) {
23138
+ if (response.header) {
23139
+ this.validateSchema(
23140
+ response.header,
23141
+ "header",
23142
+ response.name,
23143
+ "response",
23144
+ sourceFile
23145
+ );
23146
+ }
23147
+ if (response.body) {
23148
+ this.validateSchema(
23149
+ response.body,
23150
+ "body",
23151
+ response.name,
23152
+ "response",
23153
+ sourceFile
23154
+ );
23155
+ }
23156
+ }
23157
+ validateSchema(schema, schemaType, definitionName, context, sourceFile) {
23158
+ if (schemaType === "body") {
23159
+ if (!(schema instanceof z.ZodType)) {
23160
+ throw new InvalidSchemaError(
23161
+ schemaType,
23162
+ definitionName,
23163
+ context,
23164
+ sourceFile
23605
23165
  );
23606
23166
  }
23607
- successful.push(result.value);
23608
- this.registry.register(result.value.plugin);
23167
+ return;
23168
+ }
23169
+ if (!(schema instanceof z.ZodObject)) {
23170
+ throw new InvalidSchemaError(
23171
+ schemaType,
23172
+ definitionName,
23173
+ context,
23174
+ sourceFile
23175
+ );
23176
+ }
23177
+ if (schemaType === "param") {
23178
+ this.validateParamShape(schema, definitionName, sourceFile);
23179
+ } else {
23180
+ this.validateHeaderOrQueryShape(
23181
+ schema,
23182
+ schemaType,
23183
+ definitionName,
23184
+ context,
23185
+ sourceFile
23186
+ );
23609
23187
  }
23610
- this.reportSuccessfulLoads(successful);
23611
23188
  }
23612
- /**
23613
- * Load a plugin from a string identifier
23614
- */
23615
- async loadPlugin(pluginName) {
23616
- const possiblePaths = this.generatePluginPaths(pluginName);
23617
- const attempts = [];
23618
- for (const possiblePath of possiblePaths) {
23619
- try {
23620
- const pluginPackage = await import(possiblePath);
23621
- if (pluginPackage.default) {
23622
- return {
23623
- success: true,
23624
- value: {
23625
- plugin: new pluginPackage.default(),
23626
- source: possiblePath
23627
- }
23628
- };
23189
+ validatePathParameters(operation, sourceFile) {
23190
+ const pathParamMatches = operation.path.matchAll(/:([a-zA-Z0-9_]+)/g);
23191
+ const pathParamsSet = /* @__PURE__ */ new Set();
23192
+ for (const match of pathParamMatches) {
23193
+ const paramName = match[1];
23194
+ if (pathParamsSet.has(paramName)) {
23195
+ throw new InvalidPathParameterError(
23196
+ operation.operationId,
23197
+ operation.path,
23198
+ `Duplicate parameter name '${paramName}' in path`,
23199
+ sourceFile
23200
+ );
23201
+ }
23202
+ pathParamsSet.add(paramName);
23203
+ }
23204
+ const paramSchema = operation.request?.param;
23205
+ if (pathParamsSet.size > 0 && !paramSchema) {
23206
+ throw new InvalidPathParameterError(
23207
+ operation.operationId,
23208
+ operation.path,
23209
+ `Path contains parameters [${Array.from(pathParamsSet).join(", ")}] but request.param is not defined`,
23210
+ sourceFile
23211
+ );
23212
+ }
23213
+ if (paramSchema && paramSchema instanceof z.ZodObject) {
23214
+ const paramShape = paramSchema.shape;
23215
+ const paramKeys = new Set(Object.keys(paramShape));
23216
+ for (const pathParam of pathParamsSet) {
23217
+ if (!paramKeys.has(pathParam)) {
23218
+ throw new InvalidPathParameterError(
23219
+ operation.operationId,
23220
+ operation.path,
23221
+ `Path parameter ':${pathParam}' is not defined in request.param`,
23222
+ sourceFile
23223
+ );
23224
+ }
23225
+ }
23226
+ for (const paramKey of paramKeys) {
23227
+ if (!pathParamsSet.has(paramKey)) {
23228
+ throw new InvalidPathParameterError(
23229
+ operation.operationId,
23230
+ operation.path,
23231
+ `Parameter '${paramKey}' is defined in request.param but not used in the path`,
23232
+ sourceFile
23233
+ );
23629
23234
  }
23630
- attempts.push({
23631
- path: possiblePath,
23632
- error: "No default export found"
23633
- });
23634
- } catch (error) {
23635
- attempts.push({
23636
- path: possiblePath,
23637
- error: error instanceof Error ? error.message : String(error)
23638
- });
23639
23235
  }
23640
23236
  }
23641
- return {
23642
- success: false,
23643
- error: {
23644
- pluginName,
23645
- attempts
23237
+ }
23238
+ validateHeaderOrQueryShape(schema, schemaType, definitionName, context, sourceFile) {
23239
+ const shape = schema.shape;
23240
+ for (const [propName, propSchema] of Object.entries(shape)) {
23241
+ if (!this.isValidHeaderOrQueryValue(propSchema)) {
23242
+ const typeName = this.getZodTypeName(propSchema);
23243
+ throw new InvalidSchemaShapeError(
23244
+ schemaType,
23245
+ definitionName,
23246
+ context,
23247
+ propName,
23248
+ typeName,
23249
+ sourceFile
23250
+ );
23251
+ }
23252
+ }
23253
+ }
23254
+ validateParamShape(schema, operationId, sourceFile) {
23255
+ const shape = schema.shape;
23256
+ for (const [propName, propSchema] of Object.entries(shape)) {
23257
+ if (!this.isValidParamValue(propSchema)) {
23258
+ const typeName = this.getZodTypeName(propSchema);
23259
+ throw new InvalidSchemaShapeError(
23260
+ "param",
23261
+ operationId,
23262
+ "request",
23263
+ propName,
23264
+ typeName,
23265
+ sourceFile
23266
+ );
23646
23267
  }
23268
+ }
23269
+ }
23270
+ isValidHeaderOrQueryValue(schema) {
23271
+ if (this.isStringBasedType(schema)) {
23272
+ return true;
23273
+ }
23274
+ if (schema instanceof z.ZodOptional) {
23275
+ return this.isValidHeaderOrQueryValue(schema.unwrap());
23276
+ }
23277
+ if (schema instanceof z.ZodArray) {
23278
+ return this.isStringBasedType(schema.element);
23279
+ }
23280
+ return false;
23281
+ }
23282
+ isValidParamValue(schema) {
23283
+ if (this.isStringBasedType(schema)) {
23284
+ return true;
23285
+ }
23286
+ if (schema instanceof z.ZodOptional) {
23287
+ return this.isStringBasedType(schema.unwrap());
23288
+ }
23289
+ return false;
23290
+ }
23291
+ isStringBasedType(schema) {
23292
+ if (schema instanceof z.ZodString) {
23293
+ return true;
23294
+ }
23295
+ if (schema instanceof z.ZodLiteral && typeof schema.value === "string") {
23296
+ return true;
23297
+ }
23298
+ if (schema instanceof z.ZodEnum) {
23299
+ return true;
23300
+ }
23301
+ const typeName = schema.constructor.name;
23302
+ const stringFormatTypes = [
23303
+ "ZodULID",
23304
+ "ZodUUID",
23305
+ "ZodUUIDv4",
23306
+ "ZodUUIDv7",
23307
+ "ZodUUIDv8",
23308
+ "ZodEmail",
23309
+ "ZodURL",
23310
+ "ZodCUID",
23311
+ "ZodCUID2",
23312
+ "ZodNanoID",
23313
+ "ZodBase64",
23314
+ "ZodBase64URL",
23315
+ "ZodEmoji",
23316
+ "ZodIPv4",
23317
+ "ZodIPv6",
23318
+ "ZodCIDRv4",
23319
+ "ZodCIDRv6",
23320
+ "ZodE164",
23321
+ "ZodJWT",
23322
+ "ZodASCII",
23323
+ "ZodUTF8",
23324
+ "ZodLowercase",
23325
+ "ZodGUID",
23326
+ "ZodISODate",
23327
+ "ZodISOTime",
23328
+ "ZodISODateTime",
23329
+ "ZodISODuration"
23330
+ ];
23331
+ if (stringFormatTypes.includes(typeName)) {
23332
+ return true;
23333
+ }
23334
+ if (schema._def && schema._def.typeName && schema._def.typeName.includes("String")) {
23335
+ return true;
23336
+ }
23337
+ return false;
23338
+ }
23339
+ getZodTypeName(schema) {
23340
+ const typeName = schema.constructor.name;
23341
+ if (schema instanceof z.ZodOptional) {
23342
+ return `ZodOptional<${this.getZodTypeName(schema.unwrap())}>`;
23343
+ }
23344
+ if (schema instanceof z.ZodArray) {
23345
+ return `ZodArray<${this.getZodTypeName(schema.element)}>`;
23346
+ }
23347
+ if (schema instanceof z.ZodLiteral) {
23348
+ return `ZodLiteral<${typeof schema.value}>`;
23349
+ }
23350
+ return typeName;
23351
+ }
23352
+ getRegistry() {
23353
+ return this.registry;
23354
+ }
23355
+ }
23356
+
23357
+ class InvalidSharedDirError extends Error {
23358
+ constructor(explanation) {
23359
+ super("Invalid shared dir");
23360
+ this.explanation = explanation;
23361
+ }
23362
+ }
23363
+
23364
+ class ResourceReader {
23365
+ constructor(config) {
23366
+ this.config = config;
23367
+ }
23368
+ async getResources() {
23369
+ const contents = fs.readdirSync(this.config.sourceDir, {
23370
+ withFileTypes: true
23371
+ });
23372
+ const result = {
23373
+ entityResources: {},
23374
+ sharedResponseResources: []
23647
23375
  };
23376
+ const validator = new DefinitionValidator();
23377
+ if (fs.existsSync(this.config.sharedSourceDir)) {
23378
+ const sharedStats = fs.statSync(this.config.sharedSourceDir);
23379
+ if (!sharedStats.isDirectory()) {
23380
+ throw new InvalidSharedDirError(
23381
+ `'${this.config.sharedSourceDir}' is a file, not a directory`
23382
+ );
23383
+ }
23384
+ result.sharedResponseResources = await this.getSharedResponseResources(validator);
23385
+ console.info(
23386
+ `Found '${result.sharedResponseResources.length}' shared responses in '${this.config.sharedSourceDir}'`
23387
+ );
23388
+ } else {
23389
+ console.info(
23390
+ `No shared directory found at '${this.config.sharedSourceDir}'`
23391
+ );
23392
+ }
23393
+ const normalizedSharedPath = path__default.resolve(this.config.sharedSourceDir);
23394
+ for (const content of contents) {
23395
+ if (!content.isDirectory()) {
23396
+ console.info(`Skipping '${content.name}' as it is not a directory`);
23397
+ continue;
23398
+ }
23399
+ const entityName = content.name;
23400
+ const entitySourceDir = path__default.resolve(this.config.sourceDir, entityName);
23401
+ if (entitySourceDir === normalizedSharedPath || normalizedSharedPath.startsWith(entitySourceDir + path__default.sep)) {
23402
+ console.info(
23403
+ `Skipping '${content.name}' as it is or contains the shared directory`
23404
+ );
23405
+ continue;
23406
+ }
23407
+ const responseResources = await this.getEntityResponseResources(
23408
+ entitySourceDir,
23409
+ entityName,
23410
+ validator
23411
+ );
23412
+ const operationResources = await this.getEntityOperationResources(
23413
+ entitySourceDir,
23414
+ entityName,
23415
+ validator,
23416
+ [...result.sharedResponseResources, ...responseResources]
23417
+ );
23418
+ result.entityResources[entityName] = {
23419
+ operations: operationResources,
23420
+ responses: responseResources
23421
+ };
23422
+ console.info(
23423
+ `Found '${operationResources.length}' operation definitions for entity '${entityName}'`
23424
+ );
23425
+ if (responseResources.length > 0) {
23426
+ console.info(
23427
+ `Found '${responseResources.length}' response definitions for entity '${entityName}'`
23428
+ );
23429
+ }
23430
+ }
23431
+ return result;
23648
23432
  }
23649
- /**
23650
- * Generate possible plugin paths based on configured strategies
23651
- */
23652
- generatePluginPaths(pluginName) {
23653
- const paths = [];
23654
- for (const strategy of this.strategies) {
23655
- switch (strategy) {
23656
- case "npm":
23657
- paths.push(`@rexeus/typeweaver-${pluginName}`);
23658
- paths.push(`@rexeus/${pluginName}`);
23659
- break;
23660
- case "local":
23661
- paths.push(pluginName);
23662
- break;
23433
+ scanDirectoryRecursively(dir) {
23434
+ const files = [];
23435
+ const contents = fs.readdirSync(dir, { withFileTypes: true });
23436
+ for (const content of contents) {
23437
+ const fullPath = path__default.join(dir, content.name);
23438
+ if (content.isDirectory()) {
23439
+ files.push(...this.scanDirectoryRecursively(fullPath));
23440
+ } else if (content.isFile() && content.name.endsWith(".ts")) {
23441
+ files.push(fullPath);
23663
23442
  }
23664
23443
  }
23665
- return paths;
23444
+ return files;
23666
23445
  }
23667
- /**
23668
- * Report successful plugin loads
23669
- */
23670
- reportSuccessfulLoads(successful) {
23671
- if (successful.length > 0) {
23672
- console.info(`Successfully loaded ${successful.length} plugin(s):`);
23673
- for (const result of successful) {
23674
- console.info(` - ${result.plugin.name} (from ${result.source})`);
23446
+ async getSharedResponseResources(validator) {
23447
+ const files = this.scanDirectoryRecursively(this.config.sharedSourceDir);
23448
+ const sharedResponseResources = [];
23449
+ for (const sourceFile of files) {
23450
+ const sourceFileName = path__default.basename(sourceFile);
23451
+ const definition = await import(sourceFile);
23452
+ if (!definition.default) {
23453
+ console.info(
23454
+ `Skipping '${sourceFile}' as it does not have a default export`
23455
+ );
23456
+ continue;
23457
+ }
23458
+ if (!(definition.default instanceof HttpResponseDefinition)) {
23459
+ console.info(
23460
+ `Skipping '${sourceFile}' as it is not an instance of HttpResponseDefinition`
23461
+ );
23462
+ continue;
23463
+ }
23464
+ validator.validateResponse(definition.default, sourceFile);
23465
+ const outputDir = this.config.sharedOutputDir;
23466
+ const outputFileName = `${definition.default.name}Response.ts`;
23467
+ const outputFile = path__default.join(outputDir, outputFileName);
23468
+ sharedResponseResources.push({
23469
+ ...definition.default,
23470
+ sourceDir: this.config.sharedSourceDir,
23471
+ sourceFile,
23472
+ sourceFileName,
23473
+ outputFile,
23474
+ outputFileName,
23475
+ outputDir
23476
+ });
23477
+ }
23478
+ return sharedResponseResources;
23479
+ }
23480
+ async getEntityOperationResources(sourceDir, entityName, validator, referencedResponses) {
23481
+ const files = this.scanDirectoryRecursively(sourceDir);
23482
+ const definitions = [];
23483
+ for (const sourceFile of files) {
23484
+ const sourceFileName = path__default.basename(sourceFile);
23485
+ const definition = await import(sourceFile);
23486
+ if (!definition.default) {
23487
+ console.info(
23488
+ `Skipping '${sourceFile}' as it does not have a default export`
23489
+ );
23490
+ continue;
23675
23491
  }
23492
+ if (!(definition.default instanceof HttpOperationDefinition)) {
23493
+ console.info(
23494
+ `Skipping '${sourceFile}' as it is not an instance of HttpOperationDefinition`
23495
+ );
23496
+ continue;
23497
+ }
23498
+ validator.validateOperation(definition.default, sourceFile);
23499
+ const { operationId } = definition.default;
23500
+ const outputDir = path__default.join(this.config.outputDir, entityName);
23501
+ const outputRequestFileName = `${operationId}Request.ts`;
23502
+ const outputRequestFile = path__default.join(outputDir, outputRequestFileName);
23503
+ const outputResponseFileName = `${operationId}Response.ts`;
23504
+ const outputResponseFile = path__default.join(outputDir, outputResponseFileName);
23505
+ const outputRequestValidationFileName = `${operationId}RequestValidator.ts`;
23506
+ const outputRequestValidationFile = path__default.join(
23507
+ outputDir,
23508
+ outputRequestValidationFileName
23509
+ );
23510
+ const outputResponseValidationFileName = `${operationId}ResponseValidator.ts`;
23511
+ const outputResponseValidationFile = path__default.join(
23512
+ outputDir,
23513
+ outputResponseValidationFileName
23514
+ );
23515
+ const outputClientFileName = `${operationId}Client.ts`;
23516
+ const outputClientFile = path__default.join(outputDir, outputClientFileName);
23517
+ const operationResource = {
23518
+ sourceDir,
23519
+ sourceFile,
23520
+ sourceFileName,
23521
+ definition: {
23522
+ ...definition.default,
23523
+ responses: []
23524
+ },
23525
+ outputDir,
23526
+ entityName,
23527
+ outputRequestFile,
23528
+ outputResponseFile,
23529
+ outputResponseValidationFile,
23530
+ outputRequestValidationFile,
23531
+ outputRequestFileName,
23532
+ outputRequestValidationFileName,
23533
+ outputResponseFileName,
23534
+ outputResponseValidationFileName,
23535
+ outputClientFile,
23536
+ outputClientFileName
23537
+ };
23538
+ if (!definition.default.responses) {
23539
+ throw new Error(
23540
+ `Operation '${operationId}' does not have any responses`
23541
+ );
23542
+ }
23543
+ for (const response of definition.default.responses) {
23544
+ if (referencedResponses.some((ref) => ref.name === response.name)) {
23545
+ const referencedResponse = {
23546
+ ...response,
23547
+ statusCodeName: HttpStatusCodeNameMap[response.statusCode],
23548
+ isReference: true
23549
+ };
23550
+ operationResource.definition.responses.push(referencedResponse);
23551
+ } else {
23552
+ const extendedResponse = {
23553
+ ...response,
23554
+ statusCodeName: HttpStatusCodeNameMap[response.statusCode],
23555
+ isReference: false
23556
+ };
23557
+ operationResource.definition.responses.push(extendedResponse);
23558
+ }
23559
+ }
23560
+ definitions.push(operationResource);
23676
23561
  }
23562
+ return definitions;
23563
+ }
23564
+ async getEntityResponseResources(sourceDir, entityName, validator) {
23565
+ const files = this.scanDirectoryRecursively(sourceDir);
23566
+ const responseResources = [];
23567
+ for (const sourceFile of files) {
23568
+ const sourceFileName = path__default.basename(sourceFile);
23569
+ const definition = await import(sourceFile);
23570
+ if (!definition.default) {
23571
+ continue;
23572
+ }
23573
+ if (!(definition.default instanceof HttpResponseDefinition)) {
23574
+ continue;
23575
+ }
23576
+ validator.validateResponse(definition.default, sourceFile);
23577
+ const outputFileName = `${definition.default.name}Response.ts`;
23578
+ const outputFile = path__default.join(
23579
+ this.config.outputDir,
23580
+ entityName,
23581
+ outputFileName
23582
+ );
23583
+ responseResources.push({
23584
+ ...definition.default,
23585
+ sourceDir,
23586
+ sourceFile,
23587
+ sourceFileName,
23588
+ outputFile,
23589
+ outputFileName,
23590
+ outputDir: path__default.join(this.config.outputDir, entityName),
23591
+ entityName
23592
+ });
23593
+ }
23594
+ return responseResources;
23677
23595
  }
23678
23596
  }
23679
23597
 
@@ -23687,6 +23605,8 @@ class Generator {
23687
23605
  indexFileGenerator;
23688
23606
  resourceReader = null;
23689
23607
  prettier = null;
23608
+ inputDir = "";
23609
+ sharedInputDir = "";
23690
23610
  outputDir = "";
23691
23611
  sourceDir = "";
23692
23612
  sharedSourceDir = "";
@@ -23702,13 +23622,22 @@ class Generator {
23702
23622
  */
23703
23623
  async generate(definitionDir, outputDir, config) {
23704
23624
  console.info("Starting generation...");
23705
- this.initializeDirectories(definitionDir, outputDir);
23625
+ this.initializeDirectories(definitionDir, outputDir, config?.shared);
23706
23626
  if (config?.clean ?? true) {
23707
23627
  console.info("Cleaning output directory...");
23708
23628
  fs.rmSync(this.outputDir, { recursive: true, force: true });
23709
23629
  }
23710
23630
  fs.mkdirSync(this.outputDir, { recursive: true });
23711
23631
  fs.mkdirSync(this.sharedOutputDir, { recursive: true });
23632
+ console.info(
23633
+ `Copying definitions from '${this.inputDir}' to '${this.sourceDir}'...`
23634
+ );
23635
+ fs.cpSync(this.inputDir, this.sourceDir, {
23636
+ recursive: true,
23637
+ filter: (src) => {
23638
+ return src.endsWith(".ts") || src.endsWith(".js") || src.endsWith(".json") || src.endsWith(".mjs") || src.endsWith(".cjs") || fs.statSync(src).isDirectory();
23639
+ }
23640
+ });
23712
23641
  await this.pluginLoader.loadPlugins(config);
23713
23642
  this.resourceReader = new ResourceReader({
23714
23643
  sourceDir: this.sourceDir,
@@ -23766,22 +23695,24 @@ class Generator {
23766
23695
  `Generated files: ${this.contextBuilder.getGeneratedFiles().length}`
23767
23696
  );
23768
23697
  }
23769
- initializeDirectories(definitionDir, outputDir) {
23770
- this.sourceDir = definitionDir;
23698
+ initializeDirectories(definitionDir, outputDir, sharedDir) {
23699
+ this.inputDir = definitionDir;
23700
+ this.sharedInputDir = sharedDir ?? path__default.join(definitionDir, "shared");
23771
23701
  this.outputDir = outputDir;
23772
- this.sharedSourceDir = path__default.join(definitionDir, "shared");
23773
23702
  this.sharedOutputDir = path__default.join(outputDir, "shared");
23703
+ this.sourceDir = path__default.join(this.outputDir, "definition");
23704
+ const inputToSharedDirRelative = path__default.relative(
23705
+ this.inputDir,
23706
+ this.sharedInputDir
23707
+ );
23708
+ this.sharedSourceDir = path__default.join(this.sourceDir, inputToSharedDirRelative);
23774
23709
  }
23775
23710
  }
23776
23711
 
23777
- var version = "0.0.2";
23778
- var packageJson = {
23779
- version: version};
23780
-
23781
23712
  const program = new Command();
23782
23713
  const execDir = process.cwd();
23783
23714
  program.name("@rexeus/typeweaver").description("Type-safe API framework with code generation for TypeScript").version(packageJson.version);
23784
- program.command("generate").description("Generate types, validators, and clients from API definitions").option("-i, --input <inputDir>", "path to definition directory").option("-o, --output <outputDir>", "output directory for generated files").option("-c, --config <configFile>", "path to configuration file").option("-p, --plugins <plugins>", "comma-separated list of plugins to use").option("--prettier", "format generated code with Prettier (default: true)").option("--no-prettier", "disable Prettier formatting").option("--clean", "clean output directory before generation (default: true)").option("--no-clean", "disable cleaning output directory").action(async (options) => {
23715
+ program.command("generate").description("Generate types, validators, and clients from API definitions").option("-i, --input <inputDir>", "path to definition directory").option("-o, --output <outputDir>", "output directory for generated files").option("-s, --shared <path>", "path to shared definitions directory").option("-c, --config <configFile>", "path to configuration file").option("-p, --plugins <plugins>", "comma-separated list of plugins to use").option("--prettier", "format generated code with Prettier (default: true)").option("--no-prettier", "disable Prettier formatting").option("--clean", "clean output directory before generation (default: true)").option("--no-clean", "disable cleaning output directory").action(async (options) => {
23785
23716
  let config = {};
23786
23717
  if (options.config) {
23787
23718
  const configPath = path__default.isAbsolute(options.config) ? options.config : path__default.join(execDir, options.config);
@@ -23798,6 +23729,7 @@ program.command("generate").description("Generate types, validators, and clients
23798
23729
  }
23799
23730
  const inputDir = options.input ?? config.input;
23800
23731
  const outputDir = options.output ?? config.output;
23732
+ const sharedDir = options.shared ?? config.shared;
23801
23733
  if (!inputDir) {
23802
23734
  throw new Error(
23803
23735
  "No input directory provided. Use --input or specify in config file."
@@ -23810,9 +23742,11 @@ program.command("generate").description("Generate types, validators, and clients
23810
23742
  }
23811
23743
  const resolvedInputDir = path__default.isAbsolute(inputDir) ? inputDir : path__default.join(execDir, inputDir);
23812
23744
  const resolvedOutputDir = path__default.isAbsolute(outputDir) ? outputDir : path__default.join(execDir, outputDir);
23745
+ const resolvedSharedDir = sharedDir ? path__default.isAbsolute(sharedDir) ? sharedDir : path__default.join(execDir, sharedDir) : void 0;
23813
23746
  const finalConfig = {
23814
23747
  input: resolvedInputDir,
23815
23748
  output: resolvedOutputDir,
23749
+ shared: resolvedSharedDir,
23816
23750
  prettier: options.prettier ?? config.prettier ?? true,
23817
23751
  clean: options.clean ?? config.clean ?? true
23818
23752
  };
@@ -23824,7 +23758,7 @@ program.command("generate").description("Generate types, validators, and clients
23824
23758
  const generator = new Generator();
23825
23759
  return generator.generate(resolvedInputDir, resolvedOutputDir, finalConfig);
23826
23760
  });
23827
- program.command("init").description("Initialize a new TypeWeaver project (coming soon)").action(() => {
23761
+ program.command("init").description("Initialize a new typeweaver project (coming soon)").action(() => {
23828
23762
  console.log("The init command is coming soon!");
23829
23763
  });
23830
23764
  program.parse(process.argv);