@contractspec/lib.contracts-transformers 3.7.6 → 3.7.7
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 +41 -73
- package/dist/browser/index.js +900 -896
- package/dist/browser/openapi/index.js +965 -961
- package/dist/index.d.ts +1 -1
- package/dist/index.js +900 -896
- package/dist/node/index.js +900 -896
- package/dist/node/openapi/index.js +965 -961
- package/dist/openapi/differ.d.ts +1 -1
- package/dist/openapi/exporter/index.d.ts +7 -7
- package/dist/openapi/exporter/presentations.d.ts +1 -1
- package/dist/openapi/exporter.d.ts +2 -2
- package/dist/openapi/importer/events.d.ts +1 -1
- package/dist/openapi/importer/generator.d.ts +2 -2
- package/dist/openapi/importer/index.d.ts +5 -5
- package/dist/openapi/importer/models.d.ts +1 -1
- package/dist/openapi/index.d.ts +5 -5
- package/dist/openapi/index.js +965 -961
- package/dist/openapi/parser/index.d.ts +4 -4
- package/dist/openapi/parser/operation.d.ts +1 -1
- package/dist/openapi/parser/resolvers.d.ts +1 -1
- package/dist/openapi/parser/utils.d.ts +1 -1
- package/dist/openapi/schema-converter.d.ts +1 -1
- package/package.json +4 -4
package/dist/browser/index.js
CHANGED
|
@@ -71,306 +71,395 @@ function normalizePath(path) {
|
|
|
71
71
|
normalized = normalized.replace(/\/+/g, "/");
|
|
72
72
|
return "/" + normalized;
|
|
73
73
|
}
|
|
74
|
-
// src/openapi/
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
"post",
|
|
79
|
-
"put",
|
|
80
|
-
"delete",
|
|
81
|
-
"patch",
|
|
82
|
-
"head",
|
|
83
|
-
"options",
|
|
84
|
-
"trace"
|
|
85
|
-
];
|
|
86
|
-
function parseOpenApiString(content, format = "json") {
|
|
87
|
-
if (format === "yaml") {
|
|
88
|
-
return parseYaml(content);
|
|
89
|
-
}
|
|
90
|
-
return JSON.parse(content);
|
|
91
|
-
}
|
|
92
|
-
function detectFormat(content) {
|
|
93
|
-
const trimmed = content.trim();
|
|
94
|
-
if (trimmed.startsWith("{") || trimmed.startsWith("[")) {
|
|
95
|
-
return "json";
|
|
74
|
+
// src/openapi/differ.ts
|
|
75
|
+
function compareValues(path, oldValue, newValue, description) {
|
|
76
|
+
if (deepEqual(oldValue, newValue)) {
|
|
77
|
+
return null;
|
|
96
78
|
}
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
79
|
+
let changeType = "modified";
|
|
80
|
+
if (oldValue === undefined || oldValue === null) {
|
|
81
|
+
changeType = "added";
|
|
82
|
+
} else if (newValue === undefined || newValue === null) {
|
|
83
|
+
changeType = "removed";
|
|
84
|
+
} else if (typeof oldValue !== typeof newValue) {
|
|
85
|
+
changeType = "type_changed";
|
|
103
86
|
}
|
|
104
|
-
return
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
return part.charAt(0).toUpperCase() + part.slice(1);
|
|
112
|
-
});
|
|
113
|
-
return method + pathParts.join("");
|
|
114
|
-
}
|
|
115
|
-
// src/openapi/parser/resolvers.ts
|
|
116
|
-
function isReference(obj) {
|
|
117
|
-
return typeof obj === "object" && obj !== null && "$ref" in obj;
|
|
87
|
+
return {
|
|
88
|
+
path,
|
|
89
|
+
type: changeType,
|
|
90
|
+
oldValue,
|
|
91
|
+
newValue,
|
|
92
|
+
description
|
|
93
|
+
};
|
|
118
94
|
}
|
|
119
|
-
function
|
|
120
|
-
|
|
121
|
-
|
|
95
|
+
function diffObjects(path, oldObj, newObj, options) {
|
|
96
|
+
const changes = [];
|
|
97
|
+
if (!oldObj && !newObj)
|
|
98
|
+
return changes;
|
|
99
|
+
if (!oldObj) {
|
|
100
|
+
changes.push({
|
|
101
|
+
path,
|
|
102
|
+
type: "added",
|
|
103
|
+
newValue: newObj,
|
|
104
|
+
description: `Added ${path}`
|
|
105
|
+
});
|
|
106
|
+
return changes;
|
|
122
107
|
}
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
108
|
+
if (!newObj) {
|
|
109
|
+
changes.push({
|
|
110
|
+
path,
|
|
111
|
+
type: "removed",
|
|
112
|
+
oldValue: oldObj,
|
|
113
|
+
description: `Removed ${path}`
|
|
114
|
+
});
|
|
115
|
+
return changes;
|
|
131
116
|
}
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
if (isReference(schema)) {
|
|
138
|
-
if (seen.has(schema.$ref)) {
|
|
139
|
-
return schema;
|
|
117
|
+
const allKeys = new Set([...Object.keys(oldObj), ...Object.keys(newObj)]);
|
|
118
|
+
for (const key of allKeys) {
|
|
119
|
+
const keyPath = path ? `${path}.${key}` : key;
|
|
120
|
+
if (options.ignorePaths?.some((p) => keyPath.startsWith(p))) {
|
|
121
|
+
continue;
|
|
140
122
|
}
|
|
141
|
-
const
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
const typeName = refParts[refParts.length - 1];
|
|
151
|
-
return {
|
|
152
|
-
...dereferenced,
|
|
153
|
-
_originalRef: schema.$ref,
|
|
154
|
-
_originalTypeName: typeName
|
|
155
|
-
};
|
|
156
|
-
}
|
|
157
|
-
const schemaObj = { ...schema };
|
|
158
|
-
if (schemaObj.properties) {
|
|
159
|
-
const props = schemaObj.properties;
|
|
160
|
-
const newProps = {};
|
|
161
|
-
for (const [key, prop] of Object.entries(props)) {
|
|
162
|
-
newProps[key] = dereferenceSchema(doc, prop, seen) ?? prop;
|
|
123
|
+
const oldVal = oldObj[key];
|
|
124
|
+
const newVal = newObj[key];
|
|
125
|
+
if (typeof oldVal === "object" && typeof newVal === "object") {
|
|
126
|
+
changes.push(...diffObjects(keyPath, oldVal, newVal, options));
|
|
127
|
+
} else {
|
|
128
|
+
const change = compareValues(keyPath, oldVal, newVal, `Changed ${keyPath}`);
|
|
129
|
+
if (change) {
|
|
130
|
+
changes.push(change);
|
|
131
|
+
}
|
|
163
132
|
}
|
|
164
|
-
schemaObj.properties = newProps;
|
|
165
133
|
}
|
|
166
|
-
|
|
167
|
-
|
|
134
|
+
return changes;
|
|
135
|
+
}
|
|
136
|
+
function diffSpecVsOperation(spec, operation, options = {}) {
|
|
137
|
+
const changes = [];
|
|
138
|
+
if (!options.ignoreDescriptions) {
|
|
139
|
+
const descChange = compareValues("meta.description", spec.meta.description, operation.summary ?? operation.description, "Description changed");
|
|
140
|
+
if (descChange)
|
|
141
|
+
changes.push(descChange);
|
|
168
142
|
}
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
143
|
+
if (!options.ignoreTags) {
|
|
144
|
+
const oldTags = [...spec.meta.tags ?? []].sort();
|
|
145
|
+
const newTags = [...operation.tags].sort();
|
|
146
|
+
if (!deepEqual(oldTags, newTags)) {
|
|
147
|
+
changes.push({
|
|
148
|
+
path: "meta.tags",
|
|
149
|
+
type: "modified",
|
|
150
|
+
oldValue: oldTags,
|
|
151
|
+
newValue: newTags,
|
|
152
|
+
description: "Tags changed"
|
|
153
|
+
});
|
|
173
154
|
}
|
|
174
155
|
}
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
return result;
|
|
187
|
-
for (const param of params) {
|
|
188
|
-
let resolved;
|
|
189
|
-
if (isReference(param)) {
|
|
190
|
-
const ref = resolveRef(doc, param.$ref);
|
|
191
|
-
if (!ref)
|
|
192
|
-
continue;
|
|
193
|
-
resolved = ref;
|
|
194
|
-
} else {
|
|
195
|
-
resolved = param;
|
|
156
|
+
if (!options.ignoreTransport) {
|
|
157
|
+
const specMethod = spec.transport?.rest?.method ?? (spec.meta.kind === "query" ? "GET" : "POST");
|
|
158
|
+
const opMethod = operation.method.toUpperCase();
|
|
159
|
+
if (specMethod !== opMethod) {
|
|
160
|
+
changes.push({
|
|
161
|
+
path: "transport.rest.method",
|
|
162
|
+
type: "modified",
|
|
163
|
+
oldValue: specMethod,
|
|
164
|
+
newValue: opMethod,
|
|
165
|
+
description: "HTTP method changed"
|
|
166
|
+
});
|
|
196
167
|
}
|
|
197
|
-
const
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
}
|
|
207
|
-
return result;
|
|
208
|
-
}
|
|
209
|
-
// src/openapi/parser/operation.ts
|
|
210
|
-
function parseOperation(doc, method, path, operation, pathParams) {
|
|
211
|
-
const allParams = [...pathParams ?? [], ...operation.parameters ?? []];
|
|
212
|
-
const params = parseParameters(doc, allParams);
|
|
213
|
-
let requestBody;
|
|
214
|
-
if (operation.requestBody) {
|
|
215
|
-
const body = isReference(operation.requestBody) ? resolveRef(doc, operation.requestBody.$ref) : operation.requestBody;
|
|
216
|
-
if (body) {
|
|
217
|
-
const contentType = Object.keys(body.content ?? {})[0] ?? "application/json";
|
|
218
|
-
const content = body.content?.[contentType];
|
|
219
|
-
if (content?.schema) {
|
|
220
|
-
requestBody = {
|
|
221
|
-
required: body.required ?? false,
|
|
222
|
-
schema: dereferenceSchema(doc, content.schema) ?? {},
|
|
223
|
-
contentType
|
|
224
|
-
};
|
|
225
|
-
}
|
|
168
|
+
const specPath = spec.transport?.rest?.path;
|
|
169
|
+
if (specPath && specPath !== operation.path) {
|
|
170
|
+
changes.push({
|
|
171
|
+
path: "transport.rest.path",
|
|
172
|
+
type: "modified",
|
|
173
|
+
oldValue: specPath,
|
|
174
|
+
newValue: operation.path,
|
|
175
|
+
description: "Path changed"
|
|
176
|
+
});
|
|
226
177
|
}
|
|
227
178
|
}
|
|
228
|
-
const
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
contentType
|
|
238
|
-
};
|
|
239
|
-
}
|
|
179
|
+
const specDeprecated = spec.meta.stability === "deprecated";
|
|
180
|
+
if (specDeprecated !== operation.deprecated) {
|
|
181
|
+
changes.push({
|
|
182
|
+
path: "meta.stability",
|
|
183
|
+
type: "modified",
|
|
184
|
+
oldValue: spec.meta.stability,
|
|
185
|
+
newValue: operation.deprecated ? "deprecated" : "stable",
|
|
186
|
+
description: "Deprecation status changed"
|
|
187
|
+
});
|
|
240
188
|
}
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
189
|
+
return changes;
|
|
190
|
+
}
|
|
191
|
+
function diffSpecs(oldSpec, newSpec, options = {}) {
|
|
192
|
+
const changes = [];
|
|
193
|
+
const metaChanges = diffObjects("meta", oldSpec.meta, newSpec.meta, {
|
|
194
|
+
...options,
|
|
195
|
+
ignorePaths: [
|
|
196
|
+
...options.ignorePaths ?? [],
|
|
197
|
+
...options.ignoreDescriptions ? ["meta.description", "meta.goal", "meta.context"] : [],
|
|
198
|
+
...options.ignoreTags ? ["meta.tags"] : []
|
|
199
|
+
]
|
|
200
|
+
});
|
|
201
|
+
changes.push(...metaChanges);
|
|
202
|
+
if (!options.ignoreTransport) {
|
|
203
|
+
const transportChanges = diffObjects("transport", oldSpec.transport, newSpec.transport, options);
|
|
204
|
+
changes.push(...transportChanges);
|
|
205
|
+
}
|
|
206
|
+
const policyChanges = diffObjects("policy", oldSpec.policy, newSpec.policy, options);
|
|
207
|
+
changes.push(...policyChanges);
|
|
208
|
+
return changes;
|
|
209
|
+
}
|
|
210
|
+
function createSpecDiff(operationId, existing, incoming, options = {}) {
|
|
211
|
+
let changes = [];
|
|
212
|
+
let isEquivalent = false;
|
|
213
|
+
if (existing && incoming.operationSpec) {
|
|
214
|
+
changes = diffSpecs(existing, incoming.operationSpec, options);
|
|
215
|
+
isEquivalent = changes.length === 0;
|
|
216
|
+
} else if (existing && !incoming.operationSpec) {
|
|
217
|
+
changes = [
|
|
218
|
+
{
|
|
219
|
+
path: "",
|
|
220
|
+
type: "modified",
|
|
221
|
+
oldValue: existing,
|
|
222
|
+
newValue: incoming.code,
|
|
223
|
+
description: "Spec code imported from OpenAPI (runtime comparison not available)"
|
|
224
|
+
}
|
|
225
|
+
];
|
|
226
|
+
} else {
|
|
227
|
+
changes = [
|
|
228
|
+
{
|
|
229
|
+
path: "",
|
|
230
|
+
type: "added",
|
|
231
|
+
newValue: incoming.operationSpec ?? incoming.code,
|
|
232
|
+
description: "New spec imported from OpenAPI"
|
|
233
|
+
}
|
|
234
|
+
];
|
|
235
|
+
}
|
|
236
|
+
return {
|
|
237
|
+
operationId,
|
|
238
|
+
existing,
|
|
239
|
+
incoming,
|
|
240
|
+
changes,
|
|
241
|
+
isEquivalent
|
|
258
242
|
};
|
|
259
243
|
}
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
const
|
|
263
|
-
const
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
try {
|
|
273
|
-
operations.push(parseOperation(doc, method, path, operation, pathParams));
|
|
274
|
-
} catch (error) {
|
|
275
|
-
warnings.push(`Failed to parse ${method.toUpperCase()} ${path}: ${error}`);
|
|
276
|
-
}
|
|
244
|
+
function diffAll(existingSpecs, importedSpecs, options = {}) {
|
|
245
|
+
const diffs = [];
|
|
246
|
+
const matchedExisting = new Set;
|
|
247
|
+
for (const imported of importedSpecs) {
|
|
248
|
+
const operationId = imported.source.sourceId;
|
|
249
|
+
let existing;
|
|
250
|
+
for (const [key, spec] of existingSpecs) {
|
|
251
|
+
const specName = spec.meta.key;
|
|
252
|
+
if (key === operationId || specName.includes(operationId)) {
|
|
253
|
+
existing = spec;
|
|
254
|
+
matchedExisting.add(key);
|
|
255
|
+
break;
|
|
277
256
|
}
|
|
278
257
|
}
|
|
258
|
+
diffs.push(createSpecDiff(operationId, existing, imported, options));
|
|
279
259
|
}
|
|
280
|
-
const
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
260
|
+
for (const [key, spec] of existingSpecs) {
|
|
261
|
+
if (!matchedExisting.has(key)) {
|
|
262
|
+
diffs.push({
|
|
263
|
+
operationId: key,
|
|
264
|
+
existing: spec,
|
|
265
|
+
incoming: undefined,
|
|
266
|
+
changes: [
|
|
267
|
+
{
|
|
268
|
+
path: "",
|
|
269
|
+
type: "removed",
|
|
270
|
+
oldValue: spec,
|
|
271
|
+
description: "Spec no longer exists in OpenAPI source"
|
|
272
|
+
}
|
|
273
|
+
],
|
|
274
|
+
isEquivalent: false
|
|
275
|
+
});
|
|
285
276
|
}
|
|
286
277
|
}
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
}
|
|
278
|
+
return diffs;
|
|
279
|
+
}
|
|
280
|
+
function formatDiffChanges(changes) {
|
|
281
|
+
if (changes.length === 0) {
|
|
282
|
+
return "No changes detected";
|
|
283
|
+
}
|
|
284
|
+
const lines = [];
|
|
285
|
+
for (const change of changes) {
|
|
286
|
+
const prefix = {
|
|
287
|
+
added: "+",
|
|
288
|
+
removed: "-",
|
|
289
|
+
modified: "~",
|
|
290
|
+
type_changed: "!",
|
|
291
|
+
required_changed: "?"
|
|
292
|
+
}[change.type];
|
|
293
|
+
lines.push(`${prefix} ${change.path}: ${change.description}`);
|
|
294
|
+
if (change.type === "modified" || change.type === "type_changed") {
|
|
295
|
+
lines.push(` old: ${JSON.stringify(change.oldValue)}`);
|
|
296
|
+
lines.push(` new: ${JSON.stringify(change.newValue)}`);
|
|
297
|
+
} else if (change.type === "added") {
|
|
298
|
+
lines.push(` value: ${JSON.stringify(change.newValue)}`);
|
|
299
|
+
} else if (change.type === "removed") {
|
|
300
|
+
lines.push(` was: ${JSON.stringify(change.oldValue)}`);
|
|
311
301
|
}
|
|
312
302
|
}
|
|
303
|
+
return lines.join(`
|
|
304
|
+
`);
|
|
305
|
+
}
|
|
306
|
+
// src/openapi/exporter/data-views.ts
|
|
307
|
+
function exportDataViews(registry) {
|
|
308
|
+
return registry.list().map((dv) => ({
|
|
309
|
+
name: dv.meta.key,
|
|
310
|
+
version: dv.meta.version,
|
|
311
|
+
description: dv.meta.description,
|
|
312
|
+
stability: dv.meta.stability,
|
|
313
|
+
entity: dv.meta.entity,
|
|
314
|
+
kind: dv.view.kind,
|
|
315
|
+
source: dv.source,
|
|
316
|
+
fields: dv.view.fields
|
|
317
|
+
}));
|
|
318
|
+
}
|
|
319
|
+
function generateDataViewsRegistry(registry) {
|
|
320
|
+
const dataViews = registry.list();
|
|
321
|
+
const imports = new Set;
|
|
322
|
+
const registrations = [];
|
|
323
|
+
for (const dv of dataViews) {
|
|
324
|
+
const dvVarName = dv.meta.key.replace(/\./g, "_") + `_v${dv.meta.version}`;
|
|
325
|
+
imports.add(`import { ${dvVarName} } from './${dv.meta.key.split(".")[0]}';`);
|
|
326
|
+
registrations.push(` .register(${dvVarName})`);
|
|
327
|
+
}
|
|
328
|
+
const code = `/**
|
|
329
|
+
* Auto-generated data views registry.
|
|
330
|
+
* DO NOT EDIT - This file is generated by ContractSpec exporter.
|
|
331
|
+
*/
|
|
332
|
+
import { DataViewRegistry } from '@contractspec/lib.contracts-spec/data-views';
|
|
333
|
+
|
|
334
|
+
${Array.from(imports).join(`
|
|
335
|
+
`)}
|
|
336
|
+
|
|
337
|
+
export const dataViewsRegistry = new DataViewRegistry()
|
|
338
|
+
${registrations.join(`
|
|
339
|
+
`)};
|
|
340
|
+
`;
|
|
313
341
|
return {
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
info: {
|
|
317
|
-
title: doc.info.title,
|
|
318
|
-
version: doc.info.version,
|
|
319
|
-
description: doc.info.description
|
|
320
|
-
},
|
|
321
|
-
operations,
|
|
322
|
-
schemas,
|
|
323
|
-
servers,
|
|
324
|
-
warnings,
|
|
325
|
-
events
|
|
342
|
+
code,
|
|
343
|
+
fileName: "dataviews-registry.ts"
|
|
326
344
|
};
|
|
327
345
|
}
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
346
|
+
|
|
347
|
+
// src/openapi/exporter/events.ts
|
|
348
|
+
import { z } from "zod";
|
|
349
|
+
function exportEvents(events) {
|
|
350
|
+
return events.map((event) => ({
|
|
351
|
+
name: event.meta.key,
|
|
352
|
+
version: event.meta.version,
|
|
353
|
+
description: event.meta.description,
|
|
354
|
+
payload: event.payload ? z.toJSONSchema(event.payload.getZod()) : null,
|
|
355
|
+
pii: event.pii
|
|
356
|
+
}));
|
|
357
|
+
}
|
|
358
|
+
function generateEventsExports(events) {
|
|
359
|
+
const eventExports = [];
|
|
360
|
+
for (const event of events) {
|
|
361
|
+
const eventVarName = event.meta.key.replace(/\./g, "_") + `_v${event.meta.version}`;
|
|
362
|
+
eventExports.push(`export { ${eventVarName} } from './${event.meta.key.split(".")[0]}';`);
|
|
363
|
+
}
|
|
364
|
+
const code = `/**
|
|
365
|
+
* Auto-generated events exports.
|
|
366
|
+
* DO NOT EDIT - This file is generated by ContractSpec exporter.
|
|
367
|
+
*/
|
|
368
|
+
|
|
369
|
+
${eventExports.join(`
|
|
370
|
+
`)}
|
|
371
|
+
`;
|
|
372
|
+
return {
|
|
373
|
+
code,
|
|
374
|
+
fileName: "events-exports.ts"
|
|
375
|
+
};
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
// src/openapi/exporter/features.ts
|
|
379
|
+
function exportFeatures(registry) {
|
|
380
|
+
return registry.list().map((feature) => ({
|
|
381
|
+
key: feature.meta.key,
|
|
382
|
+
description: feature.meta.description,
|
|
383
|
+
owners: feature.meta.owners,
|
|
384
|
+
stability: feature.meta.stability,
|
|
385
|
+
operations: feature.operations,
|
|
386
|
+
events: feature.events,
|
|
387
|
+
presentations: feature.presentations
|
|
388
|
+
}));
|
|
389
|
+
}
|
|
390
|
+
function generateFeaturesRegistry(registry) {
|
|
391
|
+
const features = registry.list();
|
|
392
|
+
const imports = new Set;
|
|
393
|
+
const registrations = [];
|
|
394
|
+
for (const feature of features) {
|
|
395
|
+
const featureVarName = feature.meta.key.replace(/-/g, "_");
|
|
396
|
+
imports.add(`import { ${featureVarName} } from './${feature.meta.key}';`);
|
|
397
|
+
registrations.push(` .register(${featureVarName})`);
|
|
398
|
+
}
|
|
399
|
+
const code = `/**
|
|
400
|
+
* Auto-generated features registry.
|
|
401
|
+
* DO NOT EDIT - This file is generated by ContractSpec exporter.
|
|
402
|
+
*/
|
|
403
|
+
import { FeatureRegistry } from '@contractspec/lib.contracts-spec';
|
|
404
|
+
|
|
405
|
+
${Array.from(imports).join(`
|
|
406
|
+
`)}
|
|
407
|
+
|
|
408
|
+
export const featuresRegistry = new FeatureRegistry()
|
|
409
|
+
${registrations.join(`
|
|
410
|
+
`)};
|
|
411
|
+
`;
|
|
412
|
+
return {
|
|
413
|
+
code,
|
|
414
|
+
fileName: "features-registry.ts"
|
|
415
|
+
};
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
// src/openapi/exporter/forms.ts
|
|
419
|
+
import { z as z2 } from "zod";
|
|
420
|
+
function exportForms(registry) {
|
|
421
|
+
return registry.list().map((form) => ({
|
|
422
|
+
key: form.meta.key,
|
|
423
|
+
version: form.meta.version,
|
|
424
|
+
description: form.meta.description,
|
|
425
|
+
stability: form.meta.stability,
|
|
426
|
+
owners: form.meta.owners,
|
|
427
|
+
fields: form.fields,
|
|
428
|
+
model: form.model ? z2.toJSONSchema(form.model.getZod()) : null,
|
|
429
|
+
actions: form.actions
|
|
430
|
+
}));
|
|
431
|
+
}
|
|
432
|
+
function generateFormsRegistry(registry) {
|
|
433
|
+
const forms = registry.list();
|
|
434
|
+
const imports = new Set;
|
|
435
|
+
const registrations = [];
|
|
436
|
+
for (const form of forms) {
|
|
437
|
+
const formVarName = form.meta.key.replace(/-/g, "_") + `_v${form.meta.version}`;
|
|
438
|
+
imports.add(`import { ${formVarName} } from './${form.meta.key}';`);
|
|
439
|
+
registrations.push(` .register(${formVarName})`);
|
|
367
440
|
}
|
|
368
|
-
const
|
|
369
|
-
|
|
441
|
+
const code = `/**
|
|
442
|
+
* Auto-generated forms registry.
|
|
443
|
+
* DO NOT EDIT - This file is generated by ContractSpec exporter.
|
|
444
|
+
*/
|
|
445
|
+
import { FormRegistry } from '@contractspec/lib.contracts-spec';
|
|
446
|
+
|
|
447
|
+
${Array.from(imports).join(`
|
|
448
|
+
`)}
|
|
449
|
+
|
|
450
|
+
export const formsRegistry = new FormRegistry()
|
|
451
|
+
${registrations.join(`
|
|
452
|
+
`)};
|
|
453
|
+
`;
|
|
454
|
+
return {
|
|
455
|
+
code,
|
|
456
|
+
fileName: "forms-registry.ts"
|
|
457
|
+
};
|
|
370
458
|
}
|
|
459
|
+
|
|
371
460
|
// src/openapi/exporter/operations.ts
|
|
372
|
-
import { z } from "zod";
|
|
373
461
|
import { compareVersions } from "compare-versions";
|
|
462
|
+
import { z as z3 } from "zod";
|
|
374
463
|
function toOperationId(name, version) {
|
|
375
464
|
return `${name.replace(/\./g, "_")}_v${version.replace(/\./g, "_")}`;
|
|
376
465
|
}
|
|
@@ -391,7 +480,7 @@ function toRestPath(spec) {
|
|
|
391
480
|
function schemaModelToJsonSchema(schema) {
|
|
392
481
|
if (!schema)
|
|
393
482
|
return null;
|
|
394
|
-
return
|
|
483
|
+
return z3.toJSONSchema(schema.getZod());
|
|
395
484
|
}
|
|
396
485
|
function jsonSchemaForSpec(spec) {
|
|
397
486
|
return {
|
|
@@ -492,77 +581,6 @@ ${registrations.join(`
|
|
|
492
581
|
};
|
|
493
582
|
}
|
|
494
583
|
|
|
495
|
-
// src/openapi/exporter/events.ts
|
|
496
|
-
import { z as z2 } from "zod";
|
|
497
|
-
function exportEvents(events) {
|
|
498
|
-
return events.map((event) => ({
|
|
499
|
-
name: event.meta.key,
|
|
500
|
-
version: event.meta.version,
|
|
501
|
-
description: event.meta.description,
|
|
502
|
-
payload: event.payload ? z2.toJSONSchema(event.payload.getZod()) : null,
|
|
503
|
-
pii: event.pii
|
|
504
|
-
}));
|
|
505
|
-
}
|
|
506
|
-
function generateEventsExports(events) {
|
|
507
|
-
const eventExports = [];
|
|
508
|
-
for (const event of events) {
|
|
509
|
-
const eventVarName = event.meta.key.replace(/\./g, "_") + `_v${event.meta.version}`;
|
|
510
|
-
eventExports.push(`export { ${eventVarName} } from './${event.meta.key.split(".")[0]}';`);
|
|
511
|
-
}
|
|
512
|
-
const code = `/**
|
|
513
|
-
* Auto-generated events exports.
|
|
514
|
-
* DO NOT EDIT - This file is generated by ContractSpec exporter.
|
|
515
|
-
*/
|
|
516
|
-
|
|
517
|
-
${eventExports.join(`
|
|
518
|
-
`)}
|
|
519
|
-
`;
|
|
520
|
-
return {
|
|
521
|
-
code,
|
|
522
|
-
fileName: "events-exports.ts"
|
|
523
|
-
};
|
|
524
|
-
}
|
|
525
|
-
|
|
526
|
-
// src/openapi/exporter/features.ts
|
|
527
|
-
function exportFeatures(registry) {
|
|
528
|
-
return registry.list().map((feature) => ({
|
|
529
|
-
key: feature.meta.key,
|
|
530
|
-
description: feature.meta.description,
|
|
531
|
-
owners: feature.meta.owners,
|
|
532
|
-
stability: feature.meta.stability,
|
|
533
|
-
operations: feature.operations,
|
|
534
|
-
events: feature.events,
|
|
535
|
-
presentations: feature.presentations
|
|
536
|
-
}));
|
|
537
|
-
}
|
|
538
|
-
function generateFeaturesRegistry(registry) {
|
|
539
|
-
const features = registry.list();
|
|
540
|
-
const imports = new Set;
|
|
541
|
-
const registrations = [];
|
|
542
|
-
for (const feature of features) {
|
|
543
|
-
const featureVarName = feature.meta.key.replace(/-/g, "_");
|
|
544
|
-
imports.add(`import { ${featureVarName} } from './${feature.meta.key}';`);
|
|
545
|
-
registrations.push(` .register(${featureVarName})`);
|
|
546
|
-
}
|
|
547
|
-
const code = `/**
|
|
548
|
-
* Auto-generated features registry.
|
|
549
|
-
* DO NOT EDIT - This file is generated by ContractSpec exporter.
|
|
550
|
-
*/
|
|
551
|
-
import { FeatureRegistry } from '@contractspec/lib.contracts-spec';
|
|
552
|
-
|
|
553
|
-
${Array.from(imports).join(`
|
|
554
|
-
`)}
|
|
555
|
-
|
|
556
|
-
export const featuresRegistry = new FeatureRegistry()
|
|
557
|
-
${registrations.join(`
|
|
558
|
-
`)};
|
|
559
|
-
`;
|
|
560
|
-
return {
|
|
561
|
-
code,
|
|
562
|
-
fileName: "features-registry.ts"
|
|
563
|
-
};
|
|
564
|
-
}
|
|
565
|
-
|
|
566
584
|
// src/openapi/exporter/presentations.ts
|
|
567
585
|
function exportPresentations(registry) {
|
|
568
586
|
return registry.list().map((pres) => ({
|
|
@@ -614,137 +632,6 @@ ${registrations.join(`
|
|
|
614
632
|
};
|
|
615
633
|
}
|
|
616
634
|
|
|
617
|
-
// src/openapi/exporter/forms.ts
|
|
618
|
-
import { z as z3 } from "zod";
|
|
619
|
-
function exportForms(registry) {
|
|
620
|
-
return registry.list().map((form) => ({
|
|
621
|
-
key: form.meta.key,
|
|
622
|
-
version: form.meta.version,
|
|
623
|
-
description: form.meta.description,
|
|
624
|
-
stability: form.meta.stability,
|
|
625
|
-
owners: form.meta.owners,
|
|
626
|
-
fields: form.fields,
|
|
627
|
-
model: form.model ? z3.toJSONSchema(form.model.getZod()) : null,
|
|
628
|
-
actions: form.actions
|
|
629
|
-
}));
|
|
630
|
-
}
|
|
631
|
-
function generateFormsRegistry(registry) {
|
|
632
|
-
const forms = registry.list();
|
|
633
|
-
const imports = new Set;
|
|
634
|
-
const registrations = [];
|
|
635
|
-
for (const form of forms) {
|
|
636
|
-
const formVarName = form.meta.key.replace(/-/g, "_") + `_v${form.meta.version}`;
|
|
637
|
-
imports.add(`import { ${formVarName} } from './${form.meta.key}';`);
|
|
638
|
-
registrations.push(` .register(${formVarName})`);
|
|
639
|
-
}
|
|
640
|
-
const code = `/**
|
|
641
|
-
* Auto-generated forms registry.
|
|
642
|
-
* DO NOT EDIT - This file is generated by ContractSpec exporter.
|
|
643
|
-
*/
|
|
644
|
-
import { FormRegistry } from '@contractspec/lib.contracts-spec';
|
|
645
|
-
|
|
646
|
-
${Array.from(imports).join(`
|
|
647
|
-
`)}
|
|
648
|
-
|
|
649
|
-
export const formsRegistry = new FormRegistry()
|
|
650
|
-
${registrations.join(`
|
|
651
|
-
`)};
|
|
652
|
-
`;
|
|
653
|
-
return {
|
|
654
|
-
code,
|
|
655
|
-
fileName: "forms-registry.ts"
|
|
656
|
-
};
|
|
657
|
-
}
|
|
658
|
-
|
|
659
|
-
// src/openapi/exporter/data-views.ts
|
|
660
|
-
function exportDataViews(registry) {
|
|
661
|
-
return registry.list().map((dv) => ({
|
|
662
|
-
name: dv.meta.key,
|
|
663
|
-
version: dv.meta.version,
|
|
664
|
-
description: dv.meta.description,
|
|
665
|
-
stability: dv.meta.stability,
|
|
666
|
-
entity: dv.meta.entity,
|
|
667
|
-
kind: dv.view.kind,
|
|
668
|
-
source: dv.source,
|
|
669
|
-
fields: dv.view.fields
|
|
670
|
-
}));
|
|
671
|
-
}
|
|
672
|
-
function generateDataViewsRegistry(registry) {
|
|
673
|
-
const dataViews = registry.list();
|
|
674
|
-
const imports = new Set;
|
|
675
|
-
const registrations = [];
|
|
676
|
-
for (const dv of dataViews) {
|
|
677
|
-
const dvVarName = dv.meta.key.replace(/\./g, "_") + `_v${dv.meta.version}`;
|
|
678
|
-
imports.add(`import { ${dvVarName} } from './${dv.meta.key.split(".")[0]}';`);
|
|
679
|
-
registrations.push(` .register(${dvVarName})`);
|
|
680
|
-
}
|
|
681
|
-
const code = `/**
|
|
682
|
-
* Auto-generated data views registry.
|
|
683
|
-
* DO NOT EDIT - This file is generated by ContractSpec exporter.
|
|
684
|
-
*/
|
|
685
|
-
import { DataViewRegistry } from '@contractspec/lib.contracts-spec/data-views';
|
|
686
|
-
|
|
687
|
-
${Array.from(imports).join(`
|
|
688
|
-
`)}
|
|
689
|
-
|
|
690
|
-
export const dataViewsRegistry = new DataViewRegistry()
|
|
691
|
-
${registrations.join(`
|
|
692
|
-
`)};
|
|
693
|
-
`;
|
|
694
|
-
return {
|
|
695
|
-
code,
|
|
696
|
-
fileName: "dataviews-registry.ts"
|
|
697
|
-
};
|
|
698
|
-
}
|
|
699
|
-
|
|
700
|
-
// src/openapi/exporter/workflows.ts
|
|
701
|
-
function exportWorkflows(registry) {
|
|
702
|
-
return registry.list().map((wf) => ({
|
|
703
|
-
name: wf.meta.key,
|
|
704
|
-
version: wf.meta.version,
|
|
705
|
-
description: wf.meta.description,
|
|
706
|
-
stability: wf.meta.stability,
|
|
707
|
-
owners: wf.meta.owners,
|
|
708
|
-
steps: wf.definition.steps.map((s) => ({
|
|
709
|
-
id: s.id,
|
|
710
|
-
type: s.type,
|
|
711
|
-
label: s.label
|
|
712
|
-
})),
|
|
713
|
-
transitions: wf.definition.transitions.map((t) => ({
|
|
714
|
-
from: t.from,
|
|
715
|
-
to: t.to,
|
|
716
|
-
label: t.label
|
|
717
|
-
}))
|
|
718
|
-
}));
|
|
719
|
-
}
|
|
720
|
-
function generateWorkflowsRegistry(registry) {
|
|
721
|
-
const workflows = registry.list();
|
|
722
|
-
const imports = new Set;
|
|
723
|
-
const registrations = [];
|
|
724
|
-
for (const wf of workflows) {
|
|
725
|
-
const wfVarName = wf.meta.key.replace(/\./g, "_") + `_v${wf.meta.version}`;
|
|
726
|
-
imports.add(`import { ${wfVarName} } from './${wf.meta.key.split(".")[0]}';`);
|
|
727
|
-
registrations.push(` .register(${wfVarName})`);
|
|
728
|
-
}
|
|
729
|
-
const code = `/**
|
|
730
|
-
* Auto-generated workflows registry.
|
|
731
|
-
* DO NOT EDIT - This file is generated by ContractSpec exporter.
|
|
732
|
-
*/
|
|
733
|
-
import { WorkflowRegistry } from '@contractspec/lib.contracts-spec/workflow';
|
|
734
|
-
|
|
735
|
-
${Array.from(imports).join(`
|
|
736
|
-
`)}
|
|
737
|
-
|
|
738
|
-
export const workflowsRegistry = new WorkflowRegistry()
|
|
739
|
-
${registrations.join(`
|
|
740
|
-
`)};
|
|
741
|
-
`;
|
|
742
|
-
return {
|
|
743
|
-
code,
|
|
744
|
-
fileName: "workflows-registry.ts"
|
|
745
|
-
};
|
|
746
|
-
}
|
|
747
|
-
|
|
748
635
|
// src/openapi/exporter/registries.ts
|
|
749
636
|
function generateRegistryIndex(options = {}) {
|
|
750
637
|
const {
|
|
@@ -779,16 +666,64 @@ function generateRegistryIndex(options = {}) {
|
|
|
779
666
|
exports.push("export * from './workflows-registry';");
|
|
780
667
|
}
|
|
781
668
|
const code = `/**
|
|
782
|
-
* Auto-generated registry index.
|
|
669
|
+
* Auto-generated registry index.
|
|
670
|
+
* DO NOT EDIT - This file is generated by ContractSpec exporter.
|
|
671
|
+
*/
|
|
672
|
+
|
|
673
|
+
${exports.join(`
|
|
674
|
+
`)}
|
|
675
|
+
`;
|
|
676
|
+
return {
|
|
677
|
+
code,
|
|
678
|
+
fileName: "index.ts"
|
|
679
|
+
};
|
|
680
|
+
}
|
|
681
|
+
|
|
682
|
+
// src/openapi/exporter/workflows.ts
|
|
683
|
+
function exportWorkflows(registry) {
|
|
684
|
+
return registry.list().map((wf) => ({
|
|
685
|
+
name: wf.meta.key,
|
|
686
|
+
version: wf.meta.version,
|
|
687
|
+
description: wf.meta.description,
|
|
688
|
+
stability: wf.meta.stability,
|
|
689
|
+
owners: wf.meta.owners,
|
|
690
|
+
steps: wf.definition.steps.map((s) => ({
|
|
691
|
+
id: s.id,
|
|
692
|
+
type: s.type,
|
|
693
|
+
label: s.label
|
|
694
|
+
})),
|
|
695
|
+
transitions: wf.definition.transitions.map((t) => ({
|
|
696
|
+
from: t.from,
|
|
697
|
+
to: t.to,
|
|
698
|
+
label: t.label
|
|
699
|
+
}))
|
|
700
|
+
}));
|
|
701
|
+
}
|
|
702
|
+
function generateWorkflowsRegistry(registry) {
|
|
703
|
+
const workflows = registry.list();
|
|
704
|
+
const imports = new Set;
|
|
705
|
+
const registrations = [];
|
|
706
|
+
for (const wf of workflows) {
|
|
707
|
+
const wfVarName = wf.meta.key.replace(/\./g, "_") + `_v${wf.meta.version}`;
|
|
708
|
+
imports.add(`import { ${wfVarName} } from './${wf.meta.key.split(".")[0]}';`);
|
|
709
|
+
registrations.push(` .register(${wfVarName})`);
|
|
710
|
+
}
|
|
711
|
+
const code = `/**
|
|
712
|
+
* Auto-generated workflows registry.
|
|
783
713
|
* DO NOT EDIT - This file is generated by ContractSpec exporter.
|
|
784
714
|
*/
|
|
715
|
+
import { WorkflowRegistry } from '@contractspec/lib.contracts-spec/workflow';
|
|
785
716
|
|
|
786
|
-
${
|
|
717
|
+
${Array.from(imports).join(`
|
|
787
718
|
`)}
|
|
719
|
+
|
|
720
|
+
export const workflowsRegistry = new WorkflowRegistry()
|
|
721
|
+
${registrations.join(`
|
|
722
|
+
`)};
|
|
788
723
|
`;
|
|
789
724
|
return {
|
|
790
725
|
code,
|
|
791
|
-
fileName: "
|
|
726
|
+
fileName: "workflows-registry.ts"
|
|
792
727
|
};
|
|
793
728
|
}
|
|
794
729
|
|
|
@@ -949,7 +884,7 @@ var JSON_SCHEMA_TO_SCALAR = {
|
|
|
949
884
|
"string:uri": "ScalarTypeEnum.URL",
|
|
950
885
|
"string:uuid": "ScalarTypeEnum.ID"
|
|
951
886
|
};
|
|
952
|
-
function
|
|
887
|
+
function isReference(schema) {
|
|
953
888
|
return typeof schema === "object" && schema !== null && "$ref" in schema;
|
|
954
889
|
}
|
|
955
890
|
function typeNameFromRef(ref) {
|
|
@@ -1004,7 +939,7 @@ class ContractSpecSchemaGenerator {
|
|
|
1004
939
|
generateContractSpecSchema(schema, modelName, indent = 0) {
|
|
1005
940
|
const spaces = " ".repeat(indent);
|
|
1006
941
|
const fields = [];
|
|
1007
|
-
if (
|
|
942
|
+
if (isReference(schema)) {
|
|
1008
943
|
return {
|
|
1009
944
|
name: toPascalCase(typeNameFromRef(schema.$ref)),
|
|
1010
945
|
fields: [],
|
|
@@ -1140,7 +1075,7 @@ class ContractSpecSchemaGenerator {
|
|
|
1140
1075
|
const scalarType = getScalarType(schema);
|
|
1141
1076
|
let enumValues;
|
|
1142
1077
|
let nestedModel;
|
|
1143
|
-
if (!
|
|
1078
|
+
if (!isReference(schema)) {
|
|
1144
1079
|
const schemaObj = schema;
|
|
1145
1080
|
const enumArr = schemaObj["enum"];
|
|
1146
1081
|
if (enumArr) {
|
|
@@ -1158,7 +1093,7 @@ class ContractSpecSchemaGenerator {
|
|
|
1158
1093
|
type: {
|
|
1159
1094
|
...type,
|
|
1160
1095
|
optional: !required || type.optional,
|
|
1161
|
-
description: !
|
|
1096
|
+
description: !isReference(schema) ? schema["description"] : undefined
|
|
1162
1097
|
},
|
|
1163
1098
|
scalarType,
|
|
1164
1099
|
enumValues,
|
|
@@ -1501,7 +1436,7 @@ var JSON_SCHEMA_TO_SCALAR2 = {
|
|
|
1501
1436
|
"string:uri": "ScalarTypeEnum.URL",
|
|
1502
1437
|
"string:uuid": "ScalarTypeEnum.ID"
|
|
1503
1438
|
};
|
|
1504
|
-
function
|
|
1439
|
+
function isReference2(schema) {
|
|
1505
1440
|
return typeof schema === "object" && schema !== null && "$ref" in schema;
|
|
1506
1441
|
}
|
|
1507
1442
|
function typeNameFromRef2(ref) {
|
|
@@ -1509,7 +1444,7 @@ function typeNameFromRef2(ref) {
|
|
|
1509
1444
|
return parts[parts.length - 1] ?? "Unknown";
|
|
1510
1445
|
}
|
|
1511
1446
|
function jsonSchemaToType(schema, name) {
|
|
1512
|
-
if (
|
|
1447
|
+
if (isReference2(schema)) {
|
|
1513
1448
|
return {
|
|
1514
1449
|
type: toPascalCase(typeNameFromRef2(schema.$ref)),
|
|
1515
1450
|
optional: false,
|
|
@@ -1602,7 +1537,7 @@ function jsonSchemaToType(schema, name) {
|
|
|
1602
1537
|
};
|
|
1603
1538
|
}
|
|
1604
1539
|
function getScalarType(schema) {
|
|
1605
|
-
if (
|
|
1540
|
+
if (isReference2(schema)) {
|
|
1606
1541
|
return;
|
|
1607
1542
|
}
|
|
1608
1543
|
const schemaObj = schema;
|
|
@@ -1648,65 +1583,42 @@ function generateImports(fields, options, sameDirectory = true) {
|
|
|
1648
1583
|
`);
|
|
1649
1584
|
}
|
|
1650
1585
|
|
|
1651
|
-
// src/openapi/importer/
|
|
1652
|
-
function
|
|
1653
|
-
const
|
|
1654
|
-
|
|
1655
|
-
|
|
1656
|
-
|
|
1657
|
-
|
|
1658
|
-
|
|
1659
|
-
|
|
1660
|
-
|
|
1661
|
-
|
|
1662
|
-
|
|
1663
|
-
|
|
1664
|
-
|
|
1665
|
-
result.query = {
|
|
1666
|
-
type: "object",
|
|
1667
|
-
properties: operation2.queryParams.reduce((acc, p) => {
|
|
1668
|
-
acc[p.name] = p.schema;
|
|
1669
|
-
return acc;
|
|
1670
|
-
}, {}),
|
|
1671
|
-
required: operation2.queryParams.filter((p) => p.required).map((p) => p.name)
|
|
1672
|
-
};
|
|
1673
|
-
}
|
|
1674
|
-
const excludedHeaders = [
|
|
1675
|
-
"authorization",
|
|
1676
|
-
"content-type",
|
|
1677
|
-
"accept",
|
|
1678
|
-
"user-agent"
|
|
1679
|
-
];
|
|
1680
|
-
const actualHeaders = operation2.headerParams.filter((p) => !excludedHeaders.includes(p.name.toLowerCase()));
|
|
1681
|
-
if (actualHeaders.length > 0) {
|
|
1682
|
-
result.headers = {
|
|
1683
|
-
type: "object",
|
|
1684
|
-
properties: actualHeaders.reduce((acc, p) => {
|
|
1685
|
-
acc[p.name] = p.schema;
|
|
1686
|
-
return acc;
|
|
1687
|
-
}, {}),
|
|
1688
|
-
required: actualHeaders.filter((p) => p.required).map((p) => p.name)
|
|
1689
|
-
};
|
|
1690
|
-
}
|
|
1691
|
-
if (operation2.requestBody?.schema) {
|
|
1692
|
-
result.body = operation2.requestBody.schema;
|
|
1693
|
-
}
|
|
1694
|
-
return result;
|
|
1695
|
-
}
|
|
1696
|
-
function getOutputSchema(operation2) {
|
|
1697
|
-
const successCodes = ["200", "201", "202", "204"];
|
|
1698
|
-
for (const code of successCodes) {
|
|
1699
|
-
const response = operation2.responses[code];
|
|
1700
|
-
if (response?.schema) {
|
|
1701
|
-
return response.schema;
|
|
1702
|
-
}
|
|
1586
|
+
// src/openapi/importer/events.ts
|
|
1587
|
+
function generateEventCode(event, options) {
|
|
1588
|
+
const eventName = toValidIdentifier(event.name);
|
|
1589
|
+
const modelName = toPascalCase(eventName) + "Payload";
|
|
1590
|
+
const schemaFormat = options.schemaFormat || "contractspec";
|
|
1591
|
+
const payloadModel = generateSchemaModelCode(event.payload, modelName, schemaFormat, options);
|
|
1592
|
+
const imports = new Set;
|
|
1593
|
+
imports.add("import { defineEvent, type EventSpec } from '@contractspec/lib.contracts-spec';");
|
|
1594
|
+
if (payloadModel.imports && payloadModel.imports.length > 0) {
|
|
1595
|
+
payloadModel.imports.forEach((i) => imports.add(i));
|
|
1596
|
+
} else if (payloadModel.fields && payloadModel.fields.length > 0) {
|
|
1597
|
+
const modelImports = generateImports(payloadModel.fields, options);
|
|
1598
|
+
modelImports.split(`
|
|
1599
|
+
`).filter(Boolean).forEach((i) => imports.add(i));
|
|
1703
1600
|
}
|
|
1704
|
-
|
|
1705
|
-
|
|
1706
|
-
|
|
1707
|
-
}
|
|
1601
|
+
if (payloadModel.name !== modelName) {
|
|
1602
|
+
const modelsDir = `../${options.conventions.models}`;
|
|
1603
|
+
const kebabName = payloadModel.name.replace(/([a-z0-9])([A-Z])/g, "$1-$2").toLowerCase();
|
|
1604
|
+
imports.add(`import { ${payloadModel.name} } from '${modelsDir}/${kebabName}';`);
|
|
1708
1605
|
}
|
|
1709
|
-
|
|
1606
|
+
const allImports = Array.from(imports).join(`
|
|
1607
|
+
`);
|
|
1608
|
+
return `
|
|
1609
|
+
${allImports}
|
|
1610
|
+
|
|
1611
|
+
${payloadModel.code}
|
|
1612
|
+
|
|
1613
|
+
export const ${eventName} = defineEvent({
|
|
1614
|
+
meta: {
|
|
1615
|
+
key: '${event.name}',
|
|
1616
|
+
version: '1.0.0',
|
|
1617
|
+
description: ${JSON.stringify(event.description ?? "")},
|
|
1618
|
+
},
|
|
1619
|
+
payload: ${payloadModel.name},
|
|
1620
|
+
});
|
|
1621
|
+
`.trim();
|
|
1710
1622
|
}
|
|
1711
1623
|
|
|
1712
1624
|
// src/openapi/importer/analyzer.ts
|
|
@@ -1714,11 +1626,11 @@ var COMMAND_METHODS = ["post", "put", "delete", "patch"];
|
|
|
1714
1626
|
function inferOpKind(method) {
|
|
1715
1627
|
return COMMAND_METHODS.includes(method.toLowerCase()) ? "command" : "query";
|
|
1716
1628
|
}
|
|
1717
|
-
function inferAuthLevel(
|
|
1718
|
-
if (!
|
|
1629
|
+
function inferAuthLevel(operation, defaultAuth) {
|
|
1630
|
+
if (!operation.security || operation.security.length === 0) {
|
|
1719
1631
|
return defaultAuth;
|
|
1720
1632
|
}
|
|
1721
|
-
for (const sec of
|
|
1633
|
+
for (const sec of operation.security) {
|
|
1722
1634
|
if (Object.keys(sec).length === 0) {
|
|
1723
1635
|
return "anonymous";
|
|
1724
1636
|
}
|
|
@@ -1727,10 +1639,10 @@ function inferAuthLevel(operation2, defaultAuth) {
|
|
|
1727
1639
|
}
|
|
1728
1640
|
|
|
1729
1641
|
// src/openapi/importer/generator.ts
|
|
1730
|
-
function generateSpecCode(
|
|
1731
|
-
const specKey = toSpecKey(
|
|
1732
|
-
const kind = inferOpKind(
|
|
1733
|
-
const auth = inferAuthLevel(
|
|
1642
|
+
function generateSpecCode(operation, contractspecConfig, options = {}, inputModel, outputModel, queryModel = null, paramsModel = null, headersModel = null) {
|
|
1643
|
+
const specKey = toSpecKey(operation.operationId, options.prefix);
|
|
1644
|
+
const kind = inferOpKind(operation.method);
|
|
1645
|
+
const auth = inferAuthLevel(operation, options.defaultAuth ?? "user");
|
|
1734
1646
|
const lines = [];
|
|
1735
1647
|
lines.push("import { defineCommand, defineQuery } from '@contractspec/lib.contracts-spec';");
|
|
1736
1648
|
if (inputModel || outputModel || queryModel || paramsModel || headersModel) {
|
|
@@ -1775,15 +1687,15 @@ function generateSpecCode(operation2, contractspecConfig, options = {}, inputMod
|
|
|
1775
1687
|
}
|
|
1776
1688
|
}
|
|
1777
1689
|
const defineFunc = kind === "command" ? "defineCommand" : "defineQuery";
|
|
1778
|
-
const safeName = toValidIdentifier(toPascalCase(
|
|
1690
|
+
const safeName = toValidIdentifier(toPascalCase(operation.operationId));
|
|
1779
1691
|
lines.push(`/**`);
|
|
1780
|
-
lines.push(` * ${
|
|
1781
|
-
if (
|
|
1692
|
+
lines.push(` * ${operation.summary ?? operation.operationId}`);
|
|
1693
|
+
if (operation.description) {
|
|
1782
1694
|
lines.push(` *`);
|
|
1783
|
-
lines.push(` * ${
|
|
1695
|
+
lines.push(` * ${operation.description}`);
|
|
1784
1696
|
}
|
|
1785
1697
|
lines.push(` *`);
|
|
1786
|
-
lines.push(` * @source OpenAPI: ${
|
|
1698
|
+
lines.push(` * @source OpenAPI: ${operation.method.toUpperCase()} ${operation.path}`);
|
|
1787
1699
|
lines.push(` */`);
|
|
1788
1700
|
lines.push(`export const ${safeName}Spec = ${defineFunc}({`);
|
|
1789
1701
|
lines.push(" meta: {");
|
|
@@ -1791,10 +1703,10 @@ function generateSpecCode(operation2, contractspecConfig, options = {}, inputMod
|
|
|
1791
1703
|
lines.push(" version: '1.0.0',");
|
|
1792
1704
|
lines.push(` stability: '${options.defaultStability ?? "stable"}',`);
|
|
1793
1705
|
lines.push(` owners: [${(options.defaultOwners ?? []).map((o) => `'${o}'`).join(", ")}],`);
|
|
1794
|
-
lines.push(` tags: [${
|
|
1795
|
-
lines.push(` description: ${JSON.stringify(
|
|
1796
|
-
lines.push(` goal: ${JSON.stringify(
|
|
1797
|
-
lines.push(` context: 'Imported from OpenAPI: ${
|
|
1706
|
+
lines.push(` tags: [${operation.tags.map((t) => `'${t}'`).join(", ")}],`);
|
|
1707
|
+
lines.push(` description: ${JSON.stringify(operation.summary ?? operation.operationId)},`);
|
|
1708
|
+
lines.push(` goal: ${JSON.stringify(operation.description ?? "Imported from OpenAPI")},`);
|
|
1709
|
+
lines.push(` context: 'Imported from OpenAPI: ${operation.method.toUpperCase()} ${operation.path}',`);
|
|
1798
1710
|
lines.push(" },");
|
|
1799
1711
|
lines.push(" io: {");
|
|
1800
1712
|
lines.push(` input: ${inputModel?.name ?? "null"},`);
|
|
@@ -1813,12 +1725,12 @@ function generateSpecCode(operation2, contractspecConfig, options = {}, inputMod
|
|
|
1813
1725
|
lines.push(" policy: {");
|
|
1814
1726
|
lines.push(` auth: '${auth}',`);
|
|
1815
1727
|
lines.push(" },");
|
|
1816
|
-
const httpMethod =
|
|
1728
|
+
const httpMethod = operation.method.toUpperCase();
|
|
1817
1729
|
const restMethod = httpMethod === "GET" ? "GET" : "POST";
|
|
1818
1730
|
lines.push(" transport: {");
|
|
1819
1731
|
lines.push(" rest: {");
|
|
1820
1732
|
lines.push(` method: '${restMethod}',`);
|
|
1821
|
-
lines.push(` path: '${
|
|
1733
|
+
lines.push(` path: '${operation.path}',`);
|
|
1822
1734
|
lines.push(" },");
|
|
1823
1735
|
lines.push(" },");
|
|
1824
1736
|
lines.push("});");
|
|
@@ -1826,73 +1738,16 @@ function generateSpecCode(operation2, contractspecConfig, options = {}, inputMod
|
|
|
1826
1738
|
`);
|
|
1827
1739
|
}
|
|
1828
1740
|
|
|
1829
|
-
// src/openapi/importer/models.ts
|
|
1830
|
-
function generateModelCode(name, schema, options) {
|
|
1831
|
-
const modelName = toPascalCase(toValidIdentifier(name));
|
|
1832
|
-
const schemaFormat = options.schemaFormat || "contractspec";
|
|
1833
|
-
const model = generateSchemaModelCode(schema, modelName, schemaFormat, options);
|
|
1834
|
-
let imports = "";
|
|
1835
|
-
if (model.imports && model.imports.length > 0) {
|
|
1836
|
-
imports = model.imports.join(`
|
|
1837
|
-
`);
|
|
1838
|
-
} else if (model.fields.length > 0) {
|
|
1839
|
-
imports = generateImports(model.fields, options);
|
|
1840
|
-
}
|
|
1841
|
-
return `
|
|
1842
|
-
${imports}
|
|
1843
|
-
|
|
1844
|
-
${model.code}
|
|
1845
|
-
`.trim();
|
|
1846
|
-
}
|
|
1847
|
-
|
|
1848
|
-
// src/openapi/importer/events.ts
|
|
1849
|
-
function generateEventCode(event, options) {
|
|
1850
|
-
const eventName = toValidIdentifier(event.name);
|
|
1851
|
-
const modelName = toPascalCase(eventName) + "Payload";
|
|
1852
|
-
const schemaFormat = options.schemaFormat || "contractspec";
|
|
1853
|
-
const payloadModel = generateSchemaModelCode(event.payload, modelName, schemaFormat, options);
|
|
1854
|
-
const imports = new Set;
|
|
1855
|
-
imports.add("import { defineEvent, type EventSpec } from '@contractspec/lib.contracts-spec';");
|
|
1856
|
-
if (payloadModel.imports && payloadModel.imports.length > 0) {
|
|
1857
|
-
payloadModel.imports.forEach((i) => imports.add(i));
|
|
1858
|
-
} else if (payloadModel.fields && payloadModel.fields.length > 0) {
|
|
1859
|
-
const modelImports = generateImports(payloadModel.fields, options);
|
|
1860
|
-
modelImports.split(`
|
|
1861
|
-
`).filter(Boolean).forEach((i) => imports.add(i));
|
|
1862
|
-
}
|
|
1863
|
-
if (payloadModel.name !== modelName) {
|
|
1864
|
-
const modelsDir = `../${options.conventions.models}`;
|
|
1865
|
-
const kebabName = payloadModel.name.replace(/([a-z0-9])([A-Z])/g, "$1-$2").toLowerCase();
|
|
1866
|
-
imports.add(`import { ${payloadModel.name} } from '${modelsDir}/${kebabName}';`);
|
|
1867
|
-
}
|
|
1868
|
-
const allImports = Array.from(imports).join(`
|
|
1869
|
-
`);
|
|
1870
|
-
return `
|
|
1871
|
-
${allImports}
|
|
1872
|
-
|
|
1873
|
-
${payloadModel.code}
|
|
1874
|
-
|
|
1875
|
-
export const ${eventName} = defineEvent({
|
|
1876
|
-
meta: {
|
|
1877
|
-
key: '${event.name}',
|
|
1878
|
-
version: '1.0.0',
|
|
1879
|
-
description: ${JSON.stringify(event.description ?? "")},
|
|
1880
|
-
},
|
|
1881
|
-
payload: ${payloadModel.name},
|
|
1882
|
-
});
|
|
1883
|
-
`.trim();
|
|
1884
|
-
}
|
|
1885
|
-
|
|
1886
1741
|
// src/openapi/importer/grouping.ts
|
|
1887
|
-
function resolveOperationGroupFolder(
|
|
1742
|
+
function resolveOperationGroupFolder(operation, conventions) {
|
|
1888
1743
|
const groupingRule = conventions.operationsGrouping;
|
|
1889
1744
|
if (!groupingRule || groupingRule.strategy === "none") {
|
|
1890
1745
|
return "";
|
|
1891
1746
|
}
|
|
1892
1747
|
return applyGroupingStrategy(groupingRule, {
|
|
1893
|
-
name:
|
|
1894
|
-
tags:
|
|
1895
|
-
path:
|
|
1748
|
+
name: operation.operationId,
|
|
1749
|
+
tags: operation.tags,
|
|
1750
|
+
path: operation.path
|
|
1896
1751
|
});
|
|
1897
1752
|
}
|
|
1898
1753
|
function resolveModelGroupFolder(modelName, conventions, relatedPath, relatedTags) {
|
|
@@ -1911,48 +1766,128 @@ function resolveEventGroupFolder(eventName, conventions, relatedTags) {
|
|
|
1911
1766
|
if (!groupingRule || groupingRule.strategy === "none") {
|
|
1912
1767
|
return "";
|
|
1913
1768
|
}
|
|
1914
|
-
return applyGroupingStrategy(groupingRule, {
|
|
1915
|
-
name: eventName,
|
|
1916
|
-
tags: relatedTags ?? []
|
|
1917
|
-
});
|
|
1918
|
-
}
|
|
1919
|
-
function applyGroupingStrategy(rule, context) {
|
|
1920
|
-
switch (rule.strategy) {
|
|
1921
|
-
case "by-tag":
|
|
1922
|
-
return context.tags?.[0] ?? "untagged";
|
|
1923
|
-
case "by-owner":
|
|
1924
|
-
return context.owners?.[0] ?? "unowned";
|
|
1925
|
-
case "by-domain":
|
|
1926
|
-
return extractDomain(context.name);
|
|
1927
|
-
case "by-url-path-single":
|
|
1928
|
-
return extractUrlPathLevel(context.path, 1);
|
|
1929
|
-
case "by-url-path-multi":
|
|
1930
|
-
return extractUrlPathLevel(context.path, rule.urlPathLevel ?? 2);
|
|
1931
|
-
case "by-feature":
|
|
1932
|
-
return extractDomain(context.name);
|
|
1933
|
-
case "none":
|
|
1934
|
-
default:
|
|
1935
|
-
return "";
|
|
1769
|
+
return applyGroupingStrategy(groupingRule, {
|
|
1770
|
+
name: eventName,
|
|
1771
|
+
tags: relatedTags ?? []
|
|
1772
|
+
});
|
|
1773
|
+
}
|
|
1774
|
+
function applyGroupingStrategy(rule, context) {
|
|
1775
|
+
switch (rule.strategy) {
|
|
1776
|
+
case "by-tag":
|
|
1777
|
+
return context.tags?.[0] ?? "untagged";
|
|
1778
|
+
case "by-owner":
|
|
1779
|
+
return context.owners?.[0] ?? "unowned";
|
|
1780
|
+
case "by-domain":
|
|
1781
|
+
return extractDomain(context.name);
|
|
1782
|
+
case "by-url-path-single":
|
|
1783
|
+
return extractUrlPathLevel(context.path, 1);
|
|
1784
|
+
case "by-url-path-multi":
|
|
1785
|
+
return extractUrlPathLevel(context.path, rule.urlPathLevel ?? 2);
|
|
1786
|
+
case "by-feature":
|
|
1787
|
+
return extractDomain(context.name);
|
|
1788
|
+
case "none":
|
|
1789
|
+
default:
|
|
1790
|
+
return "";
|
|
1791
|
+
}
|
|
1792
|
+
}
|
|
1793
|
+
function extractDomain(name) {
|
|
1794
|
+
if (name.includes(".")) {
|
|
1795
|
+
return name.split(".")[0] ?? "default";
|
|
1796
|
+
}
|
|
1797
|
+
if (name.includes("_")) {
|
|
1798
|
+
return name.split("_")[0] ?? "default";
|
|
1799
|
+
}
|
|
1800
|
+
const match = name.match(/^([a-z]+)/i);
|
|
1801
|
+
return match?.[1]?.toLowerCase() ?? "default";
|
|
1802
|
+
}
|
|
1803
|
+
function extractUrlPathLevel(path, level) {
|
|
1804
|
+
if (!path)
|
|
1805
|
+
return "root";
|
|
1806
|
+
const segments = path.split("/").filter(Boolean);
|
|
1807
|
+
const nonParamSegments = segments.filter((s) => !s.startsWith("{"));
|
|
1808
|
+
if (nonParamSegments.length === 0)
|
|
1809
|
+
return "root";
|
|
1810
|
+
return nonParamSegments.slice(0, level).join("/");
|
|
1811
|
+
}
|
|
1812
|
+
|
|
1813
|
+
// src/openapi/importer/models.ts
|
|
1814
|
+
function generateModelCode(name, schema, options) {
|
|
1815
|
+
const modelName = toPascalCase(toValidIdentifier(name));
|
|
1816
|
+
const schemaFormat = options.schemaFormat || "contractspec";
|
|
1817
|
+
const model = generateSchemaModelCode(schema, modelName, schemaFormat, options);
|
|
1818
|
+
let imports = "";
|
|
1819
|
+
if (model.imports && model.imports.length > 0) {
|
|
1820
|
+
imports = model.imports.join(`
|
|
1821
|
+
`);
|
|
1822
|
+
} else if (model.fields.length > 0) {
|
|
1823
|
+
imports = generateImports(model.fields, options);
|
|
1824
|
+
}
|
|
1825
|
+
return `
|
|
1826
|
+
${imports}
|
|
1827
|
+
|
|
1828
|
+
${model.code}
|
|
1829
|
+
`.trim();
|
|
1830
|
+
}
|
|
1831
|
+
|
|
1832
|
+
// src/openapi/importer/schemas.ts
|
|
1833
|
+
function buildInputSchemas(operation) {
|
|
1834
|
+
const result = {};
|
|
1835
|
+
if (operation.pathParams.length > 0) {
|
|
1836
|
+
result.params = {
|
|
1837
|
+
type: "object",
|
|
1838
|
+
properties: operation.pathParams.reduce((acc, p) => {
|
|
1839
|
+
acc[p.name] = p.schema;
|
|
1840
|
+
return acc;
|
|
1841
|
+
}, {}),
|
|
1842
|
+
required: operation.pathParams.map((p) => p.name)
|
|
1843
|
+
};
|
|
1844
|
+
}
|
|
1845
|
+
if (operation.queryParams.length > 0) {
|
|
1846
|
+
result.query = {
|
|
1847
|
+
type: "object",
|
|
1848
|
+
properties: operation.queryParams.reduce((acc, p) => {
|
|
1849
|
+
acc[p.name] = p.schema;
|
|
1850
|
+
return acc;
|
|
1851
|
+
}, {}),
|
|
1852
|
+
required: operation.queryParams.filter((p) => p.required).map((p) => p.name)
|
|
1853
|
+
};
|
|
1854
|
+
}
|
|
1855
|
+
const excludedHeaders = [
|
|
1856
|
+
"authorization",
|
|
1857
|
+
"content-type",
|
|
1858
|
+
"accept",
|
|
1859
|
+
"user-agent"
|
|
1860
|
+
];
|
|
1861
|
+
const actualHeaders = operation.headerParams.filter((p) => !excludedHeaders.includes(p.name.toLowerCase()));
|
|
1862
|
+
if (actualHeaders.length > 0) {
|
|
1863
|
+
result.headers = {
|
|
1864
|
+
type: "object",
|
|
1865
|
+
properties: actualHeaders.reduce((acc, p) => {
|
|
1866
|
+
acc[p.name] = p.schema;
|
|
1867
|
+
return acc;
|
|
1868
|
+
}, {}),
|
|
1869
|
+
required: actualHeaders.filter((p) => p.required).map((p) => p.name)
|
|
1870
|
+
};
|
|
1871
|
+
}
|
|
1872
|
+
if (operation.requestBody?.schema) {
|
|
1873
|
+
result.body = operation.requestBody.schema;
|
|
1936
1874
|
}
|
|
1875
|
+
return result;
|
|
1937
1876
|
}
|
|
1938
|
-
function
|
|
1939
|
-
|
|
1940
|
-
|
|
1877
|
+
function getOutputSchema(operation) {
|
|
1878
|
+
const successCodes = ["200", "201", "202", "204"];
|
|
1879
|
+
for (const code of successCodes) {
|
|
1880
|
+
const response = operation.responses[code];
|
|
1881
|
+
if (response?.schema) {
|
|
1882
|
+
return response.schema;
|
|
1883
|
+
}
|
|
1941
1884
|
}
|
|
1942
|
-
|
|
1943
|
-
|
|
1885
|
+
for (const [code, response] of Object.entries(operation.responses)) {
|
|
1886
|
+
if (code.startsWith("2") && response.schema) {
|
|
1887
|
+
return response.schema;
|
|
1888
|
+
}
|
|
1944
1889
|
}
|
|
1945
|
-
|
|
1946
|
-
return match?.[1]?.toLowerCase() ?? "default";
|
|
1947
|
-
}
|
|
1948
|
-
function extractUrlPathLevel(path, level) {
|
|
1949
|
-
if (!path)
|
|
1950
|
-
return "root";
|
|
1951
|
-
const segments = path.split("/").filter(Boolean);
|
|
1952
|
-
const nonParamSegments = segments.filter((s) => !s.startsWith("{"));
|
|
1953
|
-
if (nonParamSegments.length === 0)
|
|
1954
|
-
return "root";
|
|
1955
|
-
return nonParamSegments.slice(0, level).join("/");
|
|
1890
|
+
return null;
|
|
1956
1891
|
}
|
|
1957
1892
|
|
|
1958
1893
|
// src/openapi/importer/index.ts
|
|
@@ -1961,74 +1896,74 @@ var importFromOpenApi = (parseResult, contractspecOptions, importOptions = {}) =
|
|
|
1961
1896
|
const specs = [];
|
|
1962
1897
|
const skipped = [];
|
|
1963
1898
|
const errors = [];
|
|
1964
|
-
for (const
|
|
1899
|
+
for (const operation of parseResult.operations) {
|
|
1965
1900
|
if (tags && tags.length > 0) {
|
|
1966
|
-
const hasMatchingTag =
|
|
1901
|
+
const hasMatchingTag = operation.tags.some((t) => tags.includes(t));
|
|
1967
1902
|
if (!hasMatchingTag) {
|
|
1968
1903
|
skipped.push({
|
|
1969
|
-
sourceId:
|
|
1970
|
-
reason: `No matching tags (has: ${
|
|
1904
|
+
sourceId: operation.operationId,
|
|
1905
|
+
reason: `No matching tags (has: ${operation.tags.join(", ")})`
|
|
1971
1906
|
});
|
|
1972
1907
|
continue;
|
|
1973
1908
|
}
|
|
1974
1909
|
}
|
|
1975
1910
|
if (include && include.length > 0) {
|
|
1976
|
-
if (!include.includes(
|
|
1911
|
+
if (!include.includes(operation.operationId)) {
|
|
1977
1912
|
skipped.push({
|
|
1978
|
-
sourceId:
|
|
1913
|
+
sourceId: operation.operationId,
|
|
1979
1914
|
reason: "Not in include list"
|
|
1980
1915
|
});
|
|
1981
1916
|
continue;
|
|
1982
1917
|
}
|
|
1983
|
-
} else if (exclude.includes(
|
|
1918
|
+
} else if (exclude.includes(operation.operationId)) {
|
|
1984
1919
|
skipped.push({
|
|
1985
|
-
sourceId:
|
|
1920
|
+
sourceId: operation.operationId,
|
|
1986
1921
|
reason: "In exclude list"
|
|
1987
1922
|
});
|
|
1988
1923
|
continue;
|
|
1989
1924
|
}
|
|
1990
|
-
if (
|
|
1925
|
+
if (operation.deprecated && importOptions.defaultStability !== "deprecated") {
|
|
1991
1926
|
skipped.push({
|
|
1992
|
-
sourceId:
|
|
1927
|
+
sourceId: operation.operationId,
|
|
1993
1928
|
reason: "Deprecated operation"
|
|
1994
1929
|
});
|
|
1995
1930
|
continue;
|
|
1996
1931
|
}
|
|
1997
1932
|
try {
|
|
1998
|
-
const inputSchemas = buildInputSchemas(
|
|
1933
|
+
const inputSchemas = buildInputSchemas(operation);
|
|
1999
1934
|
const schemaFormat = importOptions.schemaFormat || contractspecOptions.schemaFormat || "contractspec";
|
|
2000
|
-
const inputModel = inputSchemas.body ? generateSchemaModelCode(inputSchemas.body, `${
|
|
2001
|
-
const queryModel = inputSchemas.query ? generateSchemaModelCode(inputSchemas.query, `${
|
|
2002
|
-
const paramsModel = inputSchemas.params ? generateSchemaModelCode(inputSchemas.params, `${
|
|
2003
|
-
const headersModel = inputSchemas.headers ? generateSchemaModelCode(inputSchemas.headers, `${
|
|
2004
|
-
const outputSchema = getOutputSchema(
|
|
2005
|
-
let outputModel = outputSchema ? generateSchemaModelCode(outputSchema, `${
|
|
1935
|
+
const inputModel = inputSchemas.body ? generateSchemaModelCode(inputSchemas.body, `${operation.operationId}Input`, schemaFormat, contractspecOptions) : null;
|
|
1936
|
+
const queryModel = inputSchemas.query ? generateSchemaModelCode(inputSchemas.query, `${operation.operationId}Query`, schemaFormat, contractspecOptions) : null;
|
|
1937
|
+
const paramsModel = inputSchemas.params ? generateSchemaModelCode(inputSchemas.params, `${operation.operationId}Params`, schemaFormat, contractspecOptions) : null;
|
|
1938
|
+
const headersModel = inputSchemas.headers ? generateSchemaModelCode(inputSchemas.headers, `${operation.operationId}Headers`, schemaFormat, contractspecOptions) : null;
|
|
1939
|
+
const outputSchema = getOutputSchema(operation);
|
|
1940
|
+
let outputModel = outputSchema ? generateSchemaModelCode(outputSchema, `${operation.operationId}Output`, schemaFormat, contractspecOptions) : null;
|
|
2006
1941
|
if (outputModel && schemaFormat === "contractspec" && !outputModel.code.includes("defineSchemaModel")) {
|
|
2007
1942
|
outputModel = null;
|
|
2008
1943
|
}
|
|
2009
|
-
const code = generateSpecCode(
|
|
2010
|
-
const specName = toSpecKey(
|
|
1944
|
+
const code = generateSpecCode(operation, contractspecOptions, importOptions, inputModel, outputModel, queryModel, paramsModel, headersModel);
|
|
1945
|
+
const specName = toSpecKey(operation.operationId, importOptions.prefix);
|
|
2011
1946
|
const fileName = toFileName(specName);
|
|
2012
1947
|
const transportHints = {
|
|
2013
1948
|
rest: {
|
|
2014
|
-
method:
|
|
2015
|
-
path:
|
|
1949
|
+
method: operation.method.toUpperCase(),
|
|
1950
|
+
path: operation.path,
|
|
2016
1951
|
params: {
|
|
2017
|
-
path:
|
|
2018
|
-
query:
|
|
2019
|
-
header:
|
|
2020
|
-
cookie:
|
|
1952
|
+
path: operation.pathParams.map((p) => p.name),
|
|
1953
|
+
query: operation.queryParams.map((p) => p.name),
|
|
1954
|
+
header: operation.headerParams.map((p) => p.name),
|
|
1955
|
+
cookie: operation.cookieParams.map((p) => p.name)
|
|
2021
1956
|
}
|
|
2022
1957
|
}
|
|
2023
1958
|
};
|
|
2024
1959
|
const source = {
|
|
2025
1960
|
type: "openapi",
|
|
2026
|
-
sourceId:
|
|
2027
|
-
operationId:
|
|
1961
|
+
sourceId: operation.operationId,
|
|
1962
|
+
operationId: operation.operationId,
|
|
2028
1963
|
openApiVersion: parseResult.version,
|
|
2029
1964
|
importedAt: new Date
|
|
2030
1965
|
};
|
|
2031
|
-
const groupFolder = resolveOperationGroupFolder(
|
|
1966
|
+
const groupFolder = resolveOperationGroupFolder(operation, contractspecOptions.conventions);
|
|
2032
1967
|
specs.push({
|
|
2033
1968
|
code,
|
|
2034
1969
|
fileName,
|
|
@@ -2038,7 +1973,7 @@ var importFromOpenApi = (parseResult, contractspecOptions, importOptions = {}) =
|
|
|
2038
1973
|
});
|
|
2039
1974
|
} catch (error) {
|
|
2040
1975
|
errors.push({
|
|
2041
|
-
sourceId:
|
|
1976
|
+
sourceId: operation.operationId,
|
|
2042
1977
|
error: error instanceof Error ? error.message : String(error)
|
|
2043
1978
|
});
|
|
2044
1979
|
}
|
|
@@ -2111,248 +2046,317 @@ var importFromOpenApi = (parseResult, contractspecOptions, importOptions = {}) =
|
|
|
2111
2046
|
}
|
|
2112
2047
|
};
|
|
2113
2048
|
};
|
|
2114
|
-
function importOperation(
|
|
2115
|
-
const inputSchemas = buildInputSchemas(
|
|
2049
|
+
function importOperation(operation, options = {}, contractspecOptions) {
|
|
2050
|
+
const inputSchemas = buildInputSchemas(operation);
|
|
2116
2051
|
const schemaFormat = options.schemaFormat || contractspecOptions.schemaFormat || "contractspec";
|
|
2117
|
-
const inputModel = inputSchemas.body ? generateSchemaModelCode(inputSchemas.body, `${
|
|
2118
|
-
const queryModel = inputSchemas.query ? generateSchemaModelCode(inputSchemas.query, `${
|
|
2119
|
-
const paramsModel = inputSchemas.params ? generateSchemaModelCode(inputSchemas.params, `${
|
|
2120
|
-
const headersModel = inputSchemas.headers ? generateSchemaModelCode(inputSchemas.headers, `${
|
|
2121
|
-
const outputSchema = getOutputSchema(
|
|
2122
|
-
const outputModel = outputSchema ? generateSchemaModelCode(outputSchema, `${
|
|
2123
|
-
return generateSpecCode(
|
|
2052
|
+
const inputModel = inputSchemas.body ? generateSchemaModelCode(inputSchemas.body, `${operation.operationId}Input`, schemaFormat, contractspecOptions) : null;
|
|
2053
|
+
const queryModel = inputSchemas.query ? generateSchemaModelCode(inputSchemas.query, `${operation.operationId}Query`, schemaFormat, contractspecOptions) : null;
|
|
2054
|
+
const paramsModel = inputSchemas.params ? generateSchemaModelCode(inputSchemas.params, `${operation.operationId}Params`, schemaFormat, contractspecOptions) : null;
|
|
2055
|
+
const headersModel = inputSchemas.headers ? generateSchemaModelCode(inputSchemas.headers, `${operation.operationId}Headers`, schemaFormat, contractspecOptions) : null;
|
|
2056
|
+
const outputSchema = getOutputSchema(operation);
|
|
2057
|
+
const outputModel = outputSchema ? generateSchemaModelCode(outputSchema, `${operation.operationId}Output`, schemaFormat, contractspecOptions) : null;
|
|
2058
|
+
return generateSpecCode(operation, contractspecOptions, options, inputModel, outputModel, queryModel, paramsModel, headersModel);
|
|
2124
2059
|
}
|
|
2125
|
-
// src/openapi/
|
|
2126
|
-
function
|
|
2127
|
-
|
|
2128
|
-
|
|
2060
|
+
// src/openapi/parser/resolvers.ts
|
|
2061
|
+
function isReference3(obj) {
|
|
2062
|
+
return typeof obj === "object" && obj !== null && "$ref" in obj;
|
|
2063
|
+
}
|
|
2064
|
+
function resolveRef(doc, ref) {
|
|
2065
|
+
if (!ref.startsWith("#/")) {
|
|
2066
|
+
return;
|
|
2129
2067
|
}
|
|
2130
|
-
|
|
2131
|
-
|
|
2132
|
-
|
|
2133
|
-
|
|
2134
|
-
|
|
2135
|
-
|
|
2136
|
-
|
|
2068
|
+
const path = ref.slice(2).split("/");
|
|
2069
|
+
let current = doc;
|
|
2070
|
+
for (const part of path) {
|
|
2071
|
+
if (current === null || current === undefined)
|
|
2072
|
+
return;
|
|
2073
|
+
if (typeof current !== "object")
|
|
2074
|
+
return;
|
|
2075
|
+
current = current[part];
|
|
2137
2076
|
}
|
|
2138
|
-
return
|
|
2139
|
-
path,
|
|
2140
|
-
type: changeType,
|
|
2141
|
-
oldValue,
|
|
2142
|
-
newValue,
|
|
2143
|
-
description
|
|
2144
|
-
};
|
|
2077
|
+
return current;
|
|
2145
2078
|
}
|
|
2146
|
-
function
|
|
2147
|
-
|
|
2148
|
-
|
|
2149
|
-
|
|
2150
|
-
|
|
2151
|
-
|
|
2152
|
-
|
|
2153
|
-
|
|
2154
|
-
|
|
2155
|
-
|
|
2156
|
-
|
|
2157
|
-
|
|
2079
|
+
function dereferenceSchema(doc, schema, seen = new Set) {
|
|
2080
|
+
if (!schema)
|
|
2081
|
+
return;
|
|
2082
|
+
if (isReference3(schema)) {
|
|
2083
|
+
if (seen.has(schema.$ref)) {
|
|
2084
|
+
return schema;
|
|
2085
|
+
}
|
|
2086
|
+
const newSeen = new Set(seen);
|
|
2087
|
+
newSeen.add(schema.$ref);
|
|
2088
|
+
const resolved = resolveRef(doc, schema.$ref);
|
|
2089
|
+
if (!resolved)
|
|
2090
|
+
return schema;
|
|
2091
|
+
const dereferenced = dereferenceSchema(doc, resolved, newSeen);
|
|
2092
|
+
if (!dereferenced)
|
|
2093
|
+
return schema;
|
|
2094
|
+
const refParts = schema.$ref.split("/");
|
|
2095
|
+
const typeName = refParts[refParts.length - 1];
|
|
2096
|
+
return {
|
|
2097
|
+
...dereferenced,
|
|
2098
|
+
_originalRef: schema.$ref,
|
|
2099
|
+
_originalTypeName: typeName
|
|
2100
|
+
};
|
|
2158
2101
|
}
|
|
2159
|
-
|
|
2160
|
-
|
|
2161
|
-
|
|
2162
|
-
|
|
2163
|
-
|
|
2164
|
-
|
|
2165
|
-
}
|
|
2166
|
-
|
|
2102
|
+
const schemaObj = { ...schema };
|
|
2103
|
+
if (schemaObj.properties) {
|
|
2104
|
+
const props = schemaObj.properties;
|
|
2105
|
+
const newProps = {};
|
|
2106
|
+
for (const [key, prop] of Object.entries(props)) {
|
|
2107
|
+
newProps[key] = dereferenceSchema(doc, prop, seen) ?? prop;
|
|
2108
|
+
}
|
|
2109
|
+
schemaObj.properties = newProps;
|
|
2167
2110
|
}
|
|
2168
|
-
|
|
2169
|
-
|
|
2170
|
-
|
|
2171
|
-
|
|
2172
|
-
|
|
2111
|
+
if (schemaObj.items) {
|
|
2112
|
+
schemaObj.items = dereferenceSchema(doc, schemaObj.items, seen);
|
|
2113
|
+
}
|
|
2114
|
+
const combinators = ["allOf", "anyOf", "oneOf"];
|
|
2115
|
+
for (const comb of combinators) {
|
|
2116
|
+
if (Array.isArray(schemaObj[comb])) {
|
|
2117
|
+
schemaObj[comb] = schemaObj[comb].map((s) => dereferenceSchema(doc, s, seen) ?? s);
|
|
2173
2118
|
}
|
|
2174
|
-
|
|
2175
|
-
|
|
2176
|
-
|
|
2177
|
-
|
|
2119
|
+
}
|
|
2120
|
+
return schemaObj;
|
|
2121
|
+
}
|
|
2122
|
+
|
|
2123
|
+
// src/openapi/parser/parameters.ts
|
|
2124
|
+
function parseParameters(doc, params) {
|
|
2125
|
+
const result = {
|
|
2126
|
+
path: [],
|
|
2127
|
+
query: [],
|
|
2128
|
+
header: [],
|
|
2129
|
+
cookie: []
|
|
2130
|
+
};
|
|
2131
|
+
if (!params)
|
|
2132
|
+
return result;
|
|
2133
|
+
for (const param of params) {
|
|
2134
|
+
let resolved;
|
|
2135
|
+
if (isReference3(param)) {
|
|
2136
|
+
const ref = resolveRef(doc, param.$ref);
|
|
2137
|
+
if (!ref)
|
|
2138
|
+
continue;
|
|
2139
|
+
resolved = ref;
|
|
2178
2140
|
} else {
|
|
2179
|
-
|
|
2180
|
-
if (change) {
|
|
2181
|
-
changes.push(change);
|
|
2182
|
-
}
|
|
2141
|
+
resolved = param;
|
|
2183
2142
|
}
|
|
2143
|
+
const parsed = {
|
|
2144
|
+
name: resolved.name,
|
|
2145
|
+
in: resolved.in,
|
|
2146
|
+
required: resolved.required ?? resolved.in === "path",
|
|
2147
|
+
description: resolved.description,
|
|
2148
|
+
schema: dereferenceSchema(doc, resolved.schema),
|
|
2149
|
+
deprecated: resolved.deprecated ?? false
|
|
2150
|
+
};
|
|
2151
|
+
result[resolved.in]?.push(parsed);
|
|
2184
2152
|
}
|
|
2185
|
-
return
|
|
2153
|
+
return result;
|
|
2186
2154
|
}
|
|
2187
|
-
|
|
2188
|
-
|
|
2189
|
-
|
|
2190
|
-
|
|
2191
|
-
|
|
2192
|
-
|
|
2193
|
-
|
|
2194
|
-
|
|
2195
|
-
|
|
2196
|
-
|
|
2197
|
-
|
|
2198
|
-
|
|
2199
|
-
|
|
2200
|
-
|
|
2201
|
-
|
|
2202
|
-
|
|
2203
|
-
description: "Tags changed"
|
|
2204
|
-
});
|
|
2205
|
-
}
|
|
2155
|
+
|
|
2156
|
+
// src/openapi/parser/utils.ts
|
|
2157
|
+
import { parse as parseYaml } from "yaml";
|
|
2158
|
+
var HTTP_METHODS = [
|
|
2159
|
+
"get",
|
|
2160
|
+
"post",
|
|
2161
|
+
"put",
|
|
2162
|
+
"delete",
|
|
2163
|
+
"patch",
|
|
2164
|
+
"head",
|
|
2165
|
+
"options",
|
|
2166
|
+
"trace"
|
|
2167
|
+
];
|
|
2168
|
+
function parseOpenApiString(content, format = "json") {
|
|
2169
|
+
if (format === "yaml") {
|
|
2170
|
+
return parseYaml(content);
|
|
2206
2171
|
}
|
|
2207
|
-
|
|
2208
|
-
|
|
2209
|
-
|
|
2210
|
-
|
|
2211
|
-
|
|
2212
|
-
|
|
2213
|
-
type: "modified",
|
|
2214
|
-
oldValue: specMethod,
|
|
2215
|
-
newValue: opMethod,
|
|
2216
|
-
description: "HTTP method changed"
|
|
2217
|
-
});
|
|
2218
|
-
}
|
|
2219
|
-
const specPath = spec.transport?.rest?.path;
|
|
2220
|
-
if (specPath && specPath !== operation2.path) {
|
|
2221
|
-
changes.push({
|
|
2222
|
-
path: "transport.rest.path",
|
|
2223
|
-
type: "modified",
|
|
2224
|
-
oldValue: specPath,
|
|
2225
|
-
newValue: operation2.path,
|
|
2226
|
-
description: "Path changed"
|
|
2227
|
-
});
|
|
2228
|
-
}
|
|
2172
|
+
return JSON.parse(content);
|
|
2173
|
+
}
|
|
2174
|
+
function detectFormat(content) {
|
|
2175
|
+
const trimmed = content.trim();
|
|
2176
|
+
if (trimmed.startsWith("{") || trimmed.startsWith("[")) {
|
|
2177
|
+
return "json";
|
|
2229
2178
|
}
|
|
2230
|
-
|
|
2231
|
-
|
|
2232
|
-
|
|
2233
|
-
|
|
2234
|
-
|
|
2235
|
-
|
|
2236
|
-
newValue: operation2.deprecated ? "deprecated" : "stable",
|
|
2237
|
-
description: "Deprecation status changed"
|
|
2238
|
-
});
|
|
2179
|
+
return "yaml";
|
|
2180
|
+
}
|
|
2181
|
+
function detectVersion(doc) {
|
|
2182
|
+
const version = doc.openapi;
|
|
2183
|
+
if (version.startsWith("3.1")) {
|
|
2184
|
+
return "3.1";
|
|
2239
2185
|
}
|
|
2240
|
-
return
|
|
2186
|
+
return "3.0";
|
|
2241
2187
|
}
|
|
2242
|
-
function
|
|
2243
|
-
const
|
|
2244
|
-
|
|
2245
|
-
|
|
2246
|
-
|
|
2247
|
-
|
|
2248
|
-
...options.ignoreDescriptions ? ["meta.description", "meta.goal", "meta.context"] : [],
|
|
2249
|
-
...options.ignoreTags ? ["meta.tags"] : []
|
|
2250
|
-
]
|
|
2188
|
+
function generateOperationId(method, path) {
|
|
2189
|
+
const pathParts = path.split("/").filter(Boolean).map((part) => {
|
|
2190
|
+
if (part.startsWith("{") && part.endsWith("}")) {
|
|
2191
|
+
return "By" + part.slice(1, -1).charAt(0).toUpperCase() + part.slice(2, -1);
|
|
2192
|
+
}
|
|
2193
|
+
return part.charAt(0).toUpperCase() + part.slice(1);
|
|
2251
2194
|
});
|
|
2252
|
-
|
|
2253
|
-
if (!options.ignoreTransport) {
|
|
2254
|
-
const transportChanges = diffObjects("transport", oldSpec.transport, newSpec.transport, options);
|
|
2255
|
-
changes.push(...transportChanges);
|
|
2256
|
-
}
|
|
2257
|
-
const policyChanges = diffObjects("policy", oldSpec.policy, newSpec.policy, options);
|
|
2258
|
-
changes.push(...policyChanges);
|
|
2259
|
-
return changes;
|
|
2195
|
+
return method + pathParts.join("");
|
|
2260
2196
|
}
|
|
2261
|
-
|
|
2262
|
-
|
|
2263
|
-
|
|
2264
|
-
|
|
2265
|
-
|
|
2266
|
-
|
|
2267
|
-
|
|
2268
|
-
|
|
2269
|
-
|
|
2270
|
-
|
|
2271
|
-
|
|
2272
|
-
|
|
2273
|
-
|
|
2274
|
-
|
|
2275
|
-
|
|
2276
|
-
|
|
2277
|
-
|
|
2278
|
-
changes = [
|
|
2279
|
-
{
|
|
2280
|
-
path: "",
|
|
2281
|
-
type: "added",
|
|
2282
|
-
newValue: incoming.operationSpec ?? incoming.code,
|
|
2283
|
-
description: "New spec imported from OpenAPI"
|
|
2197
|
+
|
|
2198
|
+
// src/openapi/parser/operation.ts
|
|
2199
|
+
function parseOperation(doc, method, path, operation, pathParams) {
|
|
2200
|
+
const allParams = [...pathParams ?? [], ...operation.parameters ?? []];
|
|
2201
|
+
const params = parseParameters(doc, allParams);
|
|
2202
|
+
let requestBody;
|
|
2203
|
+
if (operation.requestBody) {
|
|
2204
|
+
const body = isReference3(operation.requestBody) ? resolveRef(doc, operation.requestBody.$ref) : operation.requestBody;
|
|
2205
|
+
if (body) {
|
|
2206
|
+
const contentType = Object.keys(body.content ?? {})[0] ?? "application/json";
|
|
2207
|
+
const content = body.content?.[contentType];
|
|
2208
|
+
if (content?.schema) {
|
|
2209
|
+
requestBody = {
|
|
2210
|
+
required: body.required ?? false,
|
|
2211
|
+
schema: dereferenceSchema(doc, content.schema) ?? {},
|
|
2212
|
+
contentType
|
|
2213
|
+
};
|
|
2284
2214
|
}
|
|
2285
|
-
|
|
2215
|
+
}
|
|
2216
|
+
}
|
|
2217
|
+
const responses = {};
|
|
2218
|
+
for (const [status, response] of Object.entries(operation.responses ?? {})) {
|
|
2219
|
+
const resolved = isReference3(response) ? resolveRef(doc, response.$ref) : response;
|
|
2220
|
+
if (resolved) {
|
|
2221
|
+
const contentType = Object.keys(resolved.content ?? {})[0];
|
|
2222
|
+
const content = contentType ? resolved.content?.[contentType] : undefined;
|
|
2223
|
+
responses[status] = {
|
|
2224
|
+
description: resolved.description,
|
|
2225
|
+
schema: content?.schema ? dereferenceSchema(doc, content.schema) : undefined,
|
|
2226
|
+
contentType
|
|
2227
|
+
};
|
|
2228
|
+
}
|
|
2286
2229
|
}
|
|
2230
|
+
const contractSpecMeta = operation?.["x-contractspec"];
|
|
2287
2231
|
return {
|
|
2288
|
-
operationId,
|
|
2289
|
-
|
|
2290
|
-
|
|
2291
|
-
|
|
2292
|
-
|
|
2232
|
+
operationId: operation.operationId ?? generateOperationId(method, path),
|
|
2233
|
+
method,
|
|
2234
|
+
path,
|
|
2235
|
+
summary: operation.summary,
|
|
2236
|
+
description: operation.description,
|
|
2237
|
+
tags: operation.tags ?? [],
|
|
2238
|
+
pathParams: params.path,
|
|
2239
|
+
queryParams: params.query,
|
|
2240
|
+
headerParams: params.header,
|
|
2241
|
+
cookieParams: params.cookie,
|
|
2242
|
+
requestBody,
|
|
2243
|
+
responses,
|
|
2244
|
+
deprecated: operation.deprecated ?? false,
|
|
2245
|
+
security: operation.security,
|
|
2246
|
+
contractSpecMeta
|
|
2293
2247
|
};
|
|
2294
2248
|
}
|
|
2295
|
-
|
|
2296
|
-
|
|
2297
|
-
|
|
2298
|
-
|
|
2299
|
-
|
|
2300
|
-
|
|
2301
|
-
|
|
2302
|
-
|
|
2303
|
-
|
|
2304
|
-
|
|
2305
|
-
|
|
2306
|
-
|
|
2249
|
+
|
|
2250
|
+
// src/openapi/parser/document.ts
|
|
2251
|
+
function parseOpenApiDocument(doc, _options = {}) {
|
|
2252
|
+
const version = detectVersion(doc);
|
|
2253
|
+
const warnings = [];
|
|
2254
|
+
const operations = [];
|
|
2255
|
+
for (const [path, pathItem] of Object.entries(doc.paths ?? {})) {
|
|
2256
|
+
if (!pathItem)
|
|
2257
|
+
continue;
|
|
2258
|
+
const pathParams = pathItem.parameters;
|
|
2259
|
+
for (const method of HTTP_METHODS) {
|
|
2260
|
+
const operation = pathItem[method];
|
|
2261
|
+
if (operation) {
|
|
2262
|
+
try {
|
|
2263
|
+
operations.push(parseOperation(doc, method, path, operation, pathParams));
|
|
2264
|
+
} catch (error) {
|
|
2265
|
+
warnings.push(`Failed to parse ${method.toUpperCase()} ${path}: ${error}`);
|
|
2266
|
+
}
|
|
2307
2267
|
}
|
|
2308
2268
|
}
|
|
2309
|
-
diffs.push(createSpecDiff(operationId, existing, imported, options));
|
|
2310
2269
|
}
|
|
2311
|
-
|
|
2312
|
-
|
|
2313
|
-
|
|
2314
|
-
|
|
2315
|
-
|
|
2316
|
-
incoming: undefined,
|
|
2317
|
-
changes: [
|
|
2318
|
-
{
|
|
2319
|
-
path: "",
|
|
2320
|
-
type: "removed",
|
|
2321
|
-
oldValue: spec,
|
|
2322
|
-
description: "Spec no longer exists in OpenAPI source"
|
|
2323
|
-
}
|
|
2324
|
-
],
|
|
2325
|
-
isEquivalent: false
|
|
2326
|
-
});
|
|
2270
|
+
const schemas2 = {};
|
|
2271
|
+
const components = doc.components;
|
|
2272
|
+
if (components?.schemas) {
|
|
2273
|
+
for (const [name, schema] of Object.entries(components.schemas)) {
|
|
2274
|
+
schemas2[name] = schema;
|
|
2327
2275
|
}
|
|
2328
2276
|
}
|
|
2329
|
-
|
|
2330
|
-
|
|
2331
|
-
|
|
2332
|
-
|
|
2333
|
-
|
|
2277
|
+
const servers = (doc.servers ?? []).map((s) => ({
|
|
2278
|
+
url: s.url,
|
|
2279
|
+
description: s.description,
|
|
2280
|
+
variables: s.variables
|
|
2281
|
+
}));
|
|
2282
|
+
const events2 = [];
|
|
2283
|
+
if ("webhooks" in doc && doc.webhooks) {
|
|
2284
|
+
for (const [name, pathItem] of Object.entries(doc.webhooks)) {
|
|
2285
|
+
if (typeof pathItem !== "object" || !pathItem)
|
|
2286
|
+
continue;
|
|
2287
|
+
const operation = pathItem["post"];
|
|
2288
|
+
if (operation && operation.requestBody) {
|
|
2289
|
+
if ("$ref" in operation.requestBody) {
|
|
2290
|
+
throw new Error(`'$ref' isn't supported`);
|
|
2291
|
+
}
|
|
2292
|
+
const content = operation.requestBody.content?.["application/json"];
|
|
2293
|
+
if (content?.schema) {
|
|
2294
|
+
events2.push({
|
|
2295
|
+
name,
|
|
2296
|
+
description: operation.summary || operation.description,
|
|
2297
|
+
payload: content.schema
|
|
2298
|
+
});
|
|
2299
|
+
}
|
|
2300
|
+
}
|
|
2301
|
+
}
|
|
2334
2302
|
}
|
|
2335
|
-
|
|
2336
|
-
|
|
2337
|
-
|
|
2338
|
-
|
|
2339
|
-
|
|
2340
|
-
|
|
2341
|
-
|
|
2342
|
-
|
|
2343
|
-
|
|
2344
|
-
|
|
2345
|
-
|
|
2346
|
-
|
|
2347
|
-
|
|
2348
|
-
|
|
2349
|
-
|
|
2350
|
-
|
|
2351
|
-
|
|
2303
|
+
return {
|
|
2304
|
+
document: doc,
|
|
2305
|
+
version,
|
|
2306
|
+
info: {
|
|
2307
|
+
title: doc.info.title,
|
|
2308
|
+
version: doc.info.version,
|
|
2309
|
+
description: doc.info.description
|
|
2310
|
+
},
|
|
2311
|
+
operations,
|
|
2312
|
+
schemas: schemas2,
|
|
2313
|
+
servers,
|
|
2314
|
+
warnings,
|
|
2315
|
+
events: events2
|
|
2316
|
+
};
|
|
2317
|
+
}
|
|
2318
|
+
async function parseOpenApi(source, options = {}) {
|
|
2319
|
+
const {
|
|
2320
|
+
fetch: fetchFn = globalThis.fetch,
|
|
2321
|
+
readFile,
|
|
2322
|
+
timeout = 30000
|
|
2323
|
+
} = options;
|
|
2324
|
+
let content;
|
|
2325
|
+
let format;
|
|
2326
|
+
if (source.startsWith("http://") || source.startsWith("https://")) {
|
|
2327
|
+
const controller = new AbortController;
|
|
2328
|
+
const timeoutId = setTimeout(() => controller.abort(), timeout);
|
|
2329
|
+
try {
|
|
2330
|
+
const response = await fetchFn(source, { signal: controller.signal });
|
|
2331
|
+
if (!response.ok) {
|
|
2332
|
+
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
|
|
2333
|
+
}
|
|
2334
|
+
content = await response.text();
|
|
2335
|
+
} finally {
|
|
2336
|
+
clearTimeout(timeoutId);
|
|
2337
|
+
}
|
|
2338
|
+
if (source.endsWith(".yaml") || source.endsWith(".yml")) {
|
|
2339
|
+
format = "yaml";
|
|
2340
|
+
} else if (source.endsWith(".json")) {
|
|
2341
|
+
format = "json";
|
|
2342
|
+
} else {
|
|
2343
|
+
format = detectFormat(content);
|
|
2344
|
+
}
|
|
2345
|
+
} else {
|
|
2346
|
+
if (!readFile) {
|
|
2347
|
+
throw new Error("readFile adapter required for file paths");
|
|
2348
|
+
}
|
|
2349
|
+
content = await readFile(source);
|
|
2350
|
+
if (source.endsWith(".yaml") || source.endsWith(".yml")) {
|
|
2351
|
+
format = "yaml";
|
|
2352
|
+
} else if (source.endsWith(".json")) {
|
|
2353
|
+
format = "json";
|
|
2354
|
+
} else {
|
|
2355
|
+
format = detectFormat(content);
|
|
2352
2356
|
}
|
|
2353
2357
|
}
|
|
2354
|
-
|
|
2355
|
-
|
|
2358
|
+
const doc = parseOpenApiString(content, format);
|
|
2359
|
+
return parseOpenApiDocument(doc, options);
|
|
2356
2360
|
}
|
|
2357
2361
|
export {
|
|
2358
2362
|
toValidIdentifier,
|