ts-openapi-codegen 2.1.0-beta.8 → 2.1.0-beta.9

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.
Files changed (133) hide show
  1. package/README.md +4 -0
  2. package/README.rus.md +4 -0
  3. package/dist/cli/analyzeDiff/__tests__/analyzeDiff.cli.test.js +31 -24
  4. package/dist/cli/analyzeDiff/__tests__/analyzeDiffLomMiracles.test.d.ts +2 -0
  5. package/dist/cli/analyzeDiff/__tests__/analyzeDiffLomMiracles.test.d.ts.map +1 -0
  6. package/dist/cli/analyzeDiff/__tests__/analyzeDiffLomMiracles.test.js +47 -0
  7. package/dist/cli/analyzeDiff/__tests__/analyzeDiffRenameAndInvalidRegex.test.js +8 -7
  8. package/dist/cli/analyzeDiff/__tests__/analyzeDiffTypeCoercion.test.js +8 -7
  9. package/dist/cli/analyzeDiff/analyzeDiff.d.ts +14 -2
  10. package/dist/cli/analyzeDiff/analyzeDiff.d.ts.map +1 -1
  11. package/dist/cli/analyzeDiff/analyzeDiff.js +56 -13
  12. package/dist/cli/analyzeDiff/ciSummary.d.ts +6 -3
  13. package/dist/cli/analyzeDiff/ciSummary.d.ts.map +1 -1
  14. package/dist/cli/analyzeDiff/ciSummary.js +10 -6
  15. package/dist/cli/analyzeDiff/report.d.ts +0 -1
  16. package/dist/cli/analyzeDiff/report.d.ts.map +1 -1
  17. package/dist/cli/analyzeDiff/report.js +1 -3
  18. package/dist/cli/analyzeDiff/writeLegacyReport.d.ts +2 -0
  19. package/dist/cli/analyzeDiff/writeLegacyReport.d.ts.map +1 -1
  20. package/dist/cli/analyzeDiff/writeLegacyReport.js +2 -0
  21. package/dist/cli/analyzeUsage/core/Scanner.d.ts +8 -0
  22. package/dist/cli/analyzeUsage/core/Scanner.d.ts.map +1 -1
  23. package/dist/cli/analyzeUsage/core/Scanner.js +10 -0
  24. package/dist/cli/analyzeUsage/rules/ClientRule.d.ts +8 -0
  25. package/dist/cli/analyzeUsage/rules/ClientRule.d.ts.map +1 -1
  26. package/dist/cli/analyzeUsage/rules/ClientRule.js +10 -0
  27. package/dist/cli/analyzeUsage/rules/ServiceRule.d.ts +8 -0
  28. package/dist/cli/analyzeUsage/rules/ServiceRule.d.ts.map +1 -1
  29. package/dist/cli/analyzeUsage/rules/ServiceRule.js +10 -0
  30. package/dist/common/LoggerMessages.d.ts +5 -0
  31. package/dist/common/LoggerMessages.d.ts.map +1 -1
  32. package/dist/common/LoggerMessages.js +4 -0
  33. package/dist/core/OpenApiClient.d.ts +8 -0
  34. package/dist/core/OpenApiClient.d.ts.map +1 -1
  35. package/dist/core/OpenApiClient.js +16 -2
  36. package/dist/core/WriteClient.d.ts +52 -15
  37. package/dist/core/WriteClient.d.ts.map +1 -1
  38. package/dist/core/WriteClient.js +36 -4
  39. package/dist/core/semanticDiff/__tests__/analyzeOpenApiDiff.test.js +72 -0
  40. package/dist/core/semanticDiff/__tests__/semanticDiffReportSchema.test.js +20 -1
  41. package/dist/core/semanticDiff/analyzeOpenApiDiff.d.ts +58 -6
  42. package/dist/core/semanticDiff/analyzeOpenApiDiff.d.ts.map +1 -1
  43. package/dist/core/semanticDiff/analyzeOpenApiDiff.js +47 -19
  44. package/dist/core/semanticDiff/semanticDiffReportSchema.d.ts +11 -1
  45. package/dist/core/semanticDiff/semanticDiffReportSchema.d.ts.map +1 -1
  46. package/dist/core/semanticDiff/semanticDiffReportSchema.js +140 -50
  47. package/dist/core/types/DiffReport.model.d.ts +101 -0
  48. package/dist/core/types/DiffReport.model.d.ts.map +1 -0
  49. package/dist/core/types/DiffReport.model.js +5 -0
  50. package/dist/core/types/shared/Model.model.d.ts +36 -0
  51. package/dist/core/types/shared/Model.model.d.ts.map +1 -1
  52. package/dist/core/utils/__tests__/applyDiffReportToClient.test.js +182 -0
  53. package/dist/core/utils/__tests__/buildMiraclesFromSemanticChanges.test.d.ts +2 -0
  54. package/dist/core/utils/__tests__/buildMiraclesFromSemanticChanges.test.d.ts.map +1 -0
  55. package/dist/core/utils/__tests__/buildMiraclesFromSemanticChanges.test.js +77 -0
  56. package/dist/core/utils/__tests__/expandOpenApiRefsForSemanticDiff.test.d.ts +2 -0
  57. package/dist/core/utils/__tests__/expandOpenApiRefsForSemanticDiff.test.d.ts.map +1 -0
  58. package/dist/core/utils/__tests__/expandOpenApiRefsForSemanticDiff.test.js +159 -0
  59. package/dist/core/utils/__tests__/loadDiffReport.test.js +131 -0
  60. package/dist/core/utils/__tests__/modelHelpers.test.js +27 -9
  61. package/dist/core/utils/__tests__/prepareDtoModels.test.js +74 -2
  62. package/dist/core/utils/__tests__/resolveClassesModeTypes.test.d.ts +2 -0
  63. package/dist/core/utils/__tests__/resolveClassesModeTypes.test.d.ts.map +1 -0
  64. package/dist/core/utils/__tests__/resolveClassesModeTypes.test.js +111 -0
  65. package/dist/core/utils/__tests__/semanticChangesToDiffEntries.test.d.ts +2 -0
  66. package/dist/core/utils/__tests__/semanticChangesToDiffEntries.test.d.ts.map +1 -0
  67. package/dist/core/utils/__tests__/semanticChangesToDiffEntries.test.js +68 -0
  68. package/dist/core/utils/__tests__/serviceHelpers.test.js +10 -11
  69. package/dist/core/utils/__tests__/templateRendering.test.js +71 -0
  70. package/dist/core/utils/adapters/__tests__/semanticToStructural.test.d.ts +2 -0
  71. package/dist/core/utils/adapters/__tests__/semanticToStructural.test.d.ts.map +1 -0
  72. package/dist/core/utils/adapters/__tests__/semanticToStructural.test.js +63 -0
  73. package/dist/core/utils/adapters/extractMiraclesFromSemantic.d.ts +10 -0
  74. package/dist/core/utils/adapters/extractMiraclesFromSemantic.d.ts.map +1 -0
  75. package/dist/core/utils/adapters/extractMiraclesFromSemantic.js +13 -0
  76. package/dist/core/utils/adapters/index.d.ts +4 -0
  77. package/dist/core/utils/adapters/index.d.ts.map +1 -0
  78. package/dist/core/utils/adapters/index.js +8 -0
  79. package/dist/core/utils/adapters/semanticToStructural.d.ts +12 -0
  80. package/dist/core/utils/adapters/semanticToStructural.d.ts.map +1 -0
  81. package/dist/core/utils/adapters/semanticToStructural.js +36 -0
  82. package/dist/core/utils/applyDiffReportToClient.d.ts +13 -1
  83. package/dist/core/utils/applyDiffReportToClient.d.ts.map +1 -1
  84. package/dist/core/utils/applyDiffReportToClient.js +187 -107
  85. package/dist/core/utils/buildMiraclesFromSemanticChanges.d.ts +25 -0
  86. package/dist/core/utils/buildMiraclesFromSemanticChanges.d.ts.map +1 -0
  87. package/dist/core/utils/buildMiraclesFromSemanticChanges.js +146 -0
  88. package/dist/core/utils/expandOpenApiRefsForSemanticDiff.d.ts +23 -0
  89. package/dist/core/utils/expandOpenApiRefsForSemanticDiff.d.ts.map +1 -0
  90. package/dist/core/utils/expandOpenApiRefsForSemanticDiff.js +163 -0
  91. package/dist/core/utils/getOpenApiSpec.d.ts +18 -0
  92. package/dist/core/utils/getOpenApiSpec.d.ts.map +1 -1
  93. package/dist/core/utils/getOpenApiSpec.js +35 -0
  94. package/dist/core/utils/loadDiffReport.d.ts +11 -30
  95. package/dist/core/utils/loadDiffReport.d.ts.map +1 -1
  96. package/dist/core/utils/loadDiffReport.js +69 -3
  97. package/dist/core/utils/loadSemanticOpenApiSpec.d.ts +15 -0
  98. package/dist/core/utils/loadSemanticOpenApiSpec.d.ts.map +1 -0
  99. package/dist/core/utils/loadSemanticOpenApiSpec.js +61 -0
  100. package/dist/core/utils/modelHelpers.d.ts +13 -5
  101. package/dist/core/utils/modelHelpers.d.ts.map +1 -1
  102. package/dist/core/utils/modelHelpers.js +28 -23
  103. package/dist/core/utils/prepareDtoModels.d.ts +5 -0
  104. package/dist/core/utils/prepareDtoModels.d.ts.map +1 -1
  105. package/dist/core/utils/prepareDtoModels.js +55 -12
  106. package/dist/core/utils/resolveClassesModeTypes.d.ts +8 -0
  107. package/dist/core/utils/resolveClassesModeTypes.d.ts.map +1 -0
  108. package/dist/core/utils/resolveClassesModeTypes.js +77 -0
  109. package/dist/core/utils/semanticChangesToDiffEntries.d.ts +37 -0
  110. package/dist/core/utils/semanticChangesToDiffEntries.d.ts.map +1 -0
  111. package/dist/core/utils/semanticChangesToDiffEntries.js +99 -0
  112. package/dist/core/utils/semanticPointerToJsonPath.d.ts +7 -0
  113. package/dist/core/utils/semanticPointerToJsonPath.d.ts.map +1 -0
  114. package/dist/core/utils/semanticPointerToJsonPath.js +67 -0
  115. package/dist/core/utils/serviceHelpers.d.ts +6 -7
  116. package/dist/core/utils/serviceHelpers.d.ts.map +1 -1
  117. package/dist/core/utils/serviceHelpers.js +8 -25
  118. package/dist/core/utils/writeClientServices.d.ts +14 -14
  119. package/dist/core/utils/writeClientServices.d.ts.map +1 -1
  120. package/dist/core/utils/writeClientServices.js +4 -8
  121. package/dist/templatesCompiled/client/exportModels.d.ts +17 -11
  122. package/dist/templatesCompiled/client/exportModels.d.ts.map +1 -1
  123. package/dist/templatesCompiled/client/exportModels.js +96 -49
  124. package/dist/templatesCompiled/client/exportService.d.ts +13 -10
  125. package/dist/templatesCompiled/client/exportService.d.ts.map +1 -1
  126. package/dist/templatesCompiled/client/exportService.js +95 -67
  127. package/package.json +1 -3
  128. package/dist/cli/analyzeDiff/buildLegacyReport.d.ts +0 -17
  129. package/dist/cli/analyzeDiff/buildLegacyReport.d.ts.map +0 -1
  130. package/dist/cli/analyzeDiff/buildLegacyReport.js +0 -54
  131. package/dist/cli/analyzeDiff/diffEngine.d.ts +0 -54
  132. package/dist/cli/analyzeDiff/diffEngine.d.ts.map +0 -1
  133. package/dist/cli/analyzeDiff/diffEngine.js +0 -209
@@ -0,0 +1,146 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.parseSchemaPropertyPointer = parseSchemaPropertyPointer;
7
+ exports.buildMiraclesFromSemanticChanges = buildMiraclesFromSemanticChanges;
8
+ const leven_1 = __importDefault(require("leven"));
9
+ const miracles_1 = require("../../cli/analyzeDiff/miracles");
10
+ const semanticPointerToJsonPath_1 = require("./semanticPointerToJsonPath");
11
+ const SCHEMA_PROPERTY_POINTER_REGEX = /^#\/components\/schemas\/([^/]+)\/properties\/([^/]+)$/;
12
+ /**
13
+ * Разбирает JSON Pointer свойства схемы в метаданные изменения.
14
+ * @param path JSON Pointer путь к свойству
15
+ * @returns метаданные изменения или null, если путь не относится к свойству схемы
16
+ */
17
+ function parseSchemaPropertyPointer(path) {
18
+ const match = path.match(SCHEMA_PROPERTY_POINTER_REGEX);
19
+ if (!match) {
20
+ return null;
21
+ }
22
+ const schemaName = match[1];
23
+ const propertyName = match[2];
24
+ return {
25
+ schemaPath: `components.schemas.${schemaName}`,
26
+ schemaName,
27
+ propertyName,
28
+ jsonPath: (0, semanticPointerToJsonPath_1.semanticPointerToJsonPath)(path),
29
+ };
30
+ }
31
+ const toPayload = (value) => {
32
+ if (value && typeof value === 'object') {
33
+ return value;
34
+ }
35
+ if (typeof value === 'string') {
36
+ return { type: value };
37
+ }
38
+ return {};
39
+ };
40
+ const extractSemanticPropertyChange = (change, action) => {
41
+ const parsed = parseSchemaPropertyPointer(change.path);
42
+ if (!parsed) {
43
+ return null;
44
+ }
45
+ const payload = toPayload(action === 'removed' ? change.from : change.to);
46
+ const typeSignature = (0, miracles_1.getTypeSignature)(payload) ?? (typeof change.from === 'string' ? change.from : typeof change.to === 'string' ? change.to : undefined);
47
+ return {
48
+ ...parsed,
49
+ payload,
50
+ typeSignature,
51
+ };
52
+ };
53
+ const isSimilarityCandidate = (removed, added) => {
54
+ if (removed.schemaPath !== added.schemaPath) {
55
+ return false;
56
+ }
57
+ if (removed.typeSignature && added.typeSignature) {
58
+ return removed.typeSignature === added.typeSignature;
59
+ }
60
+ return true;
61
+ };
62
+ /**
63
+ * Строит miracles типов RENAME и TYPE_COERCION из семантических изменений.
64
+ * @param changes список семантических изменений
65
+ * @returns список miracles
66
+ */
67
+ function buildMiraclesFromSemanticChanges(changes) {
68
+ const removedProps = [];
69
+ const addedProps = [];
70
+ const miracles = [];
71
+ for (const change of changes) {
72
+ if (change.type === 'model.property.removed') {
73
+ const removed = extractSemanticPropertyChange(change, 'removed');
74
+ if (removed) {
75
+ removedProps.push(removed);
76
+ }
77
+ continue;
78
+ }
79
+ if (change.type === 'model.property.added') {
80
+ const added = extractSemanticPropertyChange(change, 'added');
81
+ if (added) {
82
+ addedProps.push(added);
83
+ }
84
+ continue;
85
+ }
86
+ if (change.type !== 'model.property.type.changed') {
87
+ continue;
88
+ }
89
+ const parsed = parseSchemaPropertyPointer(change.path);
90
+ if (!parsed) {
91
+ continue;
92
+ }
93
+ const fromType = (0, miracles_1.normalizeScalarType)(typeof change.from === 'string' ? change.from : undefined);
94
+ const toType = (0, miracles_1.normalizeScalarType)(typeof change.to === 'string' ? change.to : undefined);
95
+ if (!fromType || !toType || fromType === toType) {
96
+ continue;
97
+ }
98
+ miracles.push({
99
+ oldPath: parsed.jsonPath,
100
+ newPath: parsed.jsonPath,
101
+ type: 'TYPE_COERCION',
102
+ confidence: 1,
103
+ status: 'auto-generated',
104
+ });
105
+ }
106
+ for (const removed of removedProps) {
107
+ const candidates = addedProps.filter(added => isSimilarityCandidate(removed, added));
108
+ if (candidates.length === 0) {
109
+ continue;
110
+ }
111
+ const removedName = (0, miracles_1.normalizeName)(removed.propertyName);
112
+ if (!removedName) {
113
+ continue;
114
+ }
115
+ let best = null;
116
+ for (const candidate of candidates) {
117
+ const addedName = (0, miracles_1.normalizeName)(candidate.propertyName);
118
+ if (!addedName) {
119
+ continue;
120
+ }
121
+ const distance = (0, leven_1.default)(removedName, addedName);
122
+ const ratio = distance / Math.max(removedName.length, addedName.length);
123
+ if (!best || ratio < best.ratio) {
124
+ best = { candidate, ratio };
125
+ }
126
+ }
127
+ if (!best || best.ratio > 0.2) {
128
+ continue;
129
+ }
130
+ const sameType = !!removed.typeSignature && !!best.candidate.typeSignature && removed.typeSignature === best.candidate.typeSignature;
131
+ const onlyCandidate = candidates.length === 1;
132
+ const descBonus = (0, miracles_1.descriptionSimilarityBonus)(removed.description, best.candidate.description);
133
+ const confidence = (0, miracles_1.computeConfidence)(best.ratio, sameType, onlyCandidate, descBonus);
134
+ miracles.push({
135
+ oldPath: removed.jsonPath,
136
+ newPath: best.candidate.jsonPath,
137
+ type: 'RENAME',
138
+ confidence,
139
+ status: 'auto-generated',
140
+ modelName: removed.schemaName,
141
+ oldProperty: removed.propertyName,
142
+ newProperty: best.candidate.propertyName,
143
+ });
144
+ }
145
+ return miracles;
146
+ }
@@ -0,0 +1,23 @@
1
+ /**
2
+ * Резолвер ссылок для семантического diff.
3
+ * @property [exists] проверка существования ссылки
4
+ * @property get получение значения по ссылке
5
+ */
6
+ export type SemanticRefResolver = {
7
+ exists?: (ref: string) => boolean;
8
+ get: (ref: string) => unknown;
9
+ };
10
+ type ExpandOptions = {
11
+ refs?: SemanticRefResolver;
12
+ sourceFile?: string;
13
+ };
14
+ /**
15
+ * Строит клон спецификации с развёрнутыми refs для семантического сравнения.
16
+ * Циклы и неразрешённые refs остаются стабильными `$ref`-объектами.
17
+ * @param root корневой узел OpenAPI-документа
18
+ * @param [options] опции резолва ссылок
19
+ * @returns клон с развёрнутыми refs
20
+ */
21
+ export declare function expandOpenApiRefsForSemanticDiff<T = unknown>(root: T, options?: ExpandOptions): T;
22
+ export {};
23
+ //# sourceMappingURL=expandOpenApiRefsForSemanticDiff.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"expandOpenApiRefsForSemanticDiff.d.ts","sourceRoot":"","sources":["../../../src/core/utils/expandOpenApiRefsForSemanticDiff.ts"],"names":[],"mappings":"AAIA;;;;GAIG;AACH,MAAM,MAAM,mBAAmB,GAAG;IAC9B,MAAM,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,OAAO,CAAC;IAClC,GAAG,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,OAAO,CAAC;CACjC,CAAC;AAaF,KAAK,aAAa,GAAG;IACjB,IAAI,CAAC,EAAE,mBAAmB,CAAC;IAC3B,UAAU,CAAC,EAAE,MAAM,CAAC;CACvB,CAAC;AAmLF;;;;;;GAMG;AACH,wBAAgB,gCAAgC,CAAC,CAAC,GAAG,OAAO,EAAE,IAAI,EAAE,CAAC,EAAE,OAAO,GAAE,aAAkB,GAAG,CAAC,CAGrG"}
@@ -0,0 +1,163 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.expandOpenApiRefsForSemanticDiff = expandOpenApiRefsForSemanticDiff;
7
+ const path_1 = __importDefault(require("path"));
8
+ const pathHelpers_1 = require("../../common/utils/pathHelpers");
9
+ function isRecord(value) {
10
+ return !!value && typeof value === 'object' && !Array.isArray(value);
11
+ }
12
+ function isUrlLike(value) {
13
+ return /^[a-z][a-z\d+\-.]*:/i.test(value);
14
+ }
15
+ function normalizeSourceFile(sourceFile) {
16
+ if (!sourceFile || isUrlLike(sourceFile)) {
17
+ return sourceFile;
18
+ }
19
+ return (0, pathHelpers_1.normalizeHelper)(sourceFile);
20
+ }
21
+ function parseRef(ref) {
22
+ const fragmentIndex = ref.indexOf('#');
23
+ if (fragmentIndex === -1) {
24
+ return { file: ref || undefined };
25
+ }
26
+ const file = ref.slice(0, fragmentIndex);
27
+ const pointer = ref.slice(fragmentIndex);
28
+ return {
29
+ file: file || undefined,
30
+ pointer: pointer || '#',
31
+ };
32
+ }
33
+ function normalizeRefFile(file, currentSourceFile) {
34
+ if (!file) {
35
+ return normalizeSourceFile(currentSourceFile);
36
+ }
37
+ if (isUrlLike(file)) {
38
+ return file;
39
+ }
40
+ if (path_1.default.isAbsolute(file)) {
41
+ return (0, pathHelpers_1.normalizeHelper)(file);
42
+ }
43
+ if (currentSourceFile && !isUrlLike(currentSourceFile)) {
44
+ return (0, pathHelpers_1.resolveHelper)(path_1.default.dirname(currentSourceFile), file);
45
+ }
46
+ return (0, pathHelpers_1.normalizeHelper)(file);
47
+ }
48
+ function createCanonicalRef(ref, currentSourceFile) {
49
+ const parsed = parseRef(ref);
50
+ const sourceFile = normalizeRefFile(parsed.file, currentSourceFile);
51
+ const pointer = parsed.pointer;
52
+ if (!sourceFile) {
53
+ return { canonicalRef: pointer ?? ref, pointer };
54
+ }
55
+ return {
56
+ canonicalRef: `${sourceFile}${pointer ?? ''}`,
57
+ sourceFile,
58
+ pointer,
59
+ };
60
+ }
61
+ function decodeJsonPointerSegment(segment) {
62
+ return decodeURIComponent(segment).replace(/~1/g, '/').replace(/~0/g, '~');
63
+ }
64
+ function resolveJsonPointer(root, pointer) {
65
+ if (!pointer || pointer === '#') {
66
+ return root;
67
+ }
68
+ if (!pointer.startsWith('#/')) {
69
+ return undefined;
70
+ }
71
+ return pointer
72
+ .slice(2)
73
+ .split('/')
74
+ .map(decodeJsonPointerSegment)
75
+ .reduce((current, segment) => {
76
+ if (Array.isArray(current)) {
77
+ const index = Number(segment);
78
+ return Number.isInteger(index) ? current[index] : undefined;
79
+ }
80
+ if (isRecord(current)) {
81
+ return current[segment];
82
+ }
83
+ return undefined;
84
+ }, root);
85
+ }
86
+ function readRefFromResolver(refs, candidates) {
87
+ if (!refs) {
88
+ return undefined;
89
+ }
90
+ for (const candidate of candidates) {
91
+ try {
92
+ if (refs.exists && !refs.exists(candidate)) {
93
+ continue;
94
+ }
95
+ const value = refs.get(candidate);
96
+ if (value !== undefined) {
97
+ return value;
98
+ }
99
+ }
100
+ catch {
101
+ continue;
102
+ }
103
+ }
104
+ return undefined;
105
+ }
106
+ function resolveRef(root, ref, currentSourceFile, options) {
107
+ const normalized = createCanonicalRef(ref, currentSourceFile);
108
+ const candidates = [normalized.canonicalRef, ref];
109
+ const resolvedFromRefs = readRefFromResolver(options.refs, [...new Set(candidates)]);
110
+ if (resolvedFromRefs !== undefined) {
111
+ return {
112
+ canonicalRef: normalized.canonicalRef,
113
+ sourceFile: normalized.sourceFile,
114
+ value: resolvedFromRefs,
115
+ };
116
+ }
117
+ const rootSourceFile = normalizeSourceFile(options.sourceFile);
118
+ const isLocalRootRef = !normalized.sourceFile || normalized.sourceFile === rootSourceFile;
119
+ if (isLocalRootRef) {
120
+ const resolvedFromRoot = resolveJsonPointer(root, normalized.pointer);
121
+ if (resolvedFromRoot !== undefined) {
122
+ return {
123
+ canonicalRef: normalized.canonicalRef,
124
+ sourceFile: rootSourceFile,
125
+ value: resolvedFromRoot,
126
+ };
127
+ }
128
+ }
129
+ return null;
130
+ }
131
+ function expandNode(root, node, currentSourceFile, options, refStack) {
132
+ if (Array.isArray(node)) {
133
+ return node.map(item => expandNode(root, item, currentSourceFile, options, refStack));
134
+ }
135
+ if (!isRecord(node)) {
136
+ return node;
137
+ }
138
+ const ref = node.$ref;
139
+ if (typeof ref === 'string') {
140
+ const target = resolveRef(root, ref, currentSourceFile, options);
141
+ if (!target) {
142
+ return { ...node };
143
+ }
144
+ if (refStack.has(target.canonicalRef)) {
145
+ return { $ref: ref };
146
+ }
147
+ const nextStack = new Set(refStack);
148
+ nextStack.add(target.canonicalRef);
149
+ return expandNode(root, target.value, target.sourceFile ?? currentSourceFile, options, nextStack);
150
+ }
151
+ return Object.fromEntries(Object.entries(node).map(([key, value]) => [key, expandNode(root, value, currentSourceFile, options, refStack)]));
152
+ }
153
+ /**
154
+ * Строит клон спецификации с развёрнутыми refs для семантического сравнения.
155
+ * Циклы и неразрешённые refs остаются стабильными `$ref`-объектами.
156
+ * @param root корневой узел OpenAPI-документа
157
+ * @param [options] опции резолва ссылок
158
+ * @returns клон с развёрнутыми refs
159
+ */
160
+ function expandOpenApiRefsForSemanticDiff(root, options = {}) {
161
+ const sourceFile = normalizeSourceFile(options.sourceFile);
162
+ return expandNode(root, root, sourceFile, options, new Set());
163
+ }
@@ -1,4 +1,22 @@
1
1
  import { Context } from '../Context';
2
2
  import { CommonOpenApi } from '../types/shared/CommonOpenApi.model';
3
+ /**
4
+ * Загружает и резолвит OpenAPI-спецификацию для генерации клиента.
5
+ * @param context контекст генерации
6
+ * @param input путь к файлу спецификации
7
+ * @returns загруженная OpenAPI-спецификация
8
+ */
3
9
  export declare function getOpenApiSpec(context: Context, input: string): Promise<CommonOpenApi>;
10
+ /**
11
+ * Полностью разыменовывает OpenAPI-файл для семантического diff.
12
+ * @param input путь к файлу спецификации
13
+ * @returns dereference-спецификация
14
+ */
15
+ export declare function dereferenceOpenApiSpec(input: string): Promise<CommonOpenApi>;
16
+ /**
17
+ * Полностью разыменовывает OpenAPI-документ в памяти.
18
+ * @param spec загруженная спецификация
19
+ * @returns dereference-спецификация
20
+ */
21
+ export declare function dereferenceOpenApiObject(spec: unknown): Promise<CommonOpenApi>;
4
22
  //# sourceMappingURL=getOpenApiSpec.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"getOpenApiSpec.d.ts","sourceRoot":"","sources":["../../../src/core/utils/getOpenApiSpec.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,OAAO,EAAE,MAAM,YAAY,CAAC;AACrC,OAAO,EAAE,aAAa,EAAE,MAAM,qCAAqC,CAAC;AAEpE,wBAAsB,cAAc,CAAC,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,aAAa,CAAC,CAyB5F"}
1
+ {"version":3,"file":"getOpenApiSpec.d.ts","sourceRoot":"","sources":["../../../src/core/utils/getOpenApiSpec.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,OAAO,EAAE,MAAM,YAAY,CAAC;AACrC,OAAO,EAAE,aAAa,EAAE,MAAM,qCAAqC,CAAC;AAEpE;;;;;GAKG;AACH,wBAAsB,cAAc,CAAC,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,aAAa,CAAC,CAyB5F;AAED;;;;GAIG;AACH,wBAAsB,sBAAsB,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,aAAa,CAAC,CAalF;AAED;;;;GAIG;AACH,wBAAsB,wBAAwB,CAAC,IAAI,EAAE,OAAO,GAAG,OAAO,CAAC,aAAa,CAAC,CAMpF"}
@@ -4,9 +4,17 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
4
4
  };
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
6
  exports.getOpenApiSpec = getOpenApiSpec;
7
+ exports.dereferenceOpenApiSpec = dereferenceOpenApiSpec;
8
+ exports.dereferenceOpenApiObject = dereferenceOpenApiObject;
7
9
  const swagger_parser_1 = __importDefault(require("@apidevtools/swagger-parser"));
8
10
  const fileSystemHelpers_1 = require("../../common/utils/fileSystemHelpers");
9
11
  const pathHelpers_1 = require("../../common/utils/pathHelpers");
12
+ /**
13
+ * Загружает и резолвит OpenAPI-спецификацию для генерации клиента.
14
+ * @param context контекст генерации
15
+ * @param input путь к файлу спецификации
16
+ * @returns загруженная OpenAPI-спецификация
17
+ */
10
18
  async function getOpenApiSpec(context, input) {
11
19
  const absoluteInput = (0, pathHelpers_1.resolveHelper)(process.cwd(), input);
12
20
  if (!input) {
@@ -27,3 +35,30 @@ async function getOpenApiSpec(context, input) {
27
35
  context.initializeVirtualFileMap(raw, absoluteInput);
28
36
  return raw;
29
37
  }
38
+ /**
39
+ * Полностью разыменовывает OpenAPI-файл для семантического diff.
40
+ * @param input путь к файлу спецификации
41
+ * @returns dereference-спецификация
42
+ */
43
+ async function dereferenceOpenApiSpec(input) {
44
+ const absoluteInput = (0, pathHelpers_1.resolveHelper)(process.cwd(), input);
45
+ if (!input) {
46
+ throw new Error(`OpenAPI spec path is empty`);
47
+ }
48
+ const exists = await fileSystemHelpers_1.fileSystemHelpers.exists(absoluteInput);
49
+ if (!exists) {
50
+ throw new Error(`OpenAPI spec not found: ${absoluteInput}`);
51
+ }
52
+ return swagger_parser_1.default.dereference(absoluteInput);
53
+ }
54
+ /**
55
+ * Полностью разыменовывает OpenAPI-документ в памяти.
56
+ * @param spec загруженная спецификация
57
+ * @returns dereference-спецификация
58
+ */
59
+ async function dereferenceOpenApiObject(spec) {
60
+ if (!spec || typeof spec !== 'object') {
61
+ throw new Error('OpenAPI spec must be an object');
62
+ }
63
+ return swagger_parser_1.default.dereference(spec);
64
+ }
@@ -1,39 +1,20 @@
1
1
  import type { Logger } from '../../common/Logger';
2
- import type { DiffInfo } from '../types/shared/DiffInfo.model';
3
- import type { MiracleEntry } from '../types/shared/Miracle.model';
4
- export interface DiffReportEntry extends DiffInfo {
5
- action: DiffInfo['action'];
6
- }
7
- export interface DiffReport {
8
- version?: string;
9
- timestamp?: string;
10
- metadata?: {
11
- base?: string;
12
- target?: string;
13
- baseHash?: string;
14
- targetHash?: string;
15
- };
16
- stats?: {
17
- totalChanges?: number;
18
- added?: number;
19
- removed?: number;
20
- changed?: number;
21
- ignored?: number;
22
- };
23
- diff?: {
24
- breaking?: DiffReportEntry[];
25
- warnings?: DiffReportEntry[];
26
- info?: DiffReportEntry[];
27
- all?: DiffReportEntry[];
28
- };
29
- miracles?: MiracleEntry[];
30
- }
2
+ import { type DiffReport, type DiffReportEntry } from '../types/DiffReport.model';
3
+ /** Реэкспорт типов legacy diff-отчёта. */
4
+ export type { DiffReport, DiffReportEntry };
31
5
  type LoadDiffReportParams = {
32
6
  useHistory?: boolean;
33
7
  diffReport?: string;
34
8
  inputPath?: string;
35
9
  logger: Logger;
36
10
  };
11
+ /**
12
+ * Загружает diff-отчёт с диска и приводит его к legacy-формату для генерации.
13
+ * @param useHistory признак использования истории изменений
14
+ * @param [diffReport] путь к файлу отчёта
15
+ * @param [inputPath] путь к входной спецификации для проверки актуальности отчёта
16
+ * @param logger логгер для диагностических сообщений
17
+ * @returns legacy diff-отчёт или null, если отчёт отсутствует или устарел
18
+ */
37
19
  export declare const loadDiffReport: ({ useHistory, diffReport, inputPath, logger }: LoadDiffReportParams) => DiffReport | null;
38
- export {};
39
20
  //# sourceMappingURL=loadDiffReport.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"loadDiffReport.d.ts","sourceRoot":"","sources":["../../../src/core/utils/loadDiffReport.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,qBAAqB,CAAC;AAElD,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,gCAAgC,CAAC;AAC/D,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,+BAA+B,CAAC;AAElE,MAAM,WAAW,eAAgB,SAAQ,QAAQ;IAC7C,MAAM,EAAE,QAAQ,CAAC,QAAQ,CAAC,CAAC;CAC9B;AAED,MAAM,WAAW,UAAU;IACvB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,QAAQ,CAAC,EAAE;QACP,IAAI,CAAC,EAAE,MAAM,CAAC;QACd,MAAM,CAAC,EAAE,MAAM,CAAC;QAChB,QAAQ,CAAC,EAAE,MAAM,CAAC;QAClB,UAAU,CAAC,EAAE,MAAM,CAAC;KACvB,CAAC;IACF,KAAK,CAAC,EAAE;QACJ,YAAY,CAAC,EAAE,MAAM,CAAC;QACtB,KAAK,CAAC,EAAE,MAAM,CAAC;QACf,OAAO,CAAC,EAAE,MAAM,CAAC;QACjB,OAAO,CAAC,EAAE,MAAM,CAAC;QACjB,OAAO,CAAC,EAAE,MAAM,CAAC;KACpB,CAAC;IACF,IAAI,CAAC,EAAE;QACH,QAAQ,CAAC,EAAE,eAAe,EAAE,CAAC;QAC7B,QAAQ,CAAC,EAAE,eAAe,EAAE,CAAC;QAC7B,IAAI,CAAC,EAAE,eAAe,EAAE,CAAC;QACzB,GAAG,CAAC,EAAE,eAAe,EAAE,CAAC;KAC3B,CAAC;IACF,QAAQ,CAAC,EAAE,YAAY,EAAE,CAAC;CAC7B;AAED,KAAK,oBAAoB,GAAG;IACxB,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,MAAM,EAAE,MAAM,CAAC;CAClB,CAAC;AAaF,eAAO,MAAM,cAAc,GAAI,+CAA+C,oBAAoB,KAAG,UAAU,GAAG,IA8BjH,CAAC"}
1
+ {"version":3,"file":"loadDiffReport.d.ts","sourceRoot":"","sources":["../../../src/core/utils/loadDiffReport.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,qBAAqB,CAAC;AAGlD,OAAO,EAAE,KAAK,UAAU,EAAE,KAAK,eAAe,EAA8D,MAAM,2BAA2B,CAAC;AAG9I,0CAA0C;AAC1C,YAAY,EAAE,UAAU,EAAE,eAAe,EAAE,CAAC;AAE5C,KAAK,oBAAoB,GAAG;IACxB,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,MAAM,EAAE,MAAM,CAAC;CAClB,CAAC;AAqDF;;;;;;;GAOG;AACH,eAAO,MAAM,cAAc,GAAI,+CAA+C,oBAAoB,KAAG,UAAU,GAAG,IAwDjH,CAAC"}
@@ -7,6 +7,42 @@ exports.loadDiffReport = void 0;
7
7
  const fs_1 = __importDefault(require("fs"));
8
8
  const Consts_1 = require("../../common/Consts");
9
9
  const LoggerMessages_1 = require("../../common/LoggerMessages");
10
+ const analyzeOpenApiDiff_1 = require("../semanticDiff/analyzeOpenApiDiff");
11
+ const DiffReport_model_1 = require("../types/DiffReport.model");
12
+ const adapters_1 = require("./adapters");
13
+ const isSemanticDiffReport = (value) => {
14
+ if (!value || typeof value !== 'object') {
15
+ return false;
16
+ }
17
+ const report = value;
18
+ return report.schemaVersion === analyzeOpenApiDiff_1.SEMANTIC_DIFF_REPORT_SCHEMA_VERSION && Array.isArray(report.changes);
19
+ };
20
+ const isUnifiedDiffReport = (value) => {
21
+ if (!value || typeof value !== 'object') {
22
+ return false;
23
+ }
24
+ const report = value;
25
+ return report.schemaVersion === DiffReport_model_1.UNIFIED_DIFF_REPORT_SCHEMA_VERSION && !!report.structural && Array.isArray(report.structural.diff?.all);
26
+ };
27
+ const convertSemanticReportToLegacy = (report) => {
28
+ const structural = (0, adapters_1.adaptSemanticToStructural)(report);
29
+ return {
30
+ version: report.schemaVersion,
31
+ diff: structural.diff,
32
+ miracles: structural.miracles,
33
+ stats: structural.stats,
34
+ };
35
+ };
36
+ const convertUnifiedReportToLegacy = (report) => {
37
+ return {
38
+ version: report.schemaVersion,
39
+ timestamp: report.timestamp,
40
+ metadata: report.metadata,
41
+ diff: report.structural.diff,
42
+ miracles: report.structural.miracles,
43
+ stats: report.structural.stats,
44
+ };
45
+ };
10
46
  const isFreshEnough = (reportPath, inputPath) => {
11
47
  if (!inputPath)
12
48
  return true;
@@ -19,6 +55,14 @@ const isFreshEnough = (reportPath, inputPath) => {
19
55
  return true;
20
56
  }
21
57
  };
58
+ /**
59
+ * Загружает diff-отчёт с диска и приводит его к legacy-формату для генерации.
60
+ * @param useHistory признак использования истории изменений
61
+ * @param [diffReport] путь к файлу отчёта
62
+ * @param [inputPath] путь к входной спецификации для проверки актуальности отчёта
63
+ * @param logger логгер для диагностических сообщений
64
+ * @returns legacy diff-отчёт или null, если отчёт отсутствует или устарел
65
+ */
22
66
  const loadDiffReport = ({ useHistory, diffReport, inputPath, logger }) => {
23
67
  const shouldLoad = useHistory || !!diffReport;
24
68
  if (!shouldLoad)
@@ -35,13 +79,35 @@ const loadDiffReport = ({ useHistory, diffReport, inputPath, logger }) => {
35
79
  try {
36
80
  const raw = fs_1.default.readFileSync(reportPath, 'utf-8');
37
81
  const parsed = JSON.parse(raw);
38
- const hasDiffEntries = !!parsed?.diff?.all?.length;
39
- const hasMiracles = !!parsed?.miracles?.length;
82
+ if (isUnifiedDiffReport(parsed)) {
83
+ const legacyReport = convertUnifiedReportToLegacy(parsed);
84
+ const hasDiffEntries = !!legacyReport.diff?.all?.length;
85
+ const hasMiracles = !!legacyReport.miracles?.length;
86
+ if (!hasDiffEntries && !hasMiracles) {
87
+ logger.info(LoggerMessages_1.LOGGER_MESSAGES.DIFF_REPORT.EMPTY(reportPath));
88
+ return null;
89
+ }
90
+ logger.info(LoggerMessages_1.LOGGER_MESSAGES.DIFF_REPORT.LOADED(reportPath, legacyReport.diff?.all?.length ?? 0, legacyReport.miracles?.length ?? 0));
91
+ return legacyReport;
92
+ }
93
+ if (isSemanticDiffReport(parsed)) {
94
+ if (!parsed.changes.length) {
95
+ logger.info(LoggerMessages_1.LOGGER_MESSAGES.DIFF_REPORT.EMPTY(reportPath));
96
+ return null;
97
+ }
98
+ const legacyReport = convertSemanticReportToLegacy(parsed);
99
+ logger.info(LoggerMessages_1.LOGGER_MESSAGES.DIFF_REPORT.LOADED(reportPath, legacyReport.diff?.all?.length ?? 0, legacyReport.miracles?.length ?? 0));
100
+ return legacyReport;
101
+ }
102
+ const legacyReport = parsed;
103
+ const hasDiffEntries = !!legacyReport?.diff?.all?.length;
104
+ const hasMiracles = !!legacyReport?.miracles?.length;
40
105
  if (!hasDiffEntries && !hasMiracles) {
41
106
  logger.info(LoggerMessages_1.LOGGER_MESSAGES.DIFF_REPORT.EMPTY(reportPath));
42
107
  return null;
43
108
  }
44
- return parsed;
109
+ logger.info(LoggerMessages_1.LOGGER_MESSAGES.DIFF_REPORT.LOADED(reportPath, legacyReport.diff?.all?.length ?? 0, legacyReport.miracles?.length ?? 0));
110
+ return legacyReport;
45
111
  }
46
112
  catch (error) {
47
113
  const message = error instanceof Error ? error.message : String(error);
@@ -0,0 +1,15 @@
1
+ import { CommonOpenApi } from '../types/shared/CommonOpenApi.model';
2
+ /**
3
+ * Загружает OpenAPI-файл для семантического diff без полного dereference.
4
+ * @param input путь к файлу спецификации
5
+ * @returns спецификация с развёрнутыми refs для семантического сравнения
6
+ */
7
+ export declare function loadSemanticOpenApiSpec(input: string): Promise<CommonOpenApi>;
8
+ /**
9
+ * Разворачивает уже загруженный OpenAPI-объект для семантического diff.
10
+ * @param spec загруженная спецификация
11
+ * @param [sourceFile] исходный путь к файлу для резолва внешних refs
12
+ * @returns спецификация с развёрнутыми refs для семантического сравнения
13
+ */
14
+ export declare function loadSemanticOpenApiObject(spec: unknown, sourceFile?: string): Promise<CommonOpenApi>;
15
+ //# sourceMappingURL=loadSemanticOpenApiSpec.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"loadSemanticOpenApiSpec.d.ts","sourceRoot":"","sources":["../../../src/core/utils/loadSemanticOpenApiSpec.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,aAAa,EAAE,MAAM,qCAAqC,CAAC;AAqBpE;;;;GAIG;AACH,wBAAsB,uBAAuB,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,aAAa,CAAC,CAqBnF;AAED;;;;;GAKG;AACH,wBAAsB,yBAAyB,CAAC,IAAI,EAAE,OAAO,EAAE,UAAU,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,aAAa,CAAC,CAW1G"}
@@ -0,0 +1,61 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.loadSemanticOpenApiSpec = loadSemanticOpenApiSpec;
7
+ exports.loadSemanticOpenApiObject = loadSemanticOpenApiObject;
8
+ const swagger_parser_1 = __importDefault(require("@apidevtools/swagger-parser"));
9
+ const fileSystemHelpers_1 = require("../../common/utils/fileSystemHelpers");
10
+ const pathHelpers_1 = require("../../common/utils/pathHelpers");
11
+ const expandOpenApiRefsForSemanticDiff_1 = require("./expandOpenApiRefsForSemanticDiff");
12
+ function createSwaggerRefsResolver(refs) {
13
+ return {
14
+ exists: ref => refs.exists(ref),
15
+ get: ref => refs.get(ref),
16
+ };
17
+ }
18
+ function assertOpenApiObject(spec, source) {
19
+ if (!spec || typeof spec !== 'object' || Array.isArray(spec)) {
20
+ throw new Error(`Invalid OpenAPI schema at ${source}`);
21
+ }
22
+ }
23
+ /**
24
+ * Загружает OpenAPI-файл для семантического diff без полного dereference.
25
+ * @param input путь к файлу спецификации
26
+ * @returns спецификация с развёрнутыми refs для семантического сравнения
27
+ */
28
+ async function loadSemanticOpenApiSpec(input) {
29
+ const absoluteInput = (0, pathHelpers_1.resolveHelper)(process.cwd(), input);
30
+ if (!input) {
31
+ throw new Error(`OpenAPI spec path is empty`);
32
+ }
33
+ const exists = await fileSystemHelpers_1.fileSystemHelpers.exists(absoluteInput);
34
+ if (!exists) {
35
+ throw new Error(`OpenAPI spec not found: ${absoluteInput}`);
36
+ }
37
+ const parser = new swagger_parser_1.default();
38
+ const resolved = (await parser.resolve(absoluteInput));
39
+ const raw = resolved.get(absoluteInput);
40
+ assertOpenApiObject(raw, absoluteInput);
41
+ return (0, expandOpenApiRefsForSemanticDiff_1.expandOpenApiRefsForSemanticDiff)(raw, {
42
+ refs: createSwaggerRefsResolver(resolved),
43
+ sourceFile: absoluteInput,
44
+ });
45
+ }
46
+ /**
47
+ * Разворачивает уже загруженный OpenAPI-объект для семантического diff.
48
+ * @param spec загруженная спецификация
49
+ * @param [sourceFile] исходный путь к файлу для резолва внешних refs
50
+ * @returns спецификация с развёрнутыми refs для семантического сравнения
51
+ */
52
+ async function loadSemanticOpenApiObject(spec, sourceFile) {
53
+ assertOpenApiObject(spec, sourceFile ?? 'in-memory OpenAPI object');
54
+ const parser = new swagger_parser_1.default();
55
+ const resolved = (await parser.resolve(spec));
56
+ const absoluteSourceFile = sourceFile ? (0, pathHelpers_1.resolveHelper)(process.cwd(), sourceFile) : undefined;
57
+ return (0, expandOpenApiRefsForSemanticDiff_1.expandOpenApiRefsForSemanticDiff)(spec, {
58
+ refs: createSwaggerRefsResolver(resolved),
59
+ sourceFile: absoluteSourceFile,
60
+ });
61
+ }