@unispechq/unispec-core 0.3.0 → 0.3.2
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/cjs/diff/annotators.js +36 -9
- package/dist/cjs/src/cache/cache-factory.js +72 -0
- package/dist/cjs/src/cache/cache-manager.js +128 -0
- package/dist/cjs/src/cache/constants.js +25 -0
- package/dist/cjs/src/cache/hash-utils.js +19 -0
- package/dist/cjs/src/cache/hashing.js +230 -0
- package/dist/cjs/src/cache/index.js +24 -0
- package/dist/cjs/src/cache/lru-cache.js +144 -0
- package/dist/cjs/src/cache/types.js +5 -0
- package/dist/cjs/src/diff/annotators.js +160 -0
- package/dist/cjs/src/diff/change-reports.js +369 -0
- package/dist/cjs/src/diff/core.js +158 -0
- package/dist/cjs/src/diff/enhanced-diff.js +65 -0
- package/dist/cjs/src/diff/impact-strategies-refactored.js +230 -0
- package/dist/cjs/src/diff/impact-strategies.js +219 -0
- package/dist/cjs/src/diff/index.js +27 -0
- package/dist/cjs/src/diff/metrics-calculator.js +69 -0
- package/dist/cjs/src/diff/risk-calculator.js +58 -0
- package/dist/cjs/src/diff/suggestion-generator.js +78 -0
- package/dist/cjs/src/diff/types.js +11 -0
- package/dist/cjs/src/errors/base-error.js +33 -0
- package/dist/cjs/src/errors/config-error.js +11 -0
- package/dist/cjs/src/errors/error-factory.js +48 -0
- package/dist/cjs/src/errors/index.js +19 -0
- package/dist/cjs/src/errors/loader-error.js +11 -0
- package/dist/cjs/src/errors/reference-error.js +11 -0
- package/dist/cjs/src/errors/schema-error.js +11 -0
- package/dist/cjs/src/errors/security-error.js +11 -0
- package/dist/cjs/src/errors/semantic-error.js +11 -0
- package/dist/cjs/src/generated-schemas.js +2100 -0
- package/dist/cjs/src/index.js +59 -0
- package/dist/cjs/src/loader/index.js +13 -0
- package/dist/cjs/src/loader/security-validator.js +53 -0
- package/dist/cjs/src/loader/types.js +11 -0
- package/dist/cjs/src/loader/unispec-loader.js +84 -0
- package/dist/cjs/src/loader/yaml-loader.js +76 -0
- package/dist/cjs/src/normalizer/core.js +37 -0
- package/dist/cjs/src/normalizer/graphql-normalizer.js +67 -0
- package/dist/cjs/src/normalizer/index.js +7 -0
- package/dist/cjs/src/normalizer/rest-normalizer.js +51 -0
- package/dist/cjs/src/normalizer/types.js +2 -0
- package/dist/cjs/src/normalizer/utils.js +49 -0
- package/dist/cjs/src/normalizer/websocket-normalizer.js +81 -0
- package/dist/cjs/src/optimizer/core.js +140 -0
- package/dist/cjs/src/optimizer/index.js +17 -0
- package/dist/cjs/src/optimizer/optimization-functions.js +185 -0
- package/dist/cjs/src/optimizer/types.js +2 -0
- package/dist/cjs/src/optimizer/utils.js +32 -0
- package/dist/cjs/src/schemas/dedupe.js +113 -0
- package/dist/cjs/src/schemas/index.js +14 -0
- package/dist/cjs/src/schemas/resolver.js +42 -0
- package/dist/cjs/src/schemas/utils.js +53 -0
- package/dist/cjs/src/types/index.js +2 -0
- package/dist/cjs/src/validator/ajv-validator.js +82 -0
- package/dist/cjs/src/validator/config-validator-main.js +34 -0
- package/dist/cjs/src/validator/config-validator.js +17 -0
- package/dist/cjs/src/validator/index.js +23 -0
- package/dist/cjs/src/validator/object-traversal.js +112 -0
- package/dist/cjs/src/validator/reference-validator.js +233 -0
- package/dist/cjs/src/validator/schema-references.js +116 -0
- package/dist/cjs/src/validator/semantic-validator.js +328 -0
- package/dist/cjs/src/validator/tests-validator.js +16 -0
- package/dist/cjs/src/validator/types.js +2 -0
- package/dist/cjs/src/validator/unispec-validator.js +80 -0
- package/dist/cjs/src/validator/validator-factory.js +77 -0
- package/dist/cjs/src/versions.js +147 -0
- package/dist/cjs/tests/cache/cache.test.js +274 -0
- package/dist/cjs/tests/cache/utils.js +32 -0
- package/dist/cjs/tests/concurrency-normalizer-optimizer.test.js +1 -0
- package/dist/cjs/tests/diff/diff-annotators.test.js +280 -0
- package/dist/cjs/tests/diff/diff-comprehensive.test.js +262 -0
- package/dist/cjs/tests/diff/diff-extended.test.js +235 -0
- package/dist/cjs/tests/diff/diff.test.js +189 -0
- package/dist/cjs/tests/diff/utils.js +8 -0
- package/dist/cjs/tests/errors/errors-integration.test.js +173 -0
- package/dist/cjs/tests/errors/errors.test.js +280 -0
- package/dist/cjs/tests/errors/utils.js +7 -0
- package/dist/cjs/tests/loader/integration.test.js +216 -0
- package/dist/cjs/tests/loader/loader.test.js +341 -0
- package/dist/cjs/tests/normalizer/normalizer-comprehensive.test.js +648 -0
- package/dist/cjs/tests/normalizer/normalizer-invalid.test.js +258 -0
- package/dist/cjs/tests/normalizer/normalizer-valid.test.js +238 -0
- package/dist/cjs/tests/normalizer/utils.js +47 -0
- package/dist/cjs/tests/optimizer/compress-references.test.js +304 -0
- package/dist/cjs/tests/optimizer/deduplication.test.js +132 -0
- package/dist/cjs/tests/optimizer/integration.test.js +131 -0
- package/dist/cjs/tests/optimizer/optimization-report.test.js +222 -0
- package/dist/cjs/tests/optimizer/optimize-document.test.js +187 -0
- package/dist/cjs/tests/optimizer/orphaned-schemas.test.js +194 -0
- package/dist/cjs/tests/optimizer/sort-schemas.test.js +131 -0
- package/dist/cjs/tests/optimizer/utils.js +209 -0
- package/dist/cjs/tests/schemas/schemas-edge-cases.test.js +223 -0
- package/dist/cjs/tests/schemas/schemas.test.js +400 -0
- package/dist/cjs/tests/schemas/utils.js +7 -0
- package/dist/cjs/tests/utils.js +131 -0
- package/dist/cjs/tests/validator/config-validator.test.js +78 -0
- package/dist/cjs/tests/validator/debug-config.js +1 -0
- package/dist/cjs/tests/validator/debug-missing-service.js +1 -0
- package/dist/cjs/tests/validator/debug-other-configs.js +1 -0
- package/dist/cjs/tests/validator/debug-references.js +1 -0
- package/dist/cjs/tests/validator/unispec-validator.test.js +103 -0
- package/dist/cjs/tests/validator/utils.js +25 -0
- package/dist/diff/annotators.js +36 -9
- package/dist/src/cache/cache-factory.d.ts +31 -0
- package/dist/src/cache/cache-factory.js +65 -0
- package/dist/src/cache/cache-manager.d.ts +62 -0
- package/dist/src/cache/cache-manager.js +124 -0
- package/dist/src/cache/constants.d.ts +21 -0
- package/dist/src/cache/constants.js +22 -0
- package/dist/src/cache/hash-utils.d.ts +11 -0
- package/dist/src/cache/hash-utils.js +15 -0
- package/dist/src/cache/hashing.d.ts +28 -0
- package/dist/src/cache/hashing.js +193 -0
- package/dist/src/cache/index.d.ts +6 -0
- package/dist/src/cache/index.js +10 -0
- package/dist/src/cache/lru-cache.d.ts +44 -0
- package/dist/src/cache/lru-cache.js +140 -0
- package/dist/src/cache/types.d.ts +24 -0
- package/dist/src/cache/types.js +4 -0
- package/dist/src/diff/annotators.d.ts +4 -0
- package/dist/src/diff/annotators.js +155 -0
- package/dist/src/diff/change-reports.d.ts +37 -0
- package/dist/src/diff/change-reports.js +366 -0
- package/dist/src/diff/core.d.ts +26 -0
- package/dist/src/diff/core.js +155 -0
- package/dist/src/diff/enhanced-diff.d.ts +51 -0
- package/dist/src/diff/enhanced-diff.js +62 -0
- package/dist/src/diff/impact-strategies-refactored.d.ts +69 -0
- package/dist/src/diff/impact-strategies-refactored.js +223 -0
- package/dist/src/diff/impact-strategies.d.ts +41 -0
- package/dist/src/diff/impact-strategies.js +212 -0
- package/dist/src/diff/index.d.ts +8 -0
- package/dist/src/diff/index.js +11 -0
- package/dist/src/diff/metrics-calculator.d.ts +23 -0
- package/dist/src/diff/metrics-calculator.js +65 -0
- package/dist/src/diff/risk-calculator.d.ts +23 -0
- package/dist/src/diff/risk-calculator.js +55 -0
- package/dist/src/diff/suggestion-generator.d.ts +18 -0
- package/dist/src/diff/suggestion-generator.js +74 -0
- package/dist/src/diff/types.d.ts +24 -0
- package/dist/src/diff/types.js +8 -0
- package/dist/src/errors/base-error.d.ts +20 -0
- package/dist/src/errors/base-error.js +29 -0
- package/dist/src/errors/config-error.d.ts +4 -0
- package/dist/src/errors/config-error.js +7 -0
- package/dist/src/errors/error-factory.d.ts +22 -0
- package/dist/src/errors/error-factory.js +45 -0
- package/dist/src/errors/index.d.ts +8 -0
- package/dist/src/errors/index.js +8 -0
- package/dist/src/errors/loader-error.d.ts +4 -0
- package/dist/src/errors/loader-error.js +7 -0
- package/dist/src/errors/reference-error.d.ts +4 -0
- package/dist/src/errors/reference-error.js +7 -0
- package/dist/src/errors/schema-error.d.ts +4 -0
- package/dist/src/errors/schema-error.js +7 -0
- package/dist/src/errors/security-error.d.ts +4 -0
- package/dist/src/errors/security-error.js +7 -0
- package/dist/src/errors/semantic-error.d.ts +4 -0
- package/dist/src/errors/semantic-error.js +7 -0
- package/dist/src/generated-schemas.d.ts +2073 -0
- package/dist/src/generated-schemas.js +2097 -0
- package/dist/src/index.d.ts +13 -0
- package/dist/src/index.js +43 -0
- package/dist/src/loader/index.d.ts +5 -0
- package/dist/src/loader/index.js +5 -0
- package/dist/src/loader/security-validator.d.ts +5 -0
- package/dist/src/loader/security-validator.js +50 -0
- package/dist/src/loader/types.d.ts +30 -0
- package/dist/src/loader/types.js +8 -0
- package/dist/src/loader/unispec-loader.d.ts +10 -0
- package/dist/src/loader/unispec-loader.js +81 -0
- package/dist/src/loader/yaml-loader.d.ts +10 -0
- package/dist/src/loader/yaml-loader.js +39 -0
- package/dist/src/normalizer/core.d.ts +24 -0
- package/dist/src/normalizer/core.js +34 -0
- package/dist/src/normalizer/graphql-normalizer.d.ts +8 -0
- package/dist/src/normalizer/graphql-normalizer.js +64 -0
- package/dist/src/normalizer/index.d.ts +2 -0
- package/dist/src/normalizer/index.js +3 -0
- package/dist/src/normalizer/rest-normalizer.d.ts +8 -0
- package/dist/src/normalizer/rest-normalizer.js +48 -0
- package/dist/src/normalizer/types.d.ts +7 -0
- package/dist/src/normalizer/types.js +1 -0
- package/dist/src/normalizer/utils.d.ts +17 -0
- package/dist/src/normalizer/utils.js +45 -0
- package/dist/src/normalizer/websocket-normalizer.d.ts +8 -0
- package/dist/src/normalizer/websocket-normalizer.js +78 -0
- package/dist/src/optimizer/core.d.ts +17 -0
- package/dist/src/optimizer/core.js +136 -0
- package/dist/src/optimizer/index.d.ts +4 -0
- package/dist/src/optimizer/index.js +7 -0
- package/dist/src/optimizer/optimization-functions.d.ts +32 -0
- package/dist/src/optimizer/optimization-functions.js +179 -0
- package/dist/src/optimizer/types.d.ts +28 -0
- package/dist/src/optimizer/types.js +1 -0
- package/dist/src/optimizer/utils.d.ts +7 -0
- package/dist/src/optimizer/utils.js +29 -0
- package/dist/src/schemas/dedupe.d.ts +9 -0
- package/dist/src/schemas/dedupe.js +110 -0
- package/dist/src/schemas/index.d.ts +3 -0
- package/dist/src/schemas/index.js +6 -0
- package/dist/src/schemas/resolver.d.ts +19 -0
- package/dist/src/schemas/resolver.js +38 -0
- package/dist/src/schemas/utils.d.ts +20 -0
- package/dist/src/schemas/utils.js +49 -0
- package/dist/src/types/index.d.ts +434 -0
- package/dist/src/types/index.js +1 -0
- package/dist/src/validator/ajv-validator.d.ts +15 -0
- package/dist/src/validator/ajv-validator.js +75 -0
- package/dist/src/validator/config-validator-main.d.ts +10 -0
- package/dist/src/validator/config-validator-main.js +31 -0
- package/dist/src/validator/config-validator.d.ts +5 -0
- package/dist/src/validator/config-validator.js +14 -0
- package/dist/src/validator/index.d.ts +10 -0
- package/dist/src/validator/index.js +11 -0
- package/dist/src/validator/object-traversal.d.ts +52 -0
- package/dist/src/validator/object-traversal.js +104 -0
- package/dist/src/validator/reference-validator.d.ts +31 -0
- package/dist/src/validator/reference-validator.js +230 -0
- package/dist/src/validator/schema-references.d.ts +23 -0
- package/dist/src/validator/schema-references.js +111 -0
- package/dist/src/validator/semantic-validator.d.ts +26 -0
- package/dist/src/validator/semantic-validator.js +325 -0
- package/dist/src/validator/tests-validator.d.ts +9 -0
- package/dist/src/validator/tests-validator.js +13 -0
- package/dist/src/validator/types.d.ts +29 -0
- package/dist/src/validator/types.js +1 -0
- package/dist/src/validator/unispec-validator.d.ts +15 -0
- package/dist/src/validator/unispec-validator.js +77 -0
- package/dist/src/validator/validator-factory.d.ts +10 -0
- package/dist/src/validator/validator-factory.js +73 -0
- package/dist/src/versions.d.ts +10 -0
- package/dist/src/versions.js +143 -0
- package/dist/tests/cache/cache.test.d.ts +1 -0
- package/dist/tests/cache/cache.test.js +269 -0
- package/dist/tests/cache/utils.d.ts +4 -0
- package/dist/tests/cache/utils.js +24 -0
- package/dist/tests/concurrency-normalizer-optimizer.test.d.ts +0 -0
- package/dist/tests/concurrency-normalizer-optimizer.test.js +1 -0
- package/dist/tests/diff/diff-annotators.test.d.ts +1 -0
- package/dist/tests/diff/diff-annotators.test.js +275 -0
- package/dist/tests/diff/diff-comprehensive.test.d.ts +1 -0
- package/dist/tests/diff/diff-comprehensive.test.js +257 -0
- package/dist/tests/diff/diff-extended.test.d.ts +1 -0
- package/dist/tests/diff/diff-extended.test.js +230 -0
- package/dist/tests/diff/diff.test.d.ts +1 -0
- package/dist/tests/diff/diff.test.js +184 -0
- package/dist/tests/diff/utils.d.ts +2 -0
- package/dist/tests/diff/utils.js +3 -0
- package/dist/tests/errors/errors-integration.test.d.ts +1 -0
- package/dist/tests/errors/errors-integration.test.js +168 -0
- package/dist/tests/errors/errors.test.d.ts +1 -0
- package/dist/tests/errors/errors.test.js +275 -0
- package/dist/tests/errors/utils.d.ts +2 -0
- package/dist/tests/errors/utils.js +3 -0
- package/dist/tests/loader/integration.test.d.ts +1 -0
- package/dist/tests/loader/integration.test.js +211 -0
- package/dist/tests/loader/loader.test.d.ts +1 -0
- package/dist/tests/loader/loader.test.js +336 -0
- package/dist/tests/normalizer/normalizer-comprehensive.test.d.ts +1 -0
- package/dist/tests/normalizer/normalizer-comprehensive.test.js +643 -0
- package/dist/tests/normalizer/normalizer-invalid.test.d.ts +1 -0
- package/dist/tests/normalizer/normalizer-invalid.test.js +253 -0
- package/dist/tests/normalizer/normalizer-valid.test.d.ts +1 -0
- package/dist/tests/normalizer/normalizer-valid.test.js +233 -0
- package/dist/tests/normalizer/utils.d.ts +18 -0
- package/dist/tests/normalizer/utils.js +36 -0
- package/dist/tests/optimizer/compress-references.test.d.ts +1 -0
- package/dist/tests/optimizer/compress-references.test.js +299 -0
- package/dist/tests/optimizer/deduplication.test.d.ts +1 -0
- package/dist/tests/optimizer/deduplication.test.js +127 -0
- package/dist/tests/optimizer/integration.test.d.ts +1 -0
- package/dist/tests/optimizer/integration.test.js +126 -0
- package/dist/tests/optimizer/optimization-report.test.d.ts +1 -0
- package/dist/tests/optimizer/optimization-report.test.js +217 -0
- package/dist/tests/optimizer/optimize-document.test.d.ts +1 -0
- package/dist/tests/optimizer/optimize-document.test.js +182 -0
- package/dist/tests/optimizer/orphaned-schemas.test.d.ts +1 -0
- package/dist/tests/optimizer/orphaned-schemas.test.js +189 -0
- package/dist/tests/optimizer/sort-schemas.test.d.ts +1 -0
- package/dist/tests/optimizer/sort-schemas.test.js +126 -0
- package/dist/tests/optimizer/utils.d.ts +8 -0
- package/dist/tests/optimizer/utils.js +199 -0
- package/dist/tests/schemas/schemas-edge-cases.test.d.ts +1 -0
- package/dist/tests/schemas/schemas-edge-cases.test.js +218 -0
- package/dist/tests/schemas/schemas.test.d.ts +1 -0
- package/dist/tests/schemas/schemas.test.js +395 -0
- package/dist/tests/schemas/utils.d.ts +2 -0
- package/dist/tests/schemas/utils.js +3 -0
- package/dist/tests/utils.d.ts +10 -0
- package/dist/tests/utils.js +118 -0
- package/dist/tests/validator/config-validator.test.d.ts +1 -0
- package/dist/tests/validator/config-validator.test.js +73 -0
- package/dist/tests/validator/debug-config.d.ts +0 -0
- package/dist/tests/validator/debug-config.js +1 -0
- package/dist/tests/validator/debug-missing-service.d.ts +0 -0
- package/dist/tests/validator/debug-missing-service.js +1 -0
- package/dist/tests/validator/debug-other-configs.d.ts +0 -0
- package/dist/tests/validator/debug-other-configs.js +1 -0
- package/dist/tests/validator/debug-references.d.ts +0 -0
- package/dist/tests/validator/debug-references.js +1 -0
- package/dist/tests/validator/unispec-validator.test.d.ts +1 -0
- package/dist/tests/validator/unispec-validator.test.js +98 -0
- package/dist/tests/validator/utils.d.ts +6 -0
- package/dist/tests/validator/utils.js +20 -0
- package/package.json +4 -3
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.LRUCache = void 0;
|
|
4
|
+
const constants_1 = require("./constants.js");
|
|
5
|
+
/**
|
|
6
|
+
* Simple LRU (Least Recently Used) cache implementation.
|
|
7
|
+
*/
|
|
8
|
+
class LRUCache {
|
|
9
|
+
constructor(options = {}) {
|
|
10
|
+
this.cache = new Map();
|
|
11
|
+
this.maxSize = options.maxSize || constants_1.CACHE_CONSTANTS.DEFAULT_MAX_SIZE;
|
|
12
|
+
this.ttl = options.ttl || constants_1.CACHE_CONSTANTS.DEFAULT_TTL;
|
|
13
|
+
this.enableStats = options.enableStats || false;
|
|
14
|
+
this.stats = {
|
|
15
|
+
hits: 0,
|
|
16
|
+
misses: 0,
|
|
17
|
+
size: 0,
|
|
18
|
+
hitRate: 0,
|
|
19
|
+
evictions: 0,
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Get a value from the cache asynchronously.
|
|
24
|
+
*/
|
|
25
|
+
async get(key) {
|
|
26
|
+
const entry = this.cache.get(key);
|
|
27
|
+
if (!entry) {
|
|
28
|
+
if (this.enableStats) {
|
|
29
|
+
this.stats.misses++;
|
|
30
|
+
this.updateHitRate();
|
|
31
|
+
}
|
|
32
|
+
return undefined;
|
|
33
|
+
}
|
|
34
|
+
// Check TTL
|
|
35
|
+
if (this.isExpired(entry)) {
|
|
36
|
+
this.cache.delete(key);
|
|
37
|
+
if (this.enableStats) {
|
|
38
|
+
this.stats.misses++;
|
|
39
|
+
this.stats.size--;
|
|
40
|
+
this.updateHitRate();
|
|
41
|
+
}
|
|
42
|
+
return undefined;
|
|
43
|
+
}
|
|
44
|
+
// Update access info
|
|
45
|
+
entry.accessCount++;
|
|
46
|
+
entry.lastAccessed = Date.now();
|
|
47
|
+
// Move to end (most recently used)
|
|
48
|
+
this.cache.delete(key);
|
|
49
|
+
this.cache.set(key, entry);
|
|
50
|
+
if (this.enableStats) {
|
|
51
|
+
this.stats.hits++;
|
|
52
|
+
this.updateHitRate();
|
|
53
|
+
}
|
|
54
|
+
return entry.value;
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Set a value in the cache asynchronously.
|
|
58
|
+
*/
|
|
59
|
+
async set(key, value) {
|
|
60
|
+
// Remove existing entry if present
|
|
61
|
+
if (this.cache.has(key)) {
|
|
62
|
+
this.cache.delete(key);
|
|
63
|
+
}
|
|
64
|
+
// Evict oldest entries if cache is full
|
|
65
|
+
while (this.cache.size >= this.maxSize) {
|
|
66
|
+
const oldestKey = this.cache.keys().next().value;
|
|
67
|
+
if (oldestKey) {
|
|
68
|
+
this.cache.delete(oldestKey);
|
|
69
|
+
if (this.enableStats) {
|
|
70
|
+
this.stats.evictions++;
|
|
71
|
+
this.stats.size--;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
else {
|
|
75
|
+
break;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
const entry = {
|
|
79
|
+
value,
|
|
80
|
+
timestamp: Date.now(),
|
|
81
|
+
accessCount: 1,
|
|
82
|
+
lastAccessed: Date.now(),
|
|
83
|
+
};
|
|
84
|
+
this.cache.set(key, entry);
|
|
85
|
+
if (this.enableStats) {
|
|
86
|
+
this.stats.size++;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* Delete a value from the cache.
|
|
91
|
+
*/
|
|
92
|
+
delete(key) {
|
|
93
|
+
const deleted = this.cache.delete(key);
|
|
94
|
+
if (deleted && this.enableStats) {
|
|
95
|
+
this.stats.size--;
|
|
96
|
+
}
|
|
97
|
+
return deleted;
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* Clear all entries from the cache.
|
|
101
|
+
*/
|
|
102
|
+
clear() {
|
|
103
|
+
this.cache.clear();
|
|
104
|
+
if (this.enableStats) {
|
|
105
|
+
this.stats.size = 0;
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
/**
|
|
109
|
+
* Get cache statistics.
|
|
110
|
+
*/
|
|
111
|
+
getStats() {
|
|
112
|
+
return { ...this.stats };
|
|
113
|
+
}
|
|
114
|
+
/**
|
|
115
|
+
* Check if an entry is expired.
|
|
116
|
+
*/
|
|
117
|
+
isExpired(entry) {
|
|
118
|
+
return Date.now() - entry.timestamp > this.ttl;
|
|
119
|
+
}
|
|
120
|
+
/**
|
|
121
|
+
* Update hit rate statistics.
|
|
122
|
+
*/
|
|
123
|
+
updateHitRate() {
|
|
124
|
+
const total = this.stats.hits + this.stats.misses;
|
|
125
|
+
this.stats.hitRate = total > 0 ? this.stats.hits / total : 0;
|
|
126
|
+
}
|
|
127
|
+
/**
|
|
128
|
+
* Clean up expired entries.
|
|
129
|
+
*/
|
|
130
|
+
cleanup() {
|
|
131
|
+
let cleaned = 0;
|
|
132
|
+
for (const [key, entry] of this.cache.entries()) {
|
|
133
|
+
if (this.isExpired(entry)) {
|
|
134
|
+
this.cache.delete(key);
|
|
135
|
+
cleaned++;
|
|
136
|
+
if (this.enableStats) {
|
|
137
|
+
this.stats.size--;
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
return cleaned;
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
exports.LRUCache = LRUCache;
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.annotateRestChange = annotateRestChange;
|
|
4
|
+
exports.annotateWebSocketChange = annotateWebSocketChange;
|
|
5
|
+
exports.annotateGraphQLChange = annotateGraphQLChange;
|
|
6
|
+
function annotateRestChange(change) {
|
|
7
|
+
// Add defensive check for null/undefined path
|
|
8
|
+
if (!change.path || typeof change.path !== "string") {
|
|
9
|
+
return change;
|
|
10
|
+
}
|
|
11
|
+
if (!change.path.startsWith("/service/protocols/rest/routes/")) {
|
|
12
|
+
return change;
|
|
13
|
+
}
|
|
14
|
+
const segments = change.path.split("/").filter(Boolean);
|
|
15
|
+
// Expected shape: ["service", "protocols", "rest", "routes", routeName, ...]
|
|
16
|
+
if (segments[0] !== "service" ||
|
|
17
|
+
segments[1] !== "protocols" ||
|
|
18
|
+
segments[2] !== "rest" ||
|
|
19
|
+
segments[3] !== "routes") {
|
|
20
|
+
return change;
|
|
21
|
+
}
|
|
22
|
+
const routeName = segments[4];
|
|
23
|
+
if (typeof routeName === "undefined") {
|
|
24
|
+
return change;
|
|
25
|
+
}
|
|
26
|
+
const annotated = {
|
|
27
|
+
...change,
|
|
28
|
+
protocol: "rest",
|
|
29
|
+
};
|
|
30
|
+
if (change.description === "Item removed") {
|
|
31
|
+
annotated.kind = "rest.route.removed";
|
|
32
|
+
annotated.severity = "breaking";
|
|
33
|
+
}
|
|
34
|
+
else if (change.description === "Item added") {
|
|
35
|
+
annotated.kind = "rest.route.added";
|
|
36
|
+
annotated.severity = "non-breaking";
|
|
37
|
+
}
|
|
38
|
+
else if (change.path.includes("/required") &&
|
|
39
|
+
change.description === "Value changed") {
|
|
40
|
+
// Changing required field from false to true is breaking
|
|
41
|
+
annotated.kind = "rest.parameter.required_changed";
|
|
42
|
+
annotated.severity = "breaking";
|
|
43
|
+
}
|
|
44
|
+
return annotated;
|
|
45
|
+
}
|
|
46
|
+
function annotateWebSocketChange(change) {
|
|
47
|
+
// Add defensive check for null/undefined path
|
|
48
|
+
if (!change.path || typeof change.path !== "string") {
|
|
49
|
+
return change;
|
|
50
|
+
}
|
|
51
|
+
if (!change.path.startsWith("/service/protocols/websocket/channels/")) {
|
|
52
|
+
return change;
|
|
53
|
+
}
|
|
54
|
+
const segments = change.path.split("/").filter(Boolean);
|
|
55
|
+
// Expected base: ["service","protocols","websocket","channels", channelName, ...]
|
|
56
|
+
if (segments[0] !== "service" ||
|
|
57
|
+
segments[1] !== "protocols" ||
|
|
58
|
+
segments[2] !== "websocket" ||
|
|
59
|
+
segments[3] !== "channels") {
|
|
60
|
+
return change;
|
|
61
|
+
}
|
|
62
|
+
const channelName = segments[4];
|
|
63
|
+
const next = segments[5];
|
|
64
|
+
const annotated = {
|
|
65
|
+
...change,
|
|
66
|
+
protocol: "websocket",
|
|
67
|
+
};
|
|
68
|
+
if (typeof channelName === "undefined") {
|
|
69
|
+
return annotated;
|
|
70
|
+
}
|
|
71
|
+
// Channel-level changes: /service/protocols/websocket/channels/{channelName}
|
|
72
|
+
if (!next) {
|
|
73
|
+
if (change.description === "Item removed") {
|
|
74
|
+
annotated.kind = "websocket.channel.removed";
|
|
75
|
+
annotated.severity = "breaking";
|
|
76
|
+
}
|
|
77
|
+
else if (change.description === "Item added") {
|
|
78
|
+
annotated.kind = "websocket.channel.added";
|
|
79
|
+
annotated.severity = "non-breaking";
|
|
80
|
+
}
|
|
81
|
+
return annotated;
|
|
82
|
+
}
|
|
83
|
+
// Message-level changes: /service/protocols/websocket/channels/{channelName}/messages/{messageName}
|
|
84
|
+
if (next === "messages") {
|
|
85
|
+
const messageName = segments[6];
|
|
86
|
+
if (typeof messageName === "undefined") {
|
|
87
|
+
return annotated;
|
|
88
|
+
}
|
|
89
|
+
if (change.description === "Item removed") {
|
|
90
|
+
annotated.kind = "websocket.message.removed";
|
|
91
|
+
annotated.severity = "breaking";
|
|
92
|
+
}
|
|
93
|
+
else if (change.description === "Item added") {
|
|
94
|
+
annotated.kind = "websocket.message.added";
|
|
95
|
+
annotated.severity = "non-breaking";
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
return annotated;
|
|
99
|
+
}
|
|
100
|
+
function annotateGraphQLChange(change) {
|
|
101
|
+
// Add defensive check for null/undefined path
|
|
102
|
+
if (!change.path || typeof change.path !== "string") {
|
|
103
|
+
return change;
|
|
104
|
+
}
|
|
105
|
+
if (!change.path.startsWith("/service/protocols/graphql/")) {
|
|
106
|
+
return change;
|
|
107
|
+
}
|
|
108
|
+
const segments = change.path.split("/").filter(Boolean);
|
|
109
|
+
// Expected: ["service","protocols","graphql", operationType, operationName, ...]
|
|
110
|
+
if (segments[0] !== "service" ||
|
|
111
|
+
segments[1] !== "protocols" ||
|
|
112
|
+
segments[2] !== "graphql") {
|
|
113
|
+
return change;
|
|
114
|
+
}
|
|
115
|
+
const operationType = segments[3];
|
|
116
|
+
const operationName = segments[4];
|
|
117
|
+
if (!operationType || typeof operationName === "undefined") {
|
|
118
|
+
return change;
|
|
119
|
+
}
|
|
120
|
+
if (operationType !== "queries" &&
|
|
121
|
+
operationType !== "mutations" &&
|
|
122
|
+
operationType !== "subscriptions") {
|
|
123
|
+
return change;
|
|
124
|
+
}
|
|
125
|
+
const annotated = {
|
|
126
|
+
...change,
|
|
127
|
+
protocol: "graphql",
|
|
128
|
+
};
|
|
129
|
+
// Create operation-specific annotations
|
|
130
|
+
const singularMap = {
|
|
131
|
+
queries: "query",
|
|
132
|
+
mutations: "mutation",
|
|
133
|
+
subscriptions: "subscription",
|
|
134
|
+
};
|
|
135
|
+
const operationKind = `graphql.${singularMap[operationType]}`;
|
|
136
|
+
// Check for specific changes first, then general ones
|
|
137
|
+
if (change.path.includes("/returnType") &&
|
|
138
|
+
change.description === "Value changed") {
|
|
139
|
+
// Changing return type is breaking
|
|
140
|
+
annotated.kind = `${operationKind}.return_type_changed`;
|
|
141
|
+
annotated.severity = "breaking";
|
|
142
|
+
}
|
|
143
|
+
else if (change.path.includes("/args") &&
|
|
144
|
+
(change.description === "Item added" ||
|
|
145
|
+
change.description === "Item removed")) {
|
|
146
|
+
// Argument changes
|
|
147
|
+
const action = change.description === "Item added" ? "added" : "removed";
|
|
148
|
+
annotated.kind = `${operationKind}.argument_${action}`;
|
|
149
|
+
annotated.severity = action === "added" ? "non-breaking" : "breaking";
|
|
150
|
+
}
|
|
151
|
+
else if (change.description === "Item removed") {
|
|
152
|
+
annotated.kind = `${operationKind}.removed`;
|
|
153
|
+
annotated.severity = "breaking";
|
|
154
|
+
}
|
|
155
|
+
else if (change.description === "Item added") {
|
|
156
|
+
annotated.kind = `${operationKind}.added`;
|
|
157
|
+
annotated.severity = "non-breaking";
|
|
158
|
+
}
|
|
159
|
+
return annotated;
|
|
160
|
+
}
|
|
@@ -0,0 +1,369 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.generateChangeReport = generateChangeReport;
|
|
4
|
+
/**
|
|
5
|
+
* Generate a markdown report from enhanced diff result.
|
|
6
|
+
*/
|
|
7
|
+
function generateMarkdownReport(diff, options) {
|
|
8
|
+
const lines = [];
|
|
9
|
+
// Title and metadata
|
|
10
|
+
lines.push(`# ${options.title || "UniSpec Changes Report"}`);
|
|
11
|
+
lines.push("");
|
|
12
|
+
if (options.versionInfo) {
|
|
13
|
+
lines.push("## Version Information");
|
|
14
|
+
if (options.versionInfo.oldVersion) {
|
|
15
|
+
lines.push(`- **From:** ${options.versionInfo.oldVersion}`);
|
|
16
|
+
}
|
|
17
|
+
if (options.versionInfo.newVersion) {
|
|
18
|
+
lines.push(`- **To:** ${options.versionInfo.newVersion}`);
|
|
19
|
+
}
|
|
20
|
+
if (options.versionInfo.comparisonDate) {
|
|
21
|
+
lines.push(`- **Date:** ${options.versionInfo.comparisonDate}`);
|
|
22
|
+
}
|
|
23
|
+
lines.push("");
|
|
24
|
+
}
|
|
25
|
+
// Executive summary
|
|
26
|
+
lines.push("## Executive Summary");
|
|
27
|
+
lines.push("");
|
|
28
|
+
lines.push(`- **Total Changes:** ${diff.summary.totalChanges}`);
|
|
29
|
+
lines.push(`- **Critical Changes:** ${diff.summary.criticalChanges}`);
|
|
30
|
+
lines.push(`- **High Risk Changes:** ${diff.summary.highRiskChanges}`);
|
|
31
|
+
lines.push(`- **Compatibility Score:** ${diff.metrics.overallCompatibility}%`);
|
|
32
|
+
lines.push(`- **Migration Effort:** ${diff.metrics.migrationEffort}`);
|
|
33
|
+
lines.push("");
|
|
34
|
+
// Compatibility metrics
|
|
35
|
+
lines.push("## Compatibility Metrics");
|
|
36
|
+
lines.push("");
|
|
37
|
+
lines.push("| Metric | Score | Status |");
|
|
38
|
+
lines.push("|--------|-------|--------|");
|
|
39
|
+
const overallStatus = diff.metrics.overallCompatibility >= 80
|
|
40
|
+
? "✅ Good"
|
|
41
|
+
: diff.metrics.overallCompatibility >= 60
|
|
42
|
+
? "⚠️ Moderate"
|
|
43
|
+
: "❌ Poor";
|
|
44
|
+
lines.push(`| Overall Compatibility | ${diff.metrics.overallCompatibility}% | ${overallStatus} |`);
|
|
45
|
+
const clientStatus = diff.metrics.clientCompatibilityScore >= 80
|
|
46
|
+
? "✅ Good"
|
|
47
|
+
: diff.metrics.clientCompatibilityScore >= 60
|
|
48
|
+
? "⚠️ Moderate"
|
|
49
|
+
: "❌ Poor";
|
|
50
|
+
lines.push(`| Client Compatibility | ${diff.metrics.clientCompatibilityScore}% | ${clientStatus} |`);
|
|
51
|
+
const serverStatus = diff.metrics.serverCompatibilityScore >= 80
|
|
52
|
+
? "✅ Good"
|
|
53
|
+
: diff.metrics.serverCompatibilityScore >= 60
|
|
54
|
+
? "⚠️ Moderate"
|
|
55
|
+
: "❌ Poor";
|
|
56
|
+
lines.push(`| Server Compatibility | ${diff.metrics.serverCompatibilityScore}% | ${serverStatus} |`);
|
|
57
|
+
lines.push("");
|
|
58
|
+
// Group changes
|
|
59
|
+
const groupedChanges = groupChanges(diff.changes, options);
|
|
60
|
+
for (const section of groupedChanges) {
|
|
61
|
+
lines.push(`## ${section.title}`);
|
|
62
|
+
lines.push("");
|
|
63
|
+
lines.push(section.summary);
|
|
64
|
+
lines.push("");
|
|
65
|
+
if (section.changes.length === 0) {
|
|
66
|
+
lines.push("No changes in this category.");
|
|
67
|
+
lines.push("");
|
|
68
|
+
continue;
|
|
69
|
+
}
|
|
70
|
+
for (const change of section.changes) {
|
|
71
|
+
lines.push(`### ${getChangeIcon(change.riskLevel)} ${getChangeTitle(change)}`);
|
|
72
|
+
lines.push("");
|
|
73
|
+
lines.push(`- **Path:** \`${change.path}\``);
|
|
74
|
+
lines.push(`- **Protocol:** ${change.protocol || "Unknown"}`);
|
|
75
|
+
lines.push(`- **Risk Level:** ${change.riskLevel.toUpperCase()}`);
|
|
76
|
+
lines.push(`- **Impact:** ${formatImpact(change.impact)}`);
|
|
77
|
+
lines.push("");
|
|
78
|
+
if (change.description) {
|
|
79
|
+
lines.push(`**Description:** ${change.description}`);
|
|
80
|
+
lines.push("");
|
|
81
|
+
}
|
|
82
|
+
if (options.includeSuggestions && change.suggestions.length > 0) {
|
|
83
|
+
lines.push("**Migration Suggestions:**");
|
|
84
|
+
for (const suggestion of change.suggestions) {
|
|
85
|
+
lines.push(`- ${suggestion}`);
|
|
86
|
+
}
|
|
87
|
+
lines.push("");
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
// Migration recommendations
|
|
92
|
+
if (diff.summary.criticalChanges > 0 || diff.summary.highRiskChanges > 0) {
|
|
93
|
+
lines.push("## Migration Recommendations");
|
|
94
|
+
lines.push("");
|
|
95
|
+
if (diff.summary.criticalChanges > 0) {
|
|
96
|
+
lines.push("⚠️ **Critical Changes Detected**");
|
|
97
|
+
lines.push("");
|
|
98
|
+
lines.push("Immediate action required:");
|
|
99
|
+
lines.push("- Review all critical changes");
|
|
100
|
+
lines.push("- Plan migration strategy");
|
|
101
|
+
lines.push("- Communicate changes to all stakeholders");
|
|
102
|
+
lines.push("- Consider feature flags for gradual rollout");
|
|
103
|
+
lines.push("");
|
|
104
|
+
}
|
|
105
|
+
if (diff.metrics.migrationEffort === "major") {
|
|
106
|
+
lines.push("🔧 **Major Migration Effort**");
|
|
107
|
+
lines.push("");
|
|
108
|
+
lines.push("Recommended approach:");
|
|
109
|
+
lines.push("- Break migration into phases");
|
|
110
|
+
lines.push("- Use parallel development");
|
|
111
|
+
lines.push("- Implement comprehensive testing");
|
|
112
|
+
lines.push("- Provide detailed migration guides");
|
|
113
|
+
lines.push("");
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
return lines.join("\n");
|
|
117
|
+
}
|
|
118
|
+
/**
|
|
119
|
+
* Generate an HTML report from enhanced diff result.
|
|
120
|
+
*/
|
|
121
|
+
function generateHtmlReport(diff, options) {
|
|
122
|
+
const styles = `
|
|
123
|
+
<style>
|
|
124
|
+
body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; margin: 40px; }
|
|
125
|
+
.header { border-bottom: 2px solid #e1e5e9; padding-bottom: 20px; margin-bottom: 30px; }
|
|
126
|
+
.metric { display: inline-block; margin: 10px 20px 10px 0; text-align: center; }
|
|
127
|
+
.metric-value { font-size: 2em; font-weight: bold; display: block; }
|
|
128
|
+
.metric-label { color: #666; font-size: 0.9em; }
|
|
129
|
+
.critical { color: #dc3545; }
|
|
130
|
+
.high { color: #fd7e14; }
|
|
131
|
+
.medium { color: #ffc107; }
|
|
132
|
+
.low { color: #28a745; }
|
|
133
|
+
.change { border: 1px solid #e1e5e9; border-radius: 8px; padding: 20px; margin: 20px 0; }
|
|
134
|
+
.change-header { font-weight: bold; margin-bottom: 10px; }
|
|
135
|
+
.suggestions { background: #f8f9fa; padding: 15px; border-radius: 5px; margin-top: 10px; }
|
|
136
|
+
.compatibility-bar { height: 20px; background: #e9ecef; border-radius: 10px; overflow: hidden; margin: 10px 0; }
|
|
137
|
+
.compatibility-fill { height: 100%; transition: width 0.3s ease; }
|
|
138
|
+
</style>
|
|
139
|
+
`;
|
|
140
|
+
const groupedChanges = groupChanges(diff.changes, options);
|
|
141
|
+
let html = `
|
|
142
|
+
<!DOCTYPE html>
|
|
143
|
+
<html>
|
|
144
|
+
<head>
|
|
145
|
+
<title>${options.title || "UniSpec Changes Report"}</title>
|
|
146
|
+
${styles}
|
|
147
|
+
</head>
|
|
148
|
+
<body>
|
|
149
|
+
<div class="header">
|
|
150
|
+
<h1>${options.title || "UniSpec Changes Report"}</h1>
|
|
151
|
+
<p>Generated on ${new Date().toLocaleDateString()}</p>
|
|
152
|
+
</div>
|
|
153
|
+
|
|
154
|
+
<div class="summary">
|
|
155
|
+
<h2>Executive Summary</h2>
|
|
156
|
+
<div class="metric">
|
|
157
|
+
<span class="metric-value">${diff.summary.totalChanges}</span>
|
|
158
|
+
<span class="metric-label">Total Changes</span>
|
|
159
|
+
</div>
|
|
160
|
+
<div class="metric">
|
|
161
|
+
<span class="metric-value critical">${diff.summary.criticalChanges}</span>
|
|
162
|
+
<span class="metric-label">Critical</span>
|
|
163
|
+
</div>
|
|
164
|
+
<div class="metric">
|
|
165
|
+
<span class="metric-value high">${diff.summary.highRiskChanges}</span>
|
|
166
|
+
<span class="metric-label">High Risk</span>
|
|
167
|
+
</div>
|
|
168
|
+
<div class="metric">
|
|
169
|
+
<span class="metric-value">${diff.metrics.overallCompatibility}%</span>
|
|
170
|
+
<span class="metric-label">Compatibility</span>
|
|
171
|
+
</div>
|
|
172
|
+
</div>
|
|
173
|
+
|
|
174
|
+
<div class="compatibility">
|
|
175
|
+
<h2>Compatibility Metrics</h2>
|
|
176
|
+
<div>
|
|
177
|
+
<strong>Overall Compatibility: ${diff.metrics.overallCompatibility}%</strong>
|
|
178
|
+
<div class="compatibility-bar">
|
|
179
|
+
<div class="compatibility-fill ${getCompatibilityClass(diff.metrics.overallCompatibility)}"
|
|
180
|
+
style="width: ${diff.metrics.overallCompatibility}%"></div>
|
|
181
|
+
</div>
|
|
182
|
+
</div>
|
|
183
|
+
<div>
|
|
184
|
+
<strong>Client Compatibility: ${diff.metrics.clientCompatibilityScore}%</strong>
|
|
185
|
+
<div class="compatibility-bar">
|
|
186
|
+
<div class="compatibility-fill ${getCompatibilityClass(diff.metrics.clientCompatibilityScore)}"
|
|
187
|
+
style="width: ${diff.metrics.clientCompatibilityScore}%"></div>
|
|
188
|
+
</div>
|
|
189
|
+
</div>
|
|
190
|
+
<div>
|
|
191
|
+
<strong>Server Compatibility: ${diff.metrics.serverCompatibilityScore}%</strong>
|
|
192
|
+
<div class="compatibility-bar">
|
|
193
|
+
<div class="compatibility-fill ${getCompatibilityClass(diff.metrics.serverCompatibilityScore)}"
|
|
194
|
+
style="width: ${diff.metrics.serverCompatibilityScore}%"></div>
|
|
195
|
+
</div>
|
|
196
|
+
</div>
|
|
197
|
+
</div>
|
|
198
|
+
`;
|
|
199
|
+
for (const section of groupedChanges) {
|
|
200
|
+
html += `
|
|
201
|
+
<div class="section">
|
|
202
|
+
<h2>${section.title}</h2>
|
|
203
|
+
<p>${section.summary}</p>
|
|
204
|
+
`;
|
|
205
|
+
for (const change of section.changes) {
|
|
206
|
+
html += `
|
|
207
|
+
<div class="change ${change.riskLevel}">
|
|
208
|
+
<div class="change-header">
|
|
209
|
+
${getChangeIcon(change.riskLevel)} ${getChangeTitle(change)}
|
|
210
|
+
</div>
|
|
211
|
+
<p><strong>Path:</strong> <code>${change.path}</code></p>
|
|
212
|
+
<p><strong>Protocol:</strong> ${change.protocol || "Unknown"}</p>
|
|
213
|
+
<p><strong>Risk Level:</strong> <span class="${change.riskLevel}">${change.riskLevel.toUpperCase()}</span></p>
|
|
214
|
+
<p><strong>Description:</strong> ${change.description}</p>
|
|
215
|
+
<p><strong>Impact:</strong> ${formatImpact(change.impact)}</p>
|
|
216
|
+
`;
|
|
217
|
+
if (options.includeSuggestions && change.suggestions.length > 0) {
|
|
218
|
+
html += `
|
|
219
|
+
<div class="suggestions">
|
|
220
|
+
<strong>Migration Suggestions:</strong>
|
|
221
|
+
<ul>
|
|
222
|
+
`;
|
|
223
|
+
for (const suggestion of change.suggestions) {
|
|
224
|
+
html += `<li>${suggestion}</li>`;
|
|
225
|
+
}
|
|
226
|
+
html += `
|
|
227
|
+
</ul>
|
|
228
|
+
</div>
|
|
229
|
+
`;
|
|
230
|
+
}
|
|
231
|
+
html += `
|
|
232
|
+
</div>
|
|
233
|
+
`;
|
|
234
|
+
}
|
|
235
|
+
html += `
|
|
236
|
+
</div>
|
|
237
|
+
`;
|
|
238
|
+
}
|
|
239
|
+
html += `
|
|
240
|
+
</body>
|
|
241
|
+
</html>
|
|
242
|
+
`;
|
|
243
|
+
return html;
|
|
244
|
+
}
|
|
245
|
+
/**
|
|
246
|
+
* Group changes by risk level or protocol.
|
|
247
|
+
*/
|
|
248
|
+
function groupChanges(changes, options) {
|
|
249
|
+
const sections = [];
|
|
250
|
+
if (options.groupByRisk) {
|
|
251
|
+
const riskGroups = {
|
|
252
|
+
critical: changes.filter((c) => c.riskLevel === "critical"),
|
|
253
|
+
high: changes.filter((c) => c.riskLevel === "high"),
|
|
254
|
+
medium: changes.filter((c) => c.riskLevel === "medium"),
|
|
255
|
+
low: changes.filter((c) => c.riskLevel === "low"),
|
|
256
|
+
};
|
|
257
|
+
for (const [risk, riskChanges] of Object.entries(riskGroups)) {
|
|
258
|
+
sections.push({
|
|
259
|
+
title: `${risk.charAt(0).toUpperCase() + risk.slice(1)} Risk Changes`,
|
|
260
|
+
changes: riskChanges,
|
|
261
|
+
summary: `${riskChanges.length} ${risk} risk changes that ${getRiskDescription(risk)}.`,
|
|
262
|
+
});
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
if (options.groupByProtocol) {
|
|
266
|
+
const protocolGroups = {
|
|
267
|
+
rest: changes.filter((c) => c.protocol === "rest"),
|
|
268
|
+
graphql: changes.filter((c) => c.protocol === "graphql"),
|
|
269
|
+
websocket: changes.filter((c) => c.protocol === "websocket"),
|
|
270
|
+
other: changes.filter((c) => !c.protocol || !["rest", "graphql", "websocket"].includes(c.protocol)),
|
|
271
|
+
};
|
|
272
|
+
for (const [protocol, protocolChanges] of Object.entries(protocolGroups)) {
|
|
273
|
+
if (protocolChanges.length > 0) {
|
|
274
|
+
sections.push({
|
|
275
|
+
title: `${protocol.charAt(0).toUpperCase() + protocol.slice(1)} Protocol Changes`,
|
|
276
|
+
changes: protocolChanges,
|
|
277
|
+
summary: `${protocolChanges.length} changes affecting ${protocol} protocol.`,
|
|
278
|
+
});
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
// If no grouping, create a single section
|
|
283
|
+
if (sections.length === 0) {
|
|
284
|
+
sections.push({
|
|
285
|
+
title: "All Changes",
|
|
286
|
+
changes,
|
|
287
|
+
summary: `${changes.length} total changes detected.`,
|
|
288
|
+
});
|
|
289
|
+
}
|
|
290
|
+
return sections;
|
|
291
|
+
}
|
|
292
|
+
/**
|
|
293
|
+
* Helper functions for formatting.
|
|
294
|
+
*/
|
|
295
|
+
function getChangeIcon(riskLevel) {
|
|
296
|
+
switch (riskLevel) {
|
|
297
|
+
case "critical":
|
|
298
|
+
return "🚨";
|
|
299
|
+
case "high":
|
|
300
|
+
return "⚠️";
|
|
301
|
+
case "medium":
|
|
302
|
+
return "⚡";
|
|
303
|
+
case "low":
|
|
304
|
+
return "✅";
|
|
305
|
+
default:
|
|
306
|
+
return "📝";
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
function getChangeTitle(change) {
|
|
310
|
+
const parts = change.path.split("/").filter(Boolean);
|
|
311
|
+
const lastPart = parts[parts.length - 1];
|
|
312
|
+
if (change.kind) {
|
|
313
|
+
return `${change.kind.replace(/\./g, " ").toUpperCase()} - ${lastPart}`;
|
|
314
|
+
}
|
|
315
|
+
return `${change.description} - ${lastPart}`;
|
|
316
|
+
}
|
|
317
|
+
function formatImpact(impact) {
|
|
318
|
+
const parts = [];
|
|
319
|
+
if (impact.backwardCompatibility !== "unknown") {
|
|
320
|
+
parts.push(`Compatibility: ${impact.backwardCompatibility}`);
|
|
321
|
+
}
|
|
322
|
+
if (impact.clientImpact !== "none") {
|
|
323
|
+
parts.push(`Client: ${impact.clientImpact}`);
|
|
324
|
+
}
|
|
325
|
+
if (impact.serverImpact !== "none") {
|
|
326
|
+
parts.push(`Server: ${impact.serverImpact}`);
|
|
327
|
+
}
|
|
328
|
+
return parts.join(", ");
|
|
329
|
+
}
|
|
330
|
+
function getRiskDescription(risk) {
|
|
331
|
+
switch (risk) {
|
|
332
|
+
case "critical":
|
|
333
|
+
return "require immediate attention and breaking changes";
|
|
334
|
+
case "high":
|
|
335
|
+
return "involve breaking changes or significant impact";
|
|
336
|
+
case "medium":
|
|
337
|
+
return "require attention but are manageable";
|
|
338
|
+
case "low":
|
|
339
|
+
return "are minor and easily handled";
|
|
340
|
+
default:
|
|
341
|
+
return "have unknown impact";
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
function getCompatibilityClass(score) {
|
|
345
|
+
if (score >= 80)
|
|
346
|
+
return "low";
|
|
347
|
+
if (score >= 60)
|
|
348
|
+
return "medium";
|
|
349
|
+
return "high";
|
|
350
|
+
}
|
|
351
|
+
/**
|
|
352
|
+
* Generate a comprehensive change report.
|
|
353
|
+
*
|
|
354
|
+
* @param diff - Enhanced diff result
|
|
355
|
+
* @param options - Report generation options
|
|
356
|
+
* @returns Formatted report in the specified format
|
|
357
|
+
*/
|
|
358
|
+
function generateChangeReport(diff, options) {
|
|
359
|
+
switch (options.format) {
|
|
360
|
+
case "markdown":
|
|
361
|
+
return generateMarkdownReport(diff, options);
|
|
362
|
+
case "html":
|
|
363
|
+
return generateHtmlReport(diff, options);
|
|
364
|
+
case "json":
|
|
365
|
+
return JSON.stringify(diff, null, 2);
|
|
366
|
+
default:
|
|
367
|
+
throw new Error(`Unsupported format: ${options.format}`);
|
|
368
|
+
}
|
|
369
|
+
}
|