@pagopa/dx-savemoney 0.2.5 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (103) hide show
  1. package/README.md +33 -27
  2. package/dist/azure/analyzer.d.ts +49 -21
  3. package/dist/azure/analyzer.d.ts.map +1 -1
  4. package/dist/azure/analyzer.js +369 -93
  5. package/dist/azure/analyzer.js.map +1 -1
  6. package/dist/azure/analyzers/advisor.d.ts +68 -0
  7. package/dist/azure/analyzers/advisor.d.ts.map +1 -0
  8. package/dist/azure/analyzers/advisor.js +234 -0
  9. package/dist/azure/analyzers/advisor.js.map +1 -0
  10. package/dist/azure/analyzers/index.d.ts +8 -0
  11. package/dist/azure/analyzers/index.d.ts.map +1 -0
  12. package/dist/azure/analyzers/index.js +6 -0
  13. package/dist/azure/analyzers/index.js.map +1 -0
  14. package/dist/azure/analyzers/registry.d.ts +29 -0
  15. package/dist/azure/analyzers/registry.d.ts.map +1 -0
  16. package/dist/azure/analyzers/registry.js +79 -0
  17. package/dist/azure/analyzers/registry.js.map +1 -0
  18. package/dist/azure/analyzers/subscription.d.ts +53 -0
  19. package/dist/azure/analyzers/subscription.d.ts.map +1 -0
  20. package/dist/azure/analyzers/subscription.js +18 -0
  21. package/dist/azure/analyzers/subscription.js.map +1 -0
  22. package/dist/azure/analyzers/types.d.ts +62 -0
  23. package/dist/azure/analyzers/types.d.ts.map +1 -0
  24. package/dist/azure/analyzers/types.js +15 -0
  25. package/dist/azure/analyzers/types.js.map +1 -0
  26. package/dist/azure/config.d.ts.map +1 -1
  27. package/dist/azure/config.js +2 -0
  28. package/dist/azure/config.js.map +1 -1
  29. package/dist/azure/index.d.ts +1 -0
  30. package/dist/azure/index.d.ts.map +1 -1
  31. package/dist/azure/index.js +1 -0
  32. package/dist/azure/index.js.map +1 -1
  33. package/dist/azure/report.d.ts.map +1 -1
  34. package/dist/azure/report.js +178 -29
  35. package/dist/azure/report.js.map +1 -1
  36. package/dist/azure/resources/app-service.d.ts +2 -1
  37. package/dist/azure/resources/app-service.d.ts.map +1 -1
  38. package/dist/azure/resources/app-service.js +3 -3
  39. package/dist/azure/resources/app-service.js.map +1 -1
  40. package/dist/azure/resources/container-app.d.ts +2 -1
  41. package/dist/azure/resources/container-app.d.ts.map +1 -1
  42. package/dist/azure/resources/container-app.js +9 -9
  43. package/dist/azure/resources/container-app.js.map +1 -1
  44. package/dist/azure/resources/public-ip.d.ts +2 -1
  45. package/dist/azure/resources/public-ip.d.ts.map +1 -1
  46. package/dist/azure/resources/public-ip.js +2 -2
  47. package/dist/azure/resources/public-ip.js.map +1 -1
  48. package/dist/azure/resources/static-web-app.d.ts +2 -1
  49. package/dist/azure/resources/static-web-app.d.ts.map +1 -1
  50. package/dist/azure/resources/static-web-app.js +3 -3
  51. package/dist/azure/resources/static-web-app.js.map +1 -1
  52. package/dist/azure/resources/storage.d.ts +2 -1
  53. package/dist/azure/resources/storage.d.ts.map +1 -1
  54. package/dist/azure/resources/storage.js +2 -2
  55. package/dist/azure/resources/storage.js.map +1 -1
  56. package/dist/azure/resources/vm.d.ts +2 -1
  57. package/dist/azure/resources/vm.d.ts.map +1 -1
  58. package/dist/azure/resources/vm.js +3 -3
  59. package/dist/azure/resources/vm.js.map +1 -1
  60. package/dist/azure/types.d.ts +34 -1
  61. package/dist/azure/types.d.ts.map +1 -1
  62. package/dist/azure/utils.d.ts +35 -3
  63. package/dist/azure/utils.d.ts.map +1 -1
  64. package/dist/azure/utils.js +70 -29
  65. package/dist/azure/utils.js.map +1 -1
  66. package/dist/finding.d.ts +114 -0
  67. package/dist/finding.d.ts.map +1 -0
  68. package/dist/finding.js +51 -0
  69. package/dist/finding.js.map +1 -0
  70. package/dist/index.d.ts +4 -1
  71. package/dist/index.d.ts.map +1 -1
  72. package/dist/index.js +3 -0
  73. package/dist/index.js.map +1 -1
  74. package/dist/schema.d.ts +5 -0
  75. package/dist/schema.d.ts.map +1 -1
  76. package/dist/schema.js +14 -0
  77. package/dist/schema.js.map +1 -1
  78. package/package.json +4 -1
  79. package/src/__tests__/finding.test.ts +149 -0
  80. package/src/azure/__tests__/analyzer-tags.test.ts +74 -0
  81. package/src/azure/__tests__/report.test.ts +27 -0
  82. package/src/azure/__tests__/utils.test.ts +164 -2
  83. package/src/azure/analyzer.ts +513 -182
  84. package/src/azure/analyzers/__tests__/advisor.test.ts +367 -0
  85. package/src/azure/analyzers/advisor.ts +324 -0
  86. package/src/azure/analyzers/index.ts +14 -0
  87. package/src/azure/analyzers/registry.ts +196 -0
  88. package/src/azure/analyzers/subscription.ts +56 -0
  89. package/src/azure/analyzers/types.ts +66 -0
  90. package/src/azure/config.ts +2 -0
  91. package/src/azure/index.ts +1 -0
  92. package/src/azure/report.ts +206 -35
  93. package/src/azure/resources/app-service.ts +4 -0
  94. package/src/azure/resources/container-app.ts +10 -0
  95. package/src/azure/resources/public-ip.ts +3 -0
  96. package/src/azure/resources/static-web-app.ts +4 -0
  97. package/src/azure/resources/storage.ts +3 -0
  98. package/src/azure/resources/vm.ts +4 -0
  99. package/src/azure/types.ts +35 -1
  100. package/src/azure/utils.ts +110 -39
  101. package/src/finding.ts +152 -0
  102. package/src/index.ts +19 -1
  103. package/src/schema.ts +14 -0
@@ -5,6 +5,7 @@ import type { ComputeManagementClient } from "@azure/arm-compute";
5
5
  import type { MonitorClient } from "@azure/arm-monitor";
6
6
  import * as armResources from "@azure/arm-resources";
7
7
  import type { AnalysisResult, Thresholds } from "../../types.js";
8
+ import { type MetricsCache } from "../utils.js";
8
9
  /**
9
10
  * Analyzes an Azure Virtual Machine for potential cost optimization.
10
11
  *
@@ -15,5 +16,5 @@ import type { AnalysisResult, Thresholds } from "../../types.js";
15
16
  * @param verbose - Whether verbose logging is enabled
16
17
  * @returns Analysis result with cost risk and reason
17
18
  */
18
- export declare function analyzeVM(resource: armResources.GenericResource, monitorClient: MonitorClient, computeClient: ComputeManagementClient, timespanDays: number, thresholds?: Thresholds, verbose?: boolean): Promise<AnalysisResult>;
19
+ export declare function analyzeVM(resource: armResources.GenericResource, monitorClient: MonitorClient, computeClient: ComputeManagementClient, timespanDays: number, thresholds?: Thresholds, verbose?: boolean, cache?: MetricsCache): Promise<AnalysisResult>;
19
20
  //# sourceMappingURL=vm.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"vm.d.ts","sourceRoot":"","sources":["../../../src/azure/resources/vm.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,oBAAoB,CAAC;AAClE,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;AAExD,OAAO,KAAK,YAAY,MAAM,sBAAsB,CAAC;AAGrD,OAAO,KAAK,EAAE,cAAc,EAAE,UAAU,EAAE,MAAM,gBAAgB,CAAC;AAUjE;;;;;;;;;GASG;AACH,wBAAsB,SAAS,CAC7B,QAAQ,EAAE,YAAY,CAAC,eAAe,EACtC,aAAa,EAAE,aAAa,EAC5B,aAAa,EAAE,uBAAuB,EACtC,YAAY,EAAE,MAAM,EACpB,UAAU,GAAE,UAA+B,EAC3C,OAAO,UAAQ,GACd,OAAO,CAAC,cAAc,CAAC,CA2FzB"}
1
+ {"version":3,"file":"vm.d.ts","sourceRoot":"","sources":["../../../src/azure/resources/vm.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,oBAAoB,CAAC;AAClE,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;AAExD,OAAO,KAAK,YAAY,MAAM,sBAAsB,CAAC;AAGrD,OAAO,KAAK,EAAE,cAAc,EAAE,UAAU,EAAE,MAAM,gBAAgB,CAAC;AAGjE,OAAO,EAEL,KAAK,YAAY,EAIlB,MAAM,aAAa,CAAC;AAErB;;;;;;;;;GASG;AACH,wBAAsB,SAAS,CAC7B,QAAQ,EAAE,YAAY,CAAC,eAAe,EACtC,aAAa,EAAE,aAAa,EAC5B,aAAa,EAAE,uBAAuB,EACtC,YAAY,EAAE,MAAM,EACpB,UAAU,GAAE,UAA+B,EAC3C,OAAO,UAAQ,EACf,KAAK,CAAC,EAAE,YAAY,GACnB,OAAO,CAAC,cAAc,CAAC,CA6FzB"}
@@ -14,7 +14,7 @@ import { getMetric, verboseLog, verboseLogAnalysisResult, verboseLogResourceStar
14
14
  * @param verbose - Whether verbose logging is enabled
15
15
  * @returns Analysis result with cost risk and reason
16
16
  */
17
- export async function analyzeVM(resource, monitorClient, computeClient, timespanDays, thresholds = DEFAULT_THRESHOLDS, verbose = false) {
17
+ export async function analyzeVM(resource, monitorClient, computeClient, timespanDays, thresholds = DEFAULT_THRESHOLDS, verbose = false, cache) {
18
18
  verboseLogResourceStart(verbose, resource.name || "unknown", "Virtual Machine (microsoft.compute/virtualmachines)");
19
19
  verboseLog(verbose, "Resource details:", resource);
20
20
  const costRisk = "high";
@@ -61,8 +61,8 @@ export async function analyzeVM(resource, monitorClient, computeClient, timespan
61
61
  // Continue with metric analysis if instance view fails
62
62
  }
63
63
  // Check metrics for low utilization
64
- const cpuUsage = await getMetric(monitorClient, resource.id, "Percentage CPU", "Average", timespanDays);
65
- const networkIn = await getMetric(monitorClient, resource.id, "Network In Total", "Average", timespanDays);
64
+ const cpuUsage = await getMetric(monitorClient, resource.id, "Percentage CPU", "Average", timespanDays, cache);
65
+ const networkIn = await getMetric(monitorClient, resource.id, "Network In Total", "Average", timespanDays, cache);
66
66
  if (cpuUsage !== null && cpuUsage < thresholds.vm.cpuPercent) {
67
67
  reason += `Low CPU usage (avg ${cpuUsage.toFixed(2)}%). `;
68
68
  }
@@ -1 +1 @@
1
- {"version":3,"file":"vm.js","sourceRoot":"","sources":["../../../src/azure/resources/vm.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"vm.js","sourceRoot":"","sources":["../../../src/azure/resources/vm.ts"],"names":[],"mappings":"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"}
@@ -2,16 +2,31 @@
2
2
  * Azure-specific types
3
3
  */
4
4
  import type * as armResources from "@azure/arm-resources";
5
+ import type { Finding } from "../finding.js";
5
6
  import type { AnalysisResult, BaseConfig, CostRisk, Thresholds } from "../types.js";
6
7
  /**
7
8
  * Azure configuration extending base config
8
9
  */
9
10
  export type AzureConfig = BaseConfig & {
11
+ /**
12
+ * Maximum number of resources analyzed in parallel within a single
13
+ * subscription. Defaults to 8 when not provided. Set to 1 for a fully
14
+ * sequential run (useful for debugging or to be gentler on quotas).
15
+ */
16
+ concurrency?: number;
10
17
  /**
11
18
  * Only analyze resources that match ALL the given tag key-value pairs.
12
19
  * If omitted, all resources are analyzed.
13
20
  */
14
21
  filterTags?: Map<string, string>;
22
+ /**
23
+ * Which finding sources to include in the run.
24
+ * - `"custom"` → enables the per-resource analyzer plugins
25
+ * - `"advisor"` → enables the Azure Advisor subscription-level analyzer
26
+ *
27
+ * Defaults to `["advisor", "custom"]` when omitted (i.e. all sources).
28
+ */
29
+ sources?: AzureSource[];
15
30
  subscriptionIds: string[];
16
31
  /**
17
32
  * Analysis thresholds. Defaults from DEFAULT_THRESHOLDS are used when not provided.
@@ -20,10 +35,22 @@ export type AzureConfig = BaseConfig & {
20
35
  verbose?: boolean;
21
36
  };
22
37
  /**
23
- * Detailed report for a single Azure resource with full resource object
38
+ * Detailed report for a single Azure resource with full resource object.
39
+ *
40
+ * Phase 1 introduces the optional `findings` field carrying the unified
41
+ * `Finding[]` model alongside the legacy `analysis` summary. The two are
42
+ * kept in sync by the orchestrator so existing report formats keep
43
+ * working untouched while new consumers (GUI, JSON exports, Phase 2
44
+ * pricing aggregation) can read structured findings directly.
24
45
  */
25
46
  export type AzureDetailedResourceReport = {
26
47
  analysis: AnalysisResult;
48
+ /**
49
+ * Structured findings attached to the resource. Always populated by the
50
+ * orchestrator (possibly empty). Optional only for backward compatibility
51
+ * with serialised payloads produced before Phase 1.
52
+ */
53
+ findings?: Finding[];
27
54
  resource: armResources.GenericResource;
28
55
  };
29
56
  /**
@@ -39,4 +66,10 @@ export type AzureResourceReport = {
39
66
  suspectedUnused: boolean;
40
67
  type: string;
41
68
  };
69
+ /**
70
+ * Finding sources that are valid for Azure analysis.
71
+ * Narrowed from `FindingSource` to exclude "aws", which is not a valid
72
+ * filter for Azure runs and would silently produce an empty report.
73
+ */
74
+ export type AzureSource = "advisor" | "custom";
42
75
  //# sourceMappingURL=types.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/azure/types.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,KAAK,KAAK,YAAY,MAAM,sBAAsB,CAAC;AAE1D,OAAO,KAAK,EACV,cAAc,EACd,UAAU,EACV,QAAQ,EACR,UAAU,EACX,MAAM,aAAa,CAAC;AAErB;;GAEG;AACH,MAAM,MAAM,WAAW,GAAG,UAAU,GAAG;IACrC;;;OAGG;IACH,UAAU,CAAC,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACjC,eAAe,EAAE,MAAM,EAAE,CAAC;IAC1B;;OAEG;IACH,UAAU,CAAC,EAAE,UAAU,CAAC;IACxB,OAAO,CAAC,EAAE,OAAO,CAAC;CACnB,CAAC;AAEF;;GAEG;AACH,MAAM,MAAM,2BAA2B,GAAG;IACxC,QAAQ,EAAE,cAAc,CAAC;IACzB,QAAQ,EAAE,YAAY,CAAC,eAAe,CAAC;CACxC,CAAC;AAEF;;GAEG;AACH,MAAM,MAAM,mBAAmB,GAAG;IAChC,QAAQ,EAAE,QAAQ,CAAC;IACnB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;IACf,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,cAAc,EAAE,MAAM,CAAC;IACvB,eAAe,EAAE,OAAO,CAAC;IACzB,IAAI,EAAE,MAAM,CAAC;CACd,CAAC"}
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/azure/types.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,KAAK,KAAK,YAAY,MAAM,sBAAsB,CAAC;AAE1D,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,eAAe,CAAC;AAC7C,OAAO,KAAK,EACV,cAAc,EACd,UAAU,EACV,QAAQ,EACR,UAAU,EACX,MAAM,aAAa,CAAC;AAErB;;GAEG;AACH,MAAM,MAAM,WAAW,GAAG,UAAU,GAAG;IACrC;;;;OAIG;IACH,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB;;;OAGG;IACH,UAAU,CAAC,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACjC;;;;;;OAMG;IACH,OAAO,CAAC,EAAE,WAAW,EAAE,CAAC;IACxB,eAAe,EAAE,MAAM,EAAE,CAAC;IAC1B;;OAEG;IACH,UAAU,CAAC,EAAE,UAAU,CAAC;IACxB,OAAO,CAAC,EAAE,OAAO,CAAC;CACnB,CAAC;AAEF;;;;;;;;GAQG;AACH,MAAM,MAAM,2BAA2B,GAAG;IACxC,QAAQ,EAAE,cAAc,CAAC;IACzB;;;;OAIG;IACH,QAAQ,CAAC,EAAE,OAAO,EAAE,CAAC;IACrB,QAAQ,EAAE,YAAY,CAAC,eAAe,CAAC;CACxC,CAAC;AAEF;;GAEG;AACH,MAAM,MAAM,mBAAmB,GAAG;IAChC,QAAQ,EAAE,QAAQ,CAAC;IACnB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;IACf,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,cAAc,EAAE,MAAM,CAAC;IACvB,eAAe,EAAE,OAAO,CAAC;IACzB,IAAI,EAAE,MAAM,CAAC;CACd,CAAC;AAEF;;;;GAIG;AACH,MAAM,MAAM,WAAW,GAAG,SAAS,GAAG,QAAQ,CAAC"}
@@ -4,6 +4,17 @@
4
4
  import type { MonitorClient } from "@azure/arm-monitor";
5
5
  import type * as armResources from "@azure/arm-resources";
6
6
  import type { AnalysisResult } from "../types.js";
7
+ /** Per-run in-memory cache for Azure Monitor metric responses. */
8
+ export type MetricsCache = Map<string, Promise<null | number>>;
9
+ /**
10
+ * Minimal interface required by `getMetric` — only the `metrics.list` shape.
11
+ * Using a structural type instead of the full `MonitorClient` keeps tests
12
+ * strongly typed without unsafe casts and lets non-Azure callers supply a
13
+ * compatible mock.
14
+ */
15
+ export type MonitorClientLike = {
16
+ metrics: Pick<MonitorClient["metrics"], "list">;
17
+ };
7
18
  type MetricDataPoint = {
8
19
  average?: number;
9
20
  count?: number;
@@ -28,16 +39,30 @@ export declare function aggregateDataPoints(dataPoints: MetricDataPoint[], aggre
28
39
  */
29
40
  export declare function extractAggregatedValue(metricData: MetricDataPoint, aggregation: string): null | number;
30
41
  /**
31
- * Fetches a specific metric for a resource from Azure Monitor.
42
+ * @internal exposed for tests only.
43
+ */
44
+ export declare function _metricsCacheSize(): number;
45
+ /**
46
+ * Fetches a specific metric for a resource from Azure Monitor, with an
47
+ * in-memory cache to deduplicate concurrent and repeated lookups within
48
+ * the same run.
32
49
  *
33
- * @param monitorClient - The Azure Monitor client instance
50
+ * Concurrent callers for the same `(resourceId, metricName, aggregation,
51
+ * timespanDays)` tuple share the same underlying request.
52
+ *
53
+ * Pass an explicit `cache` (created per run in the orchestrator) to keep
54
+ * concurrent analysis runs isolated from each other. When omitted, the
55
+ * module-scoped fallback cache is used — safe for sequential runs.
56
+ *
57
+ * @param monitorClient - Azure Monitor client (or compatible mock)
34
58
  * @param resourceId - The Azure resource ID
35
59
  * @param metricName - The name of the metric to fetch (e.g., "Percentage CPU")
36
60
  * @param aggregation - The aggregation type (e.g., "Average", "Total")
37
61
  * @param timespanDays - Number of days to look back for metrics
62
+ * @param cache - Optional run-scoped cache; falls back to the module-scoped one
38
63
  * @returns The metric value or null if unavailable
39
64
  */
40
- export declare function getMetric(monitorClient: MonitorClient, resourceId: string, metricName: string, aggregation: string, timespanDays: number): Promise<null | number>;
65
+ export declare function getMetric(monitorClient: MonitorClientLike, resourceId: string, metricName: string, aggregation: string, timespanDays: number, cache?: MetricsCache): Promise<null | number>;
41
66
  /**
42
67
  * Returns true if the resource matches ALL specified tag key-value pairs.
43
68
  * If filterTags is empty or undefined, always returns true (no filtering).
@@ -46,6 +71,13 @@ export declare function getMetric(monitorClient: MonitorClient, resourceId: stri
46
71
  * @param filterTags - Map of required tag key→value pairs
47
72
  */
48
73
  export declare function matchesTags(resource: armResources.GenericResource, filterTags: Map<string, string> | undefined): boolean;
74
+ /**
75
+ * Clears the module-scoped fallback metrics cache.
76
+ *
77
+ * Only needed when `getMetric` is called without an explicit `cache`
78
+ * argument. Prefer passing a run-scoped `MetricsCache` instead.
79
+ */
80
+ export declare function resetMetricsCache(): void;
49
81
  /**
50
82
  * Logs a verbose message, optionally with an object.
51
83
  *
@@ -1 +1 @@
1
- {"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../../src/azure/utils.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;AACxD,OAAO,KAAK,KAAK,YAAY,MAAM,sBAAsB,CAAC;AAI1D,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAElD,KAAK,eAAe,GAAG;IACrB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB,CAAC;AAEF;;;;;;GAMG;AACH,wBAAgB,mBAAmB,CACjC,UAAU,EAAE,eAAe,EAAE,EAC7B,WAAW,EAAE,MAAM,GAClB,IAAI,GAAG,MAAM,CAiCf;AAED;;;;;;GAMG;AACH,wBAAgB,sBAAsB,CACpC,UAAU,EAAE,eAAe,EAC3B,WAAW,EAAE,MAAM,GAClB,IAAI,GAAG,MAAM,CA6Bf;AAED;;;;;;;;;GASG;AACH,wBAAsB,SAAS,CAC7B,aAAa,EAAE,aAAa,EAC5B,UAAU,EAAE,MAAM,EAClB,UAAU,EAAE,MAAM,EAClB,WAAW,EAAE,MAAM,EACnB,YAAY,EAAE,MAAM,GACnB,OAAO,CAAC,IAAI,GAAG,MAAM,CAAC,CAsCxB;AAED;;;;;;GAMG;AACH,wBAAgB,WAAW,CACzB,QAAQ,EAAE,YAAY,CAAC,eAAe,EACtC,UAAU,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,SAAS,GAC1C,OAAO,CAQT;AAED;;;;;;GAMG;AACH,wBAAgB,UAAU,CACxB,OAAO,EAAE,OAAO,EAChB,OAAO,EAAE,MAAM,EACf,MAAM,CAAC,EAAE,OAAO,QAUjB;AAED;;;;;GAKG;AACH,wBAAgB,wBAAwB,CACtC,OAAO,EAAE,OAAO,EAChB,MAAM,EAAE,cAAc,QAYvB;AAED;;;;;;GAMG;AACH,wBAAgB,uBAAuB,CACrC,OAAO,EAAE,OAAO,EAChB,YAAY,EAAE,MAAM,EACpB,YAAY,EAAE,MAAM,QASrB"}
1
+ {"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../../src/azure/utils.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;AACxD,OAAO,KAAK,KAAK,YAAY,MAAM,sBAAsB,CAAC;AAI1D,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAElD,kEAAkE;AAClE,MAAM,MAAM,YAAY,GAAG,GAAG,CAAC,MAAM,EAAE,OAAO,CAAC,IAAI,GAAG,MAAM,CAAC,CAAC,CAAC;AAE/D;;;;;GAKG;AACH,MAAM,MAAM,iBAAiB,GAAG;IAC9B,OAAO,EAAE,IAAI,CAAC,aAAa,CAAC,SAAS,CAAC,EAAE,MAAM,CAAC,CAAC;CACjD,CAAC;AAEF,KAAK,eAAe,GAAG;IACrB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB,CAAC;AAEF;;;;;;GAMG;AACH,wBAAgB,mBAAmB,CACjC,UAAU,EAAE,eAAe,EAAE,EAC7B,WAAW,EAAE,MAAM,GAClB,IAAI,GAAG,MAAM,CAiCf;AAED;;;;;;GAMG;AACH,wBAAgB,sBAAsB,CACpC,UAAU,EAAE,eAAe,EAC3B,WAAW,EAAE,MAAM,GAClB,IAAI,GAAG,MAAM,CA6Bf;AASD;;GAEG;AACH,wBAAgB,iBAAiB,IAAI,MAAM,CAE1C;AAED;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wBAAsB,SAAS,CAC7B,aAAa,EAAE,iBAAiB,EAChC,UAAU,EAAE,MAAM,EAClB,UAAU,EAAE,MAAM,EAClB,WAAW,EAAE,MAAM,EACnB,YAAY,EAAE,MAAM,EACpB,KAAK,GAAE,YAA2B,GACjC,OAAO,CAAC,IAAI,GAAG,MAAM,CAAC,CAexB;AAED;;;;;;GAMG;AACH,wBAAgB,WAAW,CACzB,QAAQ,EAAE,YAAY,CAAC,eAAe,EACtC,UAAU,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,SAAS,GAC1C,OAAO,CAQT;AAED;;;;;GAKG;AACH,wBAAgB,iBAAiB,IAAI,IAAI,CAExC;AAED;;;;;;GAMG;AACH,wBAAgB,UAAU,CACxB,OAAO,EAAE,OAAO,EAChB,OAAO,EAAE,MAAM,EACf,MAAM,CAAC,EAAE,OAAO,QAUjB;AAED;;;;;GAKG;AACH,wBAAgB,wBAAwB,CACtC,OAAO,EAAE,OAAO,EAChB,MAAM,EAAE,cAAc,QAYvB;AAED;;;;;;GAMG;AACH,wBAAgB,uBAAuB,CACrC,OAAO,EAAE,OAAO,EAChB,YAAY,EAAE,MAAM,EACpB,YAAY,EAAE,MAAM,QASrB"}
@@ -66,42 +66,46 @@ export function extractAggregatedValue(metricData, aggregation) {
66
66
  return null;
67
67
  }
68
68
  /**
69
- * Fetches a specific metric for a resource from Azure Monitor.
69
+ * Module-scoped fallback cache. Used when callers of `getMetric` do not
70
+ * supply a run-scoped cache. Prefer passing an explicit `MetricsCache`
71
+ * instance through `AnalyzerContext` so concurrent runs stay isolated.
72
+ */
73
+ const metricsCache = new Map();
74
+ /**
75
+ * @internal — exposed for tests only.
76
+ */
77
+ export function _metricsCacheSize() {
78
+ return metricsCache.size;
79
+ }
80
+ /**
81
+ * Fetches a specific metric for a resource from Azure Monitor, with an
82
+ * in-memory cache to deduplicate concurrent and repeated lookups within
83
+ * the same run.
84
+ *
85
+ * Concurrent callers for the same `(resourceId, metricName, aggregation,
86
+ * timespanDays)` tuple share the same underlying request.
70
87
  *
71
- * @param monitorClient - The Azure Monitor client instance
88
+ * Pass an explicit `cache` (created per run in the orchestrator) to keep
89
+ * concurrent analysis runs isolated from each other. When omitted, the
90
+ * module-scoped fallback cache is used — safe for sequential runs.
91
+ *
92
+ * @param monitorClient - Azure Monitor client (or compatible mock)
72
93
  * @param resourceId - The Azure resource ID
73
94
  * @param metricName - The name of the metric to fetch (e.g., "Percentage CPU")
74
95
  * @param aggregation - The aggregation type (e.g., "Average", "Total")
75
96
  * @param timespanDays - Number of days to look back for metrics
97
+ * @param cache - Optional run-scoped cache; falls back to the module-scoped one
76
98
  * @returns The metric value or null if unavailable
77
99
  */
78
- export async function getMetric(monitorClient, resourceId, metricName, aggregation, timespanDays) {
79
- try {
80
- const timespan = `P${timespanDays}D`;
81
- const result = await monitorClient.metrics.list(resourceId, {
82
- aggregation,
83
- metricnames: metricName,
84
- timespan,
85
- });
86
- if (result.value.length === 0) {
87
- return null;
88
- }
89
- const metric = result.value[0];
90
- if (!metric.timeseries || metric.timeseries.length === 0) {
91
- return null;
92
- }
93
- const timeserie = metric.timeseries[0];
94
- if (!timeserie.data || timeserie.data.length === 0) {
95
- return null;
96
- }
97
- const aggregatedValue = aggregateDataPoints(timeserie.data, aggregation);
98
- return aggregatedValue;
99
- }
100
- catch (error) {
101
- const logger = getLogger(["savemoney", "azure", "metrics"]);
102
- logger.error(`Failed to fetch metric ${metricName} for resource ${resourceId}: ${error instanceof Error ? error.message : String(error)}`);
103
- return null;
104
- }
100
+ export async function getMetric(monitorClient, resourceId, metricName, aggregation, timespanDays, cache = metricsCache) {
101
+ const key = `${resourceId}|${metricName}|${aggregation}|${timespanDays}`;
102
+ const cached = cache.get(key);
103
+ if (cached !== undefined) {
104
+ return cached;
105
+ }
106
+ const promise = fetchMetric(monitorClient, resourceId, metricName, aggregation, timespanDays);
107
+ cache.set(key, promise);
108
+ return promise;
105
109
  }
106
110
  /**
107
111
  * Returns true if the resource matches ALL specified tag key-value pairs.
@@ -117,6 +121,15 @@ export function matchesTags(resource, filterTags) {
117
121
  const resourceTags = resource.tags ?? {};
118
122
  return [...filterTags.entries()].every(([key, value]) => resourceTags[key] === value);
119
123
  }
124
+ /**
125
+ * Clears the module-scoped fallback metrics cache.
126
+ *
127
+ * Only needed when `getMetric` is called without an explicit `cache`
128
+ * argument. Prefer passing a run-scoped `MetricsCache` instead.
129
+ */
130
+ export function resetMetricsCache() {
131
+ metricsCache.clear();
132
+ }
120
133
  /**
121
134
  * Logs a verbose message, optionally with an object.
122
135
  *
@@ -167,4 +180,32 @@ export function verboseLogResourceStart(verbose, resourceName, resourceType) {
167
180
  logger.debug("=".repeat(80));
168
181
  }
169
182
  }
183
+ async function fetchMetric(monitorClient, resourceId, metricName, aggregation, timespanDays) {
184
+ try {
185
+ const timespan = `P${timespanDays}D`;
186
+ const result = await monitorClient.metrics.list(resourceId, {
187
+ aggregation,
188
+ metricnames: metricName,
189
+ timespan,
190
+ });
191
+ if (result.value.length === 0) {
192
+ return null;
193
+ }
194
+ const metric = result.value[0];
195
+ if (!metric.timeseries || metric.timeseries.length === 0) {
196
+ return null;
197
+ }
198
+ const timeserie = metric.timeseries[0];
199
+ if (!timeserie.data || timeserie.data.length === 0) {
200
+ return null;
201
+ }
202
+ const aggregatedValue = aggregateDataPoints(timeserie.data, aggregation);
203
+ return aggregatedValue;
204
+ }
205
+ catch (error) {
206
+ const logger = getLogger(["savemoney", "azure", "metrics"]);
207
+ logger.error(`Failed to fetch metric ${metricName} for resource ${resourceId}: ${error instanceof Error ? error.message : String(error)}`);
208
+ return null;
209
+ }
210
+ }
170
211
  //# sourceMappingURL=utils.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"utils.js","sourceRoot":"","sources":["../../src/azure/utils.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"utils.js","sourceRoot":"","sources":["../../src/azure/utils.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,114 @@
1
+ /**
2
+ * Unified Finding model — the single representation of a cost-related
3
+ * observation, regardless of its source (custom analyzers, Azure Advisor,
4
+ * future AWS Trusted Advisor, …).
5
+ *
6
+ * Introduced in Phase 0 of the savemoney evolution roadmap. Existing
7
+ * `AnalysisResult`-based analyzers keep working untouched: an adapter
8
+ * (`findingsFromAnalysisResult`) splits the concatenated `reason` string
9
+ * into one `Finding` per sentence, so downstream consumers can already
10
+ * reason in terms of `Finding[]`.
11
+ */
12
+ import type { CostRisk } from "./types.js";
13
+ /**
14
+ * A single, atomic observation about a resource.
15
+ *
16
+ * One resource can produce multiple findings (e.g. "no tags" + "low CPU").
17
+ * Findings are designed to be deduplicated by `(resourceId, source, code)`.
18
+ */
19
+ export type Finding = {
20
+ /**
21
+ * Cloud category. Today every custom finding is "cost"; Advisor may
22
+ * surface other categories that we currently ignore.
23
+ */
24
+ category: FindingCategory;
25
+ /**
26
+ * Stable machine-readable identifier for the kind of finding, e.g.
27
+ * `vm.deallocated`, `disk.unattached`, `advisor.right-size-vm`.
28
+ * Used for deduplication and grouping. Free-form for now to keep the
29
+ * adapter from existing analyzers cheap; can be tightened later.
30
+ */
31
+ code: string;
32
+ /**
33
+ * Estimated monthly cost that could be recovered by acting on this
34
+ * finding. Populated by Advisor and (in later phases) by the Retail
35
+ * Prices integration. Absent when the analyzer cannot estimate it.
36
+ */
37
+ estimatedMonthlySavings?: Money;
38
+ /**
39
+ * Free-text, human-readable description. Backward compatible with the
40
+ * legacy `AnalysisResult.reason` field.
41
+ */
42
+ reason: string;
43
+ /**
44
+ * Optional, machine-friendly hint about how to remediate. For Advisor
45
+ * this typically maps to `shortDescription.solution`.
46
+ */
47
+ recommendedAction?: string;
48
+ /**
49
+ * Fully qualified Azure / cloud resource ID this finding refers to.
50
+ */
51
+ resourceId: string;
52
+ /**
53
+ * Cost risk classification. Maps Advisor's `impact` (High/Medium/Low)
54
+ * 1:1 onto the savemoney scale.
55
+ */
56
+ severity: CostRisk;
57
+ /**
58
+ * Provenance of the finding.
59
+ */
60
+ source: FindingSource;
61
+ };
62
+ /**
63
+ * Cloud category a finding belongs to. For now we focus on cost, but the
64
+ * model is open to future Advisor categories.
65
+ */
66
+ export type FindingCategory = "cost" | "operationalExcellence" | "performance" | "reliability" | "security";
67
+ /**
68
+ * Where the finding originated from.
69
+ *
70
+ * - `custom` → emitted by a savemoney analyzer plugin
71
+ * - `advisor` → fetched from Azure Advisor recommendations
72
+ * - `aws` → reserved for future AWS Trusted Advisor / Compute Optimizer
73
+ */
74
+ export type FindingSource = "advisor" | "aws" | "custom";
75
+ /**
76
+ * Monetary value associated with a finding, when known.
77
+ * Amounts use ISO 4217 currency codes (e.g. "EUR", "USD").
78
+ */
79
+ export type Money = {
80
+ amount: number;
81
+ currency: string;
82
+ };
83
+ /**
84
+ * Aggregate view: one resource with the list of findings emitted for it.
85
+ *
86
+ * This is the type future report generators should consume. The current
87
+ * report layer still works on `AzureDetailedResourceReport`; a helper
88
+ * (`legacyReportFromResourceReport`) bridges the two until the report
89
+ * layer is migrated.
90
+ */
91
+ export type ResourceReport<TResource = unknown> = {
92
+ findings: Finding[];
93
+ resource: TResource;
94
+ };
95
+ /**
96
+ * Adapter: converts a legacy `AnalysisResult` (single concatenated
97
+ * reason) into a list of `Finding`s, one per sentence.
98
+ *
99
+ * @param resourceId Fully qualified resource ID
100
+ * @param severity Cost risk classification produced by the analyzer
101
+ * @param reason Concatenated reason string (sentences joined by ". ")
102
+ * @param source Provenance (default: "custom")
103
+ * @param code Optional stable identifier for the finding kind
104
+ * (e.g. `"vm.deallocated"`). Defaults to
105
+ * `"custom.unknown"` when omitted.
106
+ */
107
+ export declare function findingsFromAnalysisResult(args: {
108
+ code?: string;
109
+ reason: string;
110
+ resourceId: string;
111
+ severity: CostRisk;
112
+ source?: FindingSource;
113
+ }): Finding[];
114
+ //# sourceMappingURL=finding.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"finding.d.ts","sourceRoot":"","sources":["../src/finding.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AAE3C;;;;;GAKG;AACH,MAAM,MAAM,OAAO,GAAG;IACpB;;;OAGG;IACH,QAAQ,EAAE,eAAe,CAAC;IAC1B;;;;;OAKG;IACH,IAAI,EAAE,MAAM,CAAC;IACb;;;;OAIG;IACH,uBAAuB,CAAC,EAAE,KAAK,CAAC;IAChC;;;OAGG;IACH,MAAM,EAAE,MAAM,CAAC;IACf;;;OAGG;IACH,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B;;OAEG;IACH,UAAU,EAAE,MAAM,CAAC;IACnB;;;OAGG;IACH,QAAQ,EAAE,QAAQ,CAAC;IACnB;;OAEG;IACH,MAAM,EAAE,aAAa,CAAC;CACvB,CAAC;AAEF;;;GAGG;AACH,MAAM,MAAM,eAAe,GACvB,MAAM,GACN,uBAAuB,GACvB,aAAa,GACb,aAAa,GACb,UAAU,CAAC;AAEf;;;;;;GAMG;AACH,MAAM,MAAM,aAAa,GAAG,SAAS,GAAG,KAAK,GAAG,QAAQ,CAAC;AAEzD;;;GAGG;AACH,MAAM,MAAM,KAAK,GAAG;IAClB,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,MAAM,CAAC;CAClB,CAAC;AAEF;;;;;;;GAOG;AACH,MAAM,MAAM,cAAc,CAAC,SAAS,GAAG,OAAO,IAAI;IAChD,QAAQ,EAAE,OAAO,EAAE,CAAC;IACpB,QAAQ,EAAE,SAAS,CAAC;CACrB,CAAC;AAEF;;;;;;;;;;;GAWG;AACH,wBAAgB,0BAA0B,CAAC,IAAI,EAAE;IAC/C,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;IACf,UAAU,EAAE,MAAM,CAAC;IACnB,QAAQ,EAAE,QAAQ,CAAC;IACnB,MAAM,CAAC,EAAE,aAAa,CAAC;CACxB,GAAG,OAAO,EAAE,CAcZ"}
@@ -0,0 +1,51 @@
1
+ /**
2
+ * Unified Finding model — the single representation of a cost-related
3
+ * observation, regardless of its source (custom analyzers, Azure Advisor,
4
+ * future AWS Trusted Advisor, …).
5
+ *
6
+ * Introduced in Phase 0 of the savemoney evolution roadmap. Existing
7
+ * `AnalysisResult`-based analyzers keep working untouched: an adapter
8
+ * (`findingsFromAnalysisResult`) splits the concatenated `reason` string
9
+ * into one `Finding` per sentence, so downstream consumers can already
10
+ * reason in terms of `Finding[]`.
11
+ */
12
+ /**
13
+ * Adapter: converts a legacy `AnalysisResult` (single concatenated
14
+ * reason) into a list of `Finding`s, one per sentence.
15
+ *
16
+ * @param resourceId Fully qualified resource ID
17
+ * @param severity Cost risk classification produced by the analyzer
18
+ * @param reason Concatenated reason string (sentences joined by ". ")
19
+ * @param source Provenance (default: "custom")
20
+ * @param code Optional stable identifier for the finding kind
21
+ * (e.g. `"vm.deallocated"`). Defaults to
22
+ * `"custom.unknown"` when omitted.
23
+ */
24
+ export function findingsFromAnalysisResult(args) {
25
+ const { code, reason, resourceId, severity, source = "custom" } = args;
26
+ const sentences = splitReasonIntoSentences(reason);
27
+ if (sentences.length === 0) {
28
+ return [];
29
+ }
30
+ return sentences.map((sentence) => ({
31
+ category: "cost",
32
+ code: code ?? "custom.unknown",
33
+ reason: sentence,
34
+ resourceId,
35
+ severity,
36
+ source,
37
+ }));
38
+ }
39
+ /**
40
+ * Splits a concatenated reason string (sentences joined by ". ") into
41
+ * individual non-empty sentences. Mirrors the logic already used by the
42
+ * lint reporter so behaviour stays consistent.
43
+ */
44
+ function splitReasonIntoSentences(reason) {
45
+ return reason
46
+ .split(/\.\s+|\.$/)
47
+ .map((s) => s.trim())
48
+ .filter((s) => s.length > 0)
49
+ .map((s) => (s.endsWith(".") ? s : `${s}.`));
50
+ }
51
+ //# sourceMappingURL=finding.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"finding.js","sourceRoot":"","sources":["../src/finding.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAgGH;;;;;;;;;;;GAWG;AACH,MAAM,UAAU,0BAA0B,CAAC,IAM1C;IACC,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,UAAU,EAAE,QAAQ,EAAE,MAAM,GAAG,QAAQ,EAAE,GAAG,IAAI,CAAC;IACvE,MAAM,SAAS,GAAG,wBAAwB,CAAC,MAAM,CAAC,CAAC;IACnD,IAAI,SAAS,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QAC3B,OAAO,EAAE,CAAC;IACZ,CAAC;IACD,OAAO,SAAS,CAAC,GAAG,CAAC,CAAC,QAAQ,EAAE,EAAE,CAAC,CAAC;QAClC,QAAQ,EAAE,MAAe;QACzB,IAAI,EAAE,IAAI,IAAI,gBAAgB;QAC9B,MAAM,EAAE,QAAQ;QAChB,UAAU;QACV,QAAQ;QACR,MAAM;KACP,CAAC,CAAC,CAAC;AACN,CAAC;AAED;;;;GAIG;AACH,SAAS,wBAAwB,CAAC,MAAc;IAC9C,OAAO,MAAM;SACV,KAAK,CAAC,WAAW,CAAC;SAClB,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;SACpB,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,MAAM,GAAG,CAAC,CAAC;SAC3B,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,CAAC;AACjD,CAAC"}
package/dist/index.d.ts CHANGED
@@ -12,9 +12,12 @@
12
12
  *
13
13
  * This tool does NOT modify, tag, or delete any resources.
14
14
  */
15
- export type { AzureConfig } from "./azure/types.js";
15
+ export { type Analyzer, type AnalyzerContext, type AzureClients, createDefaultAnalyzers, } from "./azure/analyzers/index.js";
16
+ export type { AzureConfig, AzureSource } from "./azure/types.js";
16
17
  import * as azureModule from "./azure/index.js";
17
18
  export declare const azure: typeof azureModule;
19
+ export { type MetricsCache, type MonitorClientLike } from "./azure/utils.js";
20
+ export { type Finding, type FindingCategory, findingsFromAnalysisResult, type FindingSource, type Money, type ResourceReport, } from "./finding.js";
18
21
  export * from "./types.js";
19
22
  import type { AzureConfig } from "./azure/types.js";
20
23
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAGH,YAAY,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAGpD,OAAO,KAAK,WAAW,MAAM,kBAAkB,CAAC;AAChD,eAAO,MAAM,KAAK,oBAAc,CAAC;AAEjC,cAAc,YAAY,CAAC;AAE3B,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAIpD;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAsB,UAAU,CAAC,UAAU,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,WAAW,CAAC,CAE1E"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAEH,OAAO,EACL,KAAK,QAAQ,EACb,KAAK,eAAe,EACpB,KAAK,YAAY,EACjB,sBAAsB,GACvB,MAAM,4BAA4B,CAAC;AAGpC,YAAY,EAAE,WAAW,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAGjE,OAAO,KAAK,WAAW,MAAM,kBAAkB,CAAC;AAChD,eAAO,MAAM,KAAK,oBAAc,CAAC;AAEjC,OAAO,EAAE,KAAK,YAAY,EAAE,KAAK,iBAAiB,EAAE,MAAM,kBAAkB,CAAC;AAG7E,OAAO,EACL,KAAK,OAAO,EACZ,KAAK,eAAe,EACpB,0BAA0B,EAC1B,KAAK,aAAa,EAClB,KAAK,KAAK,EACV,KAAK,cAAc,GACpB,MAAM,cAAc,CAAC;AACtB,cAAc,YAAY,CAAC;AAE3B,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAIpD;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAsB,UAAU,CAAC,UAAU,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,WAAW,CAAC,CAE1E"}
package/dist/index.js CHANGED
@@ -12,9 +12,12 @@
12
12
  *
13
13
  * This tool does NOT modify, tag, or delete any resources.
14
14
  */
15
+ export { createDefaultAnalyzers, } from "./azure/analyzers/index.js";
15
16
  // Export Azure module
16
17
  import * as azureModule from "./azure/index.js";
17
18
  export const azure = azureModule;
19
+ // Phase 0: unified Finding model and analyzer plugin layer
20
+ export { findingsFromAnalysisResult, } from "./finding.js";
18
21
  export * from "./types.js";
19
22
  import { loadAzureConfig } from "./azure/config.js";
20
23
  /**
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAKH,sBAAsB;AACtB,OAAO,KAAK,WAAW,MAAM,kBAAkB,CAAC;AAChD,MAAM,CAAC,MAAM,KAAK,GAAG,WAAW,CAAC;AAEjC,cAAc,YAAY,CAAC;AAI3B,OAAO,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAC;AAEpD;;;;;;;;;;;;;;;;;GAiBG;AACH,MAAM,CAAC,KAAK,UAAU,UAAU,CAAC,UAAmB;IAClD,OAAO,eAAe,CAAC,UAAU,CAAC,CAAC;AACrC,CAAC"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAEH,OAAO,EAIL,sBAAsB,GACvB,MAAM,4BAA4B,CAAC;AAKpC,sBAAsB;AACtB,OAAO,KAAK,WAAW,MAAM,kBAAkB,CAAC;AAChD,MAAM,CAAC,MAAM,KAAK,GAAG,WAAW,CAAC;AAIjC,2DAA2D;AAC3D,OAAO,EAGL,0BAA0B,GAI3B,MAAM,cAAc,CAAC;AACtB,cAAc,YAAY,CAAC;AAI3B,OAAO,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAC;AAEpD;;;;;;;;;;;;;;;;;GAiBG;AACH,MAAM,CAAC,KAAK,UAAU,UAAU,CAAC,UAAmB;IAClD,OAAO,eAAe,CAAC,UAAU,CAAC,CAAC;AACrC,CAAC"}
package/dist/schema.d.ts CHANGED
@@ -86,7 +86,12 @@ export declare const ThresholdsSchema: z.ZodObject<{
86
86
  */
87
87
  export declare const ConfigSchema: z.ZodObject<{
88
88
  azure: z.ZodObject<{
89
+ concurrency: z.ZodOptional<z.ZodNumber>;
89
90
  preferredLocation: z.ZodDefault<z.ZodString>;
91
+ sources: z.ZodDefault<z.ZodArray<z.ZodEnum<{
92
+ custom: "custom";
93
+ advisor: "advisor";
94
+ }>>>;
90
95
  subscriptionIds: z.ZodArray<z.ZodString>;
91
96
  thresholds: z.ZodPipe<z.ZodOptional<z.ZodObject<{
92
97
  appService: z.ZodPipe<z.ZodOptional<z.ZodObject<{
@@ -1 +1 @@
1
- {"version":3,"file":"schema.d.ts","sourceRoot":"","sources":["../src/schema.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAEH,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AA4DxB,eAAO,MAAM,gBAAgB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;kBAqBlB,CAAC;AAoBZ;;;GAGG;AACH,eAAO,MAAM,YAAY;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;kBAAmD,CAAC;AAI7E,2DAA2D;AAC3D,MAAM,MAAM,MAAM,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,YAAY,CAAC,CAAC;AAElD,wDAAwD;AACxD,MAAM,MAAM,UAAU,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,gBAAgB,CAAC,CAAC"}
1
+ {"version":3,"file":"schema.d.ts","sourceRoot":"","sources":["../src/schema.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAEH,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AA4DxB,eAAO,MAAM,gBAAgB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;kBAqBlB,CAAC;AAkCZ;;;GAGG;AACH,eAAO,MAAM,YAAY;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;kBAAmD,CAAC;AAI7E,2DAA2D;AAC3D,MAAM,MAAM,MAAM,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,YAAY,CAAC,CAAC;AAElD,wDAAwD;AACxD,MAAM,MAAM,UAAU,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,gBAAgB,CAAC,CAAC"}
package/dist/schema.js CHANGED
@@ -81,7 +81,21 @@ export const ThresholdsSchema = z
81
81
  // ── top-level config schema ──────────────────────────────────────────────────
82
82
  const AzureSectionSchema = z
83
83
  .object({
84
+ /**
85
+ * Maximum number of resources analyzed in parallel within a single
86
+ * subscription. Defaults to 8 when not provided.
87
+ */
88
+ concurrency: z.number().int().positive().optional(),
84
89
  preferredLocation: z.string().default("italynorth"),
90
+ /**
91
+ * Which finding sources to include. Defaults to all known sources.
92
+ * Authors can narrow the run to e.g. `["advisor"]` to fetch only
93
+ * Azure Advisor recommendations, or `["custom"]` to skip Advisor.
94
+ */
95
+ sources: z
96
+ .array(z.enum(["advisor", "custom"]))
97
+ .nonempty()
98
+ .default(["advisor", "custom"]),
85
99
  subscriptionIds: z
86
100
  .array(z.string())
87
101
  .min(1, "Config file must contain at least one entry in 'azure.subscriptionIds'"),