@ebowwa/hetzner 0.3.2 → 0.3.3

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 (45) hide show
  1. package/dist/actions.js +782 -0
  2. package/dist/bootstrap/index.js +1 -0
  3. package/dist/client.js +1867 -0
  4. package/dist/errors.js +236 -0
  5. package/dist/index.js +40 -38
  6. package/dist/onboarding/index.js +2 -2
  7. package/dist/pricing.js +587 -0
  8. package/dist/schemas.js +475 -0
  9. package/dist/servers.js +688 -0
  10. package/dist/ssh-keys.js +474 -0
  11. package/dist/types.js +101 -0
  12. package/dist/volumes.js +119 -0
  13. package/package.json +51 -1
  14. package/dist/actions.d.ts +0 -355
  15. package/dist/auth.d.ts +0 -6
  16. package/dist/bootstrap/cloud-init.d.ts +0 -78
  17. package/dist/bootstrap/firewall.d.ts +0 -118
  18. package/dist/bootstrap/genesis.d.ts +0 -82
  19. package/dist/bootstrap/index.d.ts +0 -29
  20. package/dist/bootstrap/kernel-hardening.d.ts +0 -69
  21. package/dist/bootstrap/security-audit.d.ts +0 -45
  22. package/dist/bootstrap/ssh-hardening.d.ts +0 -67
  23. package/dist/client.d.ts +0 -62
  24. package/dist/config.d.ts +0 -4
  25. package/dist/cpufeatures-mvwrkyaq.node +0 -0
  26. package/dist/errors.d.ts +0 -170
  27. package/dist/index.d.ts +0 -21
  28. package/dist/onboarding/claude.d.ts +0 -37
  29. package/dist/onboarding/cpufeatures-mvwrkyaq.node +0 -0
  30. package/dist/onboarding/doppler.d.ts +0 -37
  31. package/dist/onboarding/git.d.ts +0 -38
  32. package/dist/onboarding/index.d.ts +0 -19
  33. package/dist/onboarding/onboarding.d.ts +0 -41
  34. package/dist/onboarding/sshcrypto-6mayxj08.node +0 -0
  35. package/dist/onboarding/tailscale.d.ts +0 -38
  36. package/dist/onboarding/types.d.ts +0 -111
  37. package/dist/pricing.d.ts +0 -330
  38. package/dist/schemas.d.ts +0 -6629
  39. package/dist/server-status.d.ts +0 -25
  40. package/dist/servers.d.ts +0 -164
  41. package/dist/ssh-keys.d.ts +0 -35
  42. package/dist/ssh-setup.d.ts +0 -47
  43. package/dist/sshcrypto-6mayxj08.node +0 -0
  44. package/dist/types.d.ts +0 -303
  45. package/dist/volumes.d.ts +0 -105
@@ -0,0 +1,587 @@
1
+ // @bun
2
+ var __create = Object.create;
3
+ var __getProtoOf = Object.getPrototypeOf;
4
+ var __defProp = Object.defineProperty;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
7
+ var __toESM = (mod, isNodeMode, target) => {
8
+ target = mod != null ? __create(__getProtoOf(mod)) : {};
9
+ const to = isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target;
10
+ for (let key of __getOwnPropNames(mod))
11
+ if (!__hasOwnProp.call(to, key))
12
+ __defProp(to, key, {
13
+ get: () => mod[key],
14
+ enumerable: true
15
+ });
16
+ return to;
17
+ };
18
+ var __require = import.meta.require;
19
+
20
+ // src/schemas.ts
21
+ import { z } from "zod";
22
+ import {
23
+ EnvironmentStatus,
24
+ ActionStatus,
25
+ VolumeStatus
26
+ } from "@ebowwa/codespaces-types/compile";
27
+ var ipv4Regex = /^(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$/;
28
+ var ipv6Regex = /^[0-9a-fA-F:]+(?:\/\d{1,3})?$/;
29
+ var ipRegex = /^(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$|^(?:[0-9a-fA-F]{1,4}:){7}[0-9a-fA-F]{1,4}$/;
30
+ var HetznerErrorSchema = z.object({
31
+ code: z.string(),
32
+ message: z.string(),
33
+ details: z.any().optional()
34
+ });
35
+ var HetznerPaginationSchema = z.object({
36
+ page: z.number(),
37
+ per_page: z.number(),
38
+ previous_page: z.number().nullable(),
39
+ next_page: z.number().nullable(),
40
+ last_page: z.number(),
41
+ total_entries: z.number()
42
+ });
43
+ var HetznerMetaSchema = z.object({
44
+ pagination: HetznerPaginationSchema.optional()
45
+ });
46
+ var HetznerActionResourceSchema = z.object({
47
+ id: z.number(),
48
+ type: z.enum([
49
+ "server",
50
+ "volume",
51
+ "network",
52
+ "floating_ip",
53
+ "load_balancer",
54
+ "certificate",
55
+ "firewall",
56
+ "image"
57
+ ])
58
+ });
59
+ var HetznerActionErrorSchema = z.object({
60
+ code: z.string(),
61
+ message: z.string()
62
+ });
63
+ var HetznerActionSchema = z.object({
64
+ id: z.number(),
65
+ command: z.string(),
66
+ status: z.nativeEnum(ActionStatus),
67
+ started: z.string(),
68
+ finished: z.string().nullable(),
69
+ progress: z.number().min(0).max(100),
70
+ resources: z.array(HetznerActionResourceSchema),
71
+ error: HetznerActionErrorSchema.nullable()
72
+ });
73
+ var HetznerActionResponseSchema = z.object({
74
+ action: HetznerActionSchema
75
+ });
76
+ var HetznerActionsResponseSchema = z.object({
77
+ actions: z.array(HetznerActionSchema),
78
+ meta: HetznerMetaSchema
79
+ });
80
+ var HetznerServerImageSchema = z.object({
81
+ id: z.number(),
82
+ name: z.string(),
83
+ description: z.string(),
84
+ type: z.enum(["snapshot", "backup", "system"])
85
+ });
86
+ var HetznerIPv4Schema = z.object({
87
+ ip: z.string().regex(ipv4Regex),
88
+ blocked: z.boolean()
89
+ });
90
+ var HetznerIPv6Schema = z.object({
91
+ ip: z.string().regex(ipv6Regex),
92
+ blocked: z.boolean()
93
+ });
94
+ var HetznerFloatingIpRefSchema = z.object({
95
+ id: z.number(),
96
+ ip: z.string()
97
+ });
98
+ var HetznerFirewallRefSchema = z.object({
99
+ id: z.number(),
100
+ name: z.string(),
101
+ status: z.enum(["applied", "pending"])
102
+ });
103
+ var HetznerPublicNetSchema = z.object({
104
+ ipv4: HetznerIPv4Schema,
105
+ ipv6: HetznerIPv6Schema.optional(),
106
+ floating_ips: z.array(HetznerFloatingIpRefSchema),
107
+ firewalls: z.array(HetznerFirewallRefSchema)
108
+ });
109
+ var HetznerServerTypeSchema = z.object({
110
+ id: z.number(),
111
+ name: z.string(),
112
+ description: z.string(),
113
+ cores: z.number(),
114
+ memory: z.number(),
115
+ disk: z.number()
116
+ });
117
+ var HetznerLocationSchema = z.object({
118
+ id: z.number(),
119
+ name: z.string(),
120
+ description: z.string(),
121
+ country: z.string(),
122
+ city: z.string(),
123
+ latitude: z.number(),
124
+ longitude: z.number(),
125
+ network_zone: z.string()
126
+ });
127
+ var HetznerDatacenterSchema = z.object({
128
+ id: z.number(),
129
+ name: z.string(),
130
+ description: z.string(),
131
+ location: HetznerLocationSchema,
132
+ supported_server_types: z.array(z.object({
133
+ id: z.number(),
134
+ name: z.string()
135
+ })).optional().nullable()
136
+ });
137
+ var HetznerVolumeRefSchema = z.object({
138
+ id: z.number(),
139
+ name: z.string(),
140
+ size: z.number().positive(),
141
+ linux_device: z.string()
142
+ });
143
+ var HetznerServerProtectionSchema = z.object({
144
+ delete: z.boolean(),
145
+ rebuild: z.boolean()
146
+ });
147
+ var HetznerServerSchema = z.object({
148
+ id: z.number().positive(),
149
+ name: z.string().min(1),
150
+ status: z.nativeEnum(EnvironmentStatus),
151
+ image: HetznerServerImageSchema.nullable().optional(),
152
+ public_net: HetznerPublicNetSchema,
153
+ server_type: HetznerServerTypeSchema,
154
+ datacenter: HetznerDatacenterSchema,
155
+ labels: z.record(z.string(), z.any()),
156
+ created: z.string().datetime(),
157
+ protection: HetznerServerProtectionSchema,
158
+ volumes: z.array(HetznerVolumeRefSchema)
159
+ });
160
+ var HetznerListServersResponseSchema = z.object({
161
+ servers: z.array(HetznerServerSchema),
162
+ meta: HetznerMetaSchema
163
+ });
164
+ var HetznerGetServerResponseSchema = z.object({
165
+ server: HetznerServerSchema
166
+ });
167
+ var HetznerCreateServerRequestSchema = z.object({
168
+ name: z.string().min(1).max(64).regex(/^[a-zA-Z0-9][a-zA-Z0-9-]*$/, "Name must start with letter/number and contain only letters, numbers, and hyphens"),
169
+ server_type: z.string().min(1),
170
+ image: z.string().min(1),
171
+ location: z.string().min(1).optional(),
172
+ datacenter: z.string().min(1).optional(),
173
+ ssh_keys: z.array(z.union([z.string(), z.number()])).optional(),
174
+ volumes: z.array(z.number().positive()).optional(),
175
+ labels: z.record(z.string(), z.any()).optional(),
176
+ start_after_create: z.boolean().optional()
177
+ }).refine((data) => !(data.location && data.datacenter), "Cannot specify both location and datacenter");
178
+ var HetznerCreateServerResponseSchema = z.object({
179
+ server: HetznerServerSchema,
180
+ action: HetznerActionSchema,
181
+ next_actions: z.array(HetznerActionSchema),
182
+ root_password: z.string().nullable()
183
+ });
184
+ var HetznerUpdateServerRequestSchema = z.object({
185
+ name: z.string().min(1).max(64).optional(),
186
+ labels: z.record(z.string(), z.any()).optional()
187
+ });
188
+ var HetznerUpdateServerResponseSchema = z.object({
189
+ server: HetznerServerSchema
190
+ });
191
+ var HetznerVolumeLocationSchema = z.object({
192
+ id: z.number(),
193
+ name: z.string(),
194
+ description: z.string(),
195
+ country: z.string(),
196
+ city: z.string(),
197
+ latitude: z.number(),
198
+ longitude: z.number()
199
+ });
200
+ var HetznerVolumeProtectionSchema = z.object({
201
+ delete: z.boolean()
202
+ });
203
+ var HetznerVolumeSchema = z.object({
204
+ id: z.number().positive(),
205
+ name: z.string().min(1),
206
+ status: z.nativeEnum(VolumeStatus),
207
+ server: z.number().positive().nullable().optional(),
208
+ size: z.number().positive(),
209
+ linux_device: z.string().nullable().optional(),
210
+ format: z.string().nullable().optional(),
211
+ location: HetznerVolumeLocationSchema.nullable().optional(),
212
+ labels: z.record(z.string(), z.any()),
213
+ created: z.string().datetime(),
214
+ protection: HetznerVolumeProtectionSchema
215
+ });
216
+ var HetznerListVolumesResponseSchema = z.object({
217
+ volumes: z.array(HetznerVolumeSchema),
218
+ meta: HetznerMetaSchema
219
+ });
220
+ var HetznerGetVolumeResponseSchema = z.object({
221
+ volume: HetznerVolumeSchema
222
+ });
223
+ var HetznerCreateVolumeRequestSchema = z.object({
224
+ name: z.string().min(1).max(64),
225
+ size: z.number().positive().multipleOf(1),
226
+ server: z.number().positive().optional(),
227
+ location: z.string().min(1).optional(),
228
+ automount: z.boolean().optional(),
229
+ format: z.string().optional(),
230
+ labels: z.record(z.string(), z.any()).optional()
231
+ }).refine((data) => {
232
+ return Number.isInteger(data.size);
233
+ }, "Volume size must be a whole number in GB");
234
+ var HetznerCreateVolumeResponseSchema = z.object({
235
+ volume: HetznerVolumeSchema,
236
+ action: HetznerActionSchema,
237
+ next_actions: z.array(HetznerActionSchema)
238
+ });
239
+ var HetznerSubnetSchema = z.object({
240
+ type: z.enum(["server", "cloud", "vswitch"]),
241
+ ip_range: z.string().regex(ipRegex),
242
+ network_zone: z.string(),
243
+ gateway: z.string().regex(ipRegex)
244
+ });
245
+ var HetznerRouteSchema = z.object({
246
+ destination: z.string().regex(ipRegex),
247
+ gateway: z.string().regex(ipRegex)
248
+ });
249
+ var HetznerNetworkProtectionSchema = z.object({
250
+ delete: z.boolean()
251
+ });
252
+ var HetznerNetworkSchema = z.object({
253
+ id: z.number().positive(),
254
+ name: z.string().min(1).max(64),
255
+ ip_range: z.string().regex(ipRegex),
256
+ subnets: z.array(HetznerSubnetSchema),
257
+ routes: z.array(HetznerRouteSchema),
258
+ servers: z.array(z.number().positive()),
259
+ protection: HetznerNetworkProtectionSchema,
260
+ labels: z.record(z.string(), z.any()),
261
+ created: z.string().datetime()
262
+ });
263
+ var HetznerListNetworksResponseSchema = z.object({
264
+ networks: z.array(HetznerNetworkSchema),
265
+ meta: HetznerMetaSchema
266
+ });
267
+ var HetznerGetNetworkResponseSchema = z.object({
268
+ network: HetznerNetworkSchema
269
+ });
270
+ var HetznerSSHKeySchema = z.object({
271
+ id: z.number().positive(),
272
+ name: z.string().min(1).max(64),
273
+ fingerprint: z.string(),
274
+ public_key: z.string(),
275
+ labels: z.record(z.string(), z.any()),
276
+ created: z.string().datetime()
277
+ });
278
+ var HetznerListSSHKeysResponseSchema = z.object({
279
+ ssh_keys: z.array(HetznerSSHKeySchema),
280
+ meta: HetznerMetaSchema
281
+ });
282
+ var HetznerGetSSHKeyResponseSchema = z.object({
283
+ ssh_key: HetznerSSHKeySchema
284
+ });
285
+ var HetznerCreateSSHKeyRequestSchema = z.object({
286
+ name: z.string().min(1).max(64).regex(/^[a-zA-Z0-9][a-zA-Z0-9-]*$/, "Name must start with letter/number and contain only letters, numbers, and hyphens"),
287
+ public_key: z.string().min(1),
288
+ labels: z.record(z.string(), z.any()).optional()
289
+ });
290
+ var HetznerCreateSSHKeyResponseSchema = z.object({
291
+ ssh_key: HetznerSSHKeySchema
292
+ });
293
+ var HetznerFloatingIpSchema = z.object({
294
+ id: z.number().positive(),
295
+ name: z.string().min(1).max(64),
296
+ description: z.string().optional(),
297
+ type: z.enum(["ipv4", "ipv6"]),
298
+ ip: z.string().regex(ipRegex),
299
+ server: z.number().positive().nullable(),
300
+ dns_ptr: z.array(z.object({
301
+ ip: z.string(),
302
+ dns_ptr: z.string()
303
+ })),
304
+ home_location: HetznerLocationSchema,
305
+ blocked: z.boolean(),
306
+ protection: z.object({
307
+ delete: z.boolean()
308
+ }),
309
+ labels: z.record(z.string(), z.any()),
310
+ created: z.string().datetime()
311
+ });
312
+ var HetznerListFloatingIpsResponseSchema = z.object({
313
+ floating_ips: z.array(HetznerFloatingIpSchema),
314
+ meta: HetznerMetaSchema
315
+ });
316
+ var HetznerFirewallRuleSchema = z.object({
317
+ direction: z.enum(["in", "out"]),
318
+ source_ips: z.array(z.string().regex(ipRegex)).optional(),
319
+ destination_ips: z.array(z.string().regex(ipRegex)).optional(),
320
+ source_port: z.string().optional(),
321
+ destination_port: z.string().optional(),
322
+ protocol: z.enum(["tcp", "udp", "icmp", "esp", "gre"])
323
+ });
324
+ var HetznerFirewallResourceSchema = z.object({
325
+ type: z.enum(["server", "label_selector"]),
326
+ server: z.object({
327
+ id: z.number().positive()
328
+ }).optional(),
329
+ label_selector: z.object({
330
+ selector: z.string()
331
+ }).optional()
332
+ });
333
+ var HetznerFirewallSchema = z.object({
334
+ id: z.number().positive(),
335
+ name: z.string().min(1).max(64),
336
+ rules: z.array(HetznerFirewallRuleSchema),
337
+ apply_to: z.array(HetznerFirewallResourceSchema),
338
+ labels: z.record(z.string(), z.any()),
339
+ created: z.string().datetime()
340
+ });
341
+ var HetznerListFirewallsResponseSchema = z.object({
342
+ firewalls: z.array(HetznerFirewallSchema),
343
+ meta: HetznerMetaSchema
344
+ });
345
+ var HetznerIsoSchema = z.object({
346
+ id: z.number().positive(),
347
+ name: z.string(),
348
+ description: z.string(),
349
+ type: z.enum(["public", "private"]),
350
+ deprecated: z.date().nullable().optional(),
351
+ architecture: z.array(z.enum(["x86", "arm"])).optional()
352
+ });
353
+ var HetznerListIsosResponseSchema = z.object({
354
+ isos: z.array(HetznerIsoSchema),
355
+ meta: HetznerMetaSchema
356
+ });
357
+ var HetznerListLocationsResponseSchema = z.object({
358
+ locations: z.array(HetznerLocationSchema)
359
+ });
360
+ var HetznerListDatacentersResponseSchema = z.object({
361
+ datacenters: z.array(HetznerDatacenterSchema)
362
+ });
363
+ var HetznerServerTypePricingSchema = z.object({
364
+ location: z.string().nullable().optional(),
365
+ price_hourly: z.object({
366
+ net: z.string(),
367
+ gross: z.string()
368
+ }),
369
+ price_monthly: z.object({
370
+ net: z.string(),
371
+ gross: z.string()
372
+ })
373
+ });
374
+ var HetznerServerTypeExtendedSchema = HetznerServerTypeSchema.extend({
375
+ deprecated: z.boolean().optional(),
376
+ prices: z.array(HetznerServerTypePricingSchema),
377
+ storage_type: z.enum(["local", "network"]),
378
+ cpu_type: z.enum(["shared", "dedicated"])
379
+ });
380
+ var HetznerListServerTypesResponseSchema = z.object({
381
+ server_types: z.array(HetznerServerTypeExtendedSchema)
382
+ });
383
+ var HetznerCertificateSchema = z.object({
384
+ id: z.number().positive(),
385
+ name: z.string().min(1).max(64),
386
+ labels: z.record(z.string(), z.any()),
387
+ certificate: z.string(),
388
+ not_valid_before: z.string().datetime(),
389
+ not_valid_after: z.string().datetime(),
390
+ domain_names: z.array(z.string().url()),
391
+ fingerprint: z.string(),
392
+ created: z.string().datetime(),
393
+ status: z.enum(["pending", "issued", "failed", "revoked"]),
394
+ failed: z.boolean().optional(),
395
+ type: z.enum(["uploaded", "managed"]),
396
+ usage: z.array(z.enum(["dual_stack", "server", "load_balancer", "dns"])).optional()
397
+ });
398
+ var HetznerListCertificatesResponseSchema = z.object({
399
+ certificates: z.array(HetznerCertificateSchema),
400
+ meta: HetznerMetaSchema
401
+ });
402
+ function createPaginatedResponseSchema(itemSchema, itemName) {
403
+ return z.object({
404
+ [itemName]: z.array(itemSchema),
405
+ meta: HetznerMetaSchema
406
+ });
407
+ }
408
+ function createItemResponseSchema(itemSchema, itemName) {
409
+ return z.object({
410
+ [itemName]: itemSchema
411
+ });
412
+ }
413
+
414
+ // src/pricing.ts
415
+ import { z as z2 } from "zod";
416
+ var DEFAULT_FALLBACK_PRICE_MONTHLY = 5;
417
+ var HOURS_PER_MONTH = 730;
418
+ var ServerTypePriceSchema = z2.object({
419
+ serverType: z2.string(),
420
+ priceMonthly: z2.number().nonnegative(),
421
+ priceHourly: z2.number().nonnegative(),
422
+ deprecated: z2.boolean().optional().default(false)
423
+ });
424
+ var EnvironmentCostSchema = z2.object({
425
+ environmentId: z2.string(),
426
+ environmentName: z2.string(),
427
+ serverType: z2.string(),
428
+ isRunning: z2.boolean(),
429
+ costMonthly: z2.number().nonnegative(),
430
+ costHourly: z2.number().nonnegative(),
431
+ priceInfo: ServerTypePriceSchema.optional()
432
+ });
433
+ var CostCalculationResultSchema = z2.object({
434
+ totalMonthly: z2.number().nonnegative(),
435
+ totalHourly: z2.number().nonnegative(),
436
+ runningEnvironmentCount: z2.number().nonnegative().int(),
437
+ stoppedEnvironmentCount: z2.number().nonnegative().int(),
438
+ unknownServerTypeCount: z2.number().nonnegative().int(),
439
+ breakdown: z2.array(EnvironmentCostSchema),
440
+ priceMap: z2.custom()
441
+ });
442
+ function parseHetznerPrice(priceString) {
443
+ const parsed = parseFloat(priceString);
444
+ if (isNaN(parsed)) {
445
+ console.warn(`Failed to parse Hetzner price string: ${priceString}`);
446
+ return 0;
447
+ }
448
+ return parsed;
449
+ }
450
+ function extractServerTypePrice(serverType) {
451
+ if (!serverType.prices || serverType.prices.length === 0) {
452
+ console.warn(`Server type ${serverType.name} has no pricing information`);
453
+ return;
454
+ }
455
+ const priceEntry = serverType.prices[0];
456
+ const priceMonthly = parseHetznerPrice(priceEntry.price_monthly.gross);
457
+ const priceHourly = parseHetznerPrice(priceEntry.price_hourly.gross);
458
+ return {
459
+ serverType: serverType.name,
460
+ priceMonthly,
461
+ priceHourly,
462
+ deprecated: serverType.deprecated ?? false
463
+ };
464
+ }
465
+ function buildPriceMap(serverTypes) {
466
+ const priceMap = new Map;
467
+ for (const serverType of serverTypes) {
468
+ const priceInfo = extractServerTypePrice(serverType);
469
+ if (priceInfo) {
470
+ priceMap.set(serverType.name, priceInfo);
471
+ }
472
+ }
473
+ return priceMap;
474
+ }
475
+ function getServerTypeMonthlyPrice(serverTypeName, priceMap, fallbackPrice = DEFAULT_FALLBACK_PRICE_MONTHLY) {
476
+ const priceInfo = priceMap.get(serverTypeName);
477
+ return priceInfo?.priceMonthly ?? fallbackPrice;
478
+ }
479
+ function calculateHourlyFromMonthly(monthlyPrice) {
480
+ return monthlyPrice / HOURS_PER_MONTH;
481
+ }
482
+ function calculateCosts(environments, serverTypes, options = {}) {
483
+ const { fallbackPrice = DEFAULT_FALLBACK_PRICE_MONTHLY, includeStopped = false } = options;
484
+ const priceMap = buildPriceMap(serverTypes);
485
+ const breakdown = [];
486
+ let totalMonthly = 0;
487
+ let runningCount = 0;
488
+ let stoppedCount = 0;
489
+ let unknownTypeCount = 0;
490
+ for (const env of environments) {
491
+ const isRunning = env.status === "running";
492
+ const priceInfo = priceMap.get(env.serverType);
493
+ const isUnknownType = !priceInfo;
494
+ if (isRunning) {
495
+ runningCount++;
496
+ } else {
497
+ stoppedCount++;
498
+ }
499
+ if (isRunning && isUnknownType) {
500
+ unknownTypeCount++;
501
+ }
502
+ const monthlyPrice = isRunning ? priceInfo?.priceMonthly ?? fallbackPrice : 0;
503
+ const hourlyPrice = isRunning ? priceInfo?.priceHourly ?? calculateHourlyFromMonthly(fallbackPrice) : 0;
504
+ if (isRunning) {
505
+ totalMonthly += monthlyPrice;
506
+ }
507
+ if (isRunning || includeStopped) {
508
+ breakdown.push({
509
+ environmentId: env.id,
510
+ environmentName: env.name,
511
+ serverType: env.serverType,
512
+ isRunning,
513
+ costMonthly: monthlyPrice,
514
+ costHourly: hourlyPrice,
515
+ priceInfo
516
+ });
517
+ }
518
+ }
519
+ const totalHourly = calculateHourlyFromMonthly(totalMonthly);
520
+ return {
521
+ totalMonthly,
522
+ totalHourly,
523
+ runningEnvironmentCount: runningCount,
524
+ stoppedEnvironmentCount: stoppedCount,
525
+ unknownServerTypeCount: unknownTypeCount,
526
+ breakdown,
527
+ priceMap
528
+ };
529
+ }
530
+
531
+ class PricingOperations {
532
+ client;
533
+ constructor(client) {
534
+ this.client = client;
535
+ }
536
+ async listServerTypes() {
537
+ const response = await this.client.request("/server_types");
538
+ const validated = HetznerListServerTypesResponseSchema.safeParse(response);
539
+ if (!validated.success) {
540
+ console.warn("Hetzner list server types validation warning:", validated.error.issues);
541
+ return response.server_types;
542
+ }
543
+ return validated.data.server_types;
544
+ }
545
+ async getServerType(name) {
546
+ const types = await this.listServerTypes();
547
+ return types.find((t) => t.name === name);
548
+ }
549
+ async listLocations() {
550
+ const response = await this.client.request("/locations");
551
+ const validated = HetznerListLocationsResponseSchema.safeParse(response);
552
+ if (!validated.success) {
553
+ console.warn("Hetzner list locations validation warning:", validated.error.issues);
554
+ return response.locations;
555
+ }
556
+ return validated.data.locations;
557
+ }
558
+ async getLocation(name) {
559
+ const locations = await this.listLocations();
560
+ return locations.find((l) => l.name === name);
561
+ }
562
+ async listDatacenters() {
563
+ const response = await this.client.request("/datacenters");
564
+ const validated = HetznerListDatacentersResponseSchema.safeParse(response);
565
+ if (!validated.success) {
566
+ console.warn("Hetzner list datacenters validation warning:", validated.error.issues);
567
+ return response.datacenters;
568
+ }
569
+ return validated.data.datacenters;
570
+ }
571
+ async calculateEnvironmentCosts(environments, options) {
572
+ const serverTypes = await this.listServerTypes();
573
+ return calculateCosts(environments, serverTypes, options);
574
+ }
575
+ }
576
+ export {
577
+ parseHetznerPrice,
578
+ getServerTypeMonthlyPrice,
579
+ extractServerTypePrice,
580
+ calculateHourlyFromMonthly,
581
+ calculateCosts,
582
+ buildPriceMap,
583
+ ServerTypePriceSchema,
584
+ PricingOperations,
585
+ EnvironmentCostSchema,
586
+ CostCalculationResultSchema
587
+ };