cdk-cost-analyzer 0.1.41 → 0.1.43
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/.cdk-cost-analyzer-cache/metadata.json +8 -8
- package/dist/action/api/index.d.ts +1 -0
- package/dist/action/api/single-template-types.d.ts +4 -0
- package/dist/action/api/types.d.ts +3 -0
- package/dist/action/config/types.d.ts +2 -0
- package/dist/action/optimization/OptimizationEngine.d.ts +11 -0
- package/dist/action/optimization/analyzers/GravitonMigrationAnalyzer.d.ts +17 -0
- package/dist/action/optimization/analyzers/NATGatewayOptimizationAnalyzer.d.ts +13 -0
- package/dist/action/optimization/analyzers/ReservedInstanceAnalyzer.d.ts +13 -0
- package/dist/action/optimization/analyzers/RightSizingAnalyzer.d.ts +13 -0
- package/dist/action/optimization/analyzers/SavingsPlansAnalyzer.d.ts +11 -0
- package/dist/action/optimization/analyzers/SpotInstanceAnalyzer.d.ts +12 -0
- package/dist/action/optimization/analyzers/StorageOptimizationAnalyzer.d.ts +14 -0
- package/dist/action/optimization/defaults.d.ts +2 -0
- package/dist/action/optimization/index.d.ts +10 -0
- package/dist/action/optimization/types.d.ts +35 -0
- package/dist/action/reporter/RecommendationReporter.d.ts +3 -0
- package/dist/analysis/SingleTemplateAnalyzer.js +12 -1
- package/dist/api/index.d.ts +1 -0
- package/dist/api/index.js +2 -1
- package/dist/api/single-template-types.d.ts +4 -0
- package/dist/api/single-template-types.js +1 -1
- package/dist/api/types.d.ts +3 -0
- package/dist/api/types.js +1 -1
- package/dist/cli/index.js +13 -2
- package/dist/config/types.d.ts +2 -0
- package/dist/config/types.js +1 -1
- package/dist/optimization/OptimizationEngine.d.ts +11 -0
- package/dist/optimization/OptimizationEngine.js +51 -0
- package/dist/optimization/analyzers/GravitonMigrationAnalyzer.d.ts +17 -0
- package/dist/optimization/analyzers/GravitonMigrationAnalyzer.js +209 -0
- package/dist/optimization/analyzers/NATGatewayOptimizationAnalyzer.d.ts +13 -0
- package/dist/optimization/analyzers/NATGatewayOptimizationAnalyzer.js +142 -0
- package/dist/optimization/analyzers/ReservedInstanceAnalyzer.d.ts +13 -0
- package/dist/optimization/analyzers/ReservedInstanceAnalyzer.js +102 -0
- package/dist/optimization/analyzers/RightSizingAnalyzer.d.ts +13 -0
- package/dist/optimization/analyzers/RightSizingAnalyzer.js +109 -0
- package/dist/optimization/analyzers/SavingsPlansAnalyzer.d.ts +11 -0
- package/dist/optimization/analyzers/SavingsPlansAnalyzer.js +102 -0
- package/dist/optimization/analyzers/SpotInstanceAnalyzer.d.ts +12 -0
- package/dist/optimization/analyzers/SpotInstanceAnalyzer.js +113 -0
- package/dist/optimization/analyzers/StorageOptimizationAnalyzer.d.ts +14 -0
- package/dist/optimization/analyzers/StorageOptimizationAnalyzer.js +142 -0
- package/dist/optimization/defaults.d.ts +2 -0
- package/dist/optimization/defaults.js +22 -0
- package/dist/optimization/index.d.ts +10 -0
- package/dist/optimization/index.js +22 -0
- package/dist/optimization/types.d.ts +35 -0
- package/dist/optimization/types.js +3 -0
- package/dist/releasetag.txt +1 -1
- package/dist/reporter/RecommendationReporter.d.ts +3 -0
- package/dist/reporter/RecommendationReporter.js +133 -0
- package/dist/reporter/SingleTemplateReporter.js +11 -1
- package/docs/CALCULATORS.md +374 -8
- package/docs/README.md +3 -2
- package/docs/RECOMMENDATIONS.md +466 -0
- package/docs/SINGLE-TEMPLATE-ANALYSIS.md +59 -16
- package/docs/index.md +38 -8
- package/examples/recommendations-demo.json +74 -0
- package/package.json +1 -1
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.SpotInstanceAnalyzer = void 0;
|
|
4
|
+
const SPOT_SAVINGS_PERCENT = 60;
|
|
5
|
+
class SpotInstanceAnalyzer {
|
|
6
|
+
category = 'spot-instance';
|
|
7
|
+
name = 'Spot Instances';
|
|
8
|
+
isApplicable(resources) {
|
|
9
|
+
return resources.some((r) => r.type === 'AWS::AutoScaling::AutoScalingGroup' ||
|
|
10
|
+
r.type === 'AWS::ECS::Service');
|
|
11
|
+
}
|
|
12
|
+
async analyze(resources, resourceCosts, _region) {
|
|
13
|
+
const recommendations = [];
|
|
14
|
+
for (const resource of resources) {
|
|
15
|
+
if (resource.type === 'AWS::AutoScaling::AutoScalingGroup') {
|
|
16
|
+
const rec = this.analyzeASG(resource, resourceCosts);
|
|
17
|
+
if (rec)
|
|
18
|
+
recommendations.push(rec);
|
|
19
|
+
}
|
|
20
|
+
if (resource.type === 'AWS::ECS::Service') {
|
|
21
|
+
const rec = this.analyzeECSService(resource, resourceCosts);
|
|
22
|
+
if (rec)
|
|
23
|
+
recommendations.push(rec);
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
return recommendations;
|
|
27
|
+
}
|
|
28
|
+
analyzeASG(resource, resourceCosts) {
|
|
29
|
+
const props = resource.properties;
|
|
30
|
+
// Skip if already using mixed instances / spot
|
|
31
|
+
const mixedPolicy = props.MixedInstancesPolicy;
|
|
32
|
+
if (mixedPolicy) {
|
|
33
|
+
const instancesDistribution = mixedPolicy.InstancesDistribution;
|
|
34
|
+
const spotPercent = instancesDistribution?.OnDemandPercentageAboveBaseCapacity;
|
|
35
|
+
if (spotPercent !== undefined && spotPercent < 100) {
|
|
36
|
+
return null;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
const cost = this.findCost(resource.logicalId, resourceCosts);
|
|
40
|
+
// Suggest using 50% spot for a balanced approach
|
|
41
|
+
const spotPercentage = 50;
|
|
42
|
+
const savings = cost * (spotPercentage / 100) * (SPOT_SAVINGS_PERCENT / 100);
|
|
43
|
+
return {
|
|
44
|
+
id: `spot-${resource.logicalId}`,
|
|
45
|
+
title: `Use Spot Instances in ${resource.logicalId}`,
|
|
46
|
+
description: `ASG ${resource.logicalId} uses only on-demand instances. A mixed instances policy with ${spotPercentage}% Spot capacity can save ~$${savings.toFixed(2)}/month. Spot Instances are up to ${SPOT_SAVINGS_PERCENT}% cheaper than on-demand.`,
|
|
47
|
+
category: this.category,
|
|
48
|
+
priority: savings >= 100 ? 'high' : savings >= 30 ? 'medium' : 'low',
|
|
49
|
+
estimatedMonthlySavings: Math.round(savings * 100) / 100,
|
|
50
|
+
estimatedSavingsPercent: Math.round((SPOT_SAVINGS_PERCENT * spotPercentage) / 100),
|
|
51
|
+
affectedResources: [resource.logicalId],
|
|
52
|
+
actionItems: [
|
|
53
|
+
'Add MixedInstancesPolicy with OnDemandPercentageAboveBaseCapacity: 50',
|
|
54
|
+
'Diversify across multiple instance types for better Spot availability',
|
|
55
|
+
'Use Capacity Rebalancing to proactively replace Spot instances before termination',
|
|
56
|
+
'Ensure application handles graceful shutdown (2-minute termination notice)',
|
|
57
|
+
],
|
|
58
|
+
caveats: [
|
|
59
|
+
'Spot Instances can be interrupted with 2 minutes notice',
|
|
60
|
+
'Only suitable for fault-tolerant, stateless workloads',
|
|
61
|
+
'Not recommended for databases or stateful services',
|
|
62
|
+
'Spot availability varies by instance type, AZ, and time',
|
|
63
|
+
],
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
analyzeECSService(resource, resourceCosts) {
|
|
67
|
+
const props = resource.properties;
|
|
68
|
+
// Check if already using Spot
|
|
69
|
+
const capacityProviders = props.CapacityProviderStrategy;
|
|
70
|
+
if (capacityProviders) {
|
|
71
|
+
const hasSpot = capacityProviders.some((cp) => {
|
|
72
|
+
const name = cp.CapacityProvider || '';
|
|
73
|
+
return name.includes('SPOT') || name.includes('Spot');
|
|
74
|
+
});
|
|
75
|
+
if (hasSpot)
|
|
76
|
+
return null;
|
|
77
|
+
}
|
|
78
|
+
// Check launch type; only FARGATE services can easily use FARGATE_SPOT
|
|
79
|
+
const launchType = props.LaunchType;
|
|
80
|
+
if (launchType && launchType !== 'FARGATE')
|
|
81
|
+
return null;
|
|
82
|
+
const cost = this.findCost(resource.logicalId, resourceCosts);
|
|
83
|
+
const spotPercentage = 50;
|
|
84
|
+
const fargateSpotDiscount = 70;
|
|
85
|
+
const savings = cost * (spotPercentage / 100) * (fargateSpotDiscount / 100);
|
|
86
|
+
return {
|
|
87
|
+
id: `spot-ecs-${resource.logicalId}`,
|
|
88
|
+
title: `Use Fargate Spot for ${resource.logicalId}`,
|
|
89
|
+
description: `ECS service ${resource.logicalId} uses only standard Fargate. Fargate Spot is up to ${fargateSpotDiscount}% cheaper. Using ${spotPercentage}% Spot capacity can save ~$${savings.toFixed(2)}/month.`,
|
|
90
|
+
category: this.category,
|
|
91
|
+
priority: savings >= 50 ? 'high' : savings >= 20 ? 'medium' : 'low',
|
|
92
|
+
estimatedMonthlySavings: Math.round(savings * 100) / 100,
|
|
93
|
+
estimatedSavingsPercent: Math.round((fargateSpotDiscount * spotPercentage) / 100),
|
|
94
|
+
affectedResources: [resource.logicalId],
|
|
95
|
+
actionItems: [
|
|
96
|
+
'Add CapacityProviderStrategy with FARGATE (base) and FARGATE_SPOT',
|
|
97
|
+
'Set FARGATE as base capacity and FARGATE_SPOT for scaling',
|
|
98
|
+
'Ensure tasks handle SIGTERM gracefully for Spot interruptions',
|
|
99
|
+
],
|
|
100
|
+
caveats: [
|
|
101
|
+
'Fargate Spot tasks can be interrupted when AWS needs capacity',
|
|
102
|
+
'Not suitable for latency-sensitive or stateful workloads',
|
|
103
|
+
'Tasks receive a 30-second warning before termination',
|
|
104
|
+
],
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
findCost(logicalId, resourceCosts) {
|
|
108
|
+
const cost = resourceCosts.find((c) => c.logicalId === logicalId);
|
|
109
|
+
return cost?.monthlyCost.amount ?? 0;
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
exports.SpotInstanceAnalyzer = SpotInstanceAnalyzer;
|
|
113
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiU3BvdEluc3RhbmNlQW5hbHl6ZXIuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi8uLi9zcmMvb3B0aW1pemF0aW9uL2FuYWx5emVycy9TcG90SW5zdGFuY2VBbmFseXplci50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiOzs7QUFRQSxNQUFNLG9CQUFvQixHQUFHLEVBQUUsQ0FBQztBQUVoQyxNQUFhLG9CQUFvQjtJQUN0QixRQUFRLEdBQXlCLGVBQWUsQ0FBQztJQUNqRCxJQUFJLEdBQUcsZ0JBQWdCLENBQUM7SUFFakMsWUFBWSxDQUFDLFNBQTJCO1FBQ3RDLE9BQU8sU0FBUyxDQUFDLElBQUksQ0FDbkIsQ0FBQyxDQUFDLEVBQUUsRUFBRSxDQUNKLENBQUMsQ0FBQyxJQUFJLEtBQUssb0NBQW9DO1lBQy9DLENBQUMsQ0FBQyxJQUFJLEtBQUssbUJBQW1CLENBQ2pDLENBQUM7SUFDSixDQUFDO0lBRUQsS0FBSyxDQUFDLE9BQU8sQ0FDWCxTQUEyQixFQUMzQixhQUE2QixFQUM3QixPQUFlO1FBRWYsTUFBTSxlQUFlLEdBQXFCLEVBQUUsQ0FBQztRQUU3QyxLQUFLLE1BQU0sUUFBUSxJQUFJLFNBQVMsRUFBRSxDQUFDO1lBQ2pDLElBQUksUUFBUSxDQUFDLElBQUksS0FBSyxvQ0FBb0MsRUFBRSxDQUFDO2dCQUMzRCxNQUFNLEdBQUcsR0FBRyxJQUFJLENBQUMsVUFBVSxDQUFDLFFBQVEsRUFBRSxhQUFhLENBQUMsQ0FBQztnQkFDckQsSUFBSSxHQUFHO29CQUFFLGVBQWUsQ0FBQyxJQUFJLENBQUMsR0FBRyxDQUFDLENBQUM7WUFDckMsQ0FBQztZQUNELElBQUksUUFBUSxDQUFDLElBQUksS0FBSyxtQkFBbUIsRUFBRSxDQUFDO2dCQUMxQyxNQUFNLEdBQUcsR0FBRyxJQUFJLENBQUMsaUJBQWlCLENBQUMsUUFBUSxFQUFFLGFBQWEsQ0FBQyxDQUFDO2dCQUM1RCxJQUFJLEdBQUc7b0JBQUUsZUFBZSxDQUFDLElBQUksQ0FBQyxHQUFH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REFBK0Q7YUFDaEU7WUFDRCxPQUFPLEVBQUU7Z0JBQ1AsK0RBQStEO2dCQUMvRCwwREFBMEQ7Z0JBQzFELHNEQUFzRDthQUN2RDtTQUNGLENBQUM7SUFDSixDQUFDO0lBRU8sUUFBUSxDQUFDLFNBQWlCLEVBQUUsYUFBNkI7UUFDL0QsTUFBTSxJQUFJLEdBQUcsYUFBYSxDQUFDLElBQUksQ0FBQyxDQUFDLENBQUMsRUFBRSxFQUFFLENBQUMsQ0FBQyxDQUFDLFNBQVMsS0FBSyxTQUFTLENBQUMsQ0FBQztRQUNsRSxPQUFPLElBQUksRUFBRSxXQUFXLENBQUMsTUFBTSxJQUFJLENBQUMsQ0FBQztJQUN2QyxDQUFDO0NBQ0Y7QUE1SUQsb0RBNElDIiwic291cmNlc0NvbnRlbnQiOlsiaW1wb3J0IHsgUmVzb3VyY2VXaXRoSWQgfSBmcm9tICcuLi8uLi9kaWZmL3R5cGVzJztcbmltcG9ydCB7IFJlc291cmNlQ29zdCB9IGZyb20gJy4uLy4uL3ByaWNpbmcvdHlwZXMnO1xuaW1wb3J0IHtcbiAgT3B0aW1pemF0aW9uQW5hbHl6ZXIsXG4gIE9wdGltaXphdGlvbkNhdGVnb3J5LFxuICBSZWNvbW1lbmRhdGlvbixcbn0gZnJvbSAnLi4vdHlwZXMnO1xuXG5jb25zdCBTUE9UX1NBVklOR1NfUEVSQ0VOVCA9IDYwO1xuXG5leHBvcnQgY2xhc3MgU3BvdEluc3RhbmNlQW5hbHl6ZXIgaW1wbGVtZW50cyBPcHRpbWl6YXRpb25BbmFseXplciB7XG4gIHJlYWRvbmx5IGNhdGVnb3J5OiBPcHRpbWl6YXRpb25DYXRlZ29yeSA9ICdzcG90LWluc3RhbmNlJztcbiAgcmVhZG9ubHkgbmFtZSA9ICdTcG90IEluc3RhbmNlcyc7XG5cbiAgaXNBcHBsaWNhYmxlKHJlc291cmNlczogUmVzb3VyY2VXaXRoSWRbXSk6IGJvb2xlYW4ge1xuICAgIHJldHVybiByZXNvdXJjZXMuc29tZShcbiAgICAgIChyKSA9PlxuICAgICAgICByLnR5cGUgPT09ICdBV1M6OkF1dG9TY2FsaW5nOjpBdXRvU2NhbGluZ0dyb3VwJyB8fFxuICAgICAgICByLnR5cGUgPT09ICdBV1M6OkVDUzo6U2VydmljZScsXG4gICAgKTtcbiAgfVxuXG4gIGFzeW5jIGFuYWx5emUoXG4gICAgcmVzb3VyY2VzOiBSZXNvdXJjZVdpdGhJZFtdLFxuICAgIHJlc291cmNlQ29zdHM6IFJlc291cmNlQ29zdFtdLFxuICAgIF9yZWdpb246IHN0cmluZyxcbiAgKTogUHJvbWlzZTxSZWNvbW1lbmRhdGlvbltdPiB7XG4gICAgY29uc3QgcmVjb21tZW5kYXRpb25zOiBSZWNvbW1lbmRhdGlvbltdID0gW107XG5cbiAgICBmb3IgKGNvbnN0IHJlc291cmNlIG9mIHJlc291cmNlcykge1xuICAgICAgaWYgKHJlc291cmNlLnR5cGUgPT09ICdBV1M6OkF1dG9TY2FsaW5nOjpBdXRvU2NhbGluZ0dyb3VwJykge1xuICAgICAgICBjb25zdCByZWMgPSB0aGlzLmFuYWx5emVBU0cocmVzb3VyY2UsIHJlc291cmNlQ29zdHMpO1xuICAgICAgICBpZiAocmVjKSByZWNvbW1lbmRhdGlvbnMucHVzaChyZWMpO1xuICAgICAgfVxuICAgICAgaWYgKHJlc291cmNlLnR5cGUgPT09ICdBV1M6OkVDUzo6U2VydmljZScpIHtcbiAgICAgICAgY29uc3QgcmVjID0gdGhpcy5hbmFseXplRUNTU2VydmljZShyZXNvdXJjZSwgcmVzb3VyY2VDb3N0cyk7XG4gICAgICAgIGlmIChyZWMpIHJlY29tbWVuZGF0aW9ucy5wdXNoKHJlYyk7XG4gICAgICB9XG4gICAgfVxuXG4gICAgcmV0dXJuIHJlY29tbWVuZGF0aW9ucztcbiAgfVxuXG4gIHByaXZhdGUgYW5hbHl6ZUFTRyhcbiAgICByZXNvdXJjZTogUmVzb3VyY2VXaXRoSWQsXG4gICAgcmVzb3VyY2VDb3N0czogUmVzb3VyY2VDb3N0W10sXG4gICk6IFJlY29tbWVuZGF0aW9uIHwgbnVsbCB7XG4gICAgY29uc3QgcHJvcHMgPSByZXNvdXJjZS5wcm9wZXJ0aWVzO1xuXG4gICAgLy8gU2tpcCBpZiBhbHJlYWR5IHVzaW5nIG1peGVkIGluc3RhbmNlcyAvIHNwb3RcbiAgICBjb25zdCBtaXhlZFBvbGljeSA9IHByb3BzLk1peGVkSW5zdGFuY2VzUG9saWN5IGFzXG4gICAgICB8IFJlY29yZDxzdHJpbmcsIHVua25vd24+XG4gICAgICB8IHVuZGVmaW5lZDtcbiAgICBpZiAobWl4ZWRQb2xpY3kpIHtcbiAgICAgIGNvbnN0IGluc3RhbmNlc0Rpc3RyaWJ1dGlvbiA9IG1peGVkUG9saWN5Lkluc3RhbmNlc0Rpc3RyaWJ1dGlvbiBhc1xuICAgICAgICB8IFJlY29yZDxzdHJpbmcsIHVua25vd24+XG4gICAgICAgIHwgdW5kZWZpbmVkO1xuICAgICAgY29uc3Qgc3BvdFBlcmNlbnQgPVxuICAgICAgICBpbnN0YW5jZXNEaXN0cmlidXRpb24/Lk9uRGVtYW5kUGVyY2VudGFnZUFib3ZlQmFzZUNhcGFjaXR5O1xuICAgICAgaWYgKHNwb3RQZXJjZW50ICE9PSB1bmRlZmluZWQgJiYgKHNwb3RQZXJjZW50IGFzIG51bWJlcikgPCAxMDApIHtcbiAgICAgICAgcmV0dXJuIG51bGw7XG4gICAgICB9XG4gICAgfVxuXG4gICAgY29uc3QgY29zdCA9IHRoaXMuZmluZENvc3QocmVzb3VyY2UubG9naWNhbElkLCByZXNvdXJjZUNvc3RzKTtcbiAgICAvLyBTdWdnZXN0IHVzaW5nIDUwJSBzcG90IGZvciBhIGJhbGFuY2VkIGFwcHJvYWNoXG4gICAgY29uc3Qgc3BvdFBlcmNlbnRhZ2UgPSA1MDtcbiAgICBjb25zdCBzYXZpbmdzID0gY29zdCAqIChzcG90UGVyY2VudGFnZSAvIDEwMCkgKiAoU1BPVF9TQVZJTkdTX1BFUkNFTlQgLyAxMDApO1xuXG4gICAgcmV0dXJuIHtcbiAgICAgIGlkOiBgc3BvdC0ke3Jlc291cmNlLmxvZ2ljYWxJZH1gLFxuICAgICAgdGl0bGU6IGBVc2UgU3BvdCBJbnN0YW5jZXMgaW4gJHtyZXNvdXJjZS5sb2dpY2FsSWR9YCxcbiAgICAgIGRlc2NyaXB0aW9uOiBgQVNHICR7cmVzb3VyY2UubG9naWNhbElkfSB1c2VzIG9ubHkgb24tZGVtYW5kIGluc3RhbmNlcy4gQSBtaXhlZCBpbnN0YW5jZXMgcG9saWN5IHdpdGggJHtzcG90UGVyY2VudGFnZX0lIFNwb3QgY2FwYWNpdHkgY2FuIHNhdmUgfiQke3NhdmluZ3MudG9GaXhlZCgyKX0vbW9udGguIFNwb3QgSW5zdGFuY2VzIGFyZSB1cCB0byAke1NQT1RfU0FWSU5HU19QRVJDRU5UfSUgY2hlYXBlciB0aGFuIG9uLWRlbWFuZC5gLFxuICAgICAgY2F0ZWdvcnk6IHRoaXMuY2F0ZWdvcnksXG4gICAgICBwcmlvcml0eTogc2F2aW5ncyA+PSAxMDAgPyAnaGlnaCcgOiBzYXZpbmdzID49IDMwID8gJ21lZGl1bScgOiAnbG93JyxcbiAgICAgIGVzdGltYXRlZE1vbnRobHlTYXZpbmdzOiBNYXRoLnJvdW5kKHNhdmluZ3MgKiAxMDApIC8gMTAwLFxuICAgICAgZXN0aW1hdGVkU2F2aW5nc1BlcmNlbnQ6IE1hdGgucm91bmQoXG4gICAgICAgIChTUE9UX1NBVklOR1NfUEVSQ0VOVCAqIHNwb3RQZXJjZW50YWdlKSAvIDEwMCxcbiAgICAgICksXG4gICAgICBhZmZlY3RlZFJlc291cmNlczogW3Jlc291cmNlLmxvZ2ljYWxJZF0sXG4gICAgICBhY3Rpb25JdGVtczogW1xuICAgICAgICAnQWRkIE1peGVkSW5zdGFuY2VzUG9saWN5IHdpdGggT25EZW1hbmRQZXJjZW50YWdlQWJvdmVCYXNlQ2FwYWNpdHk6IDUwJyxcbiAgICAgICAgJ0RpdmVyc2lmeSBhY3Jvc3MgbXVsdGlwbGUgaW5zdGFuY2UgdHlwZXMgZm9yIGJldHRlciBTcG90IGF2YWlsYWJpbGl0eScsXG4gICAgICAgICdVc2UgQ2FwYWNpdHkgUmViYWxhbmNpbmcgdG8gcHJvYWN0aXZlbHkgcmVwbGFjZSBTcG90IGluc3RhbmNlcyBiZWZvcmUgdGVybWluYXRpb24nLFxuICAgICAgICAnRW5zdXJlIGFwcGxpY2F0aW9uIGhhbmRsZXMgZ3JhY2VmdWwgc2h1dGRvd24gKDItbWludXRlIHRlcm1pbmF0aW9uIG5vdGljZSknLFxuICAgICAgXSxcbiAgICAgIGNhdmVhdHM6IFtcbiAgICAgICAgJ1Nwb3QgSW5zdGFuY2VzIGNhbiBiZSBpbnRlcnJ1cHRlZCB3aXRoIDIgbWludXRlcyBub3RpY2UnLFxuICAgICAgICAnT25seSBzdWl0YWJsZSBmb3IgZmF1bHQtdG9sZXJhbnQsIHN0YXRlbGVzcyB3b3JrbG9hZHMnLFxuICAgICAgICAnTm90IHJlY29tbWVuZGVkIGZvciBkYXRhYmFzZXMgb3Igc3RhdGVmdWwgc2VydmljZXMnLFxuICAgICAgICAnU3BvdCBhdmFpbGFiaWxpdHkgdmFyaWVzIGJ5IGluc3RhbmNlIHR5cGUsIEFaLCBhbmQgdGltZScsXG4gICAgICBdLFxuICAgIH07XG4gIH1cblxuICBwcml2YXRlIGFuYWx5emVFQ1NTZXJ2aWNlKFxuICAgIHJlc291cmNlOiBSZXNvdXJjZVdpdGhJZCxcbiAgICByZXNvdXJjZUNvc3RzOiBSZXNvdXJjZUNvc3RbXSxcbiAgKTogUmVjb21tZW5kYXRpb24gfCBudWxsIHtcbiAgICBjb25zdCBwcm9wcyA9IHJlc291cmNlLnByb3BlcnRpZXM7XG5cbiAgICAvLyBDaGVjayBpZiBhbHJlYWR5IHVzaW5nIFNwb3RcbiAgICBjb25zdCBjYXBhY2l0eVByb3ZpZGVycyA9IHByb3BzLkNhcGFjaXR5UHJvdmlkZXJTdHJhdGVneSBhc1xuICAgICAgfCBBcnJheTxSZWNvcmQ8c3RyaW5nLCB1bmtub3duPj5cbiAgICAgIHwgdW5kZWZpbmVkO1xuICAgIGlmIChjYXBhY2l0eVByb3ZpZGVycykge1xuICAgICAgY29uc3QgaGFzU3BvdCA9IGNhcGFjaXR5UHJvdmlkZXJzLnNvbWUoKGNwKSA9PiB7XG4gICAgICAgIGNvbnN0IG5hbWUgPSAoY3AuQ2FwYWNpdHlQcm92aWRlciBhcyBzdHJpbmcpIHx8ICcnO1xuICAgICAgICByZXR1cm4gbmFtZS5pbmNsdWRlcygnU1BPVCcpIHx8IG5hbWUuaW5jbHVkZXMoJ1Nwb3QnKTtcbiAgICAgIH0pO1xuICAgICAgaWYgKGhhc1Nwb3QpIHJldHVybiBudWxsO1xuICAgIH1cblxuICAgIC8vIENoZWNrIGxhdW5jaCB0eXBlOyBvbmx5IEZBUkdBVEUgc2VydmljZXMgY2FuIGVhc2lseSB1c2UgRkFSR0FURV9TUE9UXG4gICAgY29uc3QgbGF1bmNoVHlwZSA9IHByb3BzLkxhdW5jaFR5cGUgYXMgc3RyaW5nIHwgdW5kZWZpbmVkO1xuICAgIGlmIChsYXVuY2hUeXBlICYmIGxhdW5jaFR5cGUgIT09ICdGQVJHQVRFJykgcmV0dXJuIG51bGw7XG5cbiAgICBjb25zdCBjb3N0ID0gdGhpcy5maW5kQ29zdChyZXNvdXJjZS5sb2dpY2FsSWQsIHJlc291cmNlQ29zdHMpO1xuICAgIGNvbnN0IHNwb3RQZXJjZW50YWdlID0gNTA7XG4gICAgY29uc3QgZmFyZ2F0ZVNwb3REaXNjb3VudCA9IDcwO1xuICAgIGNvbnN0IHNhdmluZ3MgPSBjb3N0ICogKHNwb3RQZXJjZW50YWdlIC8gMTAwKSAqIChmYXJnYXRlU3BvdERpc2NvdW50IC8gMTAwKTtcblxuICAgIHJldHVybiB7XG4gICAgICBpZDogYHNwb3QtZWNzLSR7cmVzb3VyY2UubG9naWNhbElkfWAsXG4gICAgICB0aXRsZTogYFVzZSBGYXJnYXRlIFNwb3QgZm9yICR7cmVzb3VyY2UubG9naWNhbElkfWAsXG4gICAgICBkZXNjcmlwdGlvbjogYEVDUyBzZXJ2aWNlICR7cmVzb3VyY2UubG9naWNhbElkfSB1c2VzIG9ubHkgc3RhbmRhcmQgRmFyZ2F0ZS4gRmFyZ2F0ZSBTcG90IGlzIHVwIHRvICR7ZmFyZ2F0ZVNwb3REaXNjb3VudH0lIGNoZWFwZXIuIFVzaW5nICR7c3BvdFBlcmNlbnRhZ2V9JSBTcG90IGNhcGFjaXR5IGNhbiBzYXZlIH4kJHtzYXZpbmdzLnRvRml4ZWQoMil9L21vbnRoLmAsXG4gICAgICBjYXRlZ29yeTogdGhpcy5jYXRlZ29yeSxcbiAgICAgIHByaW9yaXR5OiBzYXZpbmdzID49IDUwID8gJ2hpZ2gnIDogc2F2aW5ncyA+PSAyMCA/ICdtZWRpdW0nIDogJ2xvdycsXG4gICAgICBlc3RpbWF0ZWRNb250aGx5U2F2aW5nczogTWF0aC5yb3VuZChzYXZpbmdzICogMTAwKSAvIDEwMCxcbiAgICAgIGVzdGltYXRlZFNhdmluZ3NQZXJjZW50OiBNYXRoLnJvdW5kKFxuICAgICAgICAoZmFyZ2F0ZVNwb3REaXNjb3VudCAqIHNwb3RQZXJjZW50YWdlKSAvIDEwMCxcbiAgICAgICksXG4gICAgICBhZmZlY3RlZFJlc291cmNlczogW3Jlc291cmNlLmxvZ2ljYWxJZF0sXG4gICAgICBhY3Rpb25JdGVtczogW1xuICAgICAgICAnQWRkIENhcGFjaXR5UHJvdmlkZXJTdHJhdGVneSB3aXRoIEZBUkdBVEUgKGJhc2UpIGFuZCBGQVJHQVRFX1NQT1QnLFxuICAgICAgICAnU2V0IEZBUkdBVEUgYXMgYmFzZSBjYXBhY2l0eSBhbmQgRkFSR0FURV9TUE9UIGZvciBzY2FsaW5nJyxcbiAgICAgICAgJ0Vuc3VyZSB0YXNrcyBoYW5kbGUgU0lHVEVSTSBncmFjZWZ1bGx5IGZvciBTcG90IGludGVycnVwdGlvbnMnLFxuICAgICAgXSxcbiAgICAgIGNhdmVhdHM6IFtcbiAgICAgICAgJ0ZhcmdhdGUgU3BvdCB0YXNrcyBjYW4gYmUgaW50ZXJydXB0ZWQgd2hlbiBBV1MgbmVlZHMgY2FwYWNpdHknLFxuICAgICAgICAnTm90IHN1aXRhYmxlIGZvciBsYXRlbmN5LXNlbnNpdGl2ZSBvciBzdGF0ZWZ1bCB3b3JrbG9hZHMnLFxuICAgICAgICAnVGFza3MgcmVjZWl2ZSBhIDMwLXNlY29uZCB3YXJuaW5nIGJlZm9yZSB0ZXJtaW5hdGlvbicsXG4gICAgICBdLFxuICAgIH07XG4gIH1cblxuICBwcml2YXRlIGZpbmRDb3N0KGxvZ2ljYWxJZDogc3RyaW5nLCByZXNvdXJjZUNvc3RzOiBSZXNvdXJjZUNvc3RbXSk6IG51bWJlciB7XG4gICAgY29uc3QgY29zdCA9IHJlc291cmNlQ29zdHMuZmluZCgoYykgPT4gYy5sb2dpY2FsSWQgPT09IGxvZ2ljYWxJZCk7XG4gICAgcmV0dXJuIGNvc3Q/Lm1vbnRobHlDb3N0LmFtb3VudCA/PyAwO1xuICB9XG59XG4iXX0=
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { ResourceWithId } from '../../diff/types';
|
|
2
|
+
import { ResourceCost } from '../../pricing/types';
|
|
3
|
+
import { OptimizationAnalyzer, OptimizationCategory, Recommendation } from '../types';
|
|
4
|
+
export declare class StorageOptimizationAnalyzer implements OptimizationAnalyzer {
|
|
5
|
+
readonly category: OptimizationCategory;
|
|
6
|
+
readonly name = "Storage Optimization";
|
|
7
|
+
isApplicable(resources: ResourceWithId[]): boolean;
|
|
8
|
+
analyze(resources: ResourceWithId[], resourceCosts: ResourceCost[], _region: string): Promise<Recommendation[]>;
|
|
9
|
+
private analyzeS3Bucket;
|
|
10
|
+
private analyzeEBSVolume;
|
|
11
|
+
private analyzeLaunchTemplate;
|
|
12
|
+
private hasIntelligentTieringConfig;
|
|
13
|
+
private findCost;
|
|
14
|
+
}
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.StorageOptimizationAnalyzer = void 0;
|
|
4
|
+
const GP2_TO_GP3_SAVINGS_PERCENT = 20;
|
|
5
|
+
class StorageOptimizationAnalyzer {
|
|
6
|
+
category = 'storage-optimization';
|
|
7
|
+
name = 'Storage Optimization';
|
|
8
|
+
isApplicable(resources) {
|
|
9
|
+
return resources.some((r) => r.type === 'AWS::S3::Bucket' ||
|
|
10
|
+
r.type === 'AWS::EC2::Volume' ||
|
|
11
|
+
r.type === 'AWS::EC2::LaunchTemplate');
|
|
12
|
+
}
|
|
13
|
+
async analyze(resources, resourceCosts, _region) {
|
|
14
|
+
const recommendations = [];
|
|
15
|
+
for (const resource of resources) {
|
|
16
|
+
if (resource.type === 'AWS::S3::Bucket') {
|
|
17
|
+
const rec = this.analyzeS3Bucket(resource);
|
|
18
|
+
if (rec)
|
|
19
|
+
recommendations.push(rec);
|
|
20
|
+
}
|
|
21
|
+
if (resource.type === 'AWS::EC2::Volume') {
|
|
22
|
+
const rec = this.analyzeEBSVolume(resource, resourceCosts);
|
|
23
|
+
if (rec)
|
|
24
|
+
recommendations.push(rec);
|
|
25
|
+
}
|
|
26
|
+
if (resource.type === 'AWS::EC2::LaunchTemplate') {
|
|
27
|
+
const recs = this.analyzeLaunchTemplate(resource, resourceCosts);
|
|
28
|
+
recommendations.push(...recs);
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
return recommendations;
|
|
32
|
+
}
|
|
33
|
+
analyzeS3Bucket(resource) {
|
|
34
|
+
const props = resource.properties;
|
|
35
|
+
const hasLifecycle = props.LifecycleConfiguration !== undefined;
|
|
36
|
+
const hasIntelligentTiering = this.hasIntelligentTieringConfig(props);
|
|
37
|
+
if (hasLifecycle && hasIntelligentTiering)
|
|
38
|
+
return null;
|
|
39
|
+
const actionItems = [];
|
|
40
|
+
if (!hasIntelligentTiering) {
|
|
41
|
+
actionItems.push('Enable S3 Intelligent-Tiering to automatically optimize storage costs based on access patterns');
|
|
42
|
+
}
|
|
43
|
+
if (!hasLifecycle) {
|
|
44
|
+
actionItems.push('Add lifecycle rules to transition infrequently accessed objects to cheaper storage classes', 'Consider archiving objects older than 90 days to S3 Glacier');
|
|
45
|
+
}
|
|
46
|
+
return {
|
|
47
|
+
id: `s3-tiering-${resource.logicalId}`,
|
|
48
|
+
title: `Optimize storage for ${resource.logicalId}`,
|
|
49
|
+
description: `S3 bucket ${resource.logicalId} ${!hasLifecycle ? 'has no lifecycle rules' : 'lacks Intelligent-Tiering'}. Adding lifecycle policies and Intelligent-Tiering can reduce storage costs by 30-75% for infrequently accessed data.`,
|
|
50
|
+
category: this.category,
|
|
51
|
+
priority: 'medium',
|
|
52
|
+
estimatedMonthlySavings: 0,
|
|
53
|
+
estimatedSavingsPercent: 0,
|
|
54
|
+
affectedResources: [resource.logicalId],
|
|
55
|
+
actionItems,
|
|
56
|
+
caveats: [
|
|
57
|
+
'Savings depend on data access patterns and total storage volume',
|
|
58
|
+
'Intelligent-Tiering has a small monitoring fee ($0.0025/1000 objects)',
|
|
59
|
+
'Glacier retrieval takes minutes to hours depending on tier',
|
|
60
|
+
],
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
analyzeEBSVolume(resource, resourceCosts) {
|
|
64
|
+
const volumeType = resource.properties.VolumeType || 'gp2';
|
|
65
|
+
if (volumeType !== 'gp2')
|
|
66
|
+
return null;
|
|
67
|
+
const cost = this.findCost(resource.logicalId, resourceCosts);
|
|
68
|
+
const savings = cost * (GP2_TO_GP3_SAVINGS_PERCENT / 100);
|
|
69
|
+
const sizeGB = resource.properties.Size || 0;
|
|
70
|
+
return {
|
|
71
|
+
id: `gp3-migration-${resource.logicalId}`,
|
|
72
|
+
title: `Migrate ${resource.logicalId} from gp2 to gp3`,
|
|
73
|
+
description: `EBS volume ${resource.logicalId} uses gp2${sizeGB ? ` (${sizeGB} GB)` : ''}. gp3 volumes are 20% cheaper with better baseline performance (3,000 IOPS vs 100 IOPS/GB for gp2).`,
|
|
74
|
+
category: this.category,
|
|
75
|
+
priority: savings >= 20 ? 'high' : 'medium',
|
|
76
|
+
estimatedMonthlySavings: Math.round(savings * 100) / 100,
|
|
77
|
+
estimatedSavingsPercent: GP2_TO_GP3_SAVINGS_PERCENT,
|
|
78
|
+
affectedResources: [resource.logicalId],
|
|
79
|
+
actionItems: [
|
|
80
|
+
'Change VolumeType from gp2 to gp3',
|
|
81
|
+
'Set desired IOPS and throughput (gp3 baseline: 3,000 IOPS, 125 MB/s)',
|
|
82
|
+
'No downtime required; modification is online',
|
|
83
|
+
],
|
|
84
|
+
caveats: [
|
|
85
|
+
'Online modification takes time to complete',
|
|
86
|
+
'If workload requires >3,000 IOPS, additional IOPS provisioning costs apply',
|
|
87
|
+
`Savings estimate of ${GP2_TO_GP3_SAVINGS_PERCENT}% is based on standard pricing`,
|
|
88
|
+
],
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
analyzeLaunchTemplate(resource, _resourceCosts) {
|
|
92
|
+
const recommendations = [];
|
|
93
|
+
const launchData = resource.properties.LaunchTemplateData;
|
|
94
|
+
if (!launchData)
|
|
95
|
+
return recommendations;
|
|
96
|
+
const blockDevices = launchData.BlockDeviceMappings;
|
|
97
|
+
if (!blockDevices)
|
|
98
|
+
return recommendations;
|
|
99
|
+
for (const bdm of blockDevices) {
|
|
100
|
+
const ebs = bdm.Ebs;
|
|
101
|
+
if (!ebs)
|
|
102
|
+
continue;
|
|
103
|
+
const volumeType = ebs.VolumeType || 'gp2';
|
|
104
|
+
if (volumeType !== 'gp2')
|
|
105
|
+
continue;
|
|
106
|
+
const deviceName = bdm.DeviceName || 'unknown';
|
|
107
|
+
recommendations.push({
|
|
108
|
+
id: `gp3-lt-${resource.logicalId}-${deviceName}`,
|
|
109
|
+
title: `Migrate ${resource.logicalId} EBS (${deviceName}) to gp3`,
|
|
110
|
+
description: `Launch template ${resource.logicalId} uses gp2 for ${deviceName}. Switching to gp3 saves ~20% with better baseline performance.`,
|
|
111
|
+
category: this.category,
|
|
112
|
+
priority: 'medium',
|
|
113
|
+
estimatedMonthlySavings: 0,
|
|
114
|
+
estimatedSavingsPercent: GP2_TO_GP3_SAVINGS_PERCENT,
|
|
115
|
+
affectedResources: [resource.logicalId],
|
|
116
|
+
actionItems: [
|
|
117
|
+
`Change VolumeType from gp2 to gp3 for ${deviceName} in LaunchTemplateData`,
|
|
118
|
+
],
|
|
119
|
+
caveats: [
|
|
120
|
+
'New instances will use gp3; existing instances are unaffected',
|
|
121
|
+
'Requires launching new instances or modifying existing volumes separately',
|
|
122
|
+
],
|
|
123
|
+
});
|
|
124
|
+
}
|
|
125
|
+
return recommendations;
|
|
126
|
+
}
|
|
127
|
+
hasIntelligentTieringConfig(props) {
|
|
128
|
+
const tiering = props.IntelligentTieringConfigurations;
|
|
129
|
+
if (tiering)
|
|
130
|
+
return true;
|
|
131
|
+
const analytics = props.AnalyticsConfigurations;
|
|
132
|
+
if (analytics?.some((a) => a.StorageClassAnalysis))
|
|
133
|
+
return true;
|
|
134
|
+
return false;
|
|
135
|
+
}
|
|
136
|
+
findCost(logicalId, resourceCosts) {
|
|
137
|
+
const cost = resourceCosts.find((c) => c.logicalId === logicalId);
|
|
138
|
+
return cost?.monthlyCost.amount ?? 0;
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
exports.StorageOptimizationAnalyzer = StorageOptimizationAnalyzer;
|
|
142
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiU3RvcmFnZU9wdGltaXphdGlvbkFuYWx5emVyLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vLi4vc3JjL29wdGltaXphdGlvbi9hbmFseXplcnMvU3RvcmFnZU9wdGltaXphdGlvbkFuYWx5emVyLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiI7OztBQVFBLE1BQU0sMEJBQTBCLEdBQUcsRUFBRSxDQUFDO0FBRXRDLE1BQWEsMkJBQTJCO0lBQzdCLFFBQVEsR0FBeUIsc0JBQXNCLENBQUM7SUFDeEQsSUFBSSxHQUFHLHNCQUFzQixDQUFDO0lBRXZDLFlBQVksQ0FBQyxTQUEyQjtRQUN0QyxPQUFPLFNBQVMsQ0FBQyxJQUFJLENBQ25CLENBQUMsQ0FBQyxFQUFFLEVBQUUsQ0FDSixDQUFDLENBQUMsSUFBSSxLQUFLLGlCQUFpQjtZQUM1QixDQUFDLENBQUMsSUFBSSxLQUFLLGtCQUFrQjtZQUM3QixDQUFDLENBQUMsSUFBSSxLQUFLLDBCQUEwQixDQUN4QyxDQUFDO0lBQ0osQ0FBQztJQUVELEtBQUssQ0FBQyxPQUFPLENBQ1gsU0FBMkIsRUFDM0IsYUFBNkIsRUFDN0IsT0FBZTtRQUVmLE1BQU0sZUFBZSxHQUFxQixFQUFFLENBQUM7UUFFN0MsS0FBSyxNQUFNLFFBQVEsSUFBSSxTQUFTLEVBQUUsQ0FBQztZQUNqQyxJQUFJLFFBQVEsQ0FBQyxJQUFJLEtBQUssaUJBQWlCLEVBQUUsQ0FBQztnQkFDeEMsTUFBTSxHQUFHLEdBQUcsSUFBSSxDQUFDLGVBQWUsQ0FBQyxRQUFRLENBQUMsQ0FBQztnQkFDM0MsSUFBSSxHQUFHO29CQUFFLGVBQWUsQ0FBQyxJQUFJLENBQUMsR0FBRyxDQUFDLENBQUM7WUFDckMsQ0FBQztZQUNELElBQUksUUFBUSxDQUFDLElBQUksS0FBSyxrQkFBa0IsRUFBRSxDQUFDO2dCQUN6QyxNQUFNLEdBQUcsR0FBRyxJQUFJLENBQUMsZ0JBQWdCLENBQUMsUUFBUSxFQUFFLGFBQWEsQ0FBQyxDQUFDO2dCQUMzRCxJ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aElkIH0gZnJvbSAnLi4vLi4vZGlmZi90eXBlcyc7XG5pbXBvcnQgeyBSZXNvdXJjZUNvc3QgfSBmcm9tICcuLi8uLi9wcmljaW5nL3R5cGVzJztcbmltcG9ydCB7XG4gIE9wdGltaXphdGlvbkFuYWx5emVyLFxuICBPcHRpbWl6YXRpb25DYXRlZ29yeSxcbiAgUmVjb21tZW5kYXRpb24sXG59IGZyb20gJy4uL3R5cGVzJztcblxuY29uc3QgR1AyX1RPX0dQM19TQVZJTkdTX1BFUkNFTlQgPSAyMDtcblxuZXhwb3J0IGNsYXNzIFN0b3JhZ2VPcHRpbWl6YXRpb25BbmFseXplciBpbXBsZW1lbnRzIE9wdGltaXphdGlvbkFuYWx5emVyIHtcbiAgcmVhZG9ubHkgY2F0ZWdvcnk6IE9wdGltaXphdGlvbkNhdGVnb3J5ID0gJ3N0b3JhZ2Utb3B0aW1pemF0aW9uJztcbiAgcmVhZG9ubHkgbmFtZSA9ICdTdG9yYWdlIE9wdGltaXphdGlvbic7XG5cbiAgaXNBcHBsaWNhYmxlKHJlc291cmNlczogUmVzb3VyY2VXaXRoSWRbXSk6IGJvb2xlYW4ge1xuICAgIHJldHVybiByZXNvdXJjZXMuc29tZShcbiAgICAgIChyKSA9PlxuICAgICAgICByLnR5cGUgPT09ICdBV1M6OlMzOjpCdWNrZXQnIHx8XG4gICAgICAgIHIudHlwZSA9PT0gJ0FXUzo6RUMyOjpWb2x1bWUnIHx8XG4gICAgICAgIHIudHlwZSA9PT0gJ0FXUzo6RUMyOjpMYXVuY2hUZW1wbGF0ZScsXG4gICAgKTtcbiAgfVxuXG4gIGFzeW5jIGFuYWx5emUoXG4gICAgcmVzb3VyY2VzOiBSZXNvdXJjZVdpdGhJZFtdLFxuICAgIHJlc291cmNlQ29zdHM6IFJlc291cmNlQ29zdFtdLFxuICAgIF9yZWdpb246IHN0cmluZyxcbiAgKTogUHJvbWlzZTxSZWNvbW1lbmRhdGlvbltdPiB7XG4gICAgY29uc3QgcmVjb21tZW5kYXRpb25zOiBSZWNvbW1lbmRhdGlvbltdID0gW107XG5cbiAgICBmb3IgKGNvbnN0IHJlc291cmNlIG9mIHJlc291cmNlcykge1xuICAgICAgaWYgKHJlc291cmNlLnR5cGUgPT09ICdBV1M6OlMzOjpCdWNrZXQnKSB7XG4gICAgICAgIGNvbnN0IHJlYyA9IHRoaXMuYW5hbHl6ZVMzQnVja2V0KHJlc291cmNlKTtcbiAgICAgICAgaWYgKHJlYykgcmVjb21tZW5kYXRpb25zLnB1c2gocmVjKTtcbiAgICAgIH1cbiAgICAgIGlmIChyZXNvdXJjZS50eXBlID09PSAnQVdTOjpFQzI6OlZvbHVtZScpIHtcbiAgICAgICAgY29uc3QgcmVjID0gdGhpcy5hbmFseXplRUJTVm9sdW1lKHJlc291cmNlLCByZXNvdXJjZUNvc3RzKTtcbiAgICAgICAgaWYgKHJlYykgcmVjb21tZW5kYXRpb25zLnB1c2gocmVjKTtcbiAgICAgIH1cbiAgICAgIGlmIChyZXNvdXJjZS50eXBlID09PSAnQVdTOjpFQzI6OkxhdW5jaFRlbXBsYXRlJykge1xuICAgICAgICBjb25zdCByZWNzID0gdGhpcy5hbmFseXplTGF1bmNoVGVtcGxhdGUocmVzb3VyY2UsIHJlc291cmNlQ29zdHMpO1xuICAgICAgICByZWNvbW1lbmRhdGlvbnMucHVzaCguLi5yZWNzKTtcbiAgICAgIH1cbiAgICB9XG5cbiAgICByZXR1cm4gcmVjb21tZW5kYXRpb25zO1xuICB9XG5cbiAgcHJpdmF0ZSBhbmFseXplUzNCdWNrZXQocmVzb3VyY2U6IFJlc291cmNlV2l0aElkKTogUmVjb21tZW5kYXRpb24gfCBudWxsIHtcbiAgICBjb25zdCBwcm9wcyA9IHJlc291cmNlLnByb3BlcnRpZXM7XG4gICAgY29uc3QgaGFzTGlmZWN5Y2xlID0gcHJvcHMuTGlmZWN5Y2xlQ29uZmlndXJhdGlvbiAhPT0gdW5kZWZpbmVkO1xuICAgIGNvbnN0IGhhc0ludGVsbGlnZW50VGllcmluZyA9IHRoaXMuaGFzSW50ZWxsaWdlbnRUaWVyaW5nQ29uZmlnKHByb3BzKTtcblxuICAgIGlmIChoYXNMaWZlY3ljbGUgJiYgaGFzSW50ZWxsaWdlbnRUaWVyaW5nKSByZXR1cm4gbnVsbDtcblxuICAgIGNvbnN0IGFjdGlvbkl0ZW1zOiBzdHJpbmdbXSA9IFtdO1xuICAgIGlmICghaGFzSW50ZWxsaWdlbnRUaWVyaW5nKSB7XG4gICAgICBhY3Rpb25JdGVtcy5wdXNoKFxuICAgICAgICAnRW5hYmxlIFMzIEludGVsbGlnZW50LVRpZXJpbmcgdG8gYXV0b21hdGljYWxseSBvcHRpbWl6ZSBzdG9yYWdlIGNvc3RzIGJhc2VkIG9uIGFjY2VzcyBwYXR0ZXJucycsXG4gICAgICApO1xuICAgIH1cbiAgICBpZiAoIWhhc0xpZmVjeWNsZSkge1xuICAgICAgYWN0aW9uSXRlbXMucHVzaChcbiAgICAgICAgJ0FkZCBsaWZlY3ljbGUgcnVsZXMgdG8gdHJhbnNpdGlvbiBpbmZyZXF1ZW50bHkgYWNjZXNzZWQgb2JqZWN0cyB0byBjaGVhcGVyIHN0b3JhZ2UgY2xhc3NlcycsXG4gICAgICAgICdDb25zaWRlciBhcmNoaXZpbmcgb2JqZWN0cyBvbGRlciB0aGFuIDkwIGRheXMgdG8gUzMgR2xhY2llcicsXG4gICAgICApO1xuICAgIH1cblxuICAgIHJldHVybiB7XG4gICAgICBpZDogYHMzLXRpZXJpbmctJHtyZXNvdXJjZS5sb2dpY2FsSWR9YCxcbiAgICAgIHRpdGxlOiBgT3B0aW1pemUgc3RvcmFnZSBmb3IgJHtyZXNvdXJjZS5sb2dpY2FsSWR9YCxcbiAgICAgIGRlc2NyaXB0aW9uOiBgUzMgYnVja2V0ICR7cmVzb3VyY2UubG9naWNhbElkfSAkeyFoYXNMaWZlY3ljbGUgPyAnaGFzIG5vIGxpZmVjeWNsZSBydWxlcycgOiAnbGFja3MgSW50ZWxsaWdlbnQtVGllcmluZyd9LiBBZGRpbmcgbGlmZWN5Y2xlIHBvbGljaWVzIGFuZCBJbnRlbGxpZ2VudC1UaWVyaW5nIGNhbiByZWR1Y2Ugc3RvcmFnZSBjb3N0cyBieSAzMC03NSUgZm9yIGluZnJlcXVlbnRseSBhY2Nlc3NlZCBkYXRhLmAsXG4gICAgICBjYXRlZ29yeTogdGhpcy5jYXRlZ29yeSxcbiAgICAgIHByaW9yaXR5OiAnbWVkaXVtJyxcbiAgICAgIGVzdGltYXRlZE1vbnRobHlTYXZpbmdzOiAwLFxuICAgICAgZXN0aW1hdGVkU2F2aW5nc1BlcmNlbnQ6IDAsXG4gICAgICBhZmZlY3RlZFJlc291cmNlczogW3Jlc291cmNlLmxvZ2ljYWxJZF0sXG4gICAgICBhY3Rpb25JdGVtcyxcbiAgICAgIGNhdmVhdHM6IFtcbiAgICAgICAgJ1NhdmluZ3MgZGVwZW5kIG9uIGRhdGEgYWNjZXNzIHBhdHRlcm5zIGFuZCB0b3RhbCBzdG9yYWdlIHZvbHVtZScsXG4gICAgICAgICdJbnRlbGxpZ2VudC1UaWVyaW5nIGhhcyBhIHNtYWxsIG1vbml0b3JpbmcgZmVlICgkMC4wMDI1LzEwMDAgb2JqZWN0cyknLFxuICAgICAgICAnR2xhY2llciByZXRyaWV2YWwgdGFrZXMgbWludXRlcyB0byBob3VycyBkZXBlbmRpbmcgb24gdGllcicsXG4gICAgICBdLFxuICAgIH07XG4gIH1cblxuICBwcml2YXRlIGFuYWx5emVFQlNWb2x1bWUoXG4gICAgcmVzb3VyY2U6IFJlc291cmNlV2l0aElkLFxuICAgIHJlc291cmNlQ29zdHM6IFJlc291cmNlQ29zdFtdLFxuICApOiBSZWNvbW1lbmRhdGlvbiB8IG51bGwge1xuICAgIGNvbnN0IHZvbHVtZVR5cGUgPSAocmVzb3VyY2UucHJvcGVydGllcy5Wb2x1bWVUeXBlIGFzIHN0cmluZykgfHwgJ2dwMic7XG5cbiAgICBpZiAodm9sdW1lVHlwZSAhPT0gJ2dwMicpIHJldHVybiBudWxsO1xuXG4gICAgY29uc3QgY29zdCA9IHRoaXMuZmluZENvc3QocmVzb3VyY2UubG9naWNhbElkLCByZXNvdXJjZUNvc3RzKTtcbiAgICBjb25zdCBzYXZpbmdzID0gY29zdCAqIChHUDJfVE9fR1AzX1NBVklOR1NfUEVSQ0VOVCAvIDEwMCk7XG4gICAgY29uc3Qgc2l6ZUdCID0gKHJlc291cmNlLnByb3BlcnRpZXMuU2l6ZSBhcyBudW1iZXIpIHx8IDA7XG5cbiAgICByZXR1cm4ge1xuICAgICAgaWQ6IGBncDMtbWlncmF0aW9uLSR7cmVzb3VyY2UubG9naWNhbElkfWAsXG4gICAgICB0aXRsZTogYE1pZ3JhdGUgJHtyZXNvdXJjZS5sb2dpY2FsSWR9IGZyb20gZ3AyIHRvIGdwM2AsXG4gICAgICBkZXNjcmlwdGlvbjogYEVCUyB2b2x1bWUgJHtyZXNvdXJjZS5sb2dpY2FsSWR9IHVzZXMgZ3AyJHtzaXplR0IgPyBgICgke3NpemVHQn0gR0IpYCA6ICcnfS4gZ3AzIHZvbHVtZXMgYXJlIDIwJSBjaGVhcGVyIHdpdGggYmV0dGVyIGJhc2VsaW5lIHBlcmZvcm1hbmNlICgzLDAwMCBJT1BTIHZzIDEwMCBJT1BTL0dCIGZvciBncDIpLmAsXG4gICAgICBjYXRlZ29yeTogdGhpcy5jYXRlZ29yeSxcbiAgICAgIHByaW9yaXR5OiBzYXZpbmdzID49IDIwID8gJ2hpZ2gnIDogJ21lZGl1bScsXG4gICAgICBlc3RpbWF0ZWRNb250aGx5U2F2aW5nczogTWF0aC5yb3VuZChzYXZpbmdzICogMTAwKSAvIDEwMCxcbiAgICAgIGVzdGltYXRlZFNhdmluZ3NQZXJjZW50OiBHUDJfVE9fR1AzX1NBVklOR1NfUEVSQ0VOVCxcbiAgICAgIGFmZmVjdGVkUmVzb3VyY2VzOiBbcmVzb3VyY2UubG9naWNhbElkXSxcbiAgICAgIGFjdGlvbkl0ZW1zOiBbXG4gICAgICAgICdDaGFuZ2UgVm9sdW1lVHlwZSBmcm9tIGdwMiB0byBncDMnLFxuICAgICAgICAnU2V0IGRlc2lyZWQgSU9QUyBhbmQgdGhyb3VnaHB1dCAoZ3AzIGJhc2VsaW5lOiAzLDAwMCBJT1BTLCAxMjUgTUIvcyknLFxuICAgICAgICAnTm8gZG93bnRpbWUgcmVxdWlyZWQ7IG1vZGlmaWNhdGlvbiBpcyBvbmxpbmUnLFxuICAgICAgXSxcbiAgICAgIGNhdmVhdHM6IFtcbiAgICAgICAgJ09ubGluZSBtb2RpZmljYXRpb24gdGFrZXMgdGltZSB0byBjb21wbGV0ZScsXG4gICAgICAgICdJZiB3b3JrbG9hZCByZXF1aXJlcyA+MywwMDAgSU9QUywgYWRkaXRpb25hbCBJT1BTIHByb3Zpc2lvbmluZyBjb3N0cyBhcHBseScsXG4gICAgICAgIGBTYXZpbmdzIGVzdGltYXRlIG9mICR7R1AyX1RPX0dQM19TQVZJTkdTX1BFUkNFTlR9JSBpcyBiYXNlZCBvbiBzdGFuZGFyZCBwcmljaW5nYCxcbiAgICAgIF0sXG4gICAgfTtcbiAgfVxuXG4gIHByaXZhdGUgYW5hbHl6ZUxhdW5jaFRlbXBsYXRlKFxuICAgIHJlc291cmNlOiBSZXNvdXJjZVdpdGhJZCxcbiAgICBfcmVzb3VyY2VDb3N0czogUmVzb3VyY2VDb3N0W10sXG4gICk6IFJlY29tbWVuZGF0aW9uW10ge1xuICAgIGNvbnN0IHJlY29tbWVuZGF0aW9uczogUmVjb21tZW5kYXRpb25bXSA9IFtdO1xuICAgIGNvbnN0IGxhdW5jaERhdGEgPSByZXNvdXJjZS5wcm9wZXJ0aWVzLkxhdW5jaFRlbXBsYXRlRGF0YSBhc1xuICAgICAgfCBSZWNvcmQ8c3RyaW5nLCB1bmtub3duPlxuICAgICAgfCB1bmRlZmluZWQ7XG4gICAgaWYgKCFsYXVuY2hEYXRhKSByZXR1cm4gcmVjb21tZW5kYXRpb25zO1xuXG4gICAgY29uc3QgYmxvY2tEZXZpY2VzID0gbGF1bmNoRGF0YS5CbG9ja0RldmljZU1hcHBpbmdzIGFzXG4gICAgICB8IEFycmF5PFJlY29yZDxzdHJpbmcsIHVua25vd24+PlxuICAgICAgfCB1bmRlZmluZWQ7XG4gICAgaWYgKCFibG9ja0RldmljZXMpIHJldHVybiByZWNvbW1lbmRhdGlvbnM7XG5cbiAgICBmb3IgKGNvbnN0IGJkbSBvZiBibG9ja0RldmljZXMpIHtcbiAgICAgIGNvbnN0IGVicyA9IGJkbS5FYnMgYXMgUmVjb3JkPHN0cmluZywgdW5rbm93bj4gfCB1bmRlZmluZWQ7XG4gICAgICBpZiAoIWVicykgY29udGludWU7XG5cbiAgICAgIGNvbnN0IHZvbHVtZVR5cGUgPSAoZWJzLlZvbHVtZVR5cGUgYXMgc3RyaW5nKSB8fCAnZ3AyJztcbiAgICAgIGlmICh2b2x1bWVUeXBlICE9PSAnZ3AyJykgY29udGludWU7XG5cbiAgICAgIGNvbnN0IGRldmljZU5hbWUgPSAoYmRtLkRldmljZU5hbWUgYXMgc3RyaW5nKSB8fCAndW5rbm93bic7XG4gICAgICByZWNvbW1lbmRhdGlvbnMucHVzaCh7XG4gICAgICAgIGlkOiBgZ3AzLWx0LSR7cmVzb3VyY2UubG9naWNhbElkfS0ke2RldmljZU5hbWV9YCxcbiAgICAgICAgdGl0bGU6IGBNaWdyYXRlICR7cmVzb3VyY2UubG9naWNhbElkfSBFQlMgKCR7ZGV2aWNlTmFtZX0pIHRvIGdwM2AsXG4gICAgICAgIGRlc2NyaXB0aW9uOiBgTGF1bmNoIHRlbXBsYXRlICR7cmVzb3VyY2UubG9naWNhbElkfSB1c2VzIGdwMiBmb3IgJHtkZXZpY2VOYW1lfS4gU3dpdGNoaW5nIHRvIGdwMyBzYXZlcyB+MjAlIHdpdGggYmV0dGVyIGJhc2VsaW5lIHBlcmZvcm1hbmNlLmAsXG4gICAgICAgIGNhdGVnb3J5OiB0aGlzLmNhdGVnb3J5LFxuICAgICAgICBwcmlvcml0eTogJ21lZGl1bScsXG4gICAgICAgIGVzdGltYXRlZE1vbnRobHlTYXZpbmdzOiAwLFxuICAgICAgICBlc3RpbWF0ZWRTYXZpbmdzUGVyY2VudDogR1AyX1RPX0dQM19TQVZJTkdTX1BFUkNFTlQsXG4gICAgICAgIGFmZmVjdGVkUmVzb3VyY2VzOiBbcmVzb3VyY2UubG9naWNhbElkXSxcbiAgICAgICAgYWN0aW9uSXRlbXM6IFtcbiAgICAgICAgICBgQ2hhbmdlIFZvbHVtZVR5cGUgZnJvbSBncDIgdG8gZ3AzIGZvciAke2RldmljZU5hbWV9IGluIExhdW5jaFRlbXBsYXRlRGF0YWAsXG4gICAgICAgIF0sXG4gICAgICAgIGNhdmVhdHM6IFtcbiAgICAgICAgICAnTmV3IGluc3RhbmNlcyB3aWxsIHVzZSBncDM7IGV4aXN0aW5nIGluc3RhbmNlcyBhcmUgdW5hZmZlY3RlZCcsXG4gICAgICAgICAgJ1JlcXVpcmVzIGxhdW5jaGluZyBuZXcgaW5zdGFuY2VzIG9yIG1vZGlmeWluZyBleGlzdGluZyB2b2x1bWVzIHNlcGFyYXRlbHknLFxuICAgICAgICBdLFxuICAgICAgfSk7XG4gICAgfVxuXG4gICAgcmV0dXJuIHJlY29tbWVuZGF0aW9ucztcbiAgfVxuXG4gIHByaXZhdGUgaGFzSW50ZWxsaWdlbnRUaWVyaW5nQ29uZmlnKFxuICAgIHByb3BzOiBSZWNvcmQ8c3RyaW5nLCB1bmtub3duPixcbiAgKTogYm9vbGVhbiB7XG4gICAgY29uc3QgdGllcmluZyA9IHByb3BzLkludGVsbGlnZW50VGllcmluZ0NvbmZpZ3VyYXRpb25zO1xuICAgIGlmICh0aWVyaW5nKSByZXR1cm4gdHJ1ZTtcblxuICAgIGNvbnN0IGFuYWx5dGljcyA9IHByb3BzLkFuYWx5dGljc0NvbmZpZ3VyYXRpb25zIGFzXG4gICAgICB8IEFycmF5PFJlY29yZDxzdHJpbmcsIHVua25vd24+PlxuICAgICAgfCB1bmRlZmluZWQ7XG4gICAgaWYgKGFuYWx5dGljcz8uc29tZSgoYSkgPT4gYS5TdG9yYWdlQ2xhc3NBbmFseXNpcykpIHJldHVybiB0cnVlO1xuXG4gICAgcmV0dXJuIGZhbHNlO1xuICB9XG5cbiAgcHJpdmF0ZSBmaW5kQ29zdChsb2dpY2FsSWQ6IHN0cmluZywgcmVzb3VyY2VDb3N0czogUmVzb3VyY2VDb3N0W10pOiBudW1iZXIge1xuICAgIGNvbnN0IGNvc3QgPSByZXNvdXJjZUNvc3RzLmZpbmQoKGMpID0+IGMubG9naWNhbElkID09PSBsb2dpY2FsSWQpO1xuICAgIHJldHVybiBjb3N0Py5tb250aGx5Q29zdC5hbW91bnQgPz8gMDtcbiAgfVxufVxuIl19
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.createDefaultAnalyzers = createDefaultAnalyzers;
|
|
4
|
+
const GravitonMigrationAnalyzer_1 = require("./analyzers/GravitonMigrationAnalyzer");
|
|
5
|
+
const NATGatewayOptimizationAnalyzer_1 = require("./analyzers/NATGatewayOptimizationAnalyzer");
|
|
6
|
+
const StorageOptimizationAnalyzer_1 = require("./analyzers/StorageOptimizationAnalyzer");
|
|
7
|
+
const ReservedInstanceAnalyzer_1 = require("./analyzers/ReservedInstanceAnalyzer");
|
|
8
|
+
const SavingsPlansAnalyzer_1 = require("./analyzers/SavingsPlansAnalyzer");
|
|
9
|
+
const RightSizingAnalyzer_1 = require("./analyzers/RightSizingAnalyzer");
|
|
10
|
+
const SpotInstanceAnalyzer_1 = require("./analyzers/SpotInstanceAnalyzer");
|
|
11
|
+
function createDefaultAnalyzers() {
|
|
12
|
+
return [
|
|
13
|
+
new GravitonMigrationAnalyzer_1.GravitonMigrationAnalyzer(),
|
|
14
|
+
new NATGatewayOptimizationAnalyzer_1.NATGatewayOptimizationAnalyzer(),
|
|
15
|
+
new StorageOptimizationAnalyzer_1.StorageOptimizationAnalyzer(),
|
|
16
|
+
new ReservedInstanceAnalyzer_1.ReservedInstanceAnalyzer(),
|
|
17
|
+
new SavingsPlansAnalyzer_1.SavingsPlansAnalyzer(),
|
|
18
|
+
new RightSizingAnalyzer_1.RightSizingAnalyzer(),
|
|
19
|
+
new SpotInstanceAnalyzer_1.SpotInstanceAnalyzer(),
|
|
20
|
+
];
|
|
21
|
+
}
|
|
22
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiZGVmYXVsdHMuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi9zcmMvb3B0aW1pemF0aW9uL2RlZmF1bHRzLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiI7O0FBU0Esd0RBVUM7QUFsQkQscUZBQWtGO0FBQ2xGLCtGQUE0RjtBQUM1Rix5RkFBc0Y7QUFDdEYsbUZBQWdGO0FBQ2hGLDJFQUF3RTtBQUN4RSx5RUFBc0U7QUFDdEUsMkVBQXdFO0FBRXhFLFNBQWdCLHNCQUFzQjtJQUNwQyxPQUFPO1FBQ0wsSUFBSSxxREFBeUIsRUFBRTtRQUMvQixJQUFJLCtEQUE4QixFQUFFO1FBQ3BDLElBQUkseURBQTJCLEVBQUU7UUFDakMsSUFBSSxtREFBd0IsRUFBRTtRQUM5QixJQUFJLDJDQUFvQixFQUFFO1FBQzFCLElBQUkseUNBQW1CLEVBQUU7UUFDekIsSUFBSSwyQ0FBb0IsRUFBRTtLQUMzQixDQUFDO0FBQ0osQ0FBQyIsInNvdXJjZXNDb250ZW50IjpbImltcG9ydCB7IE9wdGltaXphdGlvbkFuYWx5emVyIH0gZnJvbSAnLi90eXBlcyc7XG5pbXBvcnQgeyBHcmF2aXRvbk1pZ3JhdGlvbkFuYWx5emVyIH0gZnJvbSAnLi9hbmFseXplcnMvR3Jhdml0b25NaWdyYXRpb25BbmFseXplcic7XG5pbXBvcnQgeyBOQVRHYXRld2F5T3B0aW1pemF0aW9uQW5hbHl6ZXIgfSBmcm9tICcuL2FuYWx5emVycy9OQVRHYXRld2F5T3B0aW1pemF0aW9uQW5hbHl6ZXInO1xuaW1wb3J0IHsgU3RvcmFnZU9wdGltaXphdGlvbkFuYWx5emVyIH0gZnJvbSAnLi9hbmFseXplcnMvU3RvcmFnZU9wdGltaXphdGlvbkFuYWx5emVyJztcbmltcG9ydCB7IFJlc2VydmVkSW5zdGFuY2VBbmFseXplciB9IGZyb20gJy4vYW5hbHl6ZXJzL1Jlc2VydmVkSW5zdGFuY2VBbmFseXplcic7XG5pbXBvcnQgeyBTYXZpbmdzUGxhbnNBbmFseXplciB9IGZyb20gJy4vYW5hbHl6ZXJzL1NhdmluZ3NQbGFuc0FuYWx5emVyJztcbmltcG9ydCB7IFJpZ2h0U2l6aW5nQW5hbHl6ZXIgfSBmcm9tICcuL2FuYWx5emVycy9SaWdodFNpemluZ0FuYWx5emVyJztcbmltcG9ydCB7IFNwb3RJbnN0YW5jZUFuYWx5emVyIH0gZnJvbSAnLi9hbmFseXplcnMvU3BvdEluc3RhbmNlQW5hbHl6ZXInO1xuXG5leHBvcnQgZnVuY3Rpb24gY3JlYXRlRGVmYXVsdEFuYWx5emVycygpOiBPcHRpbWl6YXRpb25BbmFseXplcltdIHtcbiAgcmV0dXJuIFtcbiAgICBuZXcgR3Jhdml0b25NaWdyYXRpb25BbmFseXplcigpLFxuICAgIG5ldyBOQVRHYXRld2F5T3B0aW1pemF0aW9uQW5hbHl6ZXIoKSxcbiAgICBuZXcgU3RvcmFnZU9wdGltaXphdGlvbkFuYWx5emVyKCksXG4gICAgbmV3IFJlc2VydmVkSW5zdGFuY2VBbmFseXplcigpLFxuICAgIG5ldyBTYXZpbmdzUGxhbnNBbmFseXplcigpLFxuICAgIG5ldyBSaWdodFNpemluZ0FuYWx5emVyKCksXG4gICAgbmV3IFNwb3RJbnN0YW5jZUFuYWx5emVyKCksXG4gIF07XG59XG4iXX0=
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export { OptimizationEngine } from './OptimizationEngine';
|
|
2
|
+
export { createDefaultAnalyzers } from './defaults';
|
|
3
|
+
export { OptimizationAnalyzer, OptimizationCategory, OptimizationConfig, OptimizationResult, Recommendation, RecommendationPriority, } from './types';
|
|
4
|
+
export { GravitonMigrationAnalyzer } from './analyzers/GravitonMigrationAnalyzer';
|
|
5
|
+
export { NATGatewayOptimizationAnalyzer } from './analyzers/NATGatewayOptimizationAnalyzer';
|
|
6
|
+
export { StorageOptimizationAnalyzer } from './analyzers/StorageOptimizationAnalyzer';
|
|
7
|
+
export { ReservedInstanceAnalyzer } from './analyzers/ReservedInstanceAnalyzer';
|
|
8
|
+
export { SavingsPlansAnalyzer } from './analyzers/SavingsPlansAnalyzer';
|
|
9
|
+
export { RightSizingAnalyzer } from './analyzers/RightSizingAnalyzer';
|
|
10
|
+
export { SpotInstanceAnalyzer } from './analyzers/SpotInstanceAnalyzer';
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.SpotInstanceAnalyzer = exports.RightSizingAnalyzer = exports.SavingsPlansAnalyzer = exports.ReservedInstanceAnalyzer = exports.StorageOptimizationAnalyzer = exports.NATGatewayOptimizationAnalyzer = exports.GravitonMigrationAnalyzer = exports.createDefaultAnalyzers = exports.OptimizationEngine = void 0;
|
|
4
|
+
var OptimizationEngine_1 = require("./OptimizationEngine");
|
|
5
|
+
Object.defineProperty(exports, "OptimizationEngine", { enumerable: true, get: function () { return OptimizationEngine_1.OptimizationEngine; } });
|
|
6
|
+
var defaults_1 = require("./defaults");
|
|
7
|
+
Object.defineProperty(exports, "createDefaultAnalyzers", { enumerable: true, get: function () { return defaults_1.createDefaultAnalyzers; } });
|
|
8
|
+
var GravitonMigrationAnalyzer_1 = require("./analyzers/GravitonMigrationAnalyzer");
|
|
9
|
+
Object.defineProperty(exports, "GravitonMigrationAnalyzer", { enumerable: true, get: function () { return GravitonMigrationAnalyzer_1.GravitonMigrationAnalyzer; } });
|
|
10
|
+
var NATGatewayOptimizationAnalyzer_1 = require("./analyzers/NATGatewayOptimizationAnalyzer");
|
|
11
|
+
Object.defineProperty(exports, "NATGatewayOptimizationAnalyzer", { enumerable: true, get: function () { return NATGatewayOptimizationAnalyzer_1.NATGatewayOptimizationAnalyzer; } });
|
|
12
|
+
var StorageOptimizationAnalyzer_1 = require("./analyzers/StorageOptimizationAnalyzer");
|
|
13
|
+
Object.defineProperty(exports, "StorageOptimizationAnalyzer", { enumerable: true, get: function () { return StorageOptimizationAnalyzer_1.StorageOptimizationAnalyzer; } });
|
|
14
|
+
var ReservedInstanceAnalyzer_1 = require("./analyzers/ReservedInstanceAnalyzer");
|
|
15
|
+
Object.defineProperty(exports, "ReservedInstanceAnalyzer", { enumerable: true, get: function () { return ReservedInstanceAnalyzer_1.ReservedInstanceAnalyzer; } });
|
|
16
|
+
var SavingsPlansAnalyzer_1 = require("./analyzers/SavingsPlansAnalyzer");
|
|
17
|
+
Object.defineProperty(exports, "SavingsPlansAnalyzer", { enumerable: true, get: function () { return SavingsPlansAnalyzer_1.SavingsPlansAnalyzer; } });
|
|
18
|
+
var RightSizingAnalyzer_1 = require("./analyzers/RightSizingAnalyzer");
|
|
19
|
+
Object.defineProperty(exports, "RightSizingAnalyzer", { enumerable: true, get: function () { return RightSizingAnalyzer_1.RightSizingAnalyzer; } });
|
|
20
|
+
var SpotInstanceAnalyzer_1 = require("./analyzers/SpotInstanceAnalyzer");
|
|
21
|
+
Object.defineProperty(exports, "SpotInstanceAnalyzer", { enumerable: true, get: function () { return SpotInstanceAnalyzer_1.SpotInstanceAnalyzer; } });
|
|
22
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiaW5kZXguanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi9zcmMvb3B0aW1pemF0aW9uL2luZGV4LnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiI7OztBQUFBLDJEQUEwRDtBQUFqRCx3SEFBQSxrQkFBa0IsT0FBQTtBQUMzQix1Q0FBb0Q7QUFBM0Msa0hBQUEsc0JBQXNCLE9BQUE7QUFTL0IsbUZBQWtGO0FBQXpFLHNJQUFBLHlCQUF5QixPQUFBO0FBQ2xDLDZGQUE0RjtBQUFuRixnSkFBQSw4QkFBOEIsT0FBQTtBQUN2Qyx1RkFBc0Y7QUFBN0UsMElBQUEsMkJBQTJCLE9BQUE7QUFDcEMsaUZBQWdGO0FBQXZFLG9JQUFBLHdCQUF3QixPQUFBO0FBQ2pDLHlFQUF3RTtBQUEvRCw0SEFBQSxvQkFBb0IsT0FBQTtBQUM3Qix1RUFBc0U7QUFBN0QsMEhBQUEsbUJBQW1CLE9BQUE7QUFDNUIseUVBQXdFO0FBQS9ELDRIQUFBLG9CQUFvQixPQUFBIiwic291cmNlc0NvbnRlbnQiOlsiZXhwb3J0IHsgT3B0aW1pemF0aW9uRW5naW5lIH0gZnJvbSAnLi9PcHRpbWl6YXRpb25FbmdpbmUnO1xuZXhwb3J0IHsgY3JlYXRlRGVmYXVsdEFuYWx5emVycyB9IGZyb20gJy4vZGVmYXVsdHMnO1xuZXhwb3J0IHtcbiAgT3B0aW1pemF0aW9uQW5hbHl6ZXIsXG4gIE9wdGltaXphdGlvbkNhdGVnb3J5LFxuICBPcHRpbWl6YXRpb25Db25maWcsXG4gIE9wdGltaXphdGlvblJlc3VsdCxcbiAgUmVjb21tZW5kYXRpb24sXG4gIFJlY29tbWVuZGF0aW9uUHJpb3JpdHksXG59IGZyb20gJy4vdHlwZXMnO1xuZXhwb3J0IHsgR3Jhdml0b25NaWdyYXRpb25BbmFseXplciB9IGZyb20gJy4vYW5hbHl6ZXJzL0dyYXZpdG9uTWlncmF0aW9uQW5hbHl6ZXInO1xuZXhwb3J0IHsgTkFUR2F0ZXdheU9wdGltaXphdGlvbkFuYWx5emVyIH0gZnJvbSAnLi9hbmFseXplcnMvTkFUR2F0ZXdheU9wdGltaXphdGlvbkFuYWx5emVyJztcbmV4cG9ydCB7IFN0b3JhZ2VPcHRpbWl6YXRpb25BbmFseXplciB9IGZyb20gJy4vYW5hbHl6ZXJzL1N0b3JhZ2VPcHRpbWl6YXRpb25BbmFseXplcic7XG5leHBvcnQgeyBSZXNlcnZlZEluc3RhbmNlQW5hbHl6ZXIgfSBmcm9tICcuL2FuYWx5emVycy9SZXNlcnZlZEluc3RhbmNlQW5hbHl6ZXInO1xuZXhwb3J0IHsgU2F2aW5nc1BsYW5zQW5hbHl6ZXIgfSBmcm9tICcuL2FuYWx5emVycy9TYXZpbmdzUGxhbnNBbmFseXplcic7XG5leHBvcnQgeyBSaWdodFNpemluZ0FuYWx5emVyIH0gZnJvbSAnLi9hbmFseXplcnMvUmlnaHRTaXppbmdBbmFseXplcic7XG5leHBvcnQgeyBTcG90SW5zdGFuY2VBbmFseXplciB9IGZyb20gJy4vYW5hbHl6ZXJzL1Nwb3RJbnN0YW5jZUFuYWx5emVyJztcbiJdfQ==
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { ResourceWithId } from '../diff/types';
|
|
2
|
+
import { ResourceCost } from '../pricing/types';
|
|
3
|
+
export type RecommendationPriority = 'high' | 'medium' | 'low';
|
|
4
|
+
export type OptimizationCategory = 'reserved-instance' | 'savings-plan' | 'right-sizing' | 'graviton-migration' | 'storage-optimization' | 'spot-instance' | 'nat-gateway-optimization';
|
|
5
|
+
export interface Recommendation {
|
|
6
|
+
id: string;
|
|
7
|
+
title: string;
|
|
8
|
+
description: string;
|
|
9
|
+
category: OptimizationCategory;
|
|
10
|
+
priority: RecommendationPriority;
|
|
11
|
+
estimatedMonthlySavings: number;
|
|
12
|
+
estimatedSavingsPercent: number;
|
|
13
|
+
affectedResources: string[];
|
|
14
|
+
actionItems: string[];
|
|
15
|
+
caveats: string[];
|
|
16
|
+
}
|
|
17
|
+
export interface OptimizationResult {
|
|
18
|
+
recommendations: Recommendation[];
|
|
19
|
+
totalEstimatedMonthlySavings: number;
|
|
20
|
+
currency: string;
|
|
21
|
+
analyzedResourceCount: number;
|
|
22
|
+
analyzedAt: string;
|
|
23
|
+
}
|
|
24
|
+
export interface OptimizationAnalyzer {
|
|
25
|
+
readonly category: OptimizationCategory;
|
|
26
|
+
readonly name: string;
|
|
27
|
+
isApplicable(resources: ResourceWithId[]): boolean;
|
|
28
|
+
analyze(resources: ResourceWithId[], resourceCosts: ResourceCost[], region: string): Promise<Recommendation[]>;
|
|
29
|
+
}
|
|
30
|
+
export interface OptimizationConfig {
|
|
31
|
+
enabled?: boolean;
|
|
32
|
+
minimumSavingsThreshold?: number;
|
|
33
|
+
enabledCategories?: OptimizationCategory[];
|
|
34
|
+
disabledCategories?: OptimizationCategory[];
|
|
35
|
+
}
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoidHlwZXMuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi9zcmMvb3B0aW1pemF0aW9uL3R5cGVzLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiIiLCJzb3VyY2VzQ29udGVudCI6WyJpbXBvcnQgeyBSZXNvdXJjZVdpdGhJZCB9IGZyb20gJy4uL2RpZmYvdHlwZXMnO1xuaW1wb3J0IHsgUmVzb3VyY2VDb3N0IH0gZnJvbSAnLi4vcHJpY2luZy90eXBlcyc7XG5cbmV4cG9ydCB0eXBlIFJlY29tbWVuZGF0aW9uUHJpb3JpdHkgPSAnaGlnaCcgfCAnbWVkaXVtJyB8ICdsb3cnO1xuXG5leHBvcnQgdHlwZSBPcHRpbWl6YXRpb25DYXRlZ29yeSA9XG4gIHwgJ3Jlc2VydmVkLWluc3RhbmNlJ1xuICB8ICdzYXZpbmdzLXBsYW4nXG4gIHwgJ3JpZ2h0LXNpemluZydcbiAgfCAnZ3Jhdml0b24tbWlncmF0aW9uJ1xuICB8ICdzdG9yYWdlLW9wdGltaXphdGlvbidcbiAgfCAnc3BvdC1pbnN0YW5jZSdcbiAgfCAnbmF0LWdhdGV3YXktb3B0aW1pemF0aW9uJztcblxuZXhwb3J0IGludGVyZmFjZSBSZWNvbW1lbmRhdGlvbiB7XG4gIGlkOiBzdHJpbmc7XG4gIHRpdGxlOiBzdHJpbmc7XG4gIGRlc2NyaXB0aW9uOiBzdHJpbmc7XG4gIGNhdGVnb3J5OiBPcHRpbWl6YXRpb25DYXRlZ29yeTtcbiAgcHJpb3JpdHk6IFJlY29tbWVuZGF0aW9uUHJpb3JpdHk7XG4gIGVzdGltYXRlZE1vbnRobHlTYXZpbmdzOiBudW1iZXI7XG4gIGVzdGltYXRlZFNhdmluZ3NQZXJjZW50OiBudW1iZXI7XG4gIGFmZmVjdGVkUmVzb3VyY2VzOiBzdHJpbmdbXTtcbiAgYWN0aW9uSXRlbXM6IHN0cmluZ1tdO1xuICBjYXZlYXRzOiBzdHJpbmdbXTtcbn1cblxuZXhwb3J0IGludGVyZmFjZSBPcHRpbWl6YXRpb25SZXN1bHQge1xuICByZWNvbW1lbmRhdGlvbnM6IFJlY29tbWVuZGF0aW9uW107XG4gIHRvdGFsRXN0aW1hdGVkTW9udGhseVNhdmluZ3M6IG51bWJlcjtcbiAgY3VycmVuY3k6IHN0cmluZztcbiAgYW5hbHl6ZWRSZXNvdXJjZUNvdW50OiBudW1iZXI7XG4gIGFuYWx5emVkQXQ6IHN0cmluZztcbn1cblxuZXhwb3J0IGludGVyZmFjZSBPcHRpbWl6YXRpb25BbmFseXplciB7XG4gIHJlYWRvbmx5IGNhdGVnb3J5OiBPcHRpbWl6YXRpb25DYXRlZ29yeTtcbiAgcmVhZG9ubHkgbmFtZTogc3RyaW5nO1xuICBpc0FwcGxpY2FibGUocmVzb3VyY2VzOiBSZXNvdXJjZVdpdGhJZFtdKTogYm9vbGVhbjtcbiAgYW5hbHl6ZShcbiAgICByZXNvdXJjZXM6IFJlc291cmNlV2l0aElkW10sXG4gICAgcmVzb3VyY2VDb3N0czogUmVzb3VyY2VDb3N0W10sXG4gICAgcmVnaW9uOiBzdHJpbmcsXG4gICk6IFByb21pc2U8UmVjb21tZW5kYXRpb25bXT47XG59XG5cbmV4cG9ydCBpbnRlcmZhY2UgT3B0aW1pemF0aW9uQ29uZmlnIHtcbiAgZW5hYmxlZD86IGJvb2xlYW47XG4gIG1pbmltdW1TYXZpbmdzVGhyZXNob2xkPzogbnVtYmVyO1xuICBlbmFibGVkQ2F0ZWdvcmllcz86IE9wdGltaXphdGlvbkNhdGVnb3J5W107XG4gIGRpc2FibGVkQ2F0ZWdvcmllcz86IE9wdGltaXphdGlvbkNhdGVnb3J5W107XG59XG4iXX0=
|
package/dist/releasetag.txt
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
v0.1.
|
|
1
|
+
v0.1.43
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
import { OptimizationResult } from '../optimization/types';
|
|
2
|
+
export declare function formatRecommendationsText(result: OptimizationResult, minSavings?: number): string;
|
|
3
|
+
export declare function formatRecommendationsMarkdown(result: OptimizationResult, minSavings?: number): string;
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.formatRecommendationsText = formatRecommendationsText;
|
|
4
|
+
exports.formatRecommendationsMarkdown = formatRecommendationsMarkdown;
|
|
5
|
+
function filterByMinSavings(recommendations, minSavings) {
|
|
6
|
+
if (!minSavings)
|
|
7
|
+
return recommendations;
|
|
8
|
+
return recommendations.filter((r) => r.estimatedMonthlySavings >= minSavings);
|
|
9
|
+
}
|
|
10
|
+
function priorityIcon(priority) {
|
|
11
|
+
switch (priority) {
|
|
12
|
+
case 'high':
|
|
13
|
+
return '!!!';
|
|
14
|
+
case 'medium':
|
|
15
|
+
return '!!';
|
|
16
|
+
case 'low':
|
|
17
|
+
return '!';
|
|
18
|
+
default:
|
|
19
|
+
return '';
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
function priorityEmoji(priority) {
|
|
23
|
+
switch (priority) {
|
|
24
|
+
case 'high':
|
|
25
|
+
return '🔴';
|
|
26
|
+
case 'medium':
|
|
27
|
+
return '🟡';
|
|
28
|
+
case 'low':
|
|
29
|
+
return '🟢';
|
|
30
|
+
default:
|
|
31
|
+
return '';
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
function categoryLabel(category) {
|
|
35
|
+
return category
|
|
36
|
+
.split('-')
|
|
37
|
+
.map((w) => w.charAt(0).toUpperCase() + w.slice(1))
|
|
38
|
+
.join(' ');
|
|
39
|
+
}
|
|
40
|
+
function formatRecommendationsText(result, minSavings) {
|
|
41
|
+
const recs = filterByMinSavings(result.recommendations, minSavings);
|
|
42
|
+
if (recs.length === 0)
|
|
43
|
+
return '';
|
|
44
|
+
let output = '';
|
|
45
|
+
output += '\n' + '='.repeat(80) + '\n';
|
|
46
|
+
output += 'Cost Optimization Recommendations\n';
|
|
47
|
+
output += '='.repeat(80) + '\n\n';
|
|
48
|
+
output += `Found ${recs.length} optimization${recs.length === 1 ? '' : 's'}`;
|
|
49
|
+
if (result.totalEstimatedMonthlySavings > 0) {
|
|
50
|
+
output += ` | Potential savings: $${result.totalEstimatedMonthlySavings.toFixed(2)}/month ($${(result.totalEstimatedMonthlySavings * 12).toFixed(2)}/year)`;
|
|
51
|
+
}
|
|
52
|
+
output += '\n\n';
|
|
53
|
+
for (let i = 0; i < recs.length; i++) {
|
|
54
|
+
const rec = recs[i];
|
|
55
|
+
output += `-`.repeat(80) + '\n';
|
|
56
|
+
output += `${i + 1}. [${priorityIcon(rec.priority)}] ${rec.title}\n`;
|
|
57
|
+
output += `-`.repeat(80) + '\n';
|
|
58
|
+
output += ` Category: ${categoryLabel(rec.category)}\n`;
|
|
59
|
+
if (rec.estimatedMonthlySavings > 0) {
|
|
60
|
+
output += ` Savings: ~$${rec.estimatedMonthlySavings.toFixed(2)}/month (${rec.estimatedSavingsPercent}%)\n`;
|
|
61
|
+
}
|
|
62
|
+
output += ` ${rec.description}\n\n`;
|
|
63
|
+
output += ' Action items:\n';
|
|
64
|
+
for (const item of rec.actionItems) {
|
|
65
|
+
output += ` - ${item}\n`;
|
|
66
|
+
}
|
|
67
|
+
if (rec.caveats.length > 0) {
|
|
68
|
+
output += '\n Caveats:\n';
|
|
69
|
+
for (const caveat of rec.caveats) {
|
|
70
|
+
output += ` * ${caveat}\n`;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
output += '\n';
|
|
74
|
+
}
|
|
75
|
+
output += '='.repeat(80) + '\n';
|
|
76
|
+
output += 'Priority: !!! High !! Medium ! Low\n';
|
|
77
|
+
output += '='.repeat(80) + '\n';
|
|
78
|
+
return output;
|
|
79
|
+
}
|
|
80
|
+
function formatRecommendationsMarkdown(result, minSavings) {
|
|
81
|
+
const recs = filterByMinSavings(result.recommendations, minSavings);
|
|
82
|
+
if (recs.length === 0)
|
|
83
|
+
return '';
|
|
84
|
+
let output = '';
|
|
85
|
+
output += '\n## 💡 Cost Optimization Recommendations\n\n';
|
|
86
|
+
if (result.totalEstimatedMonthlySavings > 0) {
|
|
87
|
+
output += `**Total Potential Savings:** $${result.totalEstimatedMonthlySavings.toFixed(2)}/month ($${(result.totalEstimatedMonthlySavings * 12).toFixed(2)}/year)\n\n`;
|
|
88
|
+
}
|
|
89
|
+
// Summary table
|
|
90
|
+
output += '| # | Priority | Recommendation | Est. Savings |\n';
|
|
91
|
+
output += '|---|----------|---------------|-------------|\n';
|
|
92
|
+
for (let i = 0; i < recs.length; i++) {
|
|
93
|
+
const rec = recs[i];
|
|
94
|
+
const savings = rec.estimatedMonthlySavings > 0
|
|
95
|
+
? `$${rec.estimatedMonthlySavings.toFixed(2)}/mo (${rec.estimatedSavingsPercent}%)`
|
|
96
|
+
: 'Varies';
|
|
97
|
+
output += `| ${i + 1} | ${priorityEmoji(rec.priority)} ${rec.priority} | ${rec.title} | ${savings} |\n`;
|
|
98
|
+
}
|
|
99
|
+
output += '\n';
|
|
100
|
+
// Detailed recommendations in collapsible sections
|
|
101
|
+
output += '<details>\n';
|
|
102
|
+
output += '<summary><strong>View detailed recommendations</strong></summary>\n\n';
|
|
103
|
+
for (let i = 0; i < recs.length; i++) {
|
|
104
|
+
const rec = recs[i];
|
|
105
|
+
output += `### ${i + 1}. ${rec.title}\n\n`;
|
|
106
|
+
output += `**Category:** ${categoryLabel(rec.category)} | `;
|
|
107
|
+
output += `**Priority:** ${priorityEmoji(rec.priority)} ${rec.priority}`;
|
|
108
|
+
if (rec.estimatedMonthlySavings > 0) {
|
|
109
|
+
output += ` | **Savings:** ~$${rec.estimatedMonthlySavings.toFixed(2)}/month`;
|
|
110
|
+
}
|
|
111
|
+
output += '\n\n';
|
|
112
|
+
output += `${rec.description}\n\n`;
|
|
113
|
+
output += '**Action items:**\n';
|
|
114
|
+
for (const item of rec.actionItems) {
|
|
115
|
+
output += `- [ ] ${item}\n`;
|
|
116
|
+
}
|
|
117
|
+
output += '\n';
|
|
118
|
+
if (rec.caveats.length > 0) {
|
|
119
|
+
output += '**Caveats:**\n';
|
|
120
|
+
for (const caveat of rec.caveats) {
|
|
121
|
+
output += `- ${caveat}\n`;
|
|
122
|
+
}
|
|
123
|
+
output += '\n';
|
|
124
|
+
}
|
|
125
|
+
output += `**Affected resources:** ${rec.affectedResources.map((r) => `\`${r}\``).join(', ')}\n\n`;
|
|
126
|
+
if (i < recs.length - 1) {
|
|
127
|
+
output += '---\n\n';
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
output += '</details>\n';
|
|
131
|
+
return output;
|
|
132
|
+
}
|
|
133
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiUmVjb21tZW5kYXRpb25SZXBvcnRlci5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uL3NyYy9yZXBvcnRlci9SZWNvbW1lbmRhdGlvblJlcG9ydGVyLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiI7O0FBNkNBLDhEQWlEQztBQUVELHNFQWtFQztBQWhLRCxTQUFTLGtCQUFrQixDQUN6QixlQUFpQyxFQUNqQyxVQUFtQjtJQUVuQixJQUFJLENBQUMsVUFBVTtRQUFFLE9BQU8sZUFBZSxDQUFDO0lBQ3hDLE9BQU8sZUFBZSxDQUFDLE1BQU0sQ0FDM0IsQ0FBQyxDQUFDLEVBQUUsRUFBRSxDQUFDLENBQUMsQ0FBQyx1QkFBdUIsSUFBSSxVQUFVLENBQy9DLENBQUM7QUFDSixDQUFDO0FBRUQsU0FBUyxZQUFZLENBQUMsUUFBZ0I7SUFDcEMsUUFBUSxRQUFRLEVBQUUsQ0FBQztRQUNqQixLQUFLLE1BQU07WUFDVCxPQUFPLEtBQUssQ0FBQztRQUNmLEtBQUssUUFBUTtZQUNYLE9BQU8sSUFBSSxDQUFDO1FBQ2QsS0FBSyxLQUFLO1lBQ1IsT0FBTyxHQUFHLENBQUM7UUFDYjtZQUNFLE9BQU8sRUFBRSxDQUFDO0lBQ2QsQ0FBQztBQUNILENBQUM7QUFFRCxTQUFTLGFBQWEsQ0FBQyxRQUFnQjtJQUNyQyxRQUFRLFFBQVEsRUFBRSxDQUFDO1FBQ2pCLEtBQUssTUFBTTtZQUNULE9BQU8sSUFBSSxDQUFDO1FBQ2QsS0FBSyxRQUFRO1lBQ1gsT0FBTyxJQUFJLENBQUM7UUFDZCxLQUFLLEtBQUs7WUFDUixPQUFPLElBQUksQ0FBQztRQUNkO1lBQ0UsT0FBTyxFQUFFLENBQUM7SUFDZCxDQUFDO0FBQ0gsQ0FBQztBQUVELFNBQVMsYUFBYSxDQUFDLFFBQWdCO0lBQ3JDLE9BQU8sUUFBUTtTQUNaLEtBQUssQ0FBQyxHQUFHLENBQUM7U0FDVixHQUFHLENBQUMsQ0FBQyxDQUFDLEVBQUUsRUFBRSxDQUFDLENBQUMsQ0FBQyxNQUFNLENBQUMsQ0FBQyxD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Q0FBQztZQUNuQyxNQUFNLElBQUksU0FBUyxJQUFJLElBQUksQ0FBQztRQUM5QixDQUFDO1FBQ0QsTUFBTSxJQUFJLElBQUksQ0FBQztRQUVmLElBQUksR0FBRyxDQUFDLE9BQU8sQ0FBQyxNQUFNLEdBQUcsQ0FBQyxFQUFFLENBQUM7WUFDM0IsTUFBTSxJQUFJLGdCQUFnQixDQUFDO1lBQzNCLEtBQUssTUFBTSxNQUFNLElBQUksR0FBRyxDQUFDLE9BQU8sRUFBRSxDQUFDO2dCQUNqQyxNQUFNLElBQUksS0FBSyxNQUFNLElBQUksQ0FBQztZQUM1QixDQUFDO1lBQ0QsTUFBTSxJQUFJLElBQUksQ0FBQztRQUNqQixDQUFDO1FBRUQsTUFBTSxJQUFJLDJCQUEyQixHQUFHLENBQUMsaUJBQWlCLENBQUMsR0FBRyxDQUFDLENBQUMsQ0FBQyxFQUFFLEVBQUUsQ0FBQyxLQUFLLENBQUMsSUFBSSxDQUFDLENBQUMsSUFBSSxDQUFDLElBQUksQ0FBQyxNQUFNLENBQUM7UUFFbkcsSUFBSSxDQUFDLEdBQUcsSUFBSSxDQUFDLE1BQU0sR0FBRyxDQUFDLEVBQUUsQ0FBQztZQUN4QixNQUFNLElBQUksU0FBUyxDQUFDO1FBQ3RCLENBQUM7SUFDSCxDQUFDO0lBRUQsTUFBTSxJQUFJLGNBQWMsQ0FBQztJQUV6QixPQUFPLE1BQU0sQ0FBQztBQUNoQixDQUFDIiwic291cmNlc0NvbnRlbnQiOlsiaW1wb3J0IHsgT3B0aW1pemF0aW9uUmVzdWx0LCBSZWNvbW1lbmRhdGlvbiB9IGZyb20gJy4uL29wdGltaXphdGlvbi90eXBlcyc7XG5cbmZ1bmN0aW9uIGZpbHRlckJ5TWluU2F2aW5ncyhcbiAgcmVjb21tZW5kYXRpb25zOiBSZWNvbW1lbmRhdGlvbltdLFxuICBtaW5TYXZpbmdzPzogbnVtYmVyLFxuKTogUmVjb21tZW5kYXRpb25bXSB7XG4gIGlmICghbWluU2F2aW5ncykgcmV0dXJuIHJlY29tbWVuZGF0aW9ucztcbiAgcmV0dXJuIHJlY29tbWVuZGF0aW9ucy5maWx0ZXIoXG4gICAgKHIpID0+IHIuZXN0aW1hdGVkTW9udGhseVNhdmluZ3MgPj0gbWluU2F2aW5ncyxcbiAgKTtcbn1cblxuZnVuY3Rpb24gcHJpb3JpdHlJY29uKHByaW9yaXR5OiBzdHJpbmcpOiBzdHJpbmcge1xuICBzd2l0Y2ggKHByaW9yaXR5KSB7XG4gICAgY2FzZSAnaGlnaCc6XG4gICAgICByZXR1cm4gJyEhISc7XG4gICAgY2FzZSAnbWVkaXVtJzpcbiAgICAgIHJldHVybiAnISEnO1xuICAgIGNhc2UgJ2xvdyc6XG4gICAgICByZXR1cm4gJyEnO1xuICAgIGRlZmF1bHQ6XG4gICAgICByZXR1cm4gJyc7XG4gIH1cbn1cblxuZnVuY3Rpb24gcHJpb3JpdHlFbW9qaShwcmlvcml0eTogc3RyaW5nKTogc3RyaW5nIHtcbiAgc3dpdGNoIChwcmlvcml0eSkge1xuICAgIGNhc2UgJ2hpZ2gnOlxuICAgICAgcmV0dXJuICfwn5S0JztcbiAgICBjYXNlICdtZWRpdW0nOlxuICAgICAgcmV0dXJuICfwn5+hJztcbiAgICBjYXNlICdsb3cnOlxuICAgICAgcmV0dXJuICfwn5+iJztcbiAgICBkZWZhdWx0OlxuICAgICAgcmV0dXJuICcnO1xuICB9XG59XG5cbmZ1bmN0aW9uIGNhdGVnb3J5TGFiZWwoY2F0ZWdvcnk6IHN0cmluZyk6IHN0cmluZyB7XG4gIHJldHVybiBjYXRlZ29yeVxuICAgIC5zcGxpdCgnLScpXG4gICAgLm1hcCgodykgPT4gdy5jaGFyQXQoMCkudG9VcHBlckNhc2UoKSArIHcuc2xpY2UoMSkpXG4gICAgLmpvaW4oJyAnKTtcbn1cblxuZXhwb3J0IGZ1bmN0aW9uIGZvcm1hdFJlY29tbWVuZGF0aW9uc1RleHQoXG4gIHJlc3VsdDogT3B0aW1pemF0aW9uUmVzdWx0LFxuICBtaW5TYXZpbmdzPzogbnVtYmVyLFxuKTogc3RyaW5nIHtcbiAgY29uc3QgcmVjcyA9IGZpbHRlckJ5TWluU2F2aW5ncyhyZXN1bHQucmVjb21tZW5kYXRpb25zLCBtaW5TYXZpbmdzKTtcbiAgaWYgKHJlY3MubGVuZ3RoID09PSAwKSByZXR1cm4gJyc7XG5cbiAgbGV0IG91dHB1dCA9ICcnO1xuICBvdXRwdXQgKz0gJ1xcbicgKyAnPScucmVwZWF0KDgwKSArICdcXG4nO1xuICBvdXRwdXQgKz0gJ0Nvc3QgT3B0aW1pemF0aW9uIFJlY29tbWVuZGF0aW9uc1xcbic7XG4gIG91dHB1dCArPSAnPScucmVwZWF0KDgwKSArICdcXG5cXG4nO1xuXG4gIG91dHB1dCArPSBgRm91bmQgJHtyZWNzLmxlbmd0aH0gb3B0aW1pemF0aW9uJHtyZWNzLmxlbmd0aCA9PT0gMSA/ICcnIDogJ3MnfWA7XG4gIGlmIChyZXN1bHQudG90YWxFc3RpbWF0ZWRNb250aGx5U2F2aW5ncyA+IDApIHtcbiAgICBvdXRwdXQgKz0gYCB8IFBvdGVudGlhbCBzYXZpbmdzOiAkJHtyZXN1bHQudG90YWxFc3RpbWF0ZWRNb250aGx5U2F2aW5ncy50b0ZpeGVkKDIpfS9tb250aCAoJCR7KHJlc3VsdC50b3RhbEVzdGltYXRlZE1vbnRobHlTYXZpbmdzICogMTIpLnRvRml4ZWQoMil9L3llYXIpYDtcbiAgfVxuICBvdXRwdXQgKz0gJ1xcblxcbic7XG5cbiAgZm9yIChsZXQgaSA9IDA7IGkgPCByZWNzLmxlbmd0aDsgaSsrKSB7XG4gICAgY29uc3QgcmVjID0gcmVjc1tpXTtcbiAgICBvdXRwdXQgKz0gYC1gLnJlcGVhdCg4MCkgKyAnXFxuJztcbiAgICBvdXRwdXQgKz0gYCR7aSArIDF9LiBbJHtwcmlvcml0eUljb24ocmVjLnByaW9yaXR5KX1dICR7cmVjLnRpdGxlfVxcbmA7XG4gICAgb3V0cHV0ICs9IGAtYC5yZXBlYXQoODApICsgJ1xcbic7XG4gICAgb3V0cHV0ICs9IGAgICBDYXRlZ29yeTogJHtjYXRlZ29yeUxhYmVsKHJlYy5jYXRlZ29yeSl9XFxuYDtcbiAgICBpZiAocmVjLmVzdGltYXRlZE1vbnRobHlTYXZpbmdzID4gMCkge1xuICAgICAgb3V0cHV0ICs9IGAgICBTYXZpbmdzOiAgfiQke3JlYy5lc3RpbWF0ZWRNb250aGx5U2F2aW5ncy50b0ZpeGVkKDIpfS9tb250aCAoJHtyZWMuZXN0aW1hdGVkU2F2aW5nc1BlcmNlbnR9JSlcXG5gO1xuICAgIH1cbiAgICBvdXRwdXQgKz0gYCAgICR7cmVjLmRlc2NyaXB0aW9ufVxcblxcbmA7XG5cbiAgICBvdXRwdXQgKz0gJyAgIEFjdGlvbiBpdGVtczpcXG4nO1xuICAgIGZvciAoY29uc3QgaXRlbSBvZiByZWMuYWN0aW9uSXRlbXMpIHtcbiAgICAgIG91dHB1dCArPSBgICAgICAtICR7aXRlbX1cXG5gO1xuICAgIH1cblxuICAgIGlmIChyZWMuY2F2ZWF0cy5sZW5ndGggPiAwKSB7XG4gICAgICBvdXRwdXQgKz0gJ1xcbiAgIENhdmVhdHM6XFxuJztcbiAgICAgIGZvciAoY29uc3QgY2F2ZWF0IG9mIHJlYy5jYXZlYXRzKSB7XG4gICAgICAgIG91dHB1dCArPSBgICAgICAqICR7Y2F2ZWF0fVxcbmA7XG4gICAgICB9XG4gICAgfVxuXG4gICAgb3V0cHV0ICs9ICdcXG4nO1xuICB9XG5cbiAgb3V0cHV0ICs9ICc9Jy5yZXBlYXQoODApICsgJ1xcbic7XG4gIG91dHB1dCArPSAnUHJpb3JpdHk6ICEhISBIaWdoICAhISBNZWRpdW0gICEgTG93XFxuJztcbiAgb3V0cHV0ICs9ICc9Jy5yZXBlYXQoODApICsgJ1xcbic7XG5cbiAgcmV0dXJuIG91dHB1dDtcbn1cblxuZXhwb3J0IGZ1bmN0aW9uIGZvcm1hdFJlY29tbWVuZGF0aW9uc01hcmtkb3duKFxuICByZXN1bHQ6IE9wdGltaXphdGlvblJlc3VsdCxcbiAgbWluU2F2aW5ncz86IG51bWJlcixcbik6IHN0cmluZyB7XG4gIGNvbnN0IHJlY3MgPSBmaWx0ZXJCeU1pblNhdmluZ3MocmVzdWx0LnJlY29tbWVuZGF0aW9ucywgbWluU2F2aW5ncyk7XG4gIGlmIChyZWNzLmxlbmd0aCA9PT0gMCkgcmV0dXJuICcnO1xuXG4gIGxldCBvdXRwdXQgPSAnJztcbiAgb3V0cHV0ICs9ICdcXG4jIyDwn5KhIENvc3QgT3B0aW1pemF0aW9uIFJlY29tbWVuZGF0aW9uc1xcblxcbic7XG5cbiAgaWYgKHJlc3VsdC50b3RhbEVzdGltYXRlZE1vbnRobHlTYXZpbmdzID4gMCkge1xuICAgIG91dHB1dCArPSBgKipUb3RhbCBQb3RlbnRpYWwgU2F2aW5nczoqKiAkJHtyZXN1bHQudG90YWxFc3RpbWF0ZWRNb250aGx5U2F2aW5ncy50b0ZpeGVkKDIpfS9tb250aCAoJCR7KHJlc3VsdC50b3RhbEVzdGltYXRlZE1vbnRobHlTYXZpbmdzICogMTIpLnRvRml4ZWQoMil9L3llYXIpXFxuXFxuYDtcbiAgfVxuXG4gIC8vIFN1bW1hcnkgdGFibGVcbiAgb3V0cHV0ICs9ICd8ICMgfCBQcmlvcml0eSB8IFJlY29tbWVuZGF0aW9uIHwgRXN0LiBTYXZpbmdzIHxcXG4nO1xuICBvdXRwdXQgKz0gJ3wtLS18LS0tLS0tLS0tLXwtLS0tLS0tLS0tLS0tLS18LS0tLS0tLS0tLS0tLXxcXG4nO1xuXG4gIGZvciAobGV0IGkgPSAwOyBpIDwgcmVjcy5sZW5ndGg7IGkrKykge1xuICAgIGNvbnN0IHJlYyA9IHJlY3NbaV07XG4gICAgY29uc3Qgc2F2aW5ncyA9IHJlYy5lc3RpbWF0ZWRNb250aGx5U2F2aW5ncyA+IDBcbiAgICAgID8gYCQke3JlYy5lc3RpbWF0ZWRNb250aGx5U2F2aW5ncy50b0ZpeGVkKDIpfS9tbyAoJHtyZWMuZXN0aW1hdGVkU2F2aW5nc1BlcmNlbnR9JSlgXG4gICAgICA6ICdWYXJpZXMnO1xuICAgIG91dHB1dCArPSBgfCAke2kgKyAxfSB8ICR7cHJpb3JpdHlFbW9qaShyZWMucHJpb3JpdHkpfSAke3JlYy5wcmlvcml0eX0gfCAke3JlYy50aXRsZX0gfCAke3NhdmluZ3N9IHxcXG5gO1xuICB9XG4gIG91dHB1dCArPSAnXFxuJztcblxuICAvLyBEZXRhaWxlZCByZWNvbW1lbmRhdGlvbnMgaW4gY29sbGFwc2libGUgc2VjdGlvbnNcbiAgb3V0cHV0ICs9ICc8ZGV0YWlscz5cXG4nO1xuICBvdXRwdXQgKz0gJzxzdW1tYXJ5PjxzdHJvbmc+VmlldyBkZXRhaWxlZCByZWNvbW1lbmRhdGlvbnM8L3N0cm9uZz48L3N1bW1hcnk+XFxuXFxuJztcblxuICBmb3IgKGxldCBpID0gMDsgaSA8IHJlY3MubGVuZ3RoOyBpKyspIHtcbiAgICBjb25zdCByZWMgPSByZWNzW2ldO1xuICAgIG91dHB1dCArPSBgIyMjICR7aSArIDF9LiAke3JlYy50aXRsZX1cXG5cXG5gO1xuICAgIG91dHB1dCArPSBgKipDYXRlZ29yeToqKiAke2NhdGVnb3J5TGFiZWwocmVjLmNhdGVnb3J5KX0gfCBgO1xuICAgIG91dHB1dCArPSBgKipQcmlvcml0eToqKiAke3ByaW9yaXR5RW1vamkocmVjLnByaW9yaXR5KX0gJHtyZWMucHJpb3JpdHl9YDtcbiAgICBpZiAocmVjLmVzdGltYXRlZE1vbnRobHlTYXZpbmdzID4gMCkge1xuICAgICAgb3V0cHV0ICs9IGAgfCAqKlNhdmluZ3M6KiogfiQke3JlYy5lc3RpbWF0ZWRNb250aGx5U2F2aW5ncy50b0ZpeGVkKDIpfS9tb250aGA7XG4gICAgfVxuICAgIG91dHB1dCArPSAnXFxuXFxuJztcbiAgICBvdXRwdXQgKz0gYCR7cmVjLmRlc2NyaXB0aW9ufVxcblxcbmA7XG5cbiAgICBvdXRwdXQgKz0gJyoqQWN0aW9uIGl0ZW1zOioqXFxuJztcbiAgICBmb3IgKGNvbnN0IGl0ZW0gb2YgcmVjLmFjdGlvbkl0ZW1zKSB7XG4gICAgICBvdXRwdXQgKz0gYC0gWyBdICR7aXRlbX1cXG5gO1xuICAgIH1cbiAgICBvdXRwdXQgKz0gJ1xcbic7XG5cbiAgICBpZiAocmVjLmNhdmVhdHMubGVuZ3RoID4gMCkge1xuICAgICAgb3V0cHV0ICs9ICcqKkNhdmVhdHM6KipcXG4nO1xuICAgICAgZm9yIChjb25zdCBjYXZlYXQgb2YgcmVjLmNhdmVhdHMpIHtcbiAgICAgICAgb3V0cHV0ICs9IGAtICR7Y2F2ZWF0fVxcbmA7XG4gICAgICB9XG4gICAgICBvdXRwdXQgKz0gJ1xcbic7XG4gICAgfVxuXG4gICAgb3V0cHV0ICs9IGAqKkFmZmVjdGVkIHJlc291cmNlczoqKiAke3JlYy5hZmZlY3RlZFJlc291cmNlcy5tYXAoKHIpID0+IGBcXGAke3J9XFxgYCkuam9pbignLCAnKX1cXG5cXG5gO1xuXG4gICAgaWYgKGkgPCByZWNzLmxlbmd0aCAtIDEpIHtcbiAgICAgIG91dHB1dCArPSAnLS0tXFxuXFxuJztcbiAgICB9XG4gIH1cblxuICBvdXRwdXQgKz0gJzwvZGV0YWlscz5cXG4nO1xuXG4gIHJldHVybiBvdXRwdXQ7XG59XG4iXX0=
|