@pagopa/dx-savemoney 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +233 -0
- package/dist/azure/analyzer.d.ts +32 -0
- package/dist/azure/analyzer.d.ts.map +1 -0
- package/dist/azure/analyzer.js +128 -0
- package/dist/azure/analyzer.js.map +1 -0
- package/dist/azure/config.d.ts +19 -0
- package/dist/azure/config.d.ts.map +1 -0
- package/dist/azure/config.js +62 -0
- package/dist/azure/config.js.map +1 -0
- package/dist/azure/index.d.ts +9 -0
- package/dist/azure/index.d.ts.map +1 -0
- package/dist/azure/index.js +9 -0
- package/dist/azure/index.js.map +1 -0
- package/dist/azure/report.d.ts +12 -0
- package/dist/azure/report.d.ts.map +1 -0
- package/dist/azure/report.js +40 -0
- package/dist/azure/report.js.map +1 -0
- package/dist/azure/resources/app-service.d.ts +18 -0
- package/dist/azure/resources/app-service.d.ts.map +1 -0
- package/dist/azure/resources/app-service.js +65 -0
- package/dist/azure/resources/app-service.js.map +1 -0
- package/dist/azure/resources/disk.d.ts +16 -0
- package/dist/azure/resources/disk.d.ts.map +1 -0
- package/dist/azure/resources/disk.js +56 -0
- package/dist/azure/resources/disk.js.map +1 -0
- package/dist/azure/resources/index.d.ts +11 -0
- package/dist/azure/resources/index.d.ts.map +1 -0
- package/dist/azure/resources/index.js +11 -0
- package/dist/azure/resources/index.js.map +1 -0
- package/dist/azure/resources/nic.d.ts +15 -0
- package/dist/azure/resources/nic.d.ts.map +1 -0
- package/dist/azure/resources/nic.js +56 -0
- package/dist/azure/resources/nic.js.map +1 -0
- package/dist/azure/resources/private-endpoint.d.ts +15 -0
- package/dist/azure/resources/private-endpoint.d.ts.map +1 -0
- package/dist/azure/resources/private-endpoint.js +66 -0
- package/dist/azure/resources/private-endpoint.js.map +1 -0
- package/dist/azure/resources/public-ip.d.ts +18 -0
- package/dist/azure/resources/public-ip.d.ts.map +1 -0
- package/dist/azure/resources/public-ip.js +61 -0
- package/dist/azure/resources/public-ip.js.map +1 -0
- package/dist/azure/resources/storage.d.ts +16 -0
- package/dist/azure/resources/storage.d.ts.map +1 -0
- package/dist/azure/resources/storage.js +39 -0
- package/dist/azure/resources/storage.js.map +1 -0
- package/dist/azure/resources/vm.d.ts +19 -0
- package/dist/azure/resources/vm.d.ts.map +1 -0
- package/dist/azure/resources/vm.js +77 -0
- package/dist/azure/resources/vm.js.map +1 -0
- package/dist/azure/types.d.ts +34 -0
- package/dist/azure/types.d.ts.map +1 -0
- package/dist/azure/types.js +5 -0
- package/dist/azure/types.js.map +1 -0
- package/dist/azure/utils.d.ts +40 -0
- package/dist/azure/utils.d.ts.map +1 -0
- package/dist/azure/utils.js +104 -0
- package/dist/azure/utils.js.map +1 -0
- package/dist/index.d.ts +34 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +77 -0
- package/dist/index.js.map +1 -0
- package/dist/types.d.ts +21 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +14 -0
- package/dist/types.js.map +1 -0
- package/package.json +60 -0
- package/src/azure/analyzer.ts +213 -0
- package/src/azure/config.ts +81 -0
- package/src/azure/index.ts +9 -0
- package/src/azure/report.ts +52 -0
- package/src/azure/resources/app-service.ts +118 -0
- package/src/azure/resources/disk.ts +88 -0
- package/src/azure/resources/index.ts +11 -0
- package/src/azure/resources/nic.ts +90 -0
- package/src/azure/resources/private-endpoint.ts +112 -0
- package/src/azure/resources/public-ip.ts +106 -0
- package/src/azure/resources/storage.ts +67 -0
- package/src/azure/resources/vm.ts +129 -0
- package/src/azure/types.ts +38 -0
- package/src/azure/utils.ts +141 -0
- package/src/index.test.ts +95 -0
- package/src/index.ts +99 -0
- package/src/types.ts +34 -0
package/README.md
ADDED
|
@@ -0,0 +1,233 @@
|
|
|
1
|
+
# DX Save Money
|
|
2
|
+
|
|
3
|
+
A TypeScript library for analyzing CSP (Cloud Service Provider) resources to identify potential cost inefficiencies and underutilized resources. It operates in read-only mode and does not modify, tag, or delete any resources; instead, it generates detailed reports to support FinOps decisions.
|
|
4
|
+
|
|
5
|
+
## Supported Cloud Providers
|
|
6
|
+
|
|
7
|
+
### ✅ Azure
|
|
8
|
+
|
|
9
|
+
Full support for Azure resource analysis with intelligent detection and flexible reporting.
|
|
10
|
+
|
|
11
|
+
### 🚧 AWS (Coming Soon)
|
|
12
|
+
|
|
13
|
+
AWS support is planned for future releases. The architecture is designed to support multiple CSPs with provider-specific analyzers.
|
|
14
|
+
|
|
15
|
+
## Installation
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
npm install @pagopa/dx-savemoney
|
|
19
|
+
# or
|
|
20
|
+
pnpm add @pagopa/dx-savemoney
|
|
21
|
+
# or
|
|
22
|
+
yarn add @pagopa/dx-savemoney
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
## Azure
|
|
26
|
+
|
|
27
|
+
### Main Features
|
|
28
|
+
|
|
29
|
+
- **Multi-Subscription Analysis**: Scans multiple Azure subscriptions in a single command.
|
|
30
|
+
- **Intelligent Detection**: Uses Azure Monitor metrics (e.g. CPU, network traffic, transactions) to scientifically identify inactive resources.
|
|
31
|
+
- **Orphaned Resource Identification**: Detects commonly "forgotten" resources like unattached disks, unassociated public IPs, and unused network interfaces.
|
|
32
|
+
- **Flexible Reporting**: Offers multiple output formats:
|
|
33
|
+
- `table`: A human-readable summary for the terminal.
|
|
34
|
+
- `json`: Standard format for integration with other tools.
|
|
35
|
+
- `detailed-json`: A comprehensive output with all resource metadata, ideal for in-depth analysis via AI or custom scripts.
|
|
36
|
+
- **Simplified Configuration**: Supports configuration via files, command-line options, environment variables, or an interactive prompt.
|
|
37
|
+
|
|
38
|
+
### Analyzed Resources
|
|
39
|
+
|
|
40
|
+
The tool analyzes the following Azure resource types with specific detection methods and risk levels:
|
|
41
|
+
|
|
42
|
+
| Resource Type | Detection Method | Cost Risk | What's Checked |
|
|
43
|
+
| :---------------------- | :---------------------- | :-------: | :-------------------------------------------------------------------------------------- |
|
|
44
|
+
| **Virtual Machines** | Instance View + Metrics | 🔴 High | Deallocated/stopped state, Low CPU usage (<1%), Low network traffic (<10MB) |
|
|
45
|
+
| **App Service Plans** | API Details + Metrics | 🔴 High | No apps deployed, Very low CPU (<5%), Very low memory (<10%), Oversized Premium tier |
|
|
46
|
+
| **Managed Disks** | API Details | 🟡 Medium | Unattached state, No `managedBy` property |
|
|
47
|
+
| **Public IP Addresses** | API Details + Metrics | 🟡 Medium | Not associated with any resource, Static IP not in use, Very low network traffic (<1MB) |
|
|
48
|
+
| **Network Interfaces** | API Details | 🟡 Medium | Not attached to VM or Private Endpoint, No public IP assigned |
|
|
49
|
+
| **Private Endpoints** | API Details | 🟡 Medium | No private link connections, Rejected/disconnected connections, No network interfaces |
|
|
50
|
+
| **Storage Accounts** | Metrics | 🟡 Medium | Very low transaction count (<100 in timespan) |
|
|
51
|
+
|
|
52
|
+
#### Generic Checks
|
|
53
|
+
|
|
54
|
+
All resources are also checked for:
|
|
55
|
+
|
|
56
|
+
- **Missing tags**: Resources without tags are flagged as potentially unmanaged
|
|
57
|
+
- **Location mismatch**: Resources not in the preferred location are reported
|
|
58
|
+
|
|
59
|
+
### Prerequisites
|
|
60
|
+
|
|
61
|
+
1. **Node.js**: Version 22 or higher.
|
|
62
|
+
2. **Azure Credentials**: The library uses `DefaultAzureCredential` from `@azure/identity`, which supports various authentication methods:
|
|
63
|
+
- Azure CLI (`az login`)
|
|
64
|
+
- Managed Identity
|
|
65
|
+
- Environment variables
|
|
66
|
+
- Visual Studio Code
|
|
67
|
+
- And more...
|
|
68
|
+
|
|
69
|
+
### Usage
|
|
70
|
+
|
|
71
|
+
#### Quick Start
|
|
72
|
+
|
|
73
|
+
```typescript
|
|
74
|
+
import { azure, loadConfig } from "@pagopa/dx-savemoney";
|
|
75
|
+
|
|
76
|
+
// Load configuration (from file, env vars, or interactive prompt)
|
|
77
|
+
const config = await loadConfig("./config.json");
|
|
78
|
+
|
|
79
|
+
// Run analysis and generate report
|
|
80
|
+
await azure.analyzeAzureResources(config, "table");
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
#### Configuration Inputs
|
|
84
|
+
|
|
85
|
+
The tool requires the following configuration:
|
|
86
|
+
|
|
87
|
+
| Input | Type | Required | Default | Description |
|
|
88
|
+
| :------------------ | :--------- | :------: | :----------- | :----------------------------------------------------------- |
|
|
89
|
+
| `tenantId` | `string` | ✅ | - | Azure Active Directory Tenant ID |
|
|
90
|
+
| `subscriptionIds` | `string[]` | ✅ | - | Array of Azure subscription IDs to analyze |
|
|
91
|
+
| `preferredLocation` | `string` | ❌ | `italynorth` | Preferred Azure region (resources elsewhere will be flagged) |
|
|
92
|
+
| `timespanDays` | `number` | ❌ | `30` | Number of days to look back for metrics analysis |
|
|
93
|
+
| `verbose` | `boolean` | ❌ | `false` | Enable detailed logging for each resource analyzed |
|
|
94
|
+
|
|
95
|
+
#### Output Formats
|
|
96
|
+
|
|
97
|
+
The tool supports multiple output formats for different use cases:
|
|
98
|
+
|
|
99
|
+
| Format | Description | Use Case |
|
|
100
|
+
| :-------------- | :---------------------------------------------- | :----------------------------- |
|
|
101
|
+
| `table` | Human-readable table in terminal | Quick visual inspection |
|
|
102
|
+
| `json` | Structured JSON with resource summaries | Integration with other tools |
|
|
103
|
+
| `detailed-json` | Complete JSON with full Azure resource metadata | AI analysis or deep inspection |
|
|
104
|
+
|
|
105
|
+
#### How to Load Configuration
|
|
106
|
+
|
|
107
|
+
The `loadConfig()` function loads configuration in the following priority order:
|
|
108
|
+
|
|
109
|
+
1. **Configuration file** (pass file path as parameter)
|
|
110
|
+
2. **Environment variables** (`ARM_TENANT_ID`, `ARM_SUBSCRIPTION_ID`)
|
|
111
|
+
3. **Interactive prompt** (if no other configuration is found)
|
|
112
|
+
|
|
113
|
+
**Example:**
|
|
114
|
+
|
|
115
|
+
```typescript
|
|
116
|
+
// From file
|
|
117
|
+
const config1 = await loadConfig("./config.json");
|
|
118
|
+
|
|
119
|
+
// From environment variables or prompt
|
|
120
|
+
const config2 = await loadConfig();
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
#### Configuration File Example
|
|
124
|
+
|
|
125
|
+
```json
|
|
126
|
+
{
|
|
127
|
+
"tenantId": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
|
|
128
|
+
"subscriptionIds": ["xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"],
|
|
129
|
+
"preferredLocation": "italynorth",
|
|
130
|
+
"timespanDays": 30,
|
|
131
|
+
"verbose": false
|
|
132
|
+
}
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
#### Usage Examples
|
|
136
|
+
|
|
137
|
+
##### Basic Usage
|
|
138
|
+
|
|
139
|
+
```typescript
|
|
140
|
+
import { azure, loadConfig } from "@pagopa/dx-savemoney";
|
|
141
|
+
|
|
142
|
+
// Load from config file
|
|
143
|
+
const config = await loadConfig("./config.json");
|
|
144
|
+
await azure.analyzeAzureResources(config, "table");
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
##### Custom Configuration
|
|
148
|
+
|
|
149
|
+
```typescript
|
|
150
|
+
import { azure } from "@pagopa/dx-savemoney";
|
|
151
|
+
import type { AzureConfig } from "@pagopa/dx-savemoney";
|
|
152
|
+
|
|
153
|
+
const config: AzureConfig = {
|
|
154
|
+
tenantId: "your-tenant-id",
|
|
155
|
+
subscriptionIds: ["sub-id-1", "sub-id-2"],
|
|
156
|
+
preferredLocation: "italynorth",
|
|
157
|
+
timespanDays: 30,
|
|
158
|
+
verbose: true,
|
|
159
|
+
};
|
|
160
|
+
|
|
161
|
+
await azure.analyzeAzureResources(config, "json");
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
##### Generate Detailed Report
|
|
165
|
+
|
|
166
|
+
```typescript
|
|
167
|
+
import { azure, loadConfig } from "@pagopa/dx-savemoney";
|
|
168
|
+
|
|
169
|
+
const config = await loadConfig();
|
|
170
|
+
// Generate detailed JSON with full resource metadata
|
|
171
|
+
await azure.analyzeAzureResources(config, "detailed-json");
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
##### Using Environment Variables
|
|
175
|
+
|
|
176
|
+
```typescript
|
|
177
|
+
import { loadConfig, azure } from "@pagopa/dx-savemoney";
|
|
178
|
+
|
|
179
|
+
// Set environment variables
|
|
180
|
+
// ARM_TENANT_ID=xxx
|
|
181
|
+
// ARM_SUBSCRIPTION_ID=sub1,sub2
|
|
182
|
+
|
|
183
|
+
const config = await loadConfig(); // Will read from env vars
|
|
184
|
+
await azure.analyzeAzureResources(config, "json");
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
## AWS (Coming Soon)
|
|
188
|
+
|
|
189
|
+
AWS support is planned for future releases with similar capabilities:
|
|
190
|
+
|
|
191
|
+
- Multi-account analysis
|
|
192
|
+
- Resource-specific detection algorithms
|
|
193
|
+
- Flexible reporting formats
|
|
194
|
+
- AWS-specific configuration options
|
|
195
|
+
|
|
196
|
+
The API will follow a similar pattern:
|
|
197
|
+
|
|
198
|
+
```typescript
|
|
199
|
+
import { aws, loadAwsConfig } from "@pagopa/dx-savemoney";
|
|
200
|
+
|
|
201
|
+
const config = await loadAwsConfig("./aws-config.json");
|
|
202
|
+
await aws.analyzeAwsResources(config, "table");
|
|
203
|
+
```
|
|
204
|
+
|
|
205
|
+
## Development
|
|
206
|
+
|
|
207
|
+
### Type Checking
|
|
208
|
+
|
|
209
|
+
```bash
|
|
210
|
+
pnpm typecheck
|
|
211
|
+
```
|
|
212
|
+
|
|
213
|
+
### Linting
|
|
214
|
+
|
|
215
|
+
```bash
|
|
216
|
+
pnpm lint # Auto-fix issues
|
|
217
|
+
pnpm lint:check # Check without fixing
|
|
218
|
+
```
|
|
219
|
+
|
|
220
|
+
### Testing
|
|
221
|
+
|
|
222
|
+
```bash
|
|
223
|
+
pnpm test # Run tests
|
|
224
|
+
pnpm test:watch # Watch mode
|
|
225
|
+
pnpm test:coverage # With coverage report
|
|
226
|
+
```
|
|
227
|
+
|
|
228
|
+
### Formatting
|
|
229
|
+
|
|
230
|
+
```bash
|
|
231
|
+
pnpm format # Format code
|
|
232
|
+
pnpm format:check # Check formatting
|
|
233
|
+
```
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Azure resource analyzer - Main orchestration logic
|
|
3
|
+
*/
|
|
4
|
+
import { WebSiteManagementClient } from "@azure/arm-appservice";
|
|
5
|
+
import { ComputeManagementClient } from "@azure/arm-compute";
|
|
6
|
+
import { MonitorClient } from "@azure/arm-monitor";
|
|
7
|
+
import { NetworkManagementClient } from "@azure/arm-network";
|
|
8
|
+
import * as armResources from "@azure/arm-resources";
|
|
9
|
+
import type { AzureConfig } from "./types.js";
|
|
10
|
+
import { type AnalysisResult } from "../types.js";
|
|
11
|
+
/**
|
|
12
|
+
* Analyzes resources in multiple Azure subscriptions and generates a report.
|
|
13
|
+
*
|
|
14
|
+
* @param config - Azure configuration with subscription IDs and settings
|
|
15
|
+
* @param format - Output format (table, json, or detailed-json)
|
|
16
|
+
*/
|
|
17
|
+
export declare function analyzeAzureResources(config: AzureConfig, format: "detailed-json" | "json" | "table"): Promise<void>;
|
|
18
|
+
/**
|
|
19
|
+
* Analyzes a single Azure resource based on its type.
|
|
20
|
+
*
|
|
21
|
+
* @param resource - The Azure resource to analyze
|
|
22
|
+
* @param monitorClient - Azure Monitor client for metrics
|
|
23
|
+
* @param computeClient - Azure Compute client
|
|
24
|
+
* @param networkClient - Azure Network client
|
|
25
|
+
* @param webSiteClient - Azure Web Site client
|
|
26
|
+
* @param preferredLocation - Preferred Azure location
|
|
27
|
+
* @param timespanDays - Number of days to analyze metrics
|
|
28
|
+
* @param verbose - Whether verbose logging is enabled
|
|
29
|
+
* @returns Analysis result with cost risk and reason
|
|
30
|
+
*/
|
|
31
|
+
export declare function analyzeResource(resource: armResources.GenericResource, monitorClient: MonitorClient, computeClient: ComputeManagementClient, networkClient: NetworkManagementClient, webSiteClient: WebSiteManagementClient, preferredLocation: string, timespanDays: number, verbose?: boolean): Promise<AnalysisResult>;
|
|
32
|
+
//# sourceMappingURL=analyzer.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"analyzer.d.ts","sourceRoot":"","sources":["../../src/azure/analyzer.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAE,uBAAuB,EAAE,MAAM,uBAAuB,CAAC;AAChE,OAAO,EAAE,uBAAuB,EAAE,MAAM,oBAAoB,CAAC;AAC7D,OAAO,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;AACnD,OAAO,EAAE,uBAAuB,EAAE,MAAM,oBAAoB,CAAC;AAC7D,OAAO,KAAK,YAAY,MAAM,sBAAsB,CAAC;AAIrD,OAAO,KAAK,EAAE,WAAW,EAA+B,MAAM,YAAY,CAAC;AAE3E,OAAO,EAAE,KAAK,cAAc,EAAgB,MAAM,aAAa,CAAC;AAYhE;;;;;GAKG;AACH,wBAAsB,qBAAqB,CACzC,MAAM,EAAE,WAAW,EACnB,MAAM,EAAE,eAAe,GAAG,MAAM,GAAG,OAAO,iBA8D3C;AAED;;;;;;;;;;;;GAYG;AACH,wBAAsB,eAAe,CACnC,QAAQ,EAAE,YAAY,CAAC,eAAe,EACtC,aAAa,EAAE,aAAa,EAC5B,aAAa,EAAE,uBAAuB,EACtC,aAAa,EAAE,uBAAuB,EACtC,aAAa,EAAE,uBAAuB,EACtC,iBAAiB,EAAE,MAAM,EACzB,YAAY,EAAE,MAAM,EACpB,OAAO,UAAQ,GACd,OAAO,CAAC,cAAc,CAAC,CA4FzB"}
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Azure resource analyzer - Main orchestration logic
|
|
3
|
+
*/
|
|
4
|
+
import { WebSiteManagementClient } from "@azure/arm-appservice";
|
|
5
|
+
import { ComputeManagementClient } from "@azure/arm-compute";
|
|
6
|
+
import { MonitorClient } from "@azure/arm-monitor";
|
|
7
|
+
import { NetworkManagementClient } from "@azure/arm-network";
|
|
8
|
+
import * as armResources from "@azure/arm-resources";
|
|
9
|
+
import { DefaultAzureCredential } from "@azure/identity";
|
|
10
|
+
import { getLogger } from "@logtape/logtape";
|
|
11
|
+
import { mergeResults } from "../types.js";
|
|
12
|
+
import { generateReport } from "./report.js";
|
|
13
|
+
import { analyzeAppServicePlan, analyzeDisk, analyzeNic, analyzePrivateEndpoint, analyzePublicIp, analyzeStorageAccount, analyzeVM, } from "./resources/index.js";
|
|
14
|
+
/**
|
|
15
|
+
* Analyzes resources in multiple Azure subscriptions and generates a report.
|
|
16
|
+
*
|
|
17
|
+
* @param config - Azure configuration with subscription IDs and settings
|
|
18
|
+
* @param format - Output format (table, json, or detailed-json)
|
|
19
|
+
*/
|
|
20
|
+
export async function analyzeAzureResources(config, format) {
|
|
21
|
+
const logger = getLogger(["savemoney", "azure"]);
|
|
22
|
+
const credential = new DefaultAzureCredential();
|
|
23
|
+
const allReports = [];
|
|
24
|
+
for (const subscriptionId of config.subscriptionIds) {
|
|
25
|
+
logger.info(`Analyzing subscription: ${subscriptionId}`);
|
|
26
|
+
const resourceClient = new armResources.ResourceManagementClient(credential, subscriptionId.trim());
|
|
27
|
+
const monitorClient = new MonitorClient(credential, subscriptionId.trim());
|
|
28
|
+
const computeClient = new ComputeManagementClient(credential, subscriptionId.trim());
|
|
29
|
+
const networkClient = new NetworkManagementClient(credential, subscriptionId.trim());
|
|
30
|
+
const webSiteClient = new WebSiteManagementClient(credential, subscriptionId.trim());
|
|
31
|
+
// Use the async iterator to avoid memory explosion for large environments
|
|
32
|
+
for await (const resource of resourceClient.resources.list()) {
|
|
33
|
+
const { costRisk, reason, suspectedUnused } = await analyzeResource(resource, monitorClient, computeClient, networkClient, webSiteClient, config.preferredLocation, config.timespanDays, config.verbose || false);
|
|
34
|
+
if (suspectedUnused) {
|
|
35
|
+
allReports.push({
|
|
36
|
+
analysis: {
|
|
37
|
+
costRisk,
|
|
38
|
+
reason: reason || "No specific findings.",
|
|
39
|
+
suspectedUnused,
|
|
40
|
+
},
|
|
41
|
+
resource: resource,
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
// Sort to make the output more readable
|
|
47
|
+
allReports.sort((a, b) => {
|
|
48
|
+
if (a.analysis.costRisk === b.analysis.costRisk)
|
|
49
|
+
return (a.resource.name ?? "").localeCompare(b.resource.name ?? "");
|
|
50
|
+
const order = { high: 0, low: 2, medium: 1 };
|
|
51
|
+
return order[a.analysis.costRisk] - order[b.analysis.costRisk];
|
|
52
|
+
});
|
|
53
|
+
await generateReport(allReports, format);
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Analyzes a single Azure resource based on its type.
|
|
57
|
+
*
|
|
58
|
+
* @param resource - The Azure resource to analyze
|
|
59
|
+
* @param monitorClient - Azure Monitor client for metrics
|
|
60
|
+
* @param computeClient - Azure Compute client
|
|
61
|
+
* @param networkClient - Azure Network client
|
|
62
|
+
* @param webSiteClient - Azure Web Site client
|
|
63
|
+
* @param preferredLocation - Preferred Azure location
|
|
64
|
+
* @param timespanDays - Number of days to analyze metrics
|
|
65
|
+
* @param verbose - Whether verbose logging is enabled
|
|
66
|
+
* @returns Analysis result with cost risk and reason
|
|
67
|
+
*/
|
|
68
|
+
export async function analyzeResource(resource, monitorClient, computeClient, networkClient, webSiteClient, preferredLocation, timespanDays, verbose = false) {
|
|
69
|
+
const type = resource.type?.toLowerCase() || "";
|
|
70
|
+
let result = {
|
|
71
|
+
costRisk: "low",
|
|
72
|
+
reason: "",
|
|
73
|
+
suspectedUnused: false,
|
|
74
|
+
};
|
|
75
|
+
// Generic check: lack of tags is a common sign of unmanaged resources.
|
|
76
|
+
if (!resource.tags || Object.keys(resource.tags).length === 0) {
|
|
77
|
+
result.suspectedUnused = true;
|
|
78
|
+
result.reason += "No tags found. ";
|
|
79
|
+
}
|
|
80
|
+
// Route to type-specific analysis hooks
|
|
81
|
+
switch (type) {
|
|
82
|
+
case "microsoft.compute/disks": {
|
|
83
|
+
const diskResult = await analyzeDisk(resource, computeClient, verbose);
|
|
84
|
+
result = mergeResults(result, diskResult);
|
|
85
|
+
break;
|
|
86
|
+
}
|
|
87
|
+
case "microsoft.compute/virtualmachines": {
|
|
88
|
+
const vmResult = await analyzeVM(resource, monitorClient, computeClient, timespanDays, verbose);
|
|
89
|
+
result = mergeResults(result, vmResult);
|
|
90
|
+
break;
|
|
91
|
+
}
|
|
92
|
+
case "microsoft.network/networkinterfaces": {
|
|
93
|
+
const nicResult = await analyzeNic(resource, networkClient, verbose);
|
|
94
|
+
result = mergeResults(result, nicResult);
|
|
95
|
+
break;
|
|
96
|
+
}
|
|
97
|
+
case "microsoft.network/privateendpoints": {
|
|
98
|
+
const peResult = await analyzePrivateEndpoint(resource, networkClient, verbose);
|
|
99
|
+
result = mergeResults(result, peResult);
|
|
100
|
+
break;
|
|
101
|
+
}
|
|
102
|
+
case "microsoft.network/publicipaddresses": {
|
|
103
|
+
const pipResult = await analyzePublicIp(resource, networkClient, monitorClient, timespanDays, verbose);
|
|
104
|
+
result = mergeResults(result, pipResult);
|
|
105
|
+
break;
|
|
106
|
+
}
|
|
107
|
+
case "microsoft.storage/storageaccounts": {
|
|
108
|
+
const storageResult = await analyzeStorageAccount(resource, monitorClient, timespanDays, verbose);
|
|
109
|
+
result = mergeResults(result, storageResult);
|
|
110
|
+
break;
|
|
111
|
+
}
|
|
112
|
+
case "microsoft.web/serverfarms": {
|
|
113
|
+
const aspResult = await analyzeAppServicePlan(resource, webSiteClient, monitorClient, timespanDays, verbose);
|
|
114
|
+
result = mergeResults(result, aspResult);
|
|
115
|
+
break;
|
|
116
|
+
}
|
|
117
|
+
default:
|
|
118
|
+
result.reason += "No specific analysis for this resource type. ";
|
|
119
|
+
break;
|
|
120
|
+
}
|
|
121
|
+
// Generic check for location
|
|
122
|
+
if (resource.location &&
|
|
123
|
+
!resource.location.toLowerCase().includes(preferredLocation.toLowerCase())) {
|
|
124
|
+
result.reason += `Resource not in preferred location (${preferredLocation}). `;
|
|
125
|
+
}
|
|
126
|
+
return { ...result, reason: result.reason.trim() };
|
|
127
|
+
}
|
|
128
|
+
//# sourceMappingURL=analyzer.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"analyzer.js","sourceRoot":"","sources":["../../src/azure/analyzer.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Azure configuration loading utilities
|
|
3
|
+
*/
|
|
4
|
+
import type { AzureConfig } from "./types.js";
|
|
5
|
+
/**
|
|
6
|
+
* Loads Azure configuration from file, environment variables, or interactive prompts.
|
|
7
|
+
*
|
|
8
|
+
* @param configPath - Optional path to JSON configuration file
|
|
9
|
+
* @returns Azure configuration object with subscription IDs and settings
|
|
10
|
+
*/
|
|
11
|
+
export declare function loadAzureConfig(configPath?: string): Promise<AzureConfig>;
|
|
12
|
+
/**
|
|
13
|
+
* Prompts user for input via stdin.
|
|
14
|
+
*
|
|
15
|
+
* @param question - The question to display to the user
|
|
16
|
+
* @returns User's input as a string
|
|
17
|
+
*/
|
|
18
|
+
export declare function prompt(question: string): Promise<string>;
|
|
19
|
+
//# sourceMappingURL=config.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../../src/azure/config.ts"],"names":[],"mappings":"AAAA;;GAEG;AAMH,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAE9C;;;;;GAKG;AACH,wBAAsB,eAAe,CACnC,UAAU,CAAC,EAAE,MAAM,GAClB,OAAO,CAAC,WAAW,CAAC,CA2CtB;AAED;;;;;GAKG;AACH,wBAAsB,MAAM,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAW9D"}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Azure configuration loading utilities
|
|
3
|
+
*/
|
|
4
|
+
import { getLogger } from "@logtape/logtape";
|
|
5
|
+
import * as fs from "fs";
|
|
6
|
+
import * as readline from "readline";
|
|
7
|
+
/**
|
|
8
|
+
* Loads Azure configuration from file, environment variables, or interactive prompts.
|
|
9
|
+
*
|
|
10
|
+
* @param configPath - Optional path to JSON configuration file
|
|
11
|
+
* @returns Azure configuration object with subscription IDs and settings
|
|
12
|
+
*/
|
|
13
|
+
export async function loadAzureConfig(configPath) {
|
|
14
|
+
if (configPath && fs.existsSync(configPath)) {
|
|
15
|
+
try {
|
|
16
|
+
const configContent = fs.readFileSync(configPath, "utf-8");
|
|
17
|
+
const config = JSON.parse(configContent);
|
|
18
|
+
if (!config.tenantId || !config.subscriptionIds) {
|
|
19
|
+
throw new Error("Config file must contain 'tenantId' and 'subscriptionIds'");
|
|
20
|
+
}
|
|
21
|
+
return {
|
|
22
|
+
...config,
|
|
23
|
+
preferredLocation: config.preferredLocation || "italynorth",
|
|
24
|
+
subscriptionIds: config.subscriptionIds,
|
|
25
|
+
tenantId: config.tenantId,
|
|
26
|
+
timespanDays: config.timespanDays || 30,
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
catch (error) {
|
|
30
|
+
throw new Error(`Failed to load config file: ${error instanceof Error ? error.message : error}`);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
const logger = getLogger(["savemoney", "azure", "config"]);
|
|
34
|
+
logger.info("Configuration file not found. Checking environment variables...");
|
|
35
|
+
const tenantId = process.env.ARM_TENANT_ID || (await prompt("Enter Tenant ID: "));
|
|
36
|
+
const subscriptionIds = process.env.ARM_SUBSCRIPTION_ID
|
|
37
|
+
? process.env.ARM_SUBSCRIPTION_ID.split(",")
|
|
38
|
+
: (await prompt("Enter Subscription IDs (comma-separated): ")).split(",");
|
|
39
|
+
return {
|
|
40
|
+
preferredLocation: "italynorth",
|
|
41
|
+
subscriptionIds,
|
|
42
|
+
tenantId,
|
|
43
|
+
timespanDays: 30,
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Prompts user for input via stdin.
|
|
48
|
+
*
|
|
49
|
+
* @param question - The question to display to the user
|
|
50
|
+
* @returns User's input as a string
|
|
51
|
+
*/
|
|
52
|
+
export async function prompt(question) {
|
|
53
|
+
const rl = readline.createInterface({
|
|
54
|
+
input: process.stdin,
|
|
55
|
+
output: process.stdout,
|
|
56
|
+
});
|
|
57
|
+
return new Promise((resolve) => rl.question(question, (answer) => {
|
|
58
|
+
rl.close();
|
|
59
|
+
resolve(answer);
|
|
60
|
+
}));
|
|
61
|
+
}
|
|
62
|
+
//# sourceMappingURL=config.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config.js","sourceRoot":"","sources":["../../src/azure/config.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAC;AAC7C,OAAO,KAAK,EAAE,MAAM,IAAI,CAAC;AACzB,OAAO,KAAK,QAAQ,MAAM,UAAU,CAAC;AAIrC;;;;;GAKG;AACH,MAAM,CAAC,KAAK,UAAU,eAAe,CACnC,UAAmB;IAEnB,IAAI,UAAU,IAAI,EAAE,CAAC,UAAU,CAAC,UAAU,CAAC,EAAE,CAAC;QAC5C,IAAI,CAAC;YACH,MAAM,aAAa,GAAG,EAAE,CAAC,YAAY,CAAC,UAAU,EAAE,OAAO,CAAC,CAAC;YAC3D,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,aAAa,CAAyB,CAAC;YAEjE,IAAI,CAAC,MAAM,CAAC,QAAQ,IAAI,CAAC,MAAM,CAAC,eAAe,EAAE,CAAC;gBAChD,MAAM,IAAI,KAAK,CACb,2DAA2D,CAC5D,CAAC;YACJ,CAAC;YAED,OAAO;gBACL,GAAG,MAAM;gBACT,iBAAiB,EAAE,MAAM,CAAC,iBAAiB,IAAI,YAAY;gBAC3D,eAAe,EAAE,MAAM,CAAC,eAAe;gBACvC,QAAQ,EAAE,MAAM,CAAC,QAAQ;gBACzB,YAAY,EAAE,MAAM,CAAC,YAAY,IAAI,EAAE;aACxC,CAAC;QACJ,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,MAAM,IAAI,KAAK,CACb,+BAA+B,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,KAAK,EAAE,CAChF,CAAC;QACJ,CAAC;IACH,CAAC;IAED,MAAM,MAAM,GAAG,SAAS,CAAC,CAAC,WAAW,EAAE,OAAO,EAAE,QAAQ,CAAC,CAAC,CAAC;IAC3D,MAAM,CAAC,IAAI,CACT,iEAAiE,CAClE,CAAC;IAEF,MAAM,QAAQ,GACZ,OAAO,CAAC,GAAG,CAAC,aAAa,IAAI,CAAC,MAAM,MAAM,CAAC,mBAAmB,CAAC,CAAC,CAAC;IACnE,MAAM,eAAe,GAAG,OAAO,CAAC,GAAG,CAAC,mBAAmB;QACrD,CAAC,CAAC,OAAO,CAAC,GAAG,CAAC,mBAAmB,CAAC,KAAK,CAAC,GAAG,CAAC;QAC5C,CAAC,CAAC,CAAC,MAAM,MAAM,CAAC,4CAA4C,CAAC,CAAC,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;IAE5E,OAAO;QACL,iBAAiB,EAAE,YAAY;QAC/B,eAAe;QACf,QAAQ;QACR,YAAY,EAAE,EAAE;KACjB,CAAC;AACJ,CAAC;AAED;;;;;GAKG;AACH,MAAM,CAAC,KAAK,UAAU,MAAM,CAAC,QAAgB;IAC3C,MAAM,EAAE,GAAG,QAAQ,CAAC,eAAe,CAAC;QAClC,KAAK,EAAE,OAAO,CAAC,KAAK;QACpB,MAAM,EAAE,OAAO,CAAC,MAAM;KACvB,CAAC,CAAC;IACH,OAAO,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,EAAE,CAC7B,EAAE,CAAC,QAAQ,CAAC,QAAQ,EAAE,CAAC,MAAM,EAAE,EAAE;QAC/B,EAAE,CAAC,KAAK,EAAE,CAAC;QACX,OAAO,CAAC,MAAM,CAAC,CAAC;IAClB,CAAC,CAAC,CACH,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/azure/index.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,cAAc,eAAe,CAAC;AAC9B,cAAc,aAAa,CAAC;AAC5B,cAAc,YAAY,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/azure/index.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,cAAc,eAAe,CAAC;AAC9B,cAAc,aAAa,CAAC;AAC5B,cAAc,YAAY,CAAC"}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Azure report generation
|
|
3
|
+
*/
|
|
4
|
+
import type { AzureDetailedResourceReport } from "./types.js";
|
|
5
|
+
/**
|
|
6
|
+
* Generates a report from Azure resource analysis in the specified format.
|
|
7
|
+
*
|
|
8
|
+
* @param report - Array of detailed resource reports
|
|
9
|
+
* @param format - Output format (table, json, or detailed-json)
|
|
10
|
+
*/
|
|
11
|
+
export declare function generateReport(report: AzureDetailedResourceReport[], format: "detailed-json" | "json" | "table"): Promise<void>;
|
|
12
|
+
//# sourceMappingURL=report.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"report.d.ts","sourceRoot":"","sources":["../../src/azure/report.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,KAAK,EACV,2BAA2B,EAE5B,MAAM,YAAY,CAAC;AAEpB;;;;;GAKG;AACH,wBAAsB,cAAc,CAClC,MAAM,EAAE,2BAA2B,EAAE,EACrC,MAAM,EAAE,eAAe,GAAG,MAAM,GAAG,OAAO,iBAkC3C"}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Azure report generation
|
|
3
|
+
*/
|
|
4
|
+
/**
|
|
5
|
+
* Generates a report from Azure resource analysis in the specified format.
|
|
6
|
+
*
|
|
7
|
+
* @param report - Array of detailed resource reports
|
|
8
|
+
* @param format - Output format (table, json, or detailed-json)
|
|
9
|
+
*/
|
|
10
|
+
export async function generateReport(report, format) {
|
|
11
|
+
if (format === "detailed-json") {
|
|
12
|
+
console.log(JSON.stringify(report, null, 2));
|
|
13
|
+
return;
|
|
14
|
+
}
|
|
15
|
+
// For other formats, we extract the summary data.
|
|
16
|
+
const summaryReport = report.map((r) => ({
|
|
17
|
+
costRisk: r.analysis.costRisk,
|
|
18
|
+
location: r.resource.location ?? "",
|
|
19
|
+
name: r.resource.name ?? "unknown",
|
|
20
|
+
reason: r.analysis.reason,
|
|
21
|
+
resourceGroup: r.resource.id?.split("/")[4],
|
|
22
|
+
subscriptionId: r.resource.id?.split("/")[2] ?? "unknown",
|
|
23
|
+
suspectedUnused: r.analysis.suspectedUnused,
|
|
24
|
+
type: r.resource.type ?? "unknown",
|
|
25
|
+
}));
|
|
26
|
+
if (format === "json") {
|
|
27
|
+
console.log(JSON.stringify(summaryReport, null, 2));
|
|
28
|
+
}
|
|
29
|
+
else {
|
|
30
|
+
console.table(summaryReport.map((r) => ({
|
|
31
|
+
Name: r.name,
|
|
32
|
+
Reason: r.reason,
|
|
33
|
+
"Resource Group": r.resourceGroup || "N/A",
|
|
34
|
+
Risk: r.costRisk,
|
|
35
|
+
Type: r.type,
|
|
36
|
+
Unused: r.suspectedUnused ? "Yes" : "No",
|
|
37
|
+
})), ["Name", "Type", "Resource Group", "Risk", "Unused", "Reason"]);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
//# sourceMappingURL=report.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"report.js","sourceRoot":"","sources":["../../src/azure/report.ts"],"names":[],"mappings":"AAAA;;GAEG;AAOH;;;;;GAKG;AACH,MAAM,CAAC,KAAK,UAAU,cAAc,CAClC,MAAqC,EACrC,MAA0C;IAE1C,IAAI,MAAM,KAAK,eAAe,EAAE,CAAC;QAC/B,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,SAAS,CAAC,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,CAAC;QAC7C,OAAO;IACT,CAAC;IAED,kDAAkD;IAClD,MAAM,aAAa,GAA0B,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;QAC9D,QAAQ,EAAE,CAAC,CAAC,QAAQ,CAAC,QAAQ;QAC7B,QAAQ,EAAE,CAAC,CAAC,QAAQ,CAAC,QAAQ,IAAI,EAAE;QACnC,IAAI,EAAE,CAAC,CAAC,QAAQ,CAAC,IAAI,IAAI,SAAS;QAClC,MAAM,EAAE,CAAC,CAAC,QAAQ,CAAC,MAAM;QACzB,aAAa,EAAE,CAAC,CAAC,QAAQ,CAAC,EAAE,EAAE,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC;QAC3C,cAAc,EAAE,CAAC,CAAC,QAAQ,CAAC,EAAE,EAAE,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,SAAS;QACzD,eAAe,EAAE,CAAC,CAAC,QAAQ,CAAC,eAAe;QAC3C,IAAI,EAAE,CAAC,CAAC,QAAQ,CAAC,IAAI,IAAI,SAAS;KACnC,CAAC,CAAC,CAAC;IAEJ,IAAI,MAAM,KAAK,MAAM,EAAE,CAAC;QACtB,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,SAAS,CAAC,aAAa,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,CAAC;IACtD,CAAC;SAAM,CAAC;QACN,OAAO,CAAC,KAAK,CACX,aAAa,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;YACxB,IAAI,EAAE,CAAC,CAAC,IAAI;YACZ,MAAM,EAAE,CAAC,CAAC,MAAM;YAChB,gBAAgB,EAAE,CAAC,CAAC,aAAa,IAAI,KAAK;YAC1C,IAAI,EAAE,CAAC,CAAC,QAAQ;YAChB,IAAI,EAAE,CAAC,CAAC,IAAI;YACZ,MAAM,EAAE,CAAC,CAAC,eAAe,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI;SACzC,CAAC,CAAC,EACH,CAAC,MAAM,EAAE,MAAM,EAAE,gBAAgB,EAAE,MAAM,EAAE,QAAQ,EAAE,QAAQ,CAAC,CAC/D,CAAC;IACJ,CAAC;AACH,CAAC"}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Azure App Service Plan analysis
|
|
3
|
+
*/
|
|
4
|
+
import type { WebSiteManagementClient } from "@azure/arm-appservice";
|
|
5
|
+
import type { MonitorClient } from "@azure/arm-monitor";
|
|
6
|
+
import * as armResources from "@azure/arm-resources";
|
|
7
|
+
import type { AnalysisResult } from "../../types.js";
|
|
8
|
+
/**
|
|
9
|
+
* Analyzes an Azure App Service Plan for potential cost optimization.
|
|
10
|
+
*
|
|
11
|
+
* @param resource - The Azure resource object
|
|
12
|
+
* @param webSiteClient - Azure Web Site client for App Service Plan details
|
|
13
|
+
* @param monitorClient - Azure Monitor client for metrics
|
|
14
|
+
* @param timespanDays - Number of days to analyze metrics
|
|
15
|
+
* @returns Analysis result with cost risk and reason
|
|
16
|
+
*/
|
|
17
|
+
export declare function analyzeAppServicePlan(resource: armResources.GenericResource, webSiteClient: WebSiteManagementClient, monitorClient: MonitorClient, timespanDays: number, verbose?: boolean): Promise<AnalysisResult>;
|
|
18
|
+
//# sourceMappingURL=app-service.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"app-service.d.ts","sourceRoot":"","sources":["../../../src/azure/resources/app-service.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,uBAAuB,CAAC;AACrE,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;AAExD,OAAO,KAAK,YAAY,MAAM,sBAAsB,CAAC;AAGrD,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAC;AASrD;;;;;;;;GAQG;AACH,wBAAsB,qBAAqB,CACzC,QAAQ,EAAE,YAAY,CAAC,eAAe,EACtC,aAAa,EAAE,uBAAuB,EACtC,aAAa,EAAE,aAAa,EAC5B,YAAY,EAAE,MAAM,EACpB,OAAO,UAAQ,GACd,OAAO,CAAC,cAAc,CAAC,CAmFzB"}
|