@lssm/lib.contracts-transformers 0.0.0-canary-20251217080011 → 1.41.0
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/dist/common/index.js +1 -3
- package/dist/common/utils.js +1 -102
- package/dist/index.js +1 -9
- package/dist/openapi/differ.js +2 -214
- package/dist/openapi/exporter.js +1 -150
- package/dist/openapi/importer.js +2 -264
- package/dist/openapi/index.js +1 -7
- package/dist/openapi/parser.js +1 -231
- package/dist/openapi/schema-converter.js +4 -201
- package/package.json +8 -9
- package/dist/common/index.d.ts +0 -3
- package/dist/common/types.d.ts +0 -152
- package/dist/common/utils.d.ts +0 -51
- package/dist/index.d.ts +0 -9
- package/dist/openapi/differ.d.ts +0 -41
- package/dist/openapi/exporter.d.ts +0 -27
- package/dist/openapi/importer.d.ts +0 -15
- package/dist/openapi/index.d.ts +0 -7
- package/dist/openapi/parser.d.ts +0 -31
- package/dist/openapi/schema-converter.d.ts +0 -69
- package/dist/openapi/types.d.ts +0 -221
package/dist/common/index.js
CHANGED
|
@@ -1,3 +1 @@
|
|
|
1
|
-
import { deepEqual, extractPathParams, getByPath, normalizePath, toCamelCase, toFileName, toKebabCase, toPascalCase, toSnakeCase, toSpecName, toValidIdentifier
|
|
2
|
-
|
|
3
|
-
export { deepEqual, extractPathParams, getByPath, normalizePath, toCamelCase, toFileName, toKebabCase, toPascalCase, toSnakeCase, toSpecName, toValidIdentifier };
|
|
1
|
+
import{deepEqual as e,extractPathParams as t,getByPath as n,normalizePath as r,toCamelCase as i,toFileName as a,toKebabCase as o,toPascalCase as s,toSnakeCase as c,toSpecName as l,toValidIdentifier as u}from"./utils.js";export{e as deepEqual,t as extractPathParams,n as getByPath,r as normalizePath,i as toCamelCase,a as toFileName,o as toKebabCase,s as toPascalCase,c as toSnakeCase,l as toSpecName,u as toValidIdentifier};
|
package/dist/common/utils.js
CHANGED
|
@@ -1,102 +1 @@
|
|
|
1
|
-
|
|
2
|
-
/**
|
|
3
|
-
* Common utilities for contract transformations.
|
|
4
|
-
*/
|
|
5
|
-
/**
|
|
6
|
-
* Convert a string to PascalCase.
|
|
7
|
-
*/
|
|
8
|
-
function toPascalCase(str) {
|
|
9
|
-
return str.replace(/[-_./\s]+(.)?/g, (_, c) => c ? c.toUpperCase() : "").replace(/^./, (c) => c.toUpperCase());
|
|
10
|
-
}
|
|
11
|
-
/**
|
|
12
|
-
* Convert a string to camelCase.
|
|
13
|
-
*/
|
|
14
|
-
function toCamelCase(str) {
|
|
15
|
-
const pascal = toPascalCase(str);
|
|
16
|
-
return pascal.charAt(0).toLowerCase() + pascal.slice(1);
|
|
17
|
-
}
|
|
18
|
-
/**
|
|
19
|
-
* Convert a string to kebab-case.
|
|
20
|
-
*/
|
|
21
|
-
function toKebabCase(str) {
|
|
22
|
-
return str.replace(/([a-z])([A-Z])/g, "$1-$2").replace(/[\s_./]+/g, "-").toLowerCase();
|
|
23
|
-
}
|
|
24
|
-
/**
|
|
25
|
-
* Convert a string to snake_case.
|
|
26
|
-
*/
|
|
27
|
-
function toSnakeCase(str) {
|
|
28
|
-
return str.replace(/([a-z])([A-Z])/g, "$1_$2").replace(/[\s\-./]+/g, "_").toLowerCase();
|
|
29
|
-
}
|
|
30
|
-
/**
|
|
31
|
-
* Sanitize a string to be a valid TypeScript identifier.
|
|
32
|
-
*/
|
|
33
|
-
function toValidIdentifier(str) {
|
|
34
|
-
let result = str.replace(/[^a-zA-Z0-9_$]/g, "_");
|
|
35
|
-
if (/^[0-9]/.test(result)) result = "_" + result;
|
|
36
|
-
return result;
|
|
37
|
-
}
|
|
38
|
-
/**
|
|
39
|
-
* Generate a ContractSpec name from an operation identifier.
|
|
40
|
-
*/
|
|
41
|
-
function toSpecName(operationId, prefix) {
|
|
42
|
-
const name = toCamelCase(operationId);
|
|
43
|
-
return prefix ? `${prefix}.${name}` : name;
|
|
44
|
-
}
|
|
45
|
-
/**
|
|
46
|
-
* Generate a file name from a spec name.
|
|
47
|
-
*/
|
|
48
|
-
function toFileName(specName) {
|
|
49
|
-
return toKebabCase(specName.replace(/\./g, "-")) + ".ts";
|
|
50
|
-
}
|
|
51
|
-
/**
|
|
52
|
-
* Deep equality check for objects.
|
|
53
|
-
*/
|
|
54
|
-
function deepEqual(a, b) {
|
|
55
|
-
if (a === b) return true;
|
|
56
|
-
if (a === null || b === null) return false;
|
|
57
|
-
if (typeof a !== typeof b) return false;
|
|
58
|
-
if (typeof a === "object") {
|
|
59
|
-
const aObj = a;
|
|
60
|
-
const bObj = b;
|
|
61
|
-
const aKeys = Object.keys(aObj);
|
|
62
|
-
const bKeys = Object.keys(bObj);
|
|
63
|
-
if (aKeys.length !== bKeys.length) return false;
|
|
64
|
-
for (const key of aKeys) {
|
|
65
|
-
if (!bKeys.includes(key)) return false;
|
|
66
|
-
if (!deepEqual(aObj[key], bObj[key])) return false;
|
|
67
|
-
}
|
|
68
|
-
return true;
|
|
69
|
-
}
|
|
70
|
-
return false;
|
|
71
|
-
}
|
|
72
|
-
/**
|
|
73
|
-
* Get a value from an object by JSON path.
|
|
74
|
-
*/
|
|
75
|
-
function getByPath(obj, path) {
|
|
76
|
-
const parts = path.split(".").filter(Boolean);
|
|
77
|
-
let current = obj;
|
|
78
|
-
for (const part of parts) {
|
|
79
|
-
if (current === null || current === void 0) return void 0;
|
|
80
|
-
if (typeof current !== "object") return void 0;
|
|
81
|
-
current = current[part];
|
|
82
|
-
}
|
|
83
|
-
return current;
|
|
84
|
-
}
|
|
85
|
-
/**
|
|
86
|
-
* Extract path parameters from a URL path template.
|
|
87
|
-
* e.g., "/users/{userId}/orders/{orderId}" -> ["userId", "orderId"]
|
|
88
|
-
*/
|
|
89
|
-
function extractPathParams(path) {
|
|
90
|
-
return (path.match(/\{([^}]+)\}/g) || []).map((m) => m.slice(1, -1));
|
|
91
|
-
}
|
|
92
|
-
/**
|
|
93
|
-
* Normalize a URL path for comparison.
|
|
94
|
-
*/
|
|
95
|
-
function normalizePath(path) {
|
|
96
|
-
let normalized = path.replace(/^\/+|\/+$/g, "");
|
|
97
|
-
normalized = normalized.replace(/\/+/g, "/");
|
|
98
|
-
return "/" + normalized;
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
//#endregion
|
|
102
|
-
export { deepEqual, extractPathParams, getByPath, normalizePath, toCamelCase, toFileName, toKebabCase, toPascalCase, toSnakeCase, toSpecName, toValidIdentifier };
|
|
1
|
+
function e(e){return e.replace(/[-_./\s]+(.)?/g,(e,t)=>t?t.toUpperCase():``).replace(/^./,e=>e.toUpperCase())}function t(t){let n=e(t);return n.charAt(0).toLowerCase()+n.slice(1)}function n(e){return e.replace(/([a-z])([A-Z])/g,`$1-$2`).replace(/[\s_./]+/g,`-`).toLowerCase()}function r(e){return e.replace(/([a-z])([A-Z])/g,`$1_$2`).replace(/[\s\-./]+/g,`_`).toLowerCase()}function i(e){let t=e.replace(/[^a-zA-Z0-9_$]/g,`_`);return/^[0-9]/.test(t)&&(t=`_`+t),t}function a(e,n){let r=t(e);return n?`${n}.${r}`:r}function o(e){return n(e.replace(/\./g,`-`))+`.ts`}function s(e,t){if(e===t)return!0;if(e===null||t===null||typeof e!=typeof t)return!1;if(typeof e==`object`){let n=e,r=t,i=Object.keys(n),a=Object.keys(r);if(i.length!==a.length)return!1;for(let e of i)if(!a.includes(e)||!s(n[e],r[e]))return!1;return!0}return!1}function c(e,t){let n=t.split(`.`).filter(Boolean),r=e;for(let e of n){if(typeof r!=`object`||!r)return;r=r[e]}return r}function l(e){return(e.match(/\{([^}]+)\}/g)||[]).map(e=>e.slice(1,-1))}function u(e){let t=e.replace(/^\/+|\/+$/g,``);return t=t.replace(/\/+/g,`/`),`/`+t}export{s as deepEqual,l as extractPathParams,c as getByPath,u as normalizePath,t as toCamelCase,o as toFileName,n as toKebabCase,e as toPascalCase,r as toSnakeCase,a as toSpecName,i as toValidIdentifier};
|
package/dist/index.js
CHANGED
|
@@ -1,9 +1 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { defaultRestPath, openApiForRegistry, openApiToJson, openApiToYaml } from "./openapi/exporter.js";
|
|
3
|
-
import { deepEqual, extractPathParams, getByPath, normalizePath, toCamelCase, toFileName, toKebabCase, toPascalCase, toSnakeCase, toSpecName, toValidIdentifier } from "./common/utils.js";
|
|
4
|
-
import { generateImports, generateSchemaModelCode, getScalarType, jsonSchemaToField, jsonSchemaToType } from "./openapi/schema-converter.js";
|
|
5
|
-
import { importFromOpenApi, importOperation } from "./openapi/importer.js";
|
|
6
|
-
import { createSpecDiff, diffAll, diffSpecVsOperation, diffSpecs, formatDiffChanges } from "./openapi/differ.js";
|
|
7
|
-
import "./openapi/index.js";
|
|
8
|
-
|
|
9
|
-
export { createSpecDiff, deepEqual, defaultRestPath, detectFormat, detectVersion, diffAll, diffSpecVsOperation, diffSpecs, extractPathParams, formatDiffChanges, generateImports, generateSchemaModelCode, getByPath, getScalarType, importFromOpenApi, importOperation, jsonSchemaToField, jsonSchemaToType, normalizePath, openApiForRegistry, openApiToJson, openApiToYaml, parseOpenApi, parseOpenApiDocument, parseOpenApiString, toCamelCase, toFileName, toKebabCase, toPascalCase, toSnakeCase, toSpecName, toValidIdentifier };
|
|
1
|
+
import{detectFormat as e,detectVersion as t,parseOpenApi as n,parseOpenApiDocument as r,parseOpenApiString as i}from"./openapi/parser.js";import{defaultRestPath as a,openApiForRegistry as o,openApiToJson as s,openApiToYaml as c}from"./openapi/exporter.js";import{deepEqual as l,extractPathParams as u,getByPath as d,normalizePath as f,toCamelCase as p,toFileName as m,toKebabCase as h,toPascalCase as g,toSnakeCase as _,toSpecName as v,toValidIdentifier as y}from"./common/utils.js";import{generateImports as b,generateSchemaModelCode as x,getScalarType as S,jsonSchemaToField as C,jsonSchemaToType as w}from"./openapi/schema-converter.js";import{importFromOpenApi as T,importOperation as E}from"./openapi/importer.js";import{createSpecDiff as D,diffAll as O,diffSpecVsOperation as k,diffSpecs as A,formatDiffChanges as j}from"./openapi/differ.js";import"./openapi/index.js";export{D as createSpecDiff,l as deepEqual,a as defaultRestPath,e as detectFormat,t as detectVersion,O as diffAll,k as diffSpecVsOperation,A as diffSpecs,u as extractPathParams,j as formatDiffChanges,b as generateImports,x as generateSchemaModelCode,d as getByPath,S as getScalarType,T as importFromOpenApi,E as importOperation,C as jsonSchemaToField,w as jsonSchemaToType,f as normalizePath,o as openApiForRegistry,s as openApiToJson,c as openApiToYaml,n as parseOpenApi,r as parseOpenApiDocument,i as parseOpenApiString,p as toCamelCase,m as toFileName,h as toKebabCase,g as toPascalCase,_ as toSnakeCase,v as toSpecName,y as toValidIdentifier};
|
package/dist/openapi/differ.js
CHANGED
|
@@ -1,214 +1,2 @@
|
|
|
1
|
-
import
|
|
2
|
-
|
|
3
|
-
//#region src/openapi/differ.ts
|
|
4
|
-
/**
|
|
5
|
-
* Compare two values and generate a diff change if different.
|
|
6
|
-
*/
|
|
7
|
-
function compareValues(path, oldValue, newValue, description) {
|
|
8
|
-
if (deepEqual(oldValue, newValue)) return null;
|
|
9
|
-
let changeType = "modified";
|
|
10
|
-
if (oldValue === void 0 || oldValue === null) changeType = "added";
|
|
11
|
-
else if (newValue === void 0 || newValue === null) changeType = "removed";
|
|
12
|
-
else if (typeof oldValue !== typeof newValue) changeType = "type_changed";
|
|
13
|
-
return {
|
|
14
|
-
path,
|
|
15
|
-
type: changeType,
|
|
16
|
-
oldValue,
|
|
17
|
-
newValue,
|
|
18
|
-
description
|
|
19
|
-
};
|
|
20
|
-
}
|
|
21
|
-
/**
|
|
22
|
-
* Diff two objects recursively.
|
|
23
|
-
*/
|
|
24
|
-
function diffObjects(path, oldObj, newObj, options) {
|
|
25
|
-
const changes = [];
|
|
26
|
-
if (!oldObj && !newObj) return changes;
|
|
27
|
-
if (!oldObj) {
|
|
28
|
-
changes.push({
|
|
29
|
-
path,
|
|
30
|
-
type: "added",
|
|
31
|
-
newValue: newObj,
|
|
32
|
-
description: `Added ${path}`
|
|
33
|
-
});
|
|
34
|
-
return changes;
|
|
35
|
-
}
|
|
36
|
-
if (!newObj) {
|
|
37
|
-
changes.push({
|
|
38
|
-
path,
|
|
39
|
-
type: "removed",
|
|
40
|
-
oldValue: oldObj,
|
|
41
|
-
description: `Removed ${path}`
|
|
42
|
-
});
|
|
43
|
-
return changes;
|
|
44
|
-
}
|
|
45
|
-
const allKeys = new Set([...Object.keys(oldObj), ...Object.keys(newObj)]);
|
|
46
|
-
for (const key of allKeys) {
|
|
47
|
-
const keyPath = path ? `${path}.${key}` : key;
|
|
48
|
-
if (options.ignorePaths?.some((p) => keyPath.startsWith(p))) continue;
|
|
49
|
-
const oldVal = oldObj[key];
|
|
50
|
-
const newVal = newObj[key];
|
|
51
|
-
if (typeof oldVal === "object" && typeof newVal === "object") changes.push(...diffObjects(keyPath, oldVal, newVal, options));
|
|
52
|
-
else {
|
|
53
|
-
const change = compareValues(keyPath, oldVal, newVal, `Changed ${keyPath}`);
|
|
54
|
-
if (change) changes.push(change);
|
|
55
|
-
}
|
|
56
|
-
}
|
|
57
|
-
return changes;
|
|
58
|
-
}
|
|
59
|
-
/**
|
|
60
|
-
* Diff a ContractSpec against an OpenAPI operation.
|
|
61
|
-
*/
|
|
62
|
-
function diffSpecVsOperation(spec, operation, options = {}) {
|
|
63
|
-
const changes = [];
|
|
64
|
-
if (!options.ignoreDescriptions) {
|
|
65
|
-
const descChange = compareValues("meta.description", spec.meta.description, operation.summary ?? operation.description, "Description changed");
|
|
66
|
-
if (descChange) changes.push(descChange);
|
|
67
|
-
}
|
|
68
|
-
if (!options.ignoreTags) {
|
|
69
|
-
const oldTags = [...spec.meta.tags ?? []].sort();
|
|
70
|
-
const newTags = [...operation.tags].sort();
|
|
71
|
-
if (!deepEqual(oldTags, newTags)) changes.push({
|
|
72
|
-
path: "meta.tags",
|
|
73
|
-
type: "modified",
|
|
74
|
-
oldValue: oldTags,
|
|
75
|
-
newValue: newTags,
|
|
76
|
-
description: "Tags changed"
|
|
77
|
-
});
|
|
78
|
-
}
|
|
79
|
-
if (!options.ignoreTransport) {
|
|
80
|
-
const specMethod = spec.transport?.rest?.method ?? (spec.meta.kind === "query" ? "GET" : "POST");
|
|
81
|
-
const opMethod = operation.method.toUpperCase();
|
|
82
|
-
if (specMethod !== opMethod) changes.push({
|
|
83
|
-
path: "transport.rest.method",
|
|
84
|
-
type: "modified",
|
|
85
|
-
oldValue: specMethod,
|
|
86
|
-
newValue: opMethod,
|
|
87
|
-
description: "HTTP method changed"
|
|
88
|
-
});
|
|
89
|
-
const specPath = spec.transport?.rest?.path;
|
|
90
|
-
if (specPath && specPath !== operation.path) changes.push({
|
|
91
|
-
path: "transport.rest.path",
|
|
92
|
-
type: "modified",
|
|
93
|
-
oldValue: specPath,
|
|
94
|
-
newValue: operation.path,
|
|
95
|
-
description: "Path changed"
|
|
96
|
-
});
|
|
97
|
-
}
|
|
98
|
-
if (spec.meta.stability === "deprecated" !== operation.deprecated) changes.push({
|
|
99
|
-
path: "meta.stability",
|
|
100
|
-
type: "modified",
|
|
101
|
-
oldValue: spec.meta.stability,
|
|
102
|
-
newValue: operation.deprecated ? "deprecated" : "stable",
|
|
103
|
-
description: "Deprecation status changed"
|
|
104
|
-
});
|
|
105
|
-
return changes;
|
|
106
|
-
}
|
|
107
|
-
/**
|
|
108
|
-
* Diff two ContractSpecs.
|
|
109
|
-
*/
|
|
110
|
-
function diffSpecs(oldSpec, newSpec, options = {}) {
|
|
111
|
-
const changes = [];
|
|
112
|
-
const metaChanges = diffObjects("meta", oldSpec.meta, newSpec.meta, {
|
|
113
|
-
...options,
|
|
114
|
-
ignorePaths: [
|
|
115
|
-
...options.ignorePaths ?? [],
|
|
116
|
-
...options.ignoreDescriptions ? [
|
|
117
|
-
"meta.description",
|
|
118
|
-
"meta.goal",
|
|
119
|
-
"meta.context"
|
|
120
|
-
] : [],
|
|
121
|
-
...options.ignoreTags ? ["meta.tags"] : []
|
|
122
|
-
]
|
|
123
|
-
});
|
|
124
|
-
changes.push(...metaChanges);
|
|
125
|
-
if (!options.ignoreTransport) {
|
|
126
|
-
const transportChanges = diffObjects("transport", oldSpec.transport, newSpec.transport, options);
|
|
127
|
-
changes.push(...transportChanges);
|
|
128
|
-
}
|
|
129
|
-
const policyChanges = diffObjects("policy", oldSpec.policy, newSpec.policy, options);
|
|
130
|
-
changes.push(...policyChanges);
|
|
131
|
-
return changes;
|
|
132
|
-
}
|
|
133
|
-
/**
|
|
134
|
-
* Create a SpecDiff from an existing spec and an imported spec.
|
|
135
|
-
*/
|
|
136
|
-
function createSpecDiff(operationId, existing, incoming, options = {}) {
|
|
137
|
-
let changes = [];
|
|
138
|
-
let isEquivalent = false;
|
|
139
|
-
if (existing) {
|
|
140
|
-
changes = diffSpecs(existing, incoming.spec, options);
|
|
141
|
-
isEquivalent = changes.length === 0;
|
|
142
|
-
} else changes = [{
|
|
143
|
-
path: "",
|
|
144
|
-
type: "added",
|
|
145
|
-
newValue: incoming.spec,
|
|
146
|
-
description: "New spec imported from OpenAPI"
|
|
147
|
-
}];
|
|
148
|
-
return {
|
|
149
|
-
operationId,
|
|
150
|
-
existing,
|
|
151
|
-
incoming,
|
|
152
|
-
changes,
|
|
153
|
-
isEquivalent
|
|
154
|
-
};
|
|
155
|
-
}
|
|
156
|
-
/**
|
|
157
|
-
* Batch diff multiple specs against OpenAPI operations.
|
|
158
|
-
*/
|
|
159
|
-
function diffAll(existingSpecs, importedSpecs, options = {}) {
|
|
160
|
-
const diffs = [];
|
|
161
|
-
const matchedExisting = /* @__PURE__ */ new Set();
|
|
162
|
-
for (const imported of importedSpecs) {
|
|
163
|
-
const operationId = imported.source.sourceId;
|
|
164
|
-
let existing;
|
|
165
|
-
for (const [key, spec] of existingSpecs) {
|
|
166
|
-
const specName = spec.meta.name;
|
|
167
|
-
if (key === operationId || specName.includes(operationId)) {
|
|
168
|
-
existing = spec;
|
|
169
|
-
matchedExisting.add(key);
|
|
170
|
-
break;
|
|
171
|
-
}
|
|
172
|
-
}
|
|
173
|
-
diffs.push(createSpecDiff(operationId, existing, imported, options));
|
|
174
|
-
}
|
|
175
|
-
for (const [key, spec] of existingSpecs) if (!matchedExisting.has(key)) diffs.push({
|
|
176
|
-
operationId: key,
|
|
177
|
-
existing: spec,
|
|
178
|
-
incoming: void 0,
|
|
179
|
-
changes: [{
|
|
180
|
-
path: "",
|
|
181
|
-
type: "removed",
|
|
182
|
-
oldValue: spec,
|
|
183
|
-
description: "Spec no longer exists in OpenAPI source"
|
|
184
|
-
}],
|
|
185
|
-
isEquivalent: false
|
|
186
|
-
});
|
|
187
|
-
return diffs;
|
|
188
|
-
}
|
|
189
|
-
/**
|
|
190
|
-
* Format diff changes for display.
|
|
191
|
-
*/
|
|
192
|
-
function formatDiffChanges(changes) {
|
|
193
|
-
if (changes.length === 0) return "No changes detected";
|
|
194
|
-
const lines = [];
|
|
195
|
-
for (const change of changes) {
|
|
196
|
-
const prefix = {
|
|
197
|
-
added: "+",
|
|
198
|
-
removed: "-",
|
|
199
|
-
modified: "~",
|
|
200
|
-
type_changed: "!",
|
|
201
|
-
required_changed: "?"
|
|
202
|
-
}[change.type];
|
|
203
|
-
lines.push(`${prefix} ${change.path}: ${change.description}`);
|
|
204
|
-
if (change.type === "modified" || change.type === "type_changed") {
|
|
205
|
-
lines.push(` old: ${JSON.stringify(change.oldValue)}`);
|
|
206
|
-
lines.push(` new: ${JSON.stringify(change.newValue)}`);
|
|
207
|
-
} else if (change.type === "added") lines.push(` value: ${JSON.stringify(change.newValue)}`);
|
|
208
|
-
else if (change.type === "removed") lines.push(` was: ${JSON.stringify(change.oldValue)}`);
|
|
209
|
-
}
|
|
210
|
-
return lines.join("\n");
|
|
211
|
-
}
|
|
212
|
-
|
|
213
|
-
//#endregion
|
|
214
|
-
export { createSpecDiff, diffAll, diffSpecVsOperation, diffSpecs, formatDiffChanges };
|
|
1
|
+
import{deepEqual as e}from"../common/utils.js";function t(t,n,r,i){if(e(n,r))return null;let a=`modified`;return n==null?a=`added`:r==null?a=`removed`:typeof n!=typeof r&&(a=`type_changed`),{path:t,type:a,oldValue:n,newValue:r,description:i}}function n(e,r,i,a){let o=[];if(!r&&!i)return o;if(!r)return o.push({path:e,type:`added`,newValue:i,description:`Added ${e}`}),o;if(!i)return o.push({path:e,type:`removed`,oldValue:r,description:`Removed ${e}`}),o;let s=new Set([...Object.keys(r),...Object.keys(i)]);for(let c of s){let s=e?`${e}.${c}`:c;if(a.ignorePaths?.some(e=>s.startsWith(e)))continue;let l=r[c],u=i[c];if(typeof l==`object`&&typeof u==`object`)o.push(...n(s,l,u,a));else{let e=t(s,l,u,`Changed ${s}`);e&&o.push(e)}}return o}function r(n,r,i={}){let a=[];if(!i.ignoreDescriptions){let e=t(`meta.description`,n.meta.description,r.summary??r.description,`Description changed`);e&&a.push(e)}if(!i.ignoreTags){let t=[...n.meta.tags??[]].sort(),i=[...r.tags].sort();e(t,i)||a.push({path:`meta.tags`,type:`modified`,oldValue:t,newValue:i,description:`Tags changed`})}if(!i.ignoreTransport){let e=n.transport?.rest?.method??(n.meta.kind===`query`?`GET`:`POST`),t=r.method.toUpperCase();e!==t&&a.push({path:`transport.rest.method`,type:`modified`,oldValue:e,newValue:t,description:`HTTP method changed`});let i=n.transport?.rest?.path;i&&i!==r.path&&a.push({path:`transport.rest.path`,type:`modified`,oldValue:i,newValue:r.path,description:`Path changed`})}return n.meta.stability===`deprecated`!==r.deprecated&&a.push({path:`meta.stability`,type:`modified`,oldValue:n.meta.stability,newValue:r.deprecated?`deprecated`:`stable`,description:`Deprecation status changed`}),a}function i(e,t,r={}){let i=[],a=n(`meta`,e.meta,t.meta,{...r,ignorePaths:[...r.ignorePaths??[],...r.ignoreDescriptions?[`meta.description`,`meta.goal`,`meta.context`]:[],...r.ignoreTags?[`meta.tags`]:[]]});if(i.push(...a),!r.ignoreTransport){let a=n(`transport`,e.transport,t.transport,r);i.push(...a)}let o=n(`policy`,e.policy,t.policy,r);return i.push(...o),i}function a(e,t,n,r={}){let a=[],o=!1;return t?(a=i(t,n.spec,r),o=a.length===0):a=[{path:``,type:`added`,newValue:n.spec,description:`New spec imported from OpenAPI`}],{operationId:e,existing:t,incoming:n,changes:a,isEquivalent:o}}function o(e,t,n={}){let r=[],i=new Set;for(let o of t){let t=o.source.sourceId,s;for(let[n,r]of e){let e=r.meta.name;if(n===t||e.includes(t)){s=r,i.add(n);break}}r.push(a(t,s,o,n))}for(let[t,n]of e)i.has(t)||r.push({operationId:t,existing:n,incoming:void 0,changes:[{path:``,type:`removed`,oldValue:n,description:`Spec no longer exists in OpenAPI source`}],isEquivalent:!1});return r}function s(e){if(e.length===0)return`No changes detected`;let t=[];for(let n of e){let e={added:`+`,removed:`-`,modified:`~`,type_changed:`!`,required_changed:`?`}[n.type];t.push(`${e} ${n.path}: ${n.description}`),n.type===`modified`||n.type===`type_changed`?(t.push(` old: ${JSON.stringify(n.oldValue)}`),t.push(` new: ${JSON.stringify(n.newValue)}`)):n.type===`added`?t.push(` value: ${JSON.stringify(n.newValue)}`):n.type===`removed`&&t.push(` was: ${JSON.stringify(n.oldValue)}`)}return t.join(`
|
|
2
|
+
`)}export{a as createSpecDiff,o as diffAll,r as diffSpecVsOperation,i as diffSpecs,s as formatDiffChanges};
|
package/dist/openapi/exporter.js
CHANGED
|
@@ -1,150 +1 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
3
|
-
//#region src/openapi/exporter.ts
|
|
4
|
-
/**
|
|
5
|
-
* Convert a spec name and version to an operationId.
|
|
6
|
-
*/
|
|
7
|
-
function toOperationId(name, version) {
|
|
8
|
-
return `${name.replace(/\./g, "_")}_v${version}`;
|
|
9
|
-
}
|
|
10
|
-
/**
|
|
11
|
-
* Convert a spec name and version to a schema name.
|
|
12
|
-
*/
|
|
13
|
-
function toSchemaName(prefix, name, version) {
|
|
14
|
-
return `${prefix}_${toOperationId(name, version)}`;
|
|
15
|
-
}
|
|
16
|
-
/**
|
|
17
|
-
* Determine HTTP method from spec kind and override.
|
|
18
|
-
*/
|
|
19
|
-
function toHttpMethod(kind, override) {
|
|
20
|
-
return (override ?? (kind === "query" ? "GET" : "POST")).toLowerCase();
|
|
21
|
-
}
|
|
22
|
-
/**
|
|
23
|
-
* Generate default REST path from spec name and version.
|
|
24
|
-
*/
|
|
25
|
-
function defaultRestPath(name, version) {
|
|
26
|
-
return `/${name.replace(/\./g, "/")}/v${version}`;
|
|
27
|
-
}
|
|
28
|
-
/**
|
|
29
|
-
* Get REST path from spec, using transport override or default.
|
|
30
|
-
*/
|
|
31
|
-
function toRestPath(spec) {
|
|
32
|
-
const path = spec.transport?.rest?.path ?? defaultRestPath(spec.meta.name, spec.meta.version);
|
|
33
|
-
return path.startsWith("/") ? path : `/${path}`;
|
|
34
|
-
}
|
|
35
|
-
/**
|
|
36
|
-
* Convert a SchemaModel to JSON Schema using Zod.
|
|
37
|
-
*/
|
|
38
|
-
function schemaModelToJsonSchema(schema) {
|
|
39
|
-
if (!schema) return null;
|
|
40
|
-
return z.toJSONSchema(schema.getZod());
|
|
41
|
-
}
|
|
42
|
-
/**
|
|
43
|
-
* Extract JSON Schema for a spec's input and output.
|
|
44
|
-
*/
|
|
45
|
-
function jsonSchemaForSpec(spec) {
|
|
46
|
-
return {
|
|
47
|
-
input: schemaModelToJsonSchema(spec.io.input),
|
|
48
|
-
output: schemaModelToJsonSchema(spec.io.output),
|
|
49
|
-
meta: {
|
|
50
|
-
name: spec.meta.name,
|
|
51
|
-
version: spec.meta.version,
|
|
52
|
-
kind: spec.meta.kind,
|
|
53
|
-
description: spec.meta.description,
|
|
54
|
-
tags: spec.meta.tags ?? [],
|
|
55
|
-
stability: spec.meta.stability ?? "stable"
|
|
56
|
-
}
|
|
57
|
-
};
|
|
58
|
-
}
|
|
59
|
-
/**
|
|
60
|
-
* Export a SpecRegistry to an OpenAPI 3.1 document.
|
|
61
|
-
*
|
|
62
|
-
* @param registry - The SpecRegistry containing specs to export
|
|
63
|
-
* @param options - Export options (title, version, description, servers)
|
|
64
|
-
* @returns OpenAPI 3.1 document
|
|
65
|
-
*/
|
|
66
|
-
function openApiForRegistry(registry, options = {}) {
|
|
67
|
-
const specs = registry.listSpecs().filter((s) => s.meta.kind === "command" || s.meta.kind === "query").slice().sort((a, b) => {
|
|
68
|
-
const byName = a.meta.name.localeCompare(b.meta.name);
|
|
69
|
-
return byName !== 0 ? byName : a.meta.version - b.meta.version;
|
|
70
|
-
});
|
|
71
|
-
const doc = {
|
|
72
|
-
openapi: "3.1.0",
|
|
73
|
-
info: {
|
|
74
|
-
title: options.title ?? "ContractSpec API",
|
|
75
|
-
version: options.version ?? "0.0.0",
|
|
76
|
-
...options.description ? { description: options.description } : {}
|
|
77
|
-
},
|
|
78
|
-
...options.servers ? { servers: options.servers } : {},
|
|
79
|
-
paths: {},
|
|
80
|
-
components: { schemas: {} }
|
|
81
|
-
};
|
|
82
|
-
for (const spec of specs) {
|
|
83
|
-
const schema = jsonSchemaForSpec(spec);
|
|
84
|
-
const method = toHttpMethod(spec.meta.kind, spec.transport?.rest?.method);
|
|
85
|
-
const path = toRestPath(spec);
|
|
86
|
-
const operationId = toOperationId(spec.meta.name, spec.meta.version);
|
|
87
|
-
const pathItem = doc.paths[path] ??= {};
|
|
88
|
-
const op = {
|
|
89
|
-
operationId,
|
|
90
|
-
summary: spec.meta.description ?? spec.meta.name,
|
|
91
|
-
description: spec.meta.description,
|
|
92
|
-
tags: spec.meta.tags ?? [],
|
|
93
|
-
"x-contractspec": {
|
|
94
|
-
name: spec.meta.name,
|
|
95
|
-
version: spec.meta.version,
|
|
96
|
-
kind: spec.meta.kind
|
|
97
|
-
},
|
|
98
|
-
responses: {}
|
|
99
|
-
};
|
|
100
|
-
if (schema.input) {
|
|
101
|
-
const inputName = toSchemaName("Input", spec.meta.name, spec.meta.version);
|
|
102
|
-
doc.components.schemas[inputName] = schema.input;
|
|
103
|
-
op["requestBody"] = {
|
|
104
|
-
required: true,
|
|
105
|
-
content: { "application/json": { schema: { $ref: `#/components/schemas/${inputName}` } } }
|
|
106
|
-
};
|
|
107
|
-
}
|
|
108
|
-
const responses = {};
|
|
109
|
-
if (schema.output) {
|
|
110
|
-
const outputName = toSchemaName("Output", spec.meta.name, spec.meta.version);
|
|
111
|
-
doc.components.schemas[outputName] = schema.output;
|
|
112
|
-
responses["200"] = {
|
|
113
|
-
description: "OK",
|
|
114
|
-
content: { "application/json": { schema: { $ref: `#/components/schemas/${outputName}` } } }
|
|
115
|
-
};
|
|
116
|
-
} else responses["200"] = { description: "OK" };
|
|
117
|
-
op["responses"] = responses;
|
|
118
|
-
pathItem[method] = op;
|
|
119
|
-
}
|
|
120
|
-
return doc;
|
|
121
|
-
}
|
|
122
|
-
/**
|
|
123
|
-
* Export a SpecRegistry to OpenAPI JSON string.
|
|
124
|
-
*/
|
|
125
|
-
function openApiToJson(registry, options = {}) {
|
|
126
|
-
const doc = openApiForRegistry(registry, options);
|
|
127
|
-
return JSON.stringify(doc, null, 2);
|
|
128
|
-
}
|
|
129
|
-
/**
|
|
130
|
-
* Export a SpecRegistry to OpenAPI YAML string.
|
|
131
|
-
*/
|
|
132
|
-
function openApiToYaml(registry, options = {}) {
|
|
133
|
-
return jsonToYaml(openApiForRegistry(registry, options));
|
|
134
|
-
}
|
|
135
|
-
/**
|
|
136
|
-
* Simple JSON to YAML conversion.
|
|
137
|
-
*/
|
|
138
|
-
function jsonToYaml(obj, indent = 0) {
|
|
139
|
-
const spaces = " ".repeat(indent);
|
|
140
|
-
let yaml = "";
|
|
141
|
-
if (Array.isArray(obj)) for (const item of obj) if (typeof item === "object" && item !== null) yaml += `${spaces}-\n${jsonToYaml(item, indent + 1)}`;
|
|
142
|
-
else yaml += `${spaces}- ${JSON.stringify(item)}\n`;
|
|
143
|
-
else if (typeof obj === "object" && obj !== null) for (const [key, value] of Object.entries(obj)) if (Array.isArray(value)) yaml += `${spaces}${key}:\n${jsonToYaml(value, indent + 1)}`;
|
|
144
|
-
else if (typeof value === "object" && value !== null) yaml += `${spaces}${key}:\n${jsonToYaml(value, indent + 1)}`;
|
|
145
|
-
else yaml += `${spaces}${key}: ${JSON.stringify(value)}\n`;
|
|
146
|
-
return yaml;
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
//#endregion
|
|
150
|
-
export { defaultRestPath, openApiForRegistry, openApiToJson, openApiToYaml };
|
|
1
|
+
import{z as e}from"zod";function t(e,t){return`${e.replace(/\./g,`_`)}_v${t}`}function n(e,n,r){return`${e}_${t(n,r)}`}function r(e,t){return(t??(e===`query`?`GET`:`POST`)).toLowerCase()}function i(e,t){return`/${e.replace(/\./g,`/`)}/v${t}`}function a(e){let t=e.transport?.rest?.path??i(e.meta.name,e.meta.version);return t.startsWith(`/`)?t:`/${t}`}function o(t){return t?e.toJSONSchema(t.getZod()):null}function s(e){return{input:o(e.io.input),output:o(e.io.output),meta:{name:e.meta.name,version:e.meta.version,kind:e.meta.kind,description:e.meta.description,tags:e.meta.tags??[],stability:e.meta.stability??`stable`}}}function c(e,i={}){let o=e.listSpecs().filter(e=>e.meta.kind===`command`||e.meta.kind===`query`).slice().sort((e,t)=>{let n=e.meta.name.localeCompare(t.meta.name);return n===0?e.meta.version-t.meta.version:n}),c={openapi:`3.1.0`,info:{title:i.title??`ContractSpec API`,version:i.version??`0.0.0`,...i.description?{description:i.description}:{}},...i.servers?{servers:i.servers}:{},paths:{},components:{schemas:{}}};for(let e of o){let i=s(e),o=r(e.meta.kind,e.transport?.rest?.method),l=a(e),u=t(e.meta.name,e.meta.version),d=c.paths[l]??={},f={operationId:u,summary:e.meta.description??e.meta.name,description:e.meta.description,tags:e.meta.tags??[],"x-contractspec":{name:e.meta.name,version:e.meta.version,kind:e.meta.kind},responses:{}};if(i.input){let t=n(`Input`,e.meta.name,e.meta.version);c.components.schemas[t]=i.input,f.requestBody={required:!0,content:{"application/json":{schema:{$ref:`#/components/schemas/${t}`}}}}}let p={};if(i.output){let t=n(`Output`,e.meta.name,e.meta.version);c.components.schemas[t]=i.output,p[200]={description:`OK`,content:{"application/json":{schema:{$ref:`#/components/schemas/${t}`}}}}}else p[200]={description:`OK`};f.responses=p,d[o]=f}return c}function l(e,t={}){let n=c(e,t);return JSON.stringify(n,null,2)}function u(e,t={}){return d(c(e,t))}function d(e,t=0){let n=` `.repeat(t),r=``;if(Array.isArray(e))for(let i of e)typeof i==`object`&&i?r+=`${n}-\n${d(i,t+1)}`:r+=`${n}- ${JSON.stringify(i)}\n`;else if(typeof e==`object`&&e)for(let[i,a]of Object.entries(e))Array.isArray(a)||typeof a==`object`&&a?r+=`${n}${i}:\n${d(a,t+1)}`:r+=`${n}${i}: ${JSON.stringify(a)}\n`;return r}export{i as defaultRestPath,c as openApiForRegistry,l as openApiToJson,u as openApiToYaml};
|