@pagopa/dx-savemoney 0.1.6 → 0.2.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 (76) hide show
  1. package/README.md +104 -27
  2. package/dist/azure/analyzer.d.ts +4 -4
  3. package/dist/azure/analyzer.d.ts.map +1 -1
  4. package/dist/azure/analyzer.js +16 -10
  5. package/dist/azure/analyzer.js.map +1 -1
  6. package/dist/azure/config.d.ts +15 -3
  7. package/dist/azure/config.d.ts.map +1 -1
  8. package/dist/azure/config.js +32 -16
  9. package/dist/azure/config.js.map +1 -1
  10. package/dist/azure/report.d.ts +2 -2
  11. package/dist/azure/report.d.ts.map +1 -1
  12. package/dist/azure/report.js +68 -3
  13. package/dist/azure/report.js.map +1 -1
  14. package/dist/azure/resources/app-service.d.ts +2 -2
  15. package/dist/azure/resources/app-service.d.ts.map +1 -1
  16. package/dist/azure/resources/app-service.js +8 -5
  17. package/dist/azure/resources/app-service.js.map +1 -1
  18. package/dist/azure/resources/container-app.d.ts +2 -2
  19. package/dist/azure/resources/container-app.d.ts.map +1 -1
  20. package/dist/azure/resources/container-app.js +10 -8
  21. package/dist/azure/resources/container-app.js.map +1 -1
  22. package/dist/azure/resources/public-ip.d.ts +2 -2
  23. package/dist/azure/resources/public-ip.d.ts.map +1 -1
  24. package/dist/azure/resources/public-ip.js +3 -3
  25. package/dist/azure/resources/public-ip.js.map +1 -1
  26. package/dist/azure/resources/static-web-app.d.ts +2 -2
  27. package/dist/azure/resources/static-web-app.d.ts.map +1 -1
  28. package/dist/azure/resources/static-web-app.js +4 -5
  29. package/dist/azure/resources/static-web-app.js.map +1 -1
  30. package/dist/azure/resources/storage.d.ts +2 -2
  31. package/dist/azure/resources/storage.d.ts.map +1 -1
  32. package/dist/azure/resources/storage.js +4 -3
  33. package/dist/azure/resources/storage.js.map +1 -1
  34. package/dist/azure/resources/vm.d.ts +2 -2
  35. package/dist/azure/resources/vm.d.ts.map +1 -1
  36. package/dist/azure/resources/vm.js +4 -5
  37. package/dist/azure/resources/vm.js.map +1 -1
  38. package/dist/azure/types.d.ts +10 -2
  39. package/dist/azure/types.d.ts.map +1 -1
  40. package/dist/azure/utils.d.ts +9 -0
  41. package/dist/azure/utils.d.ts.map +1 -1
  42. package/dist/azure/utils.js +14 -0
  43. package/dist/azure/utils.js.map +1 -1
  44. package/dist/index.d.ts +16 -11
  45. package/dist/index.d.ts.map +1 -1
  46. package/dist/index.js +18 -54
  47. package/dist/index.js.map +1 -1
  48. package/dist/schema.d.ts +210 -0
  49. package/dist/schema.d.ts.map +1 -0
  50. package/dist/schema.js +97 -0
  51. package/dist/schema.js.map +1 -0
  52. package/dist/types.d.ts +35 -0
  53. package/dist/types.d.ts.map +1 -1
  54. package/dist/types.js +6 -0
  55. package/dist/types.js.map +1 -1
  56. package/package.json +8 -2
  57. package/src/azure/__tests__/config.test.ts +97 -0
  58. package/src/azure/__tests__/fixtures/full-override.yaml +25 -0
  59. package/src/azure/__tests__/fixtures/partial-override.yaml +10 -0
  60. package/src/azure/__tests__/report.test.ts +138 -0
  61. package/src/azure/__tests__/utils.test.ts +124 -0
  62. package/src/azure/analyzer.ts +24 -3
  63. package/src/azure/config.ts +33 -21
  64. package/src/azure/report.ts +81 -4
  65. package/src/azure/resources/__tests__/storage.test.ts +185 -0
  66. package/src/azure/resources/app-service.ts +13 -5
  67. package/src/azure/resources/container-app.ts +13 -4
  68. package/src/azure/resources/public-ip.ts +4 -3
  69. package/src/azure/resources/static-web-app.ts +5 -5
  70. package/src/azure/resources/storage.ts +7 -3
  71. package/src/azure/resources/vm.ts +5 -5
  72. package/src/azure/types.ts +15 -2
  73. package/src/azure/utils.ts +21 -0
  74. package/src/index.ts +19 -69
  75. package/src/schema.ts +134 -0
  76. package/src/types.ts +14 -0
package/dist/index.js CHANGED
@@ -12,66 +12,30 @@
12
12
  *
13
13
  * This tool does NOT modify, tag, or delete any resources.
14
14
  */
15
- export * from "./types.js";
16
15
  // Export Azure module
17
16
  import * as azureModule from "./azure/index.js";
18
17
  export const azure = azureModule;
19
- import { getLogger } from "@logtape/logtape";
20
- // Utility imports for loadConfig and prompt functions
21
- import * as fs from "fs";
22
- import * as readline from "readline";
18
+ export * from "./types.js";
19
+ import { loadAzureConfig } from "./azure/config.js";
23
20
  /**
24
- * Loads configuration from file, environment variables, or interactive prompts.
21
+ * Loads configuration from a YAML file, environment variables, or interactive prompts.
25
22
  *
26
- * @param configPath - Optional path to JSON configuration file
27
- * @returns Configuration object with subscription IDs and settings
28
- */
29
- export async function loadConfig(configPath) {
30
- const logger = getLogger(["savemoney", "config"]);
31
- if (configPath && fs.existsSync(configPath)) {
32
- try {
33
- const configContent = fs.readFileSync(configPath, "utf-8");
34
- const config = JSON.parse(configContent);
35
- // Validate required fields
36
- if (!config.tenantId || !config.subscriptionIds) {
37
- throw new Error("Config file must contain 'tenantId' and 'subscriptionIds'");
38
- }
39
- return {
40
- ...config,
41
- preferredLocation: config.preferredLocation || "italynorth",
42
- timespanDays: config.timespanDays || 30,
43
- };
44
- }
45
- catch (error) {
46
- throw new Error(`Failed to load config file: ${error instanceof Error ? error.message : error}`);
47
- }
48
- }
49
- logger.info("Configuration file not found. Checking environment variables...");
50
- const tenantId = process.env.ARM_TENANT_ID || (await prompt("Enter Tenant ID: "));
51
- const subscriptionIds = process.env.ARM_SUBSCRIPTION_ID
52
- ? process.env.ARM_SUBSCRIPTION_ID.split(",")
53
- : (await prompt("Enter Subscription IDs (comma-separated): ")).split(",");
54
- return {
55
- preferredLocation: "italynorth",
56
- subscriptionIds,
57
- tenantId,
58
- timespanDays: 30,
59
- };
60
- }
61
- /**
62
- * Prompts user for input via stdin.
23
+ * The YAML file should have an `azure` top-level key:
24
+ * ```yaml
25
+ * azure:
26
+ * subscriptionIds:
27
+ * - xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
28
+ * preferredLocation: italynorth
29
+ * timespanDays: 30
30
+ * thresholds:
31
+ * vm:
32
+ * cpuPercent: 5
33
+ * ```
63
34
  *
64
- * @param question - The question to display to the user
65
- * @returns User's input as a string
35
+ * @param configPath - Optional path to a YAML configuration file
36
+ * @returns Configuration object with subscription IDs, settings and thresholds
66
37
  */
67
- export async function prompt(question) {
68
- const rl = readline.createInterface({
69
- input: process.stdin,
70
- output: process.stdout,
71
- });
72
- return new Promise((resolve) => rl.question(question, (answer) => {
73
- rl.close();
74
- resolve(answer);
75
- }));
38
+ export async function loadConfig(configPath) {
39
+ return loadAzureConfig(configPath);
76
40
  }
77
41
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAIH,cAAc,YAAY,CAAC;AAE3B,sBAAsB;AACtB,OAAO,KAAK,WAAW,MAAM,kBAAkB,CAAC;AAChD,MAAM,CAAC,MAAM,KAAK,GAAG,WAAW,CAAC;AAEjC,OAAO,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAC;AAC7C,sDAAsD;AACtD,OAAO,KAAK,EAAE,MAAM,IAAI,CAAC;AACzB,OAAO,KAAK,QAAQ,MAAM,UAAU,CAAC;AAIrC;;;;;GAKG;AACH,MAAM,CAAC,KAAK,UAAU,UAAU,CAAC,UAAmB;IAClD,MAAM,MAAM,GAAG,SAAS,CAAC,CAAC,WAAW,EAAE,QAAQ,CAAC,CAAC,CAAC;IAElD,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,CAAC,CAAC;YAEzC,2BAA2B;YAC3B,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,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,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"}
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"}
@@ -0,0 +1,210 @@
1
+ /**
2
+ * Zod schemas for the SaveMoney YAML configuration file.
3
+ *
4
+ * The entire config is represented by a single `ConfigSchema` composed of
5
+ * sub-schemas. All types used at runtime are derived from these schemas via
6
+ * `z.infer<>` so the source of truth is always the schema.
7
+ *
8
+ * YAML structure:
9
+ * ```yaml
10
+ * azure:
11
+ * subscriptionIds:
12
+ * - xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
13
+ * preferredLocation: italynorth # optional
14
+ * timespanDays: 30 # optional
15
+ * thresholds: # optional – omit to keep built-in defaults
16
+ * vm:
17
+ * cpuPercent: 5
18
+ * ```
19
+ */
20
+ import { z } from "zod";
21
+ export declare const ThresholdsSchema: z.ZodObject<{
22
+ appService: z.ZodPipe<z.ZodOptional<z.ZodObject<{
23
+ cpuPercent: z.ZodDefault<z.ZodNumber>;
24
+ memoryPercent: z.ZodDefault<z.ZodNumber>;
25
+ premiumCpuPercent: z.ZodDefault<z.ZodNumber>;
26
+ }, z.core.$strict>>, z.ZodTransform<{
27
+ cpuPercent: number;
28
+ memoryPercent: number;
29
+ premiumCpuPercent: number;
30
+ }, {
31
+ cpuPercent: number;
32
+ memoryPercent: number;
33
+ premiumCpuPercent: number;
34
+ } | undefined>>;
35
+ containerApp: z.ZodPipe<z.ZodOptional<z.ZodObject<{
36
+ cpuNanoCores: z.ZodDefault<z.ZodNumber>;
37
+ memoryBytes: z.ZodDefault<z.ZodNumber>;
38
+ networkBytes: z.ZodDefault<z.ZodNumber>;
39
+ }, z.core.$strict>>, z.ZodTransform<{
40
+ cpuNanoCores: number;
41
+ memoryBytes: number;
42
+ networkBytes: number;
43
+ }, {
44
+ cpuNanoCores: number;
45
+ memoryBytes: number;
46
+ networkBytes: number;
47
+ } | undefined>>;
48
+ publicIp: z.ZodPipe<z.ZodOptional<z.ZodObject<{
49
+ bytesInDDoS: z.ZodDefault<z.ZodNumber>;
50
+ }, z.core.$strict>>, z.ZodTransform<{
51
+ bytesInDDoS: number;
52
+ }, {
53
+ bytesInDDoS: number;
54
+ } | undefined>>;
55
+ staticSite: z.ZodPipe<z.ZodOptional<z.ZodObject<{
56
+ bytesSent: z.ZodDefault<z.ZodNumber>;
57
+ siteHits: z.ZodDefault<z.ZodNumber>;
58
+ }, z.core.$strict>>, z.ZodTransform<{
59
+ bytesSent: number;
60
+ siteHits: number;
61
+ }, {
62
+ bytesSent: number;
63
+ siteHits: number;
64
+ } | undefined>>;
65
+ storage: z.ZodPipe<z.ZodOptional<z.ZodObject<{
66
+ transactionsPerDay: z.ZodDefault<z.ZodNumber>;
67
+ }, z.core.$strict>>, z.ZodTransform<{
68
+ transactionsPerDay: number;
69
+ }, {
70
+ transactionsPerDay: number;
71
+ } | undefined>>;
72
+ vm: z.ZodPipe<z.ZodOptional<z.ZodObject<{
73
+ cpuPercent: z.ZodDefault<z.ZodNumber>;
74
+ networkInBytesPerDay: z.ZodDefault<z.ZodNumber>;
75
+ }, z.core.$strict>>, z.ZodTransform<{
76
+ cpuPercent: number;
77
+ networkInBytesPerDay: number;
78
+ }, {
79
+ cpuPercent: number;
80
+ networkInBytesPerDay: number;
81
+ } | undefined>>;
82
+ }, z.core.$strict>;
83
+ /**
84
+ * Single Zod schema representing the entire YAML configuration file.
85
+ * Use `ConfigSchema.parse(rawYaml)` to validate and apply defaults.
86
+ */
87
+ export declare const ConfigSchema: z.ZodObject<{
88
+ azure: z.ZodObject<{
89
+ preferredLocation: z.ZodDefault<z.ZodString>;
90
+ subscriptionIds: z.ZodArray<z.ZodString>;
91
+ thresholds: z.ZodPipe<z.ZodOptional<z.ZodObject<{
92
+ appService: z.ZodPipe<z.ZodOptional<z.ZodObject<{
93
+ cpuPercent: z.ZodDefault<z.ZodNumber>;
94
+ memoryPercent: z.ZodDefault<z.ZodNumber>;
95
+ premiumCpuPercent: z.ZodDefault<z.ZodNumber>;
96
+ }, z.core.$strict>>, z.ZodTransform<{
97
+ cpuPercent: number;
98
+ memoryPercent: number;
99
+ premiumCpuPercent: number;
100
+ }, {
101
+ cpuPercent: number;
102
+ memoryPercent: number;
103
+ premiumCpuPercent: number;
104
+ } | undefined>>;
105
+ containerApp: z.ZodPipe<z.ZodOptional<z.ZodObject<{
106
+ cpuNanoCores: z.ZodDefault<z.ZodNumber>;
107
+ memoryBytes: z.ZodDefault<z.ZodNumber>;
108
+ networkBytes: z.ZodDefault<z.ZodNumber>;
109
+ }, z.core.$strict>>, z.ZodTransform<{
110
+ cpuNanoCores: number;
111
+ memoryBytes: number;
112
+ networkBytes: number;
113
+ }, {
114
+ cpuNanoCores: number;
115
+ memoryBytes: number;
116
+ networkBytes: number;
117
+ } | undefined>>;
118
+ publicIp: z.ZodPipe<z.ZodOptional<z.ZodObject<{
119
+ bytesInDDoS: z.ZodDefault<z.ZodNumber>;
120
+ }, z.core.$strict>>, z.ZodTransform<{
121
+ bytesInDDoS: number;
122
+ }, {
123
+ bytesInDDoS: number;
124
+ } | undefined>>;
125
+ staticSite: z.ZodPipe<z.ZodOptional<z.ZodObject<{
126
+ bytesSent: z.ZodDefault<z.ZodNumber>;
127
+ siteHits: z.ZodDefault<z.ZodNumber>;
128
+ }, z.core.$strict>>, z.ZodTransform<{
129
+ bytesSent: number;
130
+ siteHits: number;
131
+ }, {
132
+ bytesSent: number;
133
+ siteHits: number;
134
+ } | undefined>>;
135
+ storage: z.ZodPipe<z.ZodOptional<z.ZodObject<{
136
+ transactionsPerDay: z.ZodDefault<z.ZodNumber>;
137
+ }, z.core.$strict>>, z.ZodTransform<{
138
+ transactionsPerDay: number;
139
+ }, {
140
+ transactionsPerDay: number;
141
+ } | undefined>>;
142
+ vm: z.ZodPipe<z.ZodOptional<z.ZodObject<{
143
+ cpuPercent: z.ZodDefault<z.ZodNumber>;
144
+ networkInBytesPerDay: z.ZodDefault<z.ZodNumber>;
145
+ }, z.core.$strict>>, z.ZodTransform<{
146
+ cpuPercent: number;
147
+ networkInBytesPerDay: number;
148
+ }, {
149
+ cpuPercent: number;
150
+ networkInBytesPerDay: number;
151
+ } | undefined>>;
152
+ }, z.core.$strict>>, z.ZodTransform<{
153
+ appService: {
154
+ cpuPercent: number;
155
+ memoryPercent: number;
156
+ premiumCpuPercent: number;
157
+ };
158
+ containerApp: {
159
+ cpuNanoCores: number;
160
+ memoryBytes: number;
161
+ networkBytes: number;
162
+ };
163
+ publicIp: {
164
+ bytesInDDoS: number;
165
+ };
166
+ staticSite: {
167
+ bytesSent: number;
168
+ siteHits: number;
169
+ };
170
+ storage: {
171
+ transactionsPerDay: number;
172
+ };
173
+ vm: {
174
+ cpuPercent: number;
175
+ networkInBytesPerDay: number;
176
+ };
177
+ }, {
178
+ appService: {
179
+ cpuPercent: number;
180
+ memoryPercent: number;
181
+ premiumCpuPercent: number;
182
+ };
183
+ containerApp: {
184
+ cpuNanoCores: number;
185
+ memoryBytes: number;
186
+ networkBytes: number;
187
+ };
188
+ publicIp: {
189
+ bytesInDDoS: number;
190
+ };
191
+ staticSite: {
192
+ bytesSent: number;
193
+ siteHits: number;
194
+ };
195
+ storage: {
196
+ transactionsPerDay: number;
197
+ };
198
+ vm: {
199
+ cpuPercent: number;
200
+ networkInBytesPerDay: number;
201
+ };
202
+ } | undefined>>;
203
+ timespanDays: z.ZodDefault<z.ZodNumber>;
204
+ }, z.core.$strict>;
205
+ }, z.core.$strict>;
206
+ /** Fully-resolved configuration (all defaults applied). */
207
+ export type Config = z.infer<typeof ConfigSchema>;
208
+ /** Fully-resolved thresholds (all defaults applied). */
209
+ export type Thresholds = z.infer<typeof ThresholdsSchema>;
210
+ //# sourceMappingURL=schema.d.ts.map
@@ -0,0 +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"}
package/dist/schema.js ADDED
@@ -0,0 +1,97 @@
1
+ /**
2
+ * Zod schemas for the SaveMoney YAML configuration file.
3
+ *
4
+ * The entire config is represented by a single `ConfigSchema` composed of
5
+ * sub-schemas. All types used at runtime are derived from these schemas via
6
+ * `z.infer<>` so the source of truth is always the schema.
7
+ *
8
+ * YAML structure:
9
+ * ```yaml
10
+ * azure:
11
+ * subscriptionIds:
12
+ * - xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
13
+ * preferredLocation: italynorth # optional
14
+ * timespanDays: 30 # optional
15
+ * thresholds: # optional – omit to keep built-in defaults
16
+ * vm:
17
+ * cpuPercent: 5
18
+ * ```
19
+ */
20
+ import { z } from "zod";
21
+ // ── per-resource threshold sub-schemas ──────────────────────────────────────
22
+ const VmThresholdsSchema = z
23
+ .object({
24
+ /** CPU threshold (%) below which usage is flagged as low. Default: 1 */
25
+ cpuPercent: z.number().default(1),
26
+ /** Network inbound threshold (bytes/day) below which traffic is flagged as low. Default: 3 MB */
27
+ networkInBytesPerDay: z.number().default(1024 * 1024 * 3),
28
+ })
29
+ .strict();
30
+ const AppServiceThresholdsSchema = z
31
+ .object({
32
+ /** CPU threshold (%) below which usage is flagged as very low. Default: 5 */
33
+ cpuPercent: z.number().default(5),
34
+ /** Memory threshold (%) below which usage is flagged as very low. Default: 10 */
35
+ memoryPercent: z.number().default(10),
36
+ /** CPU threshold (%) for Premium-tier plans below which over-provisioning is flagged. Default: 10 */
37
+ premiumCpuPercent: z.number().default(10),
38
+ })
39
+ .strict();
40
+ const ContainerAppThresholdsSchema = z
41
+ .object({
42
+ /** CPU threshold (nanoCores) below which usage is flagged as very low. Default: 1 000 000 (0.001 cores) */
43
+ cpuNanoCores: z.number().default(1_000_000),
44
+ /** Memory threshold (bytes) below which usage is flagged as very low. Default: 10 MB */
45
+ memoryBytes: z.number().default(10_485_760),
46
+ /** Combined Rx+Tx network threshold (bytes/day) below which traffic is flagged as very low. Default: ~33 KB */
47
+ networkBytes: z.number().default(34_000),
48
+ })
49
+ .strict();
50
+ const StorageThresholdsSchema = z
51
+ .object({
52
+ /** Average daily transaction count below which the account is flagged. Default: 10 */
53
+ transactionsPerDay: z.number().default(10),
54
+ })
55
+ .strict();
56
+ const PublicIpThresholdsSchema = z
57
+ .object({
58
+ /** DDoS inbound bytes/day threshold below which traffic is flagged as very low. Default: ~332 KB */
59
+ bytesInDDoS: z.number().default(340_000),
60
+ })
61
+ .strict();
62
+ const StaticSiteThresholdsSchema = z
63
+ .object({
64
+ /** Total bytes sent below which data transfer is flagged as very low. Default: 1 MB */
65
+ bytesSent: z.number().default(1_048_576),
66
+ /** Total site hits below which traffic is flagged as very low. Default: 100 */
67
+ siteHits: z.number().default(100),
68
+ })
69
+ .strict();
70
+ // ── composed thresholds schema ───────────────────────────────────────────────
71
+ export const ThresholdsSchema = z
72
+ .object({
73
+ appService: AppServiceThresholdsSchema.optional().transform((v) => AppServiceThresholdsSchema.parse(v ?? {})),
74
+ containerApp: ContainerAppThresholdsSchema.optional().transform((v) => ContainerAppThresholdsSchema.parse(v ?? {})),
75
+ publicIp: PublicIpThresholdsSchema.optional().transform((v) => PublicIpThresholdsSchema.parse(v ?? {})),
76
+ staticSite: StaticSiteThresholdsSchema.optional().transform((v) => StaticSiteThresholdsSchema.parse(v ?? {})),
77
+ storage: StorageThresholdsSchema.optional().transform((v) => StorageThresholdsSchema.parse(v ?? {})),
78
+ vm: VmThresholdsSchema.optional().transform((v) => VmThresholdsSchema.parse(v ?? {})),
79
+ })
80
+ .strict();
81
+ // ── top-level config schema ──────────────────────────────────────────────────
82
+ const AzureSectionSchema = z
83
+ .object({
84
+ preferredLocation: z.string().default("italynorth"),
85
+ subscriptionIds: z
86
+ .array(z.string())
87
+ .min(1, "Config file must contain at least one entry in 'azure.subscriptionIds'"),
88
+ thresholds: ThresholdsSchema.optional().transform((v) => ThresholdsSchema.parse(v ?? {})),
89
+ timespanDays: z.number().int().positive().default(30),
90
+ })
91
+ .strict();
92
+ /**
93
+ * Single Zod schema representing the entire YAML configuration file.
94
+ * Use `ConfigSchema.parse(rawYaml)` to validate and apply defaults.
95
+ */
96
+ export const ConfigSchema = z.object({ azure: AzureSectionSchema }).strict();
97
+ //# sourceMappingURL=schema.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"schema.js","sourceRoot":"","sources":["../src/schema.ts"],"names":[],"mappings":"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"}
package/dist/types.d.ts CHANGED
@@ -14,6 +14,41 @@ export type BaseConfig = {
14
14
  timespanDays: number;
15
15
  };
16
16
  export type CostRisk = "high" | "low" | "medium";
17
+ /**
18
+ * Configurable thresholds used during resource analysis.
19
+ * Derived from `ThresholdsSchema` — the schema is the single source of truth.
20
+ */
21
+ export type { Thresholds } from "./schema.js";
22
+ /**
23
+ * Default threshold values — produced by parsing an empty object through
24
+ * `ThresholdsSchema`, which applies all schema-defined defaults.
25
+ */
26
+ export declare const DEFAULT_THRESHOLDS: {
27
+ appService: {
28
+ cpuPercent: number;
29
+ memoryPercent: number;
30
+ premiumCpuPercent: number;
31
+ };
32
+ containerApp: {
33
+ cpuNanoCores: number;
34
+ memoryBytes: number;
35
+ networkBytes: number;
36
+ };
37
+ publicIp: {
38
+ bytesInDDoS: number;
39
+ };
40
+ staticSite: {
41
+ bytesSent: number;
42
+ siteHits: number;
43
+ };
44
+ storage: {
45
+ transactionsPerDay: number;
46
+ };
47
+ vm: {
48
+ cpuPercent: number;
49
+ networkInBytesPerDay: number;
50
+ };
51
+ };
17
52
  /**
18
53
  * Merges analysis results, preserving existing reasons and combining suspectedUnused flags.
19
54
  */
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,MAAM,MAAM,cAAc,GAAG;IAC3B,QAAQ,EAAE,QAAQ,CAAC;IACnB,MAAM,EAAE,MAAM,CAAC;IACf,eAAe,EAAE,OAAO,CAAC;CAC1B,CAAC;AAEF;;GAEG;AACH,MAAM,MAAM,UAAU,GAAG;IACvB,iBAAiB,EAAE,MAAM,CAAC;IAC1B,YAAY,EAAE,MAAM,CAAC;CACtB,CAAC;AAEF,MAAM,MAAM,QAAQ,GAAG,MAAM,GAAG,KAAK,GAAG,QAAQ,CAAC;AAEjD;;GAEG;AACH,wBAAgB,YAAY,CAC1B,UAAU,EAAE,cAAc,EAC1B,cAAc,EAAE,cAAc,GAC7B,cAAc,CAOhB"}
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA;;GAEG;AAIH,MAAM,MAAM,cAAc,GAAG;IAC3B,QAAQ,EAAE,QAAQ,CAAC;IACnB,MAAM,EAAE,MAAM,CAAC;IACf,eAAe,EAAE,OAAO,CAAC;CAC1B,CAAC;AAEF;;GAEG;AACH,MAAM,MAAM,UAAU,GAAG;IACvB,iBAAiB,EAAE,MAAM,CAAC;IAC1B,YAAY,EAAE,MAAM,CAAC;CACtB,CAAC;AAEF,MAAM,MAAM,QAAQ,GAAG,MAAM,GAAG,KAAK,GAAG,QAAQ,CAAC;AAEjD;;;GAGG;AACH,YAAY,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAE9C;;;GAGG;AACH,eAAO,MAAM,kBAAkB;;;;;;;;;;;;;;;;;;;;;;;;;CAA6B,CAAC;AAE7D;;GAEG;AACH,wBAAgB,YAAY,CAC1B,UAAU,EAAE,cAAc,EAC1B,cAAc,EAAE,cAAc,GAC7B,cAAc,CAOhB"}
package/dist/types.js CHANGED
@@ -1,6 +1,12 @@
1
1
  /**
2
2
  * Common types shared across all cloud providers
3
3
  */
4
+ import { ThresholdsSchema } from "./schema.js";
5
+ /**
6
+ * Default threshold values — produced by parsing an empty object through
7
+ * `ThresholdsSchema`, which applies all schema-defined defaults.
8
+ */
9
+ export const DEFAULT_THRESHOLDS = ThresholdsSchema.parse({});
4
10
  /**
5
11
  * Merges analysis results, preserving existing reasons and combining suspectedUnused flags.
6
12
  */
package/dist/types.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA;;GAEG;AAkBH;;GAEG;AACH,MAAM,UAAU,YAAY,CAC1B,UAA0B,EAC1B,cAA8B;IAE9B,OAAO;QACL,QAAQ,EAAE,cAAc,CAAC,QAAQ;QACjC,MAAM,EAAE,UAAU,CAAC,MAAM,GAAG,cAAc,CAAC,MAAM;QACjD,eAAe,EACb,UAAU,CAAC,eAAe,IAAI,cAAc,CAAC,eAAe;KAC/D,CAAC;AACJ,CAAC"}
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AAwB/C;;;GAGG;AACH,MAAM,CAAC,MAAM,kBAAkB,GAAG,gBAAgB,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC;AAE7D;;GAEG;AACH,MAAM,UAAU,YAAY,CAC1B,UAA0B,EAC1B,cAA8B;IAE9B,OAAO;QACL,QAAQ,EAAE,cAAc,CAAC,QAAQ;QACjC,MAAM,EAAE,UAAU,CAAC,MAAM,GAAG,cAAc,CAAC,MAAM;QACjD,eAAe,EACb,UAAU,CAAC,eAAe,IAAI,cAAc,CAAC,eAAe;KAC/D,CAAC;AACJ,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pagopa/dx-savemoney",
3
- "version": "0.1.6",
3
+ "version": "0.2.0",
4
4
  "type": "module",
5
5
  "description": "Azure resource analyzer for finding unused or cost-inefficient resources.",
6
6
  "repository": {
@@ -34,10 +34,13 @@
34
34
  "@azure/arm-network": "^34.1.0",
35
35
  "@azure/arm-resources": "^7.0.0",
36
36
  "@azure/identity": "^4.13.0",
37
- "@logtape/logtape": "^1.3.7"
37
+ "@logtape/logtape": "^1.3.7",
38
+ "js-yaml": "^4.1.1",
39
+ "zod": "^4.3.6"
38
40
  },
39
41
  "devDependencies": {
40
42
  "@tsconfig/node24": "24.0.4",
43
+ "@types/js-yaml": "^4.0.9",
41
44
  "@types/node": "^22.19.15",
42
45
  "@vitest/coverage-v8": "^3.2.4",
43
46
  "eslint": "^9.39.2",
@@ -51,6 +54,9 @@
51
54
  "dev": "tsc --watch",
52
55
  "format": "prettier --write .",
53
56
  "format:check": "prettier --check .",
57
+ "test": "vitest run",
58
+ "test:watch": "vitest",
59
+ "test:coverage": "vitest run --coverage",
54
60
  "typecheck": "tsc --noEmit"
55
61
  }
56
62
  }
@@ -0,0 +1,97 @@
1
+ /**
2
+ * Tests for loadConfig() — verifies that:
3
+ * 1. Returns default thresholds and prompts for subscriptionIds when no file is given.
4
+ * 2. Loads subscriptionIds, location, timespanDays and thresholds from a YAML file.
5
+ * 3. Partial threshold overrides keep defaults for non-overridden fields.
6
+ * 4. Throws a clear error for a non-existent explicit path.
7
+ */
8
+
9
+ import path from "path";
10
+ import { fileURLToPath } from "url";
11
+ import { describe, expect, it } from "vitest";
12
+
13
+ import { loadConfig } from "../../index.js";
14
+ import { DEFAULT_THRESHOLDS } from "../../types.js";
15
+
16
+ const __dirname = path.dirname(fileURLToPath(import.meta.url));
17
+
18
+ /** Absolute paths used in tests */
19
+ const FIXTURE_PARTIAL = path.resolve(
20
+ __dirname,
21
+ "fixtures/partial-override.yaml",
22
+ );
23
+ const FIXTURE_FULL = path.resolve(__dirname, "fixtures/full-override.yaml");
24
+
25
+ describe("loadConfig", () => {
26
+ it("throws when explicit path does not exist", async () => {
27
+ await expect(loadConfig("/nonexistent/config.yaml")).rejects.toThrow(
28
+ "Config file not found",
29
+ );
30
+ });
31
+
32
+ it("loads subscriptionIds and defaults from a partial YAML file", async () => {
33
+ const result = await loadConfig(FIXTURE_PARTIAL);
34
+
35
+ expect(result.subscriptionIds).toEqual([
36
+ "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
37
+ ]);
38
+ expect(result.preferredLocation).toBe("italynorth");
39
+ expect(result.timespanDays).toBe(30);
40
+ });
41
+
42
+ it("loads partial threshold overrides and keeps defaults for missing fields", async () => {
43
+ const result = await loadConfig(FIXTURE_PARTIAL);
44
+
45
+ // Overridden values
46
+ expect(result.thresholds?.vm.cpuPercent).toBe(5);
47
+ expect(result.thresholds?.storage.transactionsPerDay).toBe(50);
48
+
49
+ // Non-overridden vm field keeps default
50
+ expect(result.thresholds?.vm.networkInBytesPerDay).toBe(
51
+ DEFAULT_THRESHOLDS.vm.networkInBytesPerDay,
52
+ );
53
+
54
+ // Entire non-overridden sections keep defaults
55
+ expect(result.thresholds?.appService).toEqual(
56
+ DEFAULT_THRESHOLDS.appService,
57
+ );
58
+ expect(result.thresholds?.containerApp).toEqual(
59
+ DEFAULT_THRESHOLDS.containerApp,
60
+ );
61
+ expect(result.thresholds?.publicIp).toEqual(DEFAULT_THRESHOLDS.publicIp);
62
+ expect(result.thresholds?.staticSite).toEqual(
63
+ DEFAULT_THRESHOLDS.staticSite,
64
+ );
65
+ });
66
+
67
+ it("loads all values from a full YAML file", async () => {
68
+ const result = await loadConfig(FIXTURE_FULL);
69
+
70
+ expect(result.subscriptionIds).toHaveLength(2);
71
+ expect(result.preferredLocation).toBe("westeurope");
72
+ expect(result.timespanDays).toBe(60);
73
+
74
+ expect(result.thresholds?.vm.cpuPercent).toBe(5);
75
+ expect(result.thresholds?.vm.networkInBytesPerDay).toBe(10485760);
76
+ expect(result.thresholds?.appService.cpuPercent).toBe(10);
77
+ expect(result.thresholds?.appService.memoryPercent).toBe(20);
78
+ expect(result.thresholds?.appService.premiumCpuPercent).toBe(15);
79
+ expect(result.thresholds?.containerApp.cpuNanoCores).toBe(5000000);
80
+ expect(result.thresholds?.containerApp.memoryBytes).toBe(52428800);
81
+ expect(result.thresholds?.containerApp.networkBytes).toBe(100000);
82
+ expect(result.thresholds?.storage.transactionsPerDay).toBe(50);
83
+ expect(result.thresholds?.publicIp.bytesInDDoS).toBe(1048576);
84
+ expect(result.thresholds?.staticSite.siteHits).toBe(500);
85
+ expect(result.thresholds?.staticSite.bytesSent).toBe(5242880);
86
+ });
87
+
88
+ it("returns a complete Thresholds object (all required keys present)", async () => {
89
+ const result = await loadConfig(FIXTURE_PARTIAL);
90
+ expect(result.thresholds).toHaveProperty("vm");
91
+ expect(result.thresholds).toHaveProperty("appService");
92
+ expect(result.thresholds).toHaveProperty("containerApp");
93
+ expect(result.thresholds).toHaveProperty("storage");
94
+ expect(result.thresholds).toHaveProperty("publicIp");
95
+ expect(result.thresholds).toHaveProperty("staticSite");
96
+ });
97
+ });
@@ -0,0 +1,25 @@
1
+ azure:
2
+ subscriptionIds:
3
+ - xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
4
+ - yyyyyyyy-yyyy-yyyy-yyyy-yyyyyyyyyyyy
5
+ preferredLocation: westeurope
6
+ timespanDays: 60
7
+ thresholds:
8
+ vm:
9
+ cpuPercent: 5
10
+ networkInBytesPerDay: 10485760
11
+ appService:
12
+ cpuPercent: 10
13
+ memoryPercent: 20
14
+ premiumCpuPercent: 15
15
+ containerApp:
16
+ cpuNanoCores: 5000000
17
+ memoryBytes: 52428800
18
+ networkBytes: 100000
19
+ storage:
20
+ transactionsPerDay: 50
21
+ publicIp:
22
+ bytesInDDoS: 1048576
23
+ staticSite:
24
+ siteHits: 500
25
+ bytesSent: 5242880
@@ -0,0 +1,10 @@
1
+ azure:
2
+ subscriptionIds:
3
+ - xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
4
+ preferredLocation: italynorth
5
+ timespanDays: 30
6
+ thresholds:
7
+ vm:
8
+ cpuPercent: 5
9
+ storage:
10
+ transactionsPerDay: 50