@stacksjs/ts-cloud 0.7.105 → 0.7.108

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 (75) hide show
  1. package/dist/bin/cli.js +784 -785
  2. package/dist/bin/dashboard-server.js +292 -292
  3. package/dist/{chunk-dmzez5f8.js → chunk-33sy72r8.js} +52 -3
  4. package/dist/{chunk-vkd2y0fs.js → chunk-eg8ck25v.js} +858 -150
  5. package/dist/deploy/dashboard-bundle-encoding.test.d.ts +1 -0
  6. package/dist/deploy/dashboard-payload.d.ts +20 -0
  7. package/dist/deploy/dashboard-payload.test.d.ts +1 -0
  8. package/dist/deploy/dashboard-topology.d.ts +85 -0
  9. package/dist/deploy/dashboard-topology.test.d.ts +1 -0
  10. package/dist/deploy/egress-collection.d.ts +60 -0
  11. package/dist/deploy/index.d.ts +2 -0
  12. package/dist/deploy/index.js +14 -2
  13. package/dist/drivers/index.js +1 -1
  14. package/dist/index.js +2 -2
  15. package/dist/ui/404.html +1 -0
  16. package/dist/ui/access-denied.html +1 -1
  17. package/dist/ui/account/automation.html +4 -4
  18. package/dist/ui/account/security.html +2 -2
  19. package/dist/ui/applications/compose.html +4 -4
  20. package/dist/ui/applications/new.html +2 -2
  21. package/dist/ui/data/backups.html +22 -9
  22. package/dist/ui/data/services.html +22 -9
  23. package/dist/ui/data/volumes.html +22 -9
  24. package/dist/ui/index.html +22 -9
  25. package/dist/ui/infrastructure/topology.html +2223 -0
  26. package/dist/ui/integrations.html +2 -2
  27. package/dist/ui/operations/alerts.html +22 -9
  28. package/dist/ui/operations/configuration.html +4 -4
  29. package/dist/ui/operations/jobs.html +22 -9
  30. package/dist/ui/operations/maintenance.html +4 -4
  31. package/dist/ui/operations/observability.html +22 -9
  32. package/dist/ui/operations/previews.html +4 -4
  33. package/dist/ui/operations/queue.html +4 -4
  34. package/dist/ui/operations/regions.html +4 -4
  35. package/dist/ui/operations/releases.html +4 -4
  36. package/dist/ui/operations/workloads.html +22 -9
  37. package/dist/ui/security.html +2 -2
  38. package/dist/ui/server/actions.html +3 -3
  39. package/dist/ui/server/activity.html +1 -1
  40. package/dist/ui/server/capacity.html +4 -4
  41. package/dist/ui/server/database.html +4 -4
  42. package/dist/ui/server/deployments.html +4 -4
  43. package/dist/ui/server/diagnostics.html +2 -2
  44. package/dist/ui/server/firewall.html +4 -4
  45. package/dist/ui/server/fleet.html +22 -9
  46. package/dist/ui/server/logs.html +22 -9
  47. package/dist/ui/server/metrics.html +130 -14
  48. package/dist/ui/server/security.html +2 -2
  49. package/dist/ui/server/services.html +2 -2
  50. package/dist/ui/server/sites.html +22 -9
  51. package/dist/ui/server/ssh-keys.html +4 -4
  52. package/dist/ui/server/team.html +4 -4
  53. package/dist/ui/server/terminal.html +2 -2
  54. package/dist/ui/serverless/alarms.html +3 -3
  55. package/dist/ui/serverless/assets.html +2 -2
  56. package/dist/ui/serverless/cost.html +2 -2
  57. package/dist/ui/serverless/data.html +4 -4
  58. package/dist/ui/serverless/deployments.html +2 -2
  59. package/dist/ui/serverless/firewall.html +2 -2
  60. package/dist/ui/serverless/functions.html +4 -4
  61. package/dist/ui/serverless/logs.html +21 -8
  62. package/dist/ui/serverless/metrics.html +2 -2
  63. package/dist/ui/serverless/queues.html +4 -4
  64. package/dist/ui/serverless/secrets.html +4 -4
  65. package/dist/ui/serverless/traces.html +4 -4
  66. package/dist/ui/serverless.html +4 -4
  67. package/dist/ui-src/functions/requestJson.ts +41 -0
  68. package/dist/ui-src/functions/useOpConfirm.ts +132 -0
  69. package/dist/ui-src/functions/usePolling.ts +98 -0
  70. package/dist/ui-src/functions/useTerminal.ts +79 -0
  71. package/dist/ui-src/pages/infrastructure/topology.stx +517 -0
  72. package/dist/ui-src/pages/partials/nav.stx +2 -0
  73. package/dist/ui-src/pages/server/metrics.stx +140 -6
  74. package/dist/ui-src/src/topology.ts +485 -0
  75. package/package.json +3 -3
@@ -47,7 +47,7 @@ import {
47
47
  resolveSiteKind,
48
48
  resolveUiSource,
49
49
  siteInstallBase
50
- } from "./chunk-dmzez5f8.js";
50
+ } from "./chunk-33sy72r8.js";
51
51
  import {
52
52
  artifactKey,
53
53
  buildCloudFormationTemplate,
@@ -7209,6 +7209,15 @@ var dashboardPageRoutes = [
7209
7209
  modes: ["server"],
7210
7210
  adminOnly: true
7211
7211
  },
7212
+ {
7213
+ id: "infrastructure.topology",
7214
+ path: "/infrastructure/topology",
7215
+ label: "Infrastructure map",
7216
+ group: "infrastructure",
7217
+ modes: ["server", "serverless"],
7218
+ adminOnly: true,
7219
+ legacyPaths: ["/topology"]
7220
+ },
7212
7221
  {
7213
7222
  id: "data.services",
7214
7223
  path: "/data/services",
@@ -8876,6 +8885,554 @@ class WAFv2Client {
8876
8885
  }
8877
8886
  }
8878
8887
 
8888
+ // src/deploy/dashboard-topology.ts
8889
+ var SERVER_LAYERS = [
8890
+ { index: 0, id: "clients", label: "Clients" },
8891
+ { index: 1, id: "dns", label: "DNS & TLS" },
8892
+ { index: 2, id: "edge", label: "Edge" },
8893
+ { index: 3, id: "routing", label: "Routing" },
8894
+ { index: 4, id: "apps", label: "Applications" },
8895
+ { index: 5, id: "runtime", label: "Background runtime" },
8896
+ { index: 6, id: "data", label: "Data services" },
8897
+ { index: 7, id: "durability", label: "Durability" }
8898
+ ];
8899
+ var SERVERLESS_LAYERS = [
8900
+ { index: 0, id: "clients", label: "Clients" },
8901
+ { index: 1, id: "dns", label: "DNS & TLS" },
8902
+ { index: 2, id: "edge", label: "Edge" },
8903
+ { index: 3, id: "entry", label: "Entry points" },
8904
+ { index: 4, id: "compute", label: "Compute" },
8905
+ { index: 5, id: "messaging", label: "Messaging & pooling" },
8906
+ { index: 6, id: "data", label: "Data services" },
8907
+ { index: 7, id: "durability", label: "Durability" }
8908
+ ];
8909
+ var DATA_SERVICE_KINDS = [
8910
+ { match: /^redis/, kind: "cache", label: "Redis" },
8911
+ { match: /^(?:mysql|mariadb)/, kind: "database", label: "MySQL" },
8912
+ { match: /^postgres/, kind: "database", label: "PostgreSQL" },
8913
+ { match: /^meilisearch/, kind: "database", label: "Meilisearch" },
8914
+ { match: /^vitess|^vtgate/, kind: "database", label: "Vitess" },
8915
+ { match: /^memcached/, kind: "cache", label: "Memcached" }
8916
+ ];
8917
+ var PROXY_SERVICES = /^(?:rpx-gateway|nginx|caddy|traefik|haproxy)$/;
8918
+ function text(value, fallback = "") {
8919
+ const out = value == null ? "" : String(value).trim();
8920
+ return out && out !== "-" && out !== "—" ? out : fallback;
8921
+ }
8922
+ function count(value) {
8923
+ return Array.isArray(value) ? value.length : 0;
8924
+ }
8925
+ function toneOf(status) {
8926
+ const value = text(status).toLowerCase();
8927
+ if (!value)
8928
+ return "unknown";
8929
+ if (["running", "live", "active", "available", "success", "ok", "healthy", "enabled"].includes(value))
8930
+ return "ok";
8931
+ if (["failed", "stopped", "unreachable", "error", "critical", "disabled"].includes(value))
8932
+ return "bad";
8933
+ if (["configured", "creating", "pending", "degraded", "shadowed", "warn", "unknown"].includes(value))
8934
+ return "warn";
8935
+ return "warn";
8936
+ }
8937
+ function pushLink(links, link, ids) {
8938
+ if (ids.has(link.source) && ids.has(link.target))
8939
+ links.push(link);
8940
+ }
8941
+ function buildServerTopology(data, context = {}) {
8942
+ const nodes = [];
8943
+ const links = [];
8944
+ const notes = [];
8945
+ const server = data.server ?? {};
8946
+ const sites = Array.isArray(data.sites) ? data.sites : [];
8947
+ const services = Array.isArray(data.services) ? data.services : [];
8948
+ const workers = Array.isArray(data.workers) ? data.workers : [];
8949
+ const security = data.security ?? {};
8950
+ const reachable = !!data._serverReachable && !data.metricsUnavailable;
8951
+ const box2 = "box";
8952
+ if (!reachable)
8953
+ notes.push("The compute box could not be probed, so live service state is missing; the shape comes from your cloud config.");
8954
+ nodes.push({
8955
+ id: "internet",
8956
+ label: "Internet",
8957
+ kind: "internet",
8958
+ layer: 0,
8959
+ status: "ok",
8960
+ sub: "Public traffic",
8961
+ facts: [],
8962
+ external: true
8963
+ });
8964
+ const domains2 = [
8965
+ ...new Set(sites.map((site) => text(site.domain) || text(site.route).split("/")[0]).filter((domain) => domain && domain !== "internal" && domain !== "loopback"))
8966
+ ];
8967
+ const certificates = Array.isArray(security.tlsCertificates) ? security.tlsCertificates : [];
8968
+ const expiringSoon = certificates.filter((cert) => Number(cert?.daysRemaining ?? 999) < 21).length;
8969
+ nodes.push({
8970
+ id: "dns",
8971
+ label: domains2.length === 1 ? domains2[0] : "Public domains",
8972
+ kind: "dns",
8973
+ layer: 1,
8974
+ status: expiringSoon ? "warn" : domains2.length ? "ok" : "unknown",
8975
+ sub: domains2.length > 1 ? `${domains2.length} domains` : "DNS & certificates",
8976
+ facts: [
8977
+ { label: "Domains", value: domains2.length ? domains2.slice(0, 6).join(", ") : "none resolved" },
8978
+ { label: "Certificates", value: certificates.length ? `${certificates.length} issued` : "none reported" },
8979
+ ...expiringSoon ? [{ label: "Expiring", value: `${expiringSoon} within 21 days` }] : []
8980
+ ],
8981
+ href: "/server/security",
8982
+ external: true
8983
+ });
8984
+ const firewall = security.firewall ?? {};
8985
+ const ports2 = Array.isArray(security.ports) ? security.ports : [];
8986
+ nodes.push({
8987
+ id: "firewall",
8988
+ label: "Host firewall",
8989
+ kind: "firewall",
8990
+ layer: 2,
8991
+ status: toneOf(firewall.status),
8992
+ sub: text(firewall.summary, "Inbound filtering"),
8993
+ facts: [
8994
+ { label: "State", value: text(firewall.status, "unknown") },
8995
+ { label: "Open ports", value: ports2.length ? ports2.map((port) => text(port.listen)).join(", ") : "not reported" }
8996
+ ],
8997
+ href: "/server/firewall",
8998
+ group: box2
8999
+ });
9000
+ const proxyService = services.find((service3) => PROXY_SERVICES.test(text(service3.name)));
9001
+ nodes.push({
9002
+ id: "proxy",
9003
+ label: text(proxyService?.name, "Reverse proxy"),
9004
+ kind: "proxy",
9005
+ layer: 3,
9006
+ status: proxyService ? toneOf(proxyService.status) : "unknown",
9007
+ sub: "TLS termination & routing",
9008
+ facts: [
9009
+ { label: "Service", value: text(proxyService?.name, "not detected") },
9010
+ { label: "State", value: text(proxyService?.status, reachable ? "not running" : "not probed") },
9011
+ { label: "Routes", value: `${sites.length} site${sites.length === 1 ? "" : "s"}` }
9012
+ ],
9013
+ href: "/server/services",
9014
+ group: box2
9015
+ });
9016
+ for (const site of sites) {
9017
+ const id = `site:${text(site.name)}`;
9018
+ if (!text(site.name))
9019
+ continue;
9020
+ nodes.push({
9021
+ id,
9022
+ label: text(site.name),
9023
+ kind: "site",
9024
+ layer: 4,
9025
+ status: toneOf(site.status),
9026
+ sub: text(site.runtime, "runtime"),
9027
+ facts: [
9028
+ { label: "Route", value: text(site.route, "internal") },
9029
+ { label: "Runtime", value: text(site.runtime, "unknown") },
9030
+ { label: "Delivery", value: text(site.deploy, "unknown") },
9031
+ { label: "TLS", value: text(site.tls, "unknown") },
9032
+ ...site.responseMs != null ? [{ label: "Response", value: `${Math.round(Number(site.responseMs))} ms` }] : [],
9033
+ ...site.httpStatus != null ? [{ label: "HTTP", value: String(site.httpStatus) }] : []
9034
+ ],
9035
+ href: "/server/sites",
9036
+ group: box2
9037
+ });
9038
+ }
9039
+ for (const worker2 of workers) {
9040
+ const id = `worker:${text(worker2.name)}`;
9041
+ if (!text(worker2.name))
9042
+ continue;
9043
+ nodes.push({
9044
+ id,
9045
+ label: text(worker2.name),
9046
+ kind: "worker",
9047
+ layer: 5,
9048
+ status: toneOf(worker2.status),
9049
+ sub: `${Number(worker2.processes ?? 1)} process${Number(worker2.processes ?? 1) === 1 ? "" : "es"}`,
9050
+ facts: [
9051
+ { label: "Processes", value: String(worker2.processes ?? 1) },
9052
+ { label: "State", value: text(worker2.status, "unknown") },
9053
+ ...text(worker2.site) ? [{ label: "Site", value: text(worker2.site) }] : []
9054
+ ],
9055
+ href: "/operations/jobs",
9056
+ group: box2
9057
+ });
9058
+ }
9059
+ const scheduler = data.serverScheduler ?? {};
9060
+ if (scheduler.enabled) {
9061
+ nodes.push({
9062
+ id: "scheduler",
9063
+ label: "Scheduler",
9064
+ kind: "scheduler",
9065
+ layer: 5,
9066
+ status: "ok",
9067
+ sub: "Cron dispatch",
9068
+ facts: [{ label: "Last run", value: text(scheduler.lastRun, "unknown") }],
9069
+ href: "/operations/jobs",
9070
+ group: box2
9071
+ });
9072
+ }
9073
+ const dataServices = services.map((service3) => {
9074
+ const name = text(service3.name);
9075
+ const match = DATA_SERVICE_KINDS.find((candidate) => candidate.match.test(name));
9076
+ return match ? { service: service3, name, ...match } : null;
9077
+ }).filter((entry) => !!entry);
9078
+ for (const entry of dataServices) {
9079
+ nodes.push({
9080
+ id: `data:${entry.name}`,
9081
+ label: entry.label,
9082
+ kind: entry.kind,
9083
+ layer: 6,
9084
+ status: toneOf(entry.service.status),
9085
+ sub: entry.name,
9086
+ facts: [
9087
+ { label: "Unit", value: entry.name },
9088
+ { label: "State", value: text(entry.service.status, "unknown") }
9089
+ ],
9090
+ href: "/data/services",
9091
+ group: box2
9092
+ });
9093
+ }
9094
+ const backup = data.backup ?? {};
9095
+ if (backup.enabled) {
9096
+ nodes.push({
9097
+ id: "backup",
9098
+ label: "Backups",
9099
+ kind: "backup",
9100
+ layer: 7,
9101
+ status: "ok",
9102
+ sub: text(backup.destination, "destination"),
9103
+ facts: [
9104
+ { label: "Destination", value: text(backup.destination, "unknown") },
9105
+ { label: "Schedule", value: text(backup.schedule, "unknown") },
9106
+ { label: "Retention", value: `${backup.retention ?? 0} copies` },
9107
+ { label: "Last run", value: text(backup.last, "unknown") }
9108
+ ],
9109
+ href: "/data/backups"
9110
+ });
9111
+ }
9112
+ const ids = new Set(nodes.map((node) => node.id));
9113
+ pushLink(links, { source: "internet", target: "dns", flow: "request", label: "https" }, ids);
9114
+ pushLink(links, { source: "dns", target: "firewall", flow: "request", label: "443/80" }, ids);
9115
+ pushLink(links, { source: "firewall", target: "proxy", flow: "request" }, ids);
9116
+ for (const site of sites) {
9117
+ const id = `site:${text(site.name)}`;
9118
+ pushLink(links, { source: "proxy", target: id, flow: "request", label: text(site.path) || undefined, status: toneOf(site.status) }, ids);
9119
+ }
9120
+ for (const worker2 of workers) {
9121
+ const id = `worker:${text(worker2.name)}`;
9122
+ const site = `site:${text(worker2.site)}`;
9123
+ if (ids.has(site))
9124
+ pushLink(links, { source: site, target: id, flow: "async", label: "queue" }, ids);
9125
+ else
9126
+ pushLink(links, { source: "proxy", target: id, flow: "async", label: "queue" }, ids);
9127
+ }
9128
+ for (const worker2 of workers) {
9129
+ pushLink(links, { source: "scheduler", target: `worker:${text(worker2.name)}`, flow: "async", label: "dispatch" }, ids);
9130
+ }
9131
+ const consumers = [...sites.map((site) => `site:${text(site.name)}`), ...workers.map((worker2) => `worker:${text(worker2.name)}`)];
9132
+ for (const entry of dataServices) {
9133
+ for (const consumer of consumers)
9134
+ pushLink(links, { source: consumer, target: `data:${entry.name}`, flow: "data", label: "localhost" }, ids);
9135
+ }
9136
+ if (dataServices.length && consumers.length)
9137
+ notes.push("Data-service edges show co-location on the box: every process on it can reach localhost. They are not a declared per-site binding.");
9138
+ for (const entry of dataServices) {
9139
+ pushLink(links, { source: `data:${entry.name}`, target: "backup", flow: "backup", label: "snapshot" }, ids);
9140
+ }
9141
+ for (const site of sites) {
9142
+ pushLink(links, { source: `site:${text(site.name)}`, target: "backup", flow: "backup" }, ids);
9143
+ }
9144
+ return {
9145
+ mode: "server",
9146
+ project: text(context.project) || text(data.project?.name) || text(server.name, "project"),
9147
+ environment: text(context.environment) || text(data.environment, "production"),
9148
+ provider: text(server.provider, "unknown"),
9149
+ region: text(server.region, "unknown"),
9150
+ generatedAt: new Date().toISOString(),
9151
+ layers: SERVER_LAYERS,
9152
+ groups: [
9153
+ {
9154
+ id: box2,
9155
+ label: text(server.name, "App server"),
9156
+ sub: [text(server.provider), text(server.region), text(server.ip)].filter(Boolean).join(" · ")
9157
+ }
9158
+ ],
9159
+ nodes,
9160
+ links,
9161
+ notes
9162
+ };
9163
+ }
9164
+ function buildServerlessTopology(data, context = {}) {
9165
+ const nodes = [];
9166
+ const links = [];
9167
+ const notes = [];
9168
+ const app = data.app ?? {};
9169
+ const functions = Array.isArray(data.functions) ? data.functions : [];
9170
+ const queues = Array.isArray(data.queues) ? data.queues : [];
9171
+ const metrics = data.metrics ?? {};
9172
+ if (!functions.length)
9173
+ notes.push("No Lambda functions were read for this environment, so the compute layer is empty rather than assumed.");
9174
+ nodes.push({
9175
+ id: "internet",
9176
+ label: "Internet",
9177
+ kind: "internet",
9178
+ layer: 0,
9179
+ status: "ok",
9180
+ sub: "Public traffic",
9181
+ facts: [],
9182
+ external: true
9183
+ });
9184
+ const url = text(app.url);
9185
+ const host = url.replace(/^https?:\/\//, "").split("/")[0];
9186
+ nodes.push({
9187
+ id: "dns",
9188
+ label: host || "Application domain",
9189
+ kind: "dns",
9190
+ layer: 1,
9191
+ status: host ? "ok" : "unknown",
9192
+ sub: "DNS & certificates",
9193
+ facts: [{ label: "Endpoint", value: url || "not resolved" }],
9194
+ external: true
9195
+ });
9196
+ const waf = data.waf;
9197
+ if (waf) {
9198
+ nodes.push({
9199
+ id: "waf",
9200
+ label: "WAF",
9201
+ kind: "waf",
9202
+ layer: 2,
9203
+ status: toneOf(waf.status ?? "active"),
9204
+ sub: text(waf.scope, "Managed rules"),
9205
+ facts: [
9206
+ { label: "Web ACL", value: text(waf.name, "unnamed") },
9207
+ ...waf.blocked != null ? [{ label: "Blocked (24h)", value: String(waf.blocked) }] : [],
9208
+ { label: "Rules", value: String(count(data.wafRules)) }
9209
+ ],
9210
+ href: "/serverless/firewall"
9211
+ });
9212
+ }
9213
+ const assets = data.assetsInfo;
9214
+ if (assets) {
9215
+ nodes.push({
9216
+ id: "cdn",
9217
+ label: "Asset CDN",
9218
+ kind: "cdn",
9219
+ layer: 2,
9220
+ status: "ok",
9221
+ sub: text(assets.cdn, "CloudFront"),
9222
+ facts: [
9223
+ { label: "Distribution", value: text(assets.cdn, "unknown") },
9224
+ { label: "Custom domain", value: text(assets.customDomain, "none") }
9225
+ ],
9226
+ href: "/serverless/assets"
9227
+ });
9228
+ nodes.push({
9229
+ id: "assets",
9230
+ label: "Asset bucket",
9231
+ kind: "storage",
9232
+ layer: 6,
9233
+ status: "ok",
9234
+ sub: text(assets.bucket, "S3"),
9235
+ facts: [
9236
+ { label: "Bucket", value: text(assets.bucket, "unknown") },
9237
+ ...assets.files ? [{ label: "Objects", value: String(assets.files) }] : [],
9238
+ ...assets.sizeMb ? [{ label: "Size", value: `${assets.sizeMb} MB` }] : []
9239
+ ],
9240
+ href: "/serverless/assets"
9241
+ });
9242
+ }
9243
+ const httpFunction = functions.find((fn) => text(fn.key).startsWith("http"));
9244
+ nodes.push({
9245
+ id: "gateway",
9246
+ label: "HTTP entry point",
9247
+ kind: "gateway",
9248
+ layer: 3,
9249
+ status: httpFunction ? "ok" : "unknown",
9250
+ sub: "Function URL / API Gateway",
9251
+ facts: [
9252
+ { label: "Endpoint", value: url || "not resolved" },
9253
+ { label: "Invocations (24h)", value: String(metrics.invocations ?? 0) },
9254
+ ...metrics.errorRatePct != null ? [{ label: "Error rate", value: `${metrics.errorRatePct}%` }] : []
9255
+ ],
9256
+ href: "/serverless/metrics"
9257
+ });
9258
+ for (const fn of functions) {
9259
+ const id = `fn:${text(fn.name)}`;
9260
+ if (!text(fn.name))
9261
+ continue;
9262
+ const errors = Number(fn.errors ?? 0);
9263
+ const invocations = Number(fn.invocations ?? 0);
9264
+ nodes.push({
9265
+ id,
9266
+ label: text(fn.key, text(fn.name)),
9267
+ kind: "function",
9268
+ layer: 4,
9269
+ status: errors > 0 ? invocations && errors / invocations > 0.02 ? "bad" : "warn" : invocations ? "ok" : "idle",
9270
+ sub: text(fn.runtime, "lambda"),
9271
+ facts: [
9272
+ { label: "Function", value: text(fn.name) },
9273
+ { label: "Memory", value: `${fn.memory ?? 0} MB` },
9274
+ { label: "Timeout", value: `${fn.timeout ?? 0}s` },
9275
+ { label: "Invocations (24h)", value: String(invocations) },
9276
+ { label: "Errors (24h)", value: String(errors) },
9277
+ { label: "p95", value: `${fn.p95 ?? 0} ms` },
9278
+ ...fn.provisioned ? [{ label: "Provisioned", value: text(fn.provisioned) }] : []
9279
+ ],
9280
+ href: "/serverless/functions"
9281
+ });
9282
+ }
9283
+ for (const queue2 of queues) {
9284
+ const id = `queue:${text(queue2.name)}`;
9285
+ if (!text(queue2.name))
9286
+ continue;
9287
+ const dlq = Number(queue2.dlq ?? 0);
9288
+ nodes.push({
9289
+ id,
9290
+ label: text(queue2.name),
9291
+ kind: "queue",
9292
+ layer: 5,
9293
+ status: dlq > 0 ? "bad" : Number(queue2.visible ?? 0) > 0 ? "warn" : "ok",
9294
+ sub: "SQS",
9295
+ facts: [
9296
+ { label: "Visible", value: String(queue2.visible ?? 0) },
9297
+ { label: "In flight", value: String(queue2.inFlight ?? 0) },
9298
+ { label: "Processed (24h)", value: String(queue2.processed ?? 0) },
9299
+ { label: "Dead letter", value: String(dlq) }
9300
+ ],
9301
+ href: "/serverless/queues"
9302
+ });
9303
+ }
9304
+ const aurora = data.aurora;
9305
+ if (aurora) {
9306
+ nodes.push({
9307
+ id: "database",
9308
+ label: "Aurora Serverless",
9309
+ kind: "database",
9310
+ layer: 6,
9311
+ status: toneOf(aurora.status),
9312
+ sub: text(aurora.engine, "aurora"),
9313
+ facts: [
9314
+ { label: "Cluster", value: text(aurora.id, "unknown") },
9315
+ { label: "Capacity", value: `${aurora.currentAcu ?? 0} ACU (${aurora.minAcu ?? 0}–${aurora.maxAcu ?? 0})` },
9316
+ { label: "Connections", value: String(aurora.connections ?? 0) }
9317
+ ],
9318
+ href: "/serverless/data"
9319
+ });
9320
+ }
9321
+ const proxy = data.proxy;
9322
+ if (proxy) {
9323
+ nodes.push({
9324
+ id: "rds-proxy",
9325
+ label: "RDS Proxy",
9326
+ kind: "database",
9327
+ layer: 5,
9328
+ status: toneOf(proxy.status),
9329
+ sub: "Connection pooling",
9330
+ facts: [
9331
+ { label: "Proxy", value: text(proxy.name, "unknown") },
9332
+ { label: "Endpoint", value: text(proxy.endpoint, "unknown") },
9333
+ { label: "Pooled", value: String(proxy.pooledConns ?? 0) }
9334
+ ],
9335
+ href: "/serverless/data"
9336
+ });
9337
+ }
9338
+ const redis = data.redis;
9339
+ if (redis) {
9340
+ nodes.push({
9341
+ id: "cache",
9342
+ label: "ElastiCache",
9343
+ kind: "cache",
9344
+ layer: 6,
9345
+ status: toneOf(redis.status),
9346
+ sub: text(redis.node, "redis"),
9347
+ facts: [
9348
+ { label: "Group", value: text(redis.id, "unknown") },
9349
+ { label: "Hit rate", value: `${redis.hitRate ?? 0}%` }
9350
+ ],
9351
+ href: "/serverless/data"
9352
+ });
9353
+ }
9354
+ const efs = data.efs;
9355
+ if (efs) {
9356
+ nodes.push({
9357
+ id: "efs",
9358
+ label: "EFS",
9359
+ kind: "filesystem",
9360
+ layer: 6,
9361
+ status: toneOf(efs.status),
9362
+ sub: text(efs.mount, "/mnt/local"),
9363
+ facts: [
9364
+ { label: "File system", value: text(efs.id, "unknown") },
9365
+ { label: "Mount", value: text(efs.mount, "unknown") },
9366
+ { label: "Size", value: `${efs.sizeMb ?? 0} MB` }
9367
+ ],
9368
+ href: "/data/volumes"
9369
+ });
9370
+ }
9371
+ const scheduler = data.scheduler ?? {};
9372
+ if (scheduler.enabled) {
9373
+ nodes.push({
9374
+ id: "scheduler",
9375
+ label: "Scheduler",
9376
+ kind: "scheduler",
9377
+ layer: 3,
9378
+ status: "ok",
9379
+ sub: text(scheduler.expression, "rate(1 minute)"),
9380
+ facts: [
9381
+ { label: "Expression", value: text(scheduler.expression, "unknown") },
9382
+ { label: "Last run", value: text(scheduler.lastRun, "unknown") }
9383
+ ],
9384
+ href: "/operations/jobs"
9385
+ });
9386
+ }
9387
+ const ids = new Set(nodes.map((node) => node.id));
9388
+ const entry = ids.has("waf") ? "waf" : "gateway";
9389
+ pushLink(links, { source: "internet", target: "dns", flow: "request", label: "https" }, ids);
9390
+ pushLink(links, { source: "dns", target: entry, flow: "request" }, ids);
9391
+ pushLink(links, { source: "dns", target: "cdn", flow: "request", label: "assets" }, ids);
9392
+ pushLink(links, { source: "waf", target: "gateway", flow: "request", label: "allowed" }, ids);
9393
+ pushLink(links, { source: "cdn", target: "assets", flow: "data", label: "origin" }, ids);
9394
+ const httpFunctions = functions.filter((fn) => text(fn.key).startsWith("http"));
9395
+ const queueFunctions = functions.filter((fn) => text(fn.key).startsWith("queue"));
9396
+ const cliFunctions = functions.filter((fn) => text(fn.key).startsWith("cli"));
9397
+ for (const fn of httpFunctions)
9398
+ pushLink(links, { source: "gateway", target: `fn:${text(fn.name)}`, flow: "request", label: "invoke" }, ids);
9399
+ for (const fn of cliFunctions)
9400
+ pushLink(links, { source: "scheduler", target: `fn:${text(fn.name)}`, flow: "async", label: "schedule" }, ids);
9401
+ for (const queue2 of queues) {
9402
+ for (const fn of httpFunctions)
9403
+ pushLink(links, { source: `fn:${text(fn.name)}`, target: `queue:${text(queue2.name)}`, flow: "async", label: "publish" }, ids);
9404
+ for (const fn of queueFunctions)
9405
+ pushLink(links, { source: `queue:${text(queue2.name)}`, target: `fn:${text(fn.name)}`, flow: "async", label: "consume" }, ids);
9406
+ }
9407
+ const stateful = ["rds-proxy", "database", "cache", "efs", "assets"];
9408
+ for (const fn of functions) {
9409
+ const id = `fn:${text(fn.name)}`;
9410
+ if (ids.has("rds-proxy"))
9411
+ pushLink(links, { source: id, target: "rds-proxy", flow: "data", label: "pooled" }, ids);
9412
+ else
9413
+ pushLink(links, { source: id, target: "database", flow: "data", label: "sql" }, ids);
9414
+ for (const target of stateful.filter((candidate) => candidate !== "rds-proxy" && candidate !== "database"))
9415
+ pushLink(links, { source: id, target, flow: "data" }, ids);
9416
+ }
9417
+ pushLink(links, { source: "rds-proxy", target: "database", flow: "data", label: "sql" }, ids);
9418
+ return {
9419
+ mode: "serverless",
9420
+ project: text(context.project) || text(app.name, "application"),
9421
+ environment: text(context.environment) || text(app.env, "production"),
9422
+ provider: "aws",
9423
+ region: text(app.region, "unknown"),
9424
+ generatedAt: new Date().toISOString(),
9425
+ layers: SERVERLESS_LAYERS,
9426
+ groups: [],
9427
+ nodes,
9428
+ links,
9429
+ notes
9430
+ };
9431
+ }
9432
+ function buildTopology(mode, data, context = {}) {
9433
+ return mode === "serverless" ? buildServerlessTopology(data, context) : buildServerTopology(data, context);
9434
+ }
9435
+
8879
9436
  // src/deploy/dashboard-data.ts
8880
9437
  function pad(n) {
8881
9438
  return String(n).padStart(2, "0");
@@ -9373,8 +9930,91 @@ async function resolveDashboardData(config, environment2) {
9373
9930
  out.schedulerTasks = tasks;
9374
9931
  } catch {}
9375
9932
  }
9933
+ out.topology = buildServerlessTopology(out, { project: config.project.name, environment: environment2 });
9376
9934
  return out;
9377
9935
  }
9936
+ // src/deploy/egress-collection.ts
9937
+ var MAX_REPLAY_DAYS = 45;
9938
+ var FETCH_TIMEOUT_MS = 1e4;
9939
+ function finite(value) {
9940
+ const parsed = Number(value);
9941
+ return Number.isFinite(parsed) && parsed >= 0 ? parsed : null;
9942
+ }
9943
+ function dayTimestamp(date) {
9944
+ if (!/^\d{4}-\d{2}-\d{2}$/.test(date))
9945
+ return null;
9946
+ const parsed = Date.parse(`${date}T12:00:00.000Z`);
9947
+ return Number.isFinite(parsed) ? new Date(parsed).toISOString() : null;
9948
+ }
9949
+ function egressReportMetrics(report, options) {
9950
+ if (!report || typeof report !== "object")
9951
+ return [];
9952
+ const attributes = { source: options.name };
9953
+ const metrics = [];
9954
+ const at = options.now;
9955
+ const push = (name, value, unit, timestamp = at) => {
9956
+ if (value !== null)
9957
+ metrics.push({ name, value, unit, timestamp, attributes });
9958
+ };
9959
+ push("storage.egress.month", finite(report.monthBytes), "bytes");
9960
+ push("storage.egress.today", finite(report.todayBytes), "bytes");
9961
+ push("storage.egress.projected_month", finite(report.projectedMonthBytes), "bytes");
9962
+ const budget = finite(report.budgetBytes);
9963
+ if (budget !== null && budget > 0) {
9964
+ push("storage.egress.budget", budget, "bytes");
9965
+ const reported = report.budgetUsedPercent;
9966
+ const percent = typeof reported === "number" && Number.isFinite(reported) ? reported : finite(report.monthBytes) !== null ? Math.round(finite(report.monthBytes) / budget * 1000) / 10 : null;
9967
+ push("storage.egress.budget_used_percent", percent, "percent");
9968
+ }
9969
+ const limit = options.maxReplayDays ?? MAX_REPLAY_DAYS;
9970
+ const days = Array.isArray(report.days) ? report.days.slice(-limit) : [];
9971
+ for (const day of days) {
9972
+ const timestamp = dayTimestamp(String(day?.date ?? ""));
9973
+ const bytes = finite(day?.bytes);
9974
+ if (!timestamp || bytes === null)
9975
+ continue;
9976
+ metrics.push({ name: "storage.egress.day", value: bytes, unit: "bytes", timestamp, attributes });
9977
+ const downloads = finite(day?.downloads);
9978
+ if (downloads !== null)
9979
+ metrics.push({ name: "storage.egress.day_downloads", value: downloads, unit: "count", timestamp, attributes });
9980
+ }
9981
+ return metrics;
9982
+ }
9983
+ async function fetchEgressReport(endpoint, env = process.env, fetchImpl = fetch) {
9984
+ const token = endpoint.tokenEnv ? env[endpoint.tokenEnv] : undefined;
9985
+ const response = await fetchImpl(endpoint.url, {
9986
+ headers: token ? { Authorization: `Bearer ${token}` } : {},
9987
+ signal: AbortSignal.timeout(FETCH_TIMEOUT_MS)
9988
+ });
9989
+ if (!response.ok)
9990
+ throw new Error(`HTTP ${response.status}`);
9991
+ return await response.json();
9992
+ }
9993
+ async function collectEgressMetrics(endpoints, options) {
9994
+ const metrics = [];
9995
+ const errors = [];
9996
+ if (!Array.isArray(endpoints) || endpoints.length === 0)
9997
+ return { metrics, errors };
9998
+ const results = await Promise.all(endpoints.map(async (endpoint) => {
9999
+ if (!endpoint?.name || !endpoint?.url)
10000
+ return { endpoint, error: "egress endpoint needs both a name and a url" };
10001
+ try {
10002
+ const report = await fetchEgressReport(endpoint, options.env, options.fetchImpl);
10003
+ return { endpoint, report };
10004
+ } catch (error2) {
10005
+ return { endpoint, error: error2 instanceof Error ? error2.message : String(error2) };
10006
+ }
10007
+ }));
10008
+ for (const result of results) {
10009
+ const source = `egress:${result.endpoint?.name ?? "unnamed"}`;
10010
+ if ("error" in result && result.error) {
10011
+ errors.push({ source, message: result.error });
10012
+ continue;
10013
+ }
10014
+ metrics.push(...egressReportMetrics(result.report, { name: result.endpoint.name, now: options.now }));
10015
+ }
10016
+ return { metrics, errors };
10017
+ }
9378
10018
  // src/deploy/local-dashboard-server.ts
9379
10019
  import { createHash as createHash33 } from "node:crypto";
9380
10020
  import { existsSync as existsSync20, mkdtempSync as mkdtempSync3, readdirSync as readdirSync8, rmSync as rmSync3, statSync as statSync6 } from "node:fs";
@@ -10238,7 +10878,7 @@ class NotificationRouter {
10238
10878
  }
10239
10879
  async send(channel2, body) {
10240
10880
  const secret = credential(this.store.channelCredential(channel2.id));
10241
- const text = typeof body.notificationText === "string" ? body.notificationText : `[${String(body.alert?.severity ?? "info").toUpperCase()}] ${String(body.alert?.title ?? "ts-cloud alert")} · ${String(body.alert?.state ?? body.event)}`;
10881
+ const text2 = typeof body.notificationText === "string" ? body.notificationText : `[${String(body.alert?.severity ?? "info").toUpperCase()}] ${String(body.alert?.title ?? "ts-cloud alert")} · ${String(body.alert?.state ?? body.event)}`;
10242
10882
  try {
10243
10883
  if (channel2.kind === "email") {
10244
10884
  if (!this.options.emailImpl)
@@ -10247,7 +10887,7 @@ class NotificationRouter {
10247
10887
  to: channel2.config.to,
10248
10888
  from: channel2.config.from,
10249
10889
  subject: `[ts-cloud] ${String(body.alert?.title ?? body.event)}`,
10250
- text: `${text}
10890
+ text: `${text2}
10251
10891
 
10252
10892
  ${JSON.stringify(body, null, 2)}`
10253
10893
  });
@@ -10257,15 +10897,15 @@ ${JSON.stringify(body, null, 2)}`
10257
10897
  let posted = body;
10258
10898
  const headers = { "content-type": "application/json" };
10259
10899
  if (channel2.kind === "slack")
10260
- posted = { text };
10900
+ posted = { text: text2 };
10261
10901
  if (channel2.kind === "discord")
10262
- posted = { content: text };
10902
+ posted = { content: text2 };
10263
10903
  if (channel2.kind === "teams")
10264
- posted = { text };
10904
+ posted = { text: text2 };
10265
10905
  if (channel2.kind === "telegram") {
10266
10906
  const token = secret.botToken ?? secret.value;
10267
10907
  url = `https://api.telegram.org/bot${token}/sendMessage`;
10268
- posted = { chat_id: channel2.config.chatId, text };
10908
+ posted = { chat_id: channel2.config.chatId, text: text2 };
10269
10909
  }
10270
10910
  if (!url || !/^https:\/\//.test(url))
10271
10911
  return { ok: false, error: "Channel endpoint is unavailable or not HTTPS." };
@@ -10392,9 +11032,9 @@ class HealthCheckRunner {
10392
11032
  redirect: "manual"
10393
11033
  });
10394
11034
  const ttfbMs = performance.now() - requestStart;
10395
- const text = check.config.expectedBody ? await response.text() : "";
11035
+ const text2 = check.config.expectedBody ? await response.text() : "";
10396
11036
  const expected = check.config.expectedStatuses ?? [200, 201, 202, 204];
10397
- const ok = expected.includes(response.status) && (!check.config.expectedBody || text.includes(check.config.expectedBody));
11037
+ const ok = expected.includes(response.status) && (!check.config.expectedBody || text2.includes(check.config.expectedBody));
10398
11038
  return {
10399
11039
  checkId: check.id,
10400
11040
  status: ok ? "healthy" : "unhealthy",
@@ -11758,9 +12398,9 @@ function inspectApplicationArchive(bytes, filename, options = {}) {
11758
12398
  const header2 = bytes.subarray(offset, offset + 512);
11759
12399
  if (header2.every((value) => value === 0))
11760
12400
  break;
11761
- const text = (start, length) => new TextDecoder().decode(header2.subarray(start, start + length)).replace(/\0.*$/, "");
11762
- const name = `${text(345, 155)}${text(345, 155) ? "/" : ""}${text(0, 100)}`;
11763
- const size = Number.parseInt(text(124, 12).trim() || "0", 8);
12401
+ const text2 = (start, length) => new TextDecoder().decode(header2.subarray(start, start + length)).replace(/\0.*$/, "");
12402
+ const name = `${text2(345, 155)}${text2(345, 155) ? "/" : ""}${text2(0, 100)}`;
12403
+ const size = Number.parseInt(text2(124, 12).trim() || "0", 8);
11764
12404
  const kind = String.fromCharCode(header2[156] ?? 0);
11765
12405
  if (!safePath(name))
11766
12406
  throw new Error(`Unsafe archive path: ${name}`);
@@ -16572,11 +17212,11 @@ function createApiV1Handler(options) {
16572
17212
  try {
16573
17213
  let body;
16574
17214
  const readBody = async () => {
16575
- const text = await request.text();
16576
- if (text.length > 1024 * 1024)
17215
+ const text2 = await request.text();
17216
+ if (text2.length > 1024 * 1024)
16577
17217
  throw new ApiServiceError("payload_too_large", "Request body exceeds 1 MB.", 413);
16578
17218
  try {
16579
- return JSON.parse(text);
17219
+ return JSON.parse(text2);
16580
17220
  } catch {
16581
17221
  throw new ApiServiceError("invalid_json", "Request body must be valid JSON.", 400);
16582
17222
  }
@@ -17487,12 +18127,12 @@ data: ${JSON.stringify(event)}
17487
18127
  const idempotencyKey = request.headers.get("idempotency-key") ?? "";
17488
18128
  if (!idempotencyKey)
17489
18129
  throw new ApiServiceError("idempotency_required", "Idempotency-Key is required for deployment requests.", 428);
17490
- const text = await request.text();
17491
- if (text.length > 1024 * 1024)
18130
+ const text2 = await request.text();
18131
+ if (text2.length > 1024 * 1024)
17492
18132
  throw new ApiServiceError("payload_too_large", "Request body exceeds 1 MB.", 413);
17493
18133
  let deployment;
17494
18134
  try {
17495
- deployment = JSON.parse(text);
18135
+ deployment = JSON.parse(text2);
17496
18136
  } catch {
17497
18137
  throw new ApiServiceError("invalid_json", "Request body must be valid JSON.", 400);
17498
18138
  }
@@ -19112,10 +19752,10 @@ function normalizeScheduleExpression(value) {
19112
19752
  description: `Cron ${expression.replace(/^cron\(|\)$/g, "")}`
19113
19753
  };
19114
19754
  }
19115
- function nextScheduleRuns(expression, zone, from = new Date, count = 5) {
19755
+ function nextScheduleRuns(expression, zone, from = new Date, count2 = 5) {
19116
19756
  const parsed = normalizeScheduleExpression(expression);
19117
19757
  const validatedZone = validateTimezone(zone);
19118
- const amount = Math.min(20, Math.max(1, count));
19758
+ const amount = Math.min(20, Math.max(1, count2));
19119
19759
  const duration2 = rateMilliseconds(parsed.normalized);
19120
19760
  if (duration2)
19121
19761
  return Array.from({ length: amount }, (_, index) => new Date(from.getTime() + duration2 * (index + 1)).toISOString());
@@ -19137,9 +19777,9 @@ function nextScheduleRuns(expression, zone, from = new Date, count = 5) {
19137
19777
  throw new Error("Schedule produced no runs within the bounded preview horizon.");
19138
19778
  return runs;
19139
19779
  }
19140
- function previewSchedule(expression, zone = "UTC", from = new Date, count = 5) {
19780
+ function previewSchedule(expression, zone = "UTC", from = new Date, count2 = 5) {
19141
19781
  const parsed = normalizeScheduleExpression(expression);
19142
- const nextRuns = nextScheduleRuns(parsed.normalized, zone, from, count);
19782
+ const nextRuns = nextScheduleRuns(parsed.normalized, zone, from, count2);
19143
19783
  const notes = parsed.kind === "cron" ? [
19144
19784
  "Server cron and EventBridge use the same wall-clock preview; provider-specific extensions are retained only when supported."
19145
19785
  ] : ["Rate schedules use elapsed-time intervals and are not shifted by DST."];
@@ -19621,14 +20261,14 @@ class JobService {
19621
20261
  for (const job2 of this.store.due(at.toISOString())) {
19622
20262
  const due = job2.nextRunAt, next = nextScheduleRuns(job2.normalizedExpression, job2.timezone, at, 1)[0];
19623
20263
  if (job2.missedRunPolicy === "catch_up") {
19624
- let cursor3 = due, count = 0;
19625
- while (cursor3 <= at.toISOString() && count < 10) {
20264
+ let cursor3 = due, count2 = 0;
20265
+ while (cursor3 <= at.toISOString() && count2 < 10) {
19626
20266
  queued.push(this.enqueue(job2, {
19627
20267
  trigger: cursor3 === due ? "scheduled" : "catch_up",
19628
20268
  scheduledFor: cursor3
19629
20269
  }));
19630
20270
  cursor3 = nextScheduleRuns(job2.normalizedExpression, job2.timezone, new Date(cursor3), 1)[0];
19631
- count++;
20271
+ count2++;
19632
20272
  }
19633
20273
  } else
19634
20274
  queued.push(this.enqueue(job2, { trigger: "scheduled", scheduledFor: due }));
@@ -20441,14 +21081,14 @@ class BackupStore {
20441
21081
  ],
20442
21082
  [policy3.retention.monthly, (date) => date.toISOString().slice(0, 7)]
20443
21083
  ];
20444
- for (const [count, bucket] of buckets) {
21084
+ for (const [count2, bucket] of buckets) {
20445
21085
  const seen = new Set;
20446
21086
  for (const item of points) {
20447
21087
  const key = bucket(new Date(item.pointInTime));
20448
21088
  if (seen.has(key))
20449
21089
  continue;
20450
21090
  seen.add(key);
20451
- if (seen.size <= Math.max(0, count ?? 0))
21091
+ if (seen.size <= Math.max(0, count2 ?? 0))
20452
21092
  protectedIds.add(item.id);
20453
21093
  }
20454
21094
  }
@@ -21730,7 +22370,7 @@ var value = (input, key) => {
21730
22370
  const desired = input.desiredState;
21731
22371
  return input[key] ?? (desired && typeof desired === "object" && !Array.isArray(desired) ? desired[key] : undefined);
21732
22372
  };
21733
- var text = (input, key, fallback) => {
22373
+ var text2 = (input, key, fallback) => {
21734
22374
  const result2 = value(input, key);
21735
22375
  return result2 == null ? fallback : String(result2);
21736
22376
  };
@@ -21746,7 +22386,7 @@ var strings2 = (input, key) => {
21746
22386
  const result2 = value(input, key);
21747
22387
  return Array.isArray(result2) ? result2.map(String) : [];
21748
22388
  };
21749
- var snapshotName = (id, input) => text(input, "snapshotId") ?? `${id}-final-${new Date().toISOString().replace(/[^0-9]/g, "").slice(0, 14)}`;
22389
+ var snapshotName = (id, input) => text2(input, "snapshotId") ?? `${id}-final-${new Date().toISOString().replace(/[^0-9]/g, "").slice(0, 14)}`;
21750
22390
  function rdsObservation(instance2) {
21751
22391
  if (!instance2)
21752
22392
  throw new Error("RDS instance was not found.");
@@ -21780,19 +22420,19 @@ class AwsRdsTransport {
21780
22420
  DBInstanceIdentifier: id,
21781
22421
  DBInstanceClass: String(input.plan),
21782
22422
  Engine: String(input.engine),
21783
- MasterUsername: text(input, "username", "app"),
22423
+ MasterUsername: text2(input, "username", "app"),
21784
22424
  MasterUserPassword: credential2,
21785
- DBName: text(input, "database"),
22425
+ DBName: text2(input, "database"),
21786
22426
  AllocatedStorage: number2(input, "storageGb"),
21787
22427
  VpcSecurityGroupIds: strings2(input, "securityGroupIds"),
21788
- DBSubnetGroupName: text(input, "subnetGroup"),
22428
+ DBSubnetGroupName: text2(input, "subnetGroup"),
21789
22429
  BackupRetentionPeriod: number2(input, "backupRetentionDays") ?? 7,
21790
22430
  MultiAZ: boolean2(input, "highAvailability"),
21791
- EngineVersion: text(input, "engineVersion"),
22431
+ EngineVersion: text2(input, "engineVersion"),
21792
22432
  PubliclyAccessible: boolean2(input, "publicExposure") ?? false,
21793
- StorageType: text(input, "storageType", "gp3"),
22433
+ StorageType: text2(input, "storageType", "gp3"),
21794
22434
  StorageEncrypted: true,
21795
- KmsKeyId: text(input, "kmsKeyId"),
22435
+ KmsKeyId: text2(input, "kmsKeyId"),
21796
22436
  DeletionProtection: true,
21797
22437
  Tags: [{ Key: "managed-by", Value: "ts-cloud" }]
21798
22438
  });
@@ -21817,11 +22457,11 @@ class AwsRdsTransport {
21817
22457
  const targetId = String(input.targetId), result2 = await this.client.restoreDBInstanceFromDBSnapshot({
21818
22458
  DBInstanceIdentifier: targetId,
21819
22459
  DBSnapshotIdentifier: String(input.backupId),
21820
- DBInstanceClass: text(input, "plan"),
21821
- DBSubnetGroupName: text(input, "subnetGroup"),
22460
+ DBInstanceClass: text2(input, "plan"),
22461
+ DBSubnetGroupName: text2(input, "subnetGroup"),
21822
22462
  MultiAZ: boolean2(input, "highAvailability"),
21823
22463
  PubliclyAccessible: false,
21824
- StorageType: text(input, "storageType"),
22464
+ StorageType: text2(input, "storageType"),
21825
22465
  VpcSecurityGroupIds: strings2(input, "securityGroupIds"),
21826
22466
  DeletionProtection: true,
21827
22467
  Tags: [{ Key: "managed-by", Value: "ts-cloud" }]
@@ -21852,10 +22492,10 @@ class AwsRdsTransport {
21852
22492
  if (["resize", "version", "rotate", "expose"].includes(action)) {
21853
22493
  const result2 = await this.client.modifyDBInstance({
21854
22494
  DBInstanceIdentifier: id,
21855
- DBInstanceClass: text(input, "plan"),
22495
+ DBInstanceClass: text2(input, "plan"),
21856
22496
  AllocatedStorage: number2(input, "storageGb"),
21857
22497
  MasterUserPassword: action === "rotate" ? credential2 : undefined,
21858
- EngineVersion: text(input, "engineVersion"),
22498
+ EngineVersion: text2(input, "engineVersion"),
21859
22499
  MultiAZ: boolean2(input, "highAvailability"),
21860
22500
  PubliclyAccessible: boolean2(input, "publicExposure"),
21861
22501
  VpcSecurityGroupIds: strings2(input, "securityGroupIds"),
@@ -21900,15 +22540,15 @@ class AwsAuroraTransport {
21900
22540
  await this.client.createDBCluster({
21901
22541
  DBClusterIdentifier: id,
21902
22542
  Engine: engine,
21903
- EngineVersion: text(input, "engineVersion"),
21904
- MasterUsername: text(input, "username", "app"),
22543
+ EngineVersion: text2(input, "engineVersion"),
22544
+ MasterUsername: text2(input, "username", "app"),
21905
22545
  MasterUserPassword: credential2,
21906
- DatabaseName: text(input, "database"),
21907
- DBSubnetGroupName: text(input, "subnetGroup"),
22546
+ DatabaseName: text2(input, "database"),
22547
+ DBSubnetGroupName: text2(input, "subnetGroup"),
21908
22548
  VpcSecurityGroupIds: strings2(input, "securityGroupIds"),
21909
22549
  BackupRetentionPeriod: number2(input, "backupRetentionDays") ?? 7,
21910
22550
  StorageEncrypted: true,
21911
- KmsKeyId: text(input, "kmsKeyId"),
22551
+ KmsKeyId: text2(input, "kmsKeyId"),
21912
22552
  DeletionProtection: true,
21913
22553
  ServerlessV2ScalingConfiguration: {
21914
22554
  MinCapacity: number2(input, "minCapacity") ?? 0.5,
@@ -21941,8 +22581,8 @@ class AwsAuroraTransport {
21941
22581
  DBClusterIdentifier: targetId,
21942
22582
  SnapshotIdentifier: String(input.backupId),
21943
22583
  Engine: engine,
21944
- EngineVersion: text(input, "engineVersion"),
21945
- DBSubnetGroupName: text(input, "subnetGroup"),
22584
+ EngineVersion: text2(input, "engineVersion"),
22585
+ DBSubnetGroupName: text2(input, "subnetGroup"),
21946
22586
  VpcSecurityGroupIds: strings2(input, "securityGroupIds"),
21947
22587
  DeletionProtection: true,
21948
22588
  ServerlessV2ScalingConfiguration: {
@@ -21953,7 +22593,7 @@ class AwsAuroraTransport {
21953
22593
  await this.client.createDBInstance({
21954
22594
  DBInstanceIdentifier: `${targetId}-writer-1`,
21955
22595
  DBClusterIdentifier: targetId,
21956
- DBInstanceClass: text(input, "plan", "db.serverless"),
22596
+ DBInstanceClass: text2(input, "plan", "db.serverless"),
21957
22597
  Engine: engine,
21958
22598
  PubliclyAccessible: false
21959
22599
  });
@@ -21982,7 +22622,7 @@ class AwsAuroraTransport {
21982
22622
  MinCapacity: number2(input, "minCapacity") ?? 0.5,
21983
22623
  MaxCapacity: number2(input, "maxCapacity") ?? 4
21984
22624
  },
21985
- EngineVersion: text(input, "engineVersion"),
22625
+ EngineVersion: text2(input, "engineVersion"),
21986
22626
  MasterUserPassword: action === "rotate" ? credential2 : undefined,
21987
22627
  ApplyImmediately: boolean2(input, "applyImmediately") ?? true
21988
22628
  });
@@ -22019,10 +22659,10 @@ class AwsElastiCacheTransport {
22019
22659
  engine: "redis",
22020
22660
  cacheNodeType: String(input.plan),
22021
22661
  numCacheNodes: number2(input, "nodes") ?? 1,
22022
- engineVersion: text(input, "engineVersion"),
22662
+ engineVersion: text2(input, "engineVersion"),
22023
22663
  port: number2(input, "port"),
22024
22664
  securityGroupIds: strings2(input, "securityGroupIds"),
22025
- subnetGroupName: text(input, "subnetGroup"),
22665
+ subnetGroupName: text2(input, "subnetGroup"),
22026
22666
  tags: [{ Key: "managed-by", Value: "ts-cloud" }]
22027
22667
  });
22028
22668
  return {
@@ -26144,8 +26784,8 @@ class Logger2 {
26144
26784
  if (!input)
26145
26785
  return input;
26146
26786
  let out = input;
26147
- out = out.replace(/\[([^\]]+)\]\(([^)]+)\)/g, (_, text2, url) => {
26148
- const label = styles2.underline(styles2.blue(text2));
26787
+ out = out.replace(/\[([^\]]+)\]\(([^)]+)\)/g, (_, text3, url) => {
26788
+ const label = styles2.underline(styles2.blue(text3));
26149
26789
  const absFile = this.toAbsoluteFilePath(url);
26150
26790
  if (absFile && this.shouldStyleConsole() && this.supportsHyperlinks()) {
26151
26791
  const href = `file://${encodeURI(absFile)}`;
@@ -28453,20 +29093,20 @@ var init_dist = __esm(async () => {
28453
29093
  };
28454
29094
  config = await loadConfig2();
28455
29095
  terminalStyles = {
28456
- red: (text2) => `\x1B[31m${text2}\x1B[0m`,
28457
- green: (text2) => `\x1B[32m${text2}\x1B[0m`,
28458
- yellow: (text2) => `\x1B[33m${text2}\x1B[0m`,
28459
- blue: (text2) => `\x1B[34m${text2}\x1B[0m`,
28460
- magenta: (text2) => `\x1B[35m${text2}\x1B[0m`,
28461
- cyan: (text2) => `\x1B[36m${text2}\x1B[0m`,
28462
- white: (text2) => `\x1B[37m${text2}\x1B[0m`,
28463
- gray: (text2) => `\x1B[90m${text2}\x1B[0m`,
28464
- bgRed: (text2) => `\x1B[41m${text2}\x1B[0m`,
28465
- bgYellow: (text2) => `\x1B[43m${text2}\x1B[0m`,
28466
- bold: (text2) => `\x1B[1m${text2}\x1B[0m`,
28467
- dim: (text2) => `\x1B[2m${text2}\x1B[0m`,
28468
- italic: (text2) => `\x1B[3m${text2}\x1B[0m`,
28469
- underline: (text2) => `\x1B[4m${text2}\x1B[0m`,
29096
+ red: (text3) => `\x1B[31m${text3}\x1B[0m`,
29097
+ green: (text3) => `\x1B[32m${text3}\x1B[0m`,
29098
+ yellow: (text3) => `\x1B[33m${text3}\x1B[0m`,
29099
+ blue: (text3) => `\x1B[34m${text3}\x1B[0m`,
29100
+ magenta: (text3) => `\x1B[35m${text3}\x1B[0m`,
29101
+ cyan: (text3) => `\x1B[36m${text3}\x1B[0m`,
29102
+ white: (text3) => `\x1B[37m${text3}\x1B[0m`,
29103
+ gray: (text3) => `\x1B[90m${text3}\x1B[0m`,
29104
+ bgRed: (text3) => `\x1B[41m${text3}\x1B[0m`,
29105
+ bgYellow: (text3) => `\x1B[43m${text3}\x1B[0m`,
29106
+ bold: (text3) => `\x1B[1m${text3}\x1B[0m`,
29107
+ dim: (text3) => `\x1B[2m${text3}\x1B[0m`,
29108
+ italic: (text3) => `\x1B[3m${text3}\x1B[0m`,
29109
+ underline: (text3) => `\x1B[4m${text3}\x1B[0m`,
28470
29110
  reset: "\x1B[0m"
28471
29111
  };
28472
29112
  styles = terminalStyles;
@@ -28530,22 +29170,22 @@ var init_dist = __esm(async () => {
28530
29170
  };
28531
29171
  config2 = await loadConfig4();
28532
29172
  terminalStyles2 = {
28533
- red: (text2) => `\x1B[31m${text2}\x1B[0m`,
28534
- green: (text2) => `\x1B[32m${text2}\x1B[0m`,
28535
- yellow: (text2) => `\x1B[33m${text2}\x1B[0m`,
28536
- blue: (text2) => `\x1B[34m${text2}\x1B[0m`,
28537
- magenta: (text2) => `\x1B[35m${text2}\x1B[0m`,
28538
- cyan: (text2) => `\x1B[36m${text2}\x1B[0m`,
28539
- white: (text2) => `\x1B[37m${text2}\x1B[0m`,
28540
- gray: (text2) => `\x1B[90m${text2}\x1B[0m`,
28541
- bgRed: (text2) => `\x1B[41m${text2}\x1B[0m`,
28542
- bgYellow: (text2) => `\x1B[43m${text2}\x1B[0m`,
28543
- bgGray: (text2) => `\x1B[100m${text2}\x1B[0m`,
28544
- bold: (text2) => `\x1B[1m${text2}\x1B[0m`,
28545
- dim: (text2) => `\x1B[2m${text2}\x1B[0m`,
28546
- italic: (text2) => `\x1B[3m${text2}\x1B[0m`,
28547
- underline: (text2) => `\x1B[4m${text2}\x1B[0m`,
28548
- strikethrough: (text2) => `\x1B[9m${text2}\x1B[0m`,
29173
+ red: (text3) => `\x1B[31m${text3}\x1B[0m`,
29174
+ green: (text3) => `\x1B[32m${text3}\x1B[0m`,
29175
+ yellow: (text3) => `\x1B[33m${text3}\x1B[0m`,
29176
+ blue: (text3) => `\x1B[34m${text3}\x1B[0m`,
29177
+ magenta: (text3) => `\x1B[35m${text3}\x1B[0m`,
29178
+ cyan: (text3) => `\x1B[36m${text3}\x1B[0m`,
29179
+ white: (text3) => `\x1B[37m${text3}\x1B[0m`,
29180
+ gray: (text3) => `\x1B[90m${text3}\x1B[0m`,
29181
+ bgRed: (text3) => `\x1B[41m${text3}\x1B[0m`,
29182
+ bgYellow: (text3) => `\x1B[43m${text3}\x1B[0m`,
29183
+ bgGray: (text3) => `\x1B[100m${text3}\x1B[0m`,
29184
+ bold: (text3) => `\x1B[1m${text3}\x1B[0m`,
29185
+ dim: (text3) => `\x1B[2m${text3}\x1B[0m`,
29186
+ italic: (text3) => `\x1B[3m${text3}\x1B[0m`,
29187
+ underline: (text3) => `\x1B[4m${text3}\x1B[0m`,
29188
+ strikethrough: (text3) => `\x1B[9m${text3}\x1B[0m`,
28549
29189
  reset: "\x1B[0m"
28550
29190
  };
28551
29191
  styles2 = terminalStyles2;
@@ -28876,22 +29516,22 @@ class JsonFormatter3 {
28876
29516
  }
28877
29517
  }
28878
29518
  var terminalStyles3 = {
28879
- red: (text2) => `\x1B[31m${text2}\x1B[0m`,
28880
- green: (text2) => `\x1B[32m${text2}\x1B[0m`,
28881
- yellow: (text2) => `\x1B[33m${text2}\x1B[0m`,
28882
- blue: (text2) => `\x1B[34m${text2}\x1B[0m`,
28883
- magenta: (text2) => `\x1B[35m${text2}\x1B[0m`,
28884
- cyan: (text2) => `\x1B[36m${text2}\x1B[0m`,
28885
- white: (text2) => `\x1B[37m${text2}\x1B[0m`,
28886
- gray: (text2) => `\x1B[90m${text2}\x1B[0m`,
28887
- bgRed: (text2) => `\x1B[41m${text2}\x1B[0m`,
28888
- bgYellow: (text2) => `\x1B[43m${text2}\x1B[0m`,
28889
- bgGray: (text2) => `\x1B[100m${text2}\x1B[0m`,
28890
- bold: (text2) => `\x1B[1m${text2}\x1B[0m`,
28891
- dim: (text2) => `\x1B[2m${text2}\x1B[0m`,
28892
- italic: (text2) => `\x1B[3m${text2}\x1B[0m`,
28893
- underline: (text2) => `\x1B[4m${text2}\x1B[0m`,
28894
- strikethrough: (text2) => `\x1B[9m${text2}\x1B[0m`,
29519
+ red: (text3) => `\x1B[31m${text3}\x1B[0m`,
29520
+ green: (text3) => `\x1B[32m${text3}\x1B[0m`,
29521
+ yellow: (text3) => `\x1B[33m${text3}\x1B[0m`,
29522
+ blue: (text3) => `\x1B[34m${text3}\x1B[0m`,
29523
+ magenta: (text3) => `\x1B[35m${text3}\x1B[0m`,
29524
+ cyan: (text3) => `\x1B[36m${text3}\x1B[0m`,
29525
+ white: (text3) => `\x1B[37m${text3}\x1B[0m`,
29526
+ gray: (text3) => `\x1B[90m${text3}\x1B[0m`,
29527
+ bgRed: (text3) => `\x1B[41m${text3}\x1B[0m`,
29528
+ bgYellow: (text3) => `\x1B[43m${text3}\x1B[0m`,
29529
+ bgGray: (text3) => `\x1B[100m${text3}\x1B[0m`,
29530
+ bold: (text3) => `\x1B[1m${text3}\x1B[0m`,
29531
+ dim: (text3) => `\x1B[2m${text3}\x1B[0m`,
29532
+ italic: (text3) => `\x1B[3m${text3}\x1B[0m`,
29533
+ underline: (text3) => `\x1B[4m${text3}\x1B[0m`,
29534
+ strikethrough: (text3) => `\x1B[9m${text3}\x1B[0m`,
28895
29535
  reset: "\x1B[0m"
28896
29536
  };
28897
29537
  var styles3 = terminalStyles3;
@@ -29543,8 +30183,8 @@ class Logger3 {
29543
30183
  if (!input)
29544
30184
  return input;
29545
30185
  let out = input;
29546
- out = out.replace(/\[([^\]]+)\]\(([^)]+)\)/g, (_, text2, url) => {
29547
- const label = styles3.underline(styles3.blue(text2));
30186
+ out = out.replace(/\[([^\]]+)\]\(([^)]+)\)/g, (_, text3, url) => {
30187
+ const label = styles3.underline(styles3.blue(text3));
29548
30188
  const absFile = this.toAbsoluteFilePath(url);
29549
30189
  if (absFile && this.shouldStyleConsole() && this.supportsHyperlinks()) {
29550
30190
  const href = `file://${encodeURI(absFile)}`;
@@ -38389,15 +39029,15 @@ function findPropertyArray(configText, start, end, property) {
38389
39029
  const arrayStart = configText.indexOf("[", propertyStart);
38390
39030
  return { propertyStart, start: arrayStart, end: findMatching(configText, arrayStart, "[", "]") };
38391
39031
  }
38392
- function findMatching(text2, start, open, close) {
39032
+ function findMatching(text3, start, open, close) {
38393
39033
  let depth = 0;
38394
39034
  let quote2;
38395
39035
  let escaped = false;
38396
39036
  let lineComment = false;
38397
39037
  let blockComment = false;
38398
- for (let index = start;index < text2.length; index++) {
38399
- const char = text2[index];
38400
- const next = text2[index + 1];
39038
+ for (let index = start;index < text3.length; index++) {
39039
+ const char = text3[index];
39040
+ const next = text3[index + 1];
38401
39041
  if (lineComment) {
38402
39042
  if (char === `
38403
39043
  `)
@@ -38646,6 +39286,14 @@ EOF`,
38646
39286
  `echo "INODEPCT=$(df -Pi / 2>/dev/null | awk 'NR==2{gsub("%","",$5);print $5}')"`,
38647
39287
  `echo "NETRX=$(awk -F'[: ]+' 'NR>2 && $2!="lo"{sum+=$3} END{print sum+0}' /proc/net/dev 2>/dev/null)"`,
38648
39288
  `echo "NETTX=$(awk -F'[: ]+' 'NR>2 && $2!="lo"{sum+=$11} END{print sum+0}' /proc/net/dev 2>/dev/null)"`,
39289
+ ". /var/lib/ts-cloud/bandwidth-state 2>/dev/null || true",
39290
+ 'echo "NETRXRATE=${RX_RATE:-0}"',
39291
+ 'echo "NETTXRATE=${TX_RATE:-0}"',
39292
+ 'echo "NETRXTODAY=${DAY_RX:-0}"',
39293
+ 'echo "NETTXTODAY=${DAY_TX:-0}"',
39294
+ 'echo "NETRXMONTH=${MONTH_RX:-0}"',
39295
+ 'echo "NETTXMONTH=${MONTH_TX:-0}"',
39296
+ 'echo "NETMONTHKEY=${MONTH_KEY:-}"',
38649
39297
  `echo "PROCESSES=$(ps -e --no-headers 2>/dev/null | awk 'END{print NR+0}')"`,
38650
39298
  `echo "UPTIME=$(uptime -p 2>/dev/null | sed 's/^up //' || echo unknown)"`,
38651
39299
  `echo "UPTIME_SECONDS=$(cut -d' ' -f1 /proc/uptime 2>/dev/null || echo 0)"`,
@@ -38734,6 +39382,11 @@ function configuredRegion(config6) {
38734
39382
  return resolveHetznerLocation(config6);
38735
39383
  return config6.project.region ?? "us-east-1";
38736
39384
  }
39385
+ function configuredBandwidthBudgetBytes(config6) {
39386
+ const monitoring = config6.infrastructure?.compute?.monitoring;
39387
+ const tb = Number(typeof monitoring === "object" ? monitoring?.alerts?.bandwidthTb ?? 0 : 0);
39388
+ return Number.isFinite(tb) && tb > 0 ? Math.round(tb * 1000 ** 4) : 0;
39389
+ }
38737
39390
  function loadLocalState(config6, environment2) {
38738
39391
  const statePath2 = join12(process.cwd(), "storage", "cloud", "state", `${config6.project.slug}-${environment2}.json`);
38739
39392
  if (!existsSync17(statePath2))
@@ -39265,10 +39918,18 @@ function resolveConfigOnlyServerDashboardData(config6, environment2) {
39265
39918
  inodeUsedPct: 0,
39266
39919
  networkRxBytes: 0,
39267
39920
  networkTxBytes: 0,
39921
+ networkRxBytesPerSec: 0,
39922
+ networkTxBytesPerSec: 0,
39923
+ networkRxBytesToday: 0,
39924
+ networkTxBytesToday: 0,
39925
+ networkRxBytesMonth: 0,
39926
+ networkTxBytesMonth: 0,
39927
+ networkMonthKey: "",
39268
39928
  processes: 0,
39269
39929
  uptimeSeconds: 0
39270
39930
  },
39271
39931
  metricsUnavailable: true,
39932
+ bandwidthBudgetBytes: configuredBandwidthBudgetBytes(config6),
39272
39933
  services,
39273
39934
  servicesDetail: services.map((s) => ({ ...s, since: UNKNOWN, memMb: 0, auto: true })),
39274
39935
  backup: configuredBackup(config6),
@@ -39304,6 +39965,7 @@ function resolveConfigOnlyServerDashboardData(config6, environment2) {
39304
39965
  };
39305
39966
  out.diagnostics = diagnosticChecks(config6, out);
39306
39967
  out.activity = activityFeed(out);
39968
+ out.topology = buildServerTopology(out, { project: config6.project.name, environment: environment2 });
39307
39969
  return out;
39308
39970
  }
39309
39971
  async function resolveServerDashboardData(config6, environment2, options = {}) {
@@ -39367,6 +40029,13 @@ async function resolveServerDashboardData(config6, environment2, options = {}) {
39367
40029
  inodeUsedPct: num(parsed.INODEPCT),
39368
40030
  networkRxBytes: num(parsed.NETRX),
39369
40031
  networkTxBytes: num(parsed.NETTX),
40032
+ networkRxBytesPerSec: num(parsed.NETRXRATE),
40033
+ networkTxBytesPerSec: num(parsed.NETTXRATE),
40034
+ networkRxBytesToday: num(parsed.NETRXTODAY),
40035
+ networkTxBytesToday: num(parsed.NETTXTODAY),
40036
+ networkRxBytesMonth: num(parsed.NETRXMONTH),
40037
+ networkTxBytesMonth: num(parsed.NETTXMONTH),
40038
+ networkMonthKey: parsed.NETMONTHKEY || "",
39370
40039
  processes: num(parsed.PROCESSES),
39371
40040
  uptimeSeconds: num(parsed.UPTIME_SECONDS)
39372
40041
  };
@@ -39485,6 +40154,7 @@ async function resolveServerDashboardData(config6, environment2, options = {}) {
39485
40154
  out._serverReachable = instanceCount > 0 && !!parsed;
39486
40155
  out.diagnostics = diagnosticChecks(config6, out);
39487
40156
  out.activity = activityFeed(out);
40157
+ out.topology = buildServerTopology(out, { project: config6.project.name, environment: environment2 });
39488
40158
  return out;
39489
40159
  }
39490
40160
 
@@ -40619,9 +41289,9 @@ function normalizeUser(raw) {
40619
41289
  createdAt: typeof raw.createdAt === "string" ? raw.createdAt : undefined
40620
41290
  };
40621
41291
  }
40622
- function parseUsersFile(text2) {
41292
+ function parseUsersFile(text3) {
40623
41293
  try {
40624
- const parsed = JSON.parse(text2);
41294
+ const parsed = JSON.parse(text3);
40625
41295
  if (!Array.isArray(parsed?.users))
40626
41296
  return [];
40627
41297
  return parsed.users.map(normalizeUser).filter((u) => u !== null);
@@ -41512,6 +42182,12 @@ function scopeCloudConfig(config6, user) {
41512
42182
  };
41513
42183
  }
41514
42184
 
42185
+ // src/deploy/dashboard-payload.ts
42186
+ function encodeDashboardPayload(data) {
42187
+ const nonAscii = new RegExp("[\\u007F-\\uFFFF]", "g");
42188
+ return JSON.stringify(data).replace(nonAscii, (character) => `\\u${character.charCodeAt(0).toString(16).padStart(4, "0")}`);
42189
+ }
42190
+
41515
42191
  // src/deploy/dashboard-site-settings.ts
41516
42192
  var MEMBER_EDITABLE_SITE_FIELDS = new Set([
41517
42193
  "name",
@@ -41620,9 +42296,9 @@ class LoginThrottle {
41620
42296
  const key2 = this.key(username, address);
41621
42297
  const entry2 = this.attempts.get(key2);
41622
42298
  const stale = !entry2 || now - entry2.last > this.windowMs;
41623
- const count = stale ? 1 : entry2.count + 1;
41624
- const next = { count, last: now };
41625
- if (count >= this.maxAttempts)
42299
+ const count2 = stale ? 1 : entry2.count + 1;
42300
+ const next = { count: count2, last: now };
42301
+ if (count2 >= this.maxAttempts)
41626
42302
  next.until = now + this.lockoutMs;
41627
42303
  this.attempts.set(key2, next);
41628
42304
  }
@@ -41659,14 +42335,14 @@ function addFirewallPort(configText, port, existing = []) {
41659
42335
  function removeFirewallPort(configText, port, existing = []) {
41660
42336
  return setFirewallPorts({ configText, ports: normalizePorts(existing.filter((p) => p !== port)) });
41661
42337
  }
41662
- function findBlock(text2, start, end, keyword, open) {
41663
- const body = text2.slice(start, end);
42338
+ function findBlock(text3, start, end, keyword, open) {
42339
+ const body = text3.slice(start, end);
41664
42340
  const match = new RegExp(`\\b${keyword}\\s*:\\s*\\${open}`).exec(body);
41665
42341
  if (!match)
41666
42342
  return null;
41667
42343
  const propStart = start + match.index;
41668
- const openIdx = text2.indexOf(open, propStart);
41669
- return { propStart, open: openIdx, close: findMatching(text2, openIdx, open, open === "{" ? "}" : "]") };
42344
+ const openIdx = text3.indexOf(open, propStart);
42345
+ return { propStart, open: openIdx, close: findMatching(text3, openIdx, open, open === "{" ? "}" : "]") };
41670
42346
  }
41671
42347
  function renderPortsArray(ports2) {
41672
42348
  return `allowedPorts: [${ports2.join(", ")}]`;
@@ -42310,21 +42986,21 @@ function removeSiteFromCloudConfig(input) {
42310
42986
  const span = findSiteSpan(input.configText, sites.start, sites.end, name);
42311
42987
  if (!span)
42312
42988
  throw new Error(`Site '${name}' does not exist in cloud.config.ts`);
42313
- const text2 = input.configText;
42989
+ const text3 = input.configText;
42314
42990
  let start = span.nameStart;
42315
- while (start > 0 && text2[start - 1] !== `
42316
- ` && /\s/.test(text2[start - 1]))
42991
+ while (start > 0 && text3[start - 1] !== `
42992
+ ` && /\s/.test(text3[start - 1]))
42317
42993
  start--;
42318
42994
  let end = span.end + 1;
42319
- if (text2[end] === ",")
42995
+ if (text3[end] === ",")
42320
42996
  end++;
42321
- while (end < text2.length && text2[end] !== `
42322
- ` && /\s/.test(text2[end]))
42997
+ while (end < text3.length && text3[end] !== `
42998
+ ` && /\s/.test(text3[end]))
42323
42999
  end++;
42324
- if (text2[end] === `
43000
+ if (text3[end] === `
42325
43001
  `)
42326
43002
  end++;
42327
- return text2.slice(0, start) + text2.slice(end);
43003
+ return text3.slice(0, start) + text3.slice(end);
42328
43004
  }
42329
43005
  function setSitePropertyInCloudConfig(input) {
42330
43006
  const name = normalizeSiteName(input.siteName);
@@ -42334,22 +43010,22 @@ function setSitePropertyInCloudConfig(input) {
42334
43010
  const span = findSiteSpan(input.configText, sites.start, sites.end, name);
42335
43011
  if (!span)
42336
43012
  throw new Error(`Site '${name}' does not exist in cloud.config.ts`);
42337
- const text2 = input.configText;
42338
- const braceStart = text2.indexOf("{", span.nameStart);
43013
+ const text3 = input.configText;
43014
+ const braceStart = text3.indexOf("{", span.nameStart);
42339
43015
  const braceEnd = span.end;
42340
- const body = text2.slice(braceStart + 1, braceEnd);
43016
+ const body = text3.slice(braceStart + 1, braceEnd);
42341
43017
  const propMatch = new RegExp(`(^|[\\s,{])(${escapeRegExp(input.key)})\\s*:`, "m").exec(body);
42342
43018
  if (propMatch) {
42343
43019
  const keyStart = braceStart + 1 + propMatch.index + propMatch[1].length;
42344
- const colon = text2.indexOf(":", keyStart);
42345
- const valueEnd = scanValueEnd(text2, colon + 1, braceEnd);
42346
- return `${text2.slice(0, keyStart)}${input.key}: ${input.valueText}${text2.slice(valueEnd)}`;
43020
+ const colon = text3.indexOf(":", keyStart);
43021
+ const valueEnd = scanValueEnd(text3, colon + 1, braceEnd);
43022
+ return `${text3.slice(0, keyStart)}${input.key}: ${input.valueText}${text3.slice(valueEnd)}`;
42347
43023
  }
42348
- const before = text2.slice(0, braceEnd).trimEnd();
43024
+ const before = text3.slice(0, braceEnd).trimEnd();
42349
43025
  const sep5 = before.endsWith("{") || before.endsWith(",") ? "" : ",";
42350
43026
  return `${before}${sep5}
42351
43027
  ${input.key}: ${input.valueText},
42352
- ${text2.slice(braceEnd)}`;
43028
+ ${text3.slice(braceEnd)}`;
42353
43029
  }
42354
43030
  function renderSslValue(ssl) {
42355
43031
  if (ssl === false)
@@ -42392,15 +43068,15 @@ ${entries.map(([from, to]) => ` '${escapeSingle(from)}': '${escapeSingle(
42392
43068
  `)}
42393
43069
  }`;
42394
43070
  }
42395
- function scanValueEnd(text2, start, limit2) {
43071
+ function scanValueEnd(text3, start, limit2) {
42396
43072
  let i = start;
42397
- while (i < limit2 && /\s/.test(text2[i]))
43073
+ while (i < limit2 && /\s/.test(text3[i]))
42398
43074
  i++;
42399
43075
  let depth = 0;
42400
43076
  let quote2;
42401
43077
  let escaped = false;
42402
43078
  for (;i < limit2; i++) {
42403
- const c = text2[i];
43079
+ const c = text3[i];
42404
43080
  if (quote2) {
42405
43081
  if (escaped) {
42406
43082
  escaped = false;
@@ -42508,15 +43184,15 @@ function hasSite(configText, start, end, siteName) {
42508
43184
  function escapeRegExp(value2) {
42509
43185
  return value2.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
42510
43186
  }
42511
- function findMatchingBrace(text2, start) {
43187
+ function findMatchingBrace(text3, start) {
42512
43188
  let depth = 0;
42513
43189
  let quote2;
42514
43190
  let escaped = false;
42515
43191
  let lineComment = false;
42516
43192
  let blockComment = false;
42517
- for (let index = start;index < text2.length; index++) {
42518
- const char = text2[index];
42519
- const next = text2[index + 1];
43193
+ for (let index = start;index < text3.length; index++) {
43194
+ const char = text3[index];
43195
+ const next = text3[index + 1];
42520
43196
  if (lineComment) {
42521
43197
  if (char === `
42522
43198
  `)
@@ -42949,6 +43625,12 @@ async function collectNow(context) {
42949
43625
  ["host.disk.inode_used_percent", Number(metrics.inodeUsedPct), "percent"],
42950
43626
  ["host.network.rx", Number(metrics.networkRxBytes), "bytes"],
42951
43627
  ["host.network.tx", Number(metrics.networkTxBytes), "bytes"],
43628
+ ["host.network.rx_rate", Number(metrics.networkRxBytesPerSec), "bytes_per_second"],
43629
+ ["host.network.tx_rate", Number(metrics.networkTxBytesPerSec), "bytes_per_second"],
43630
+ ["host.network.rx_today", Number(metrics.networkRxBytesToday), "bytes"],
43631
+ ["host.network.tx_today", Number(metrics.networkTxBytesToday), "bytes"],
43632
+ ["host.network.rx_month", Number(metrics.networkRxBytesMonth), "bytes"],
43633
+ ["host.network.tx_month", Number(metrics.networkTxBytesMonth), "bytes"],
42952
43634
  ["host.processes", Number(metrics.processes), "count"],
42953
43635
  ["host.uptime", Number(metrics.uptimeSeconds), "seconds"]
42954
43636
  ];
@@ -43009,6 +43691,24 @@ async function collectNow(context) {
43009
43691
  message: error2 instanceof Error ? error2.message : String(error2)
43010
43692
  });
43011
43693
  }
43694
+ {
43695
+ const monitoring = context.config.infrastructure?.compute?.monitoring;
43696
+ const endpoints = typeof monitoring === "object" ? monitoring?.egressEndpoints : undefined;
43697
+ const egress = await collectEgressMetrics(endpoints, { now: now.toISOString() });
43698
+ errors.push(...egress.errors);
43699
+ for (const metric of egress.metrics)
43700
+ records.push({
43701
+ ...scope2,
43702
+ id: id(metric.attributes.source, metric.name, metric.timestamp),
43703
+ kind: "metric",
43704
+ source: `egress:${metric.attributes.source}`,
43705
+ name: metric.name,
43706
+ timestamp: metric.timestamp,
43707
+ value: metric.value,
43708
+ unit: metric.unit,
43709
+ attributes: metric.attributes
43710
+ });
43711
+ }
43012
43712
  if (!context.lightweight)
43013
43713
  try {
43014
43714
  const inventory2 = await resolveRuntimeInventory(context.config, context.environment);
@@ -43343,7 +44043,7 @@ function dashboardMutationAuditPayload(method, path, input, status, durationMs)
43343
44043
  };
43344
44044
  }
43345
44045
  var DUMMY_HASH = hashPassword("ts-cloud-dummy-password");
43346
- function text2(data, status = 200) {
44046
+ function text3(data, status = 200) {
43347
44047
  return new Response(data, {
43348
44048
  status,
43349
44049
  headers: { "content-type": "text/plain; charset=utf-8" }
@@ -43387,8 +44087,8 @@ async function loadLocalEnv(cwd) {
43387
44087
  for (const file of candidates) {
43388
44088
  if (!existsSync20(file))
43389
44089
  continue;
43390
- const text3 = await readFile2(file, "utf8").catch(() => "");
43391
- for (const line of text3.split(`
44090
+ const text4 = await readFile2(file, "utf8").catch(() => "");
44091
+ for (const line of text4.split(`
43392
44092
  `)) {
43393
44093
  const trimmed = line.trim();
43394
44094
  if (!trimmed || trimmed.startsWith("#") || !trimmed.includes("="))
@@ -43614,12 +44314,19 @@ async function resolveLiveDashboardData(config6, environment2, options = {}) {
43614
44314
  function preserveDashboardSiteSnapshot(previous, next) {
43615
44315
  if (!previous)
43616
44316
  return next;
43617
- return {
44317
+ const merged = {
43618
44318
  ...next,
43619
44319
  ...Array.isArray(previous.sites) ? { sites: previous.sites } : {},
43620
44320
  ...Array.isArray(previous.sitesDetail) ? { sitesDetail: previous.sitesDetail } : {},
43621
44321
  ...Array.isArray(previous.siteHealth) ? { siteHealth: previous.siteHealth } : {}
43622
44322
  };
44323
+ if (merged.mode !== "serverless" && Array.isArray(merged.sites)) {
44324
+ merged.topology = buildServerTopology(merged, {
44325
+ project: merged.project?.name,
44326
+ environment: merged.environment
44327
+ });
44328
+ }
44329
+ return merged;
43623
44330
  }
43624
44331
  function resolveUiSourceDir(cwd) {
43625
44332
  const candidates = [
@@ -43651,7 +44358,7 @@ async function buildLiveUi(cwd, data) {
43651
44358
  stderr: "pipe",
43652
44359
  env: {
43653
44360
  ...process.env,
43654
- TSCLOUD_DASHBOARD_DATA: JSON.stringify(data)
44361
+ TSCLOUD_DASHBOARD_DATA: encodeDashboardPayload(data)
43655
44362
  }
43656
44363
  });
43657
44364
  const [stdout, stderr, exitCode] = await Promise.all([
@@ -43862,6 +44569,7 @@ var PAGE_CAPABILITIES = {
43862
44569
  "/server/actions": "runtime:restart",
43863
44570
  "/server/database": "data:read",
43864
44571
  "/server/firewall": "fleet:read",
44572
+ "/infrastructure/topology": "fleet:read",
43865
44573
  "/server/security": "security:read",
43866
44574
  "/security": "security:read",
43867
44575
  "/integrations": "sources:read",
@@ -43934,10 +44642,10 @@ async function serveStatic(uiRoot, pathname) {
43934
44642
  status: 404,
43935
44643
  headers: { "content-type": "text/html; charset=utf-8" }
43936
44644
  });
43937
- return text2("Not found", 404);
44645
+ return text3("Not found", 404);
43938
44646
  }
43939
44647
  if (statSync6(file).isDirectory())
43940
- return text2("Not found", 404);
44648
+ return text3("Not found", 404);
43941
44649
  const body = await readFile2(file);
43942
44650
  const type = contentTypes[extname3(file)] ?? "application/octet-stream";
43943
44651
  return new Response(body, { headers: { "content-type": type } });
@@ -46101,7 +46809,7 @@ This link expires in one hour and can be used once.`
46101
46809
  return json15({ ok: false, error: "The web terminal is disabled." }, 403);
46102
46810
  if (activeServer.upgrade(req))
46103
46811
  return;
46104
- return text2("WebSocket upgrade failed", 400);
46812
+ return text3("WebSocket upgrade failed", 400);
46105
46813
  }
46106
46814
  if (url.pathname === "/api/health") {
46107
46815
  const storage = controlPlane.store.health();
@@ -47055,9 +47763,9 @@ This replacement link expires ${created.invitation.expiresAt} and can be used on
47055
47763
  return json15({ ok: false, error: "Port must be a number between 1 and 65535." }, 422);
47056
47764
  if (typeof body.domain === "string" && body.domain.trim() && !isValidHostname(body.domain.trim()))
47057
47765
  return json15({ ok: false, error: `Domain '${body.domain.trim()}' is not a valid hostname.` }, 422);
47058
- let text3 = await readFile2(configPath, "utf8");
47766
+ let text4 = await readFile2(configPath, "utf8");
47059
47767
  const set = (key2, valueText) => {
47060
- text3 = setSitePropertyInCloudConfig({ configText: text3, siteName: name, key: key2, valueText });
47768
+ text4 = setSitePropertyInCloudConfig({ configText: text4, siteName: name, key: key2, valueText });
47061
47769
  };
47062
47770
  if (body.ssl !== undefined) {
47063
47771
  set("ssl", renderSslValue(body.ssl));
@@ -47086,7 +47794,7 @@ This replacement link expires ${created.invitation.expiresAt} and can be used on
47086
47794
  set("port", String(Number(body.port)));
47087
47795
  existing.port = Number(body.port);
47088
47796
  }
47089
- await writeFile4(configPath, text3);
47797
+ await writeFile4(configPath, text4);
47090
47798
  latestData = await resolveLiveDashboardData(config6, environment2);
47091
47799
  latestDataByEnvironment.set(environment2, latestData);
47092
47800
  return json15({ ok: true, configPath, site: name, data: latestData });
@@ -49599,7 +50307,7 @@ data: ${JSON.stringify({ cursor: position, at: new Date().toISOString() })}
49599
50307
  });
49600
50308
  const uiRoot = await uiRootFor(user, environment2);
49601
50309
  if (!uiRoot)
49602
- return text2("ts-cloud dashboard UI is unavailable.", 503);
50310
+ return text3("ts-cloud dashboard UI is unavailable.", 503);
49603
50311
  return serveStatic(uiRoot, url.pathname);
49604
50312
  } catch (error2) {
49605
50313
  if (options.verbose)
@@ -49657,4 +50365,4 @@ data: ${JSON.stringify({ cursor: position, at: new Date().toISOString() })}
49657
50365
  };
49658
50366
  return { server, url: `http://${host2}:${server.port}/` };
49659
50367
  }
49660
- export { defaultConfig4 as defaultConfig, getConfig, loadCloudConfig, config5 as config, encodeBase32, decodeBase32, hotp, totp, verifyTotp, matchTotpCounter, totpUri, AUTH_SESSION_IDLE_TTL_MS, AUTH_SESSION_ABSOLUTE_TTL_MS, AUTH_ACTION_TOKEN_TTL_MS, AUTH_MFA_CHALLENGE_TTL_MS, AUTH_OIDC_TRANSACTION_TTL_MS, AuthenticationStore, sendAuthenticationEmail, authEncryptionKeyFile, resolveAuthEncryptionKey, sanitizeOidcReturnPath, discoverOidcProvider, beginOidcAuthorization, completeOidcAuthorization, CONTROL_PLANE_SCHEMA_VERSION, controlPlaneMigrations, AUTHORIZATION_CAPABILITIES, roleCapabilities, scopeContains, authorizeOrganization, effectiveCapabilities, OptimisticConcurrencyError, InvalidOperationTransitionError, UnsupportedSchemaVersionError, controlPlaneDatabaseFile, MAX_CONTROL_PLANE_JSON_BYTES, MAX_CONTROL_PLANE_ERROR_BYTES, sanitizeControlPlaneValue, ControlPlaneStore, searchControlPlane, API_TOKEN_DEFAULT_TTL_MS, API_TOKEN_MAX_TTL_MS, API_IDEMPOTENCY_TTL_MS, AutomationIdentityStore, TsCloudApiError, TsCloudClient, parseCompose, exportCompose, diffCompose, listComposeTemplates, getComposeTemplate, renderComposeTemplate, parseComposeCatalog, planComposeTemplateUpgrade, composeProjectName, buildComposeRuntimeCommand, buildComposeScaleCommand, buildComposeLogsCommand, buildComposeShellCommand, QueueCancellationError, QueueTimeoutError, RetryableOperationError, DurableOperationQueue, DurableQueueWorker, PreviewEnvironmentStore, PreviewEnvironmentService, DEPLOYMENT_QUEUE_KINDS, resolveQueuedDeploymentCommand, createDeploymentQueueHandlers, unsupportedVolumeCapabilities, volumeCapabilities, validateMountPath, validateAttachment, VolumeService, createVolumeQueueHandlers, DockerNamedVolumeDriver, ServerPathVolumeDriver, CloudBlockVolumeDriver, VolumeStore, synchronizeComposeVolumes, completeComposeVolumeDeletion, ComposeApplicationStore, ComposeApplicationService, releaseStrategyCapabilities, assertReleaseStrategy, ReleaseStore, ReleaseService, releaseTrafficPlan, activateImmutableRelease, rollbackImmutableRelease, RELEASE_QUEUE_KINDS, createReleaseQueueHandlers, SourceConnectionStore, SourceProviderError, GithubSourceAdapter, GitlabSourceAdapter, BitbucketSourceAdapter, GiteaSourceAdapter, createSourceAdapter, normalizeSourceEvent, processSourceWebhook, webhookEndpoint, discoverGitRefs, cloneSourceBinding, testSourceConnection, syncSourceRepositories, listSourceReferences, reconcileSourceWebhook, removeSourceWebhook, API_VERSION, openApiDocument, ApiServiceError, requestHash, AutomationApiService, createApiV1Handler, SecurityPostureStore, SECRET_PATTERNS, PreDeployScanner, scanForSecrets, formatScanResults, SecurityScannerRunner, SecretFindingScanner, TrivyImageScanner, AwsIamCapabilityScanner, generateCycloneDxSbom, generateImageSbom, attachSbomToRelease, attachVulnerabilitySummary, createReleaseProvenance, attachProvenanceToRelease, verifyArtifactSignature, securityScope, ensureDefaultSecurityPolicies, recordPreDeploySecretScan, recordSkippedSecretScan, recordDashboardHostPosture, productionChangeReview, secureContainerRelease, runtimeId2 as runtimeId, unsupportedCapabilities, capabilities2 as capabilities, normalizeRuntimeStatus, redactRuntimeConfig, ageSeconds, bytes, discoverRuntimeInventory, ecsWorkloads, lambdaWorkloads, parseDockerInspect, dockerWorkloads, DockerDiscoveryAdapter, parseSystemdRecords, systemdWorkloads, SystemdDiscoveryAdapter, EcsRuntimeAdapter, LambdaRuntimeAdapter, createRuntimeAdapters, resolveRuntimeInventory, authorizeRuntimePath, DIAGNOSTIC_PRESETS, RuntimeOperationService, RuntimeStreamRegistry, DEFAULT_TELEMETRY_POLICY, normalizeTelemetryPolicy, telemetryPolicyKey, loadTelemetryPolicy, saveTelemetryPolicy, telemetryEstimatedMonthlyCost, redactTelemetryText, redactTelemetryValue, pathTemplate, telemetryCursor, telemetryPercentile, telemetryBucketLabel, TelemetryStore, AlertStore, AlertEvaluator, isQuietHours, NotificationRouter, HealthCheckRunner, evaluateTelemetryAlertRules, normalizeScheduleExpression, nextScheduleRuns, previewSchedule, jobProviderCapability, renderServerCron, eventBridgeScheduleInput, reconcileJobObservation, desiredState, ServerCronJobAdapter, EventBridgeJobAdapter, JobProviderReconciler, synchronizeConfiguredJobs, JobService, createJobQueueHandlers, JobStore, dataServiceCapabilities, DataServiceStore, connectionGuidance, DataServiceLifecycle, createDataServiceQueueHandlers, AwsRdsDataAdapter, AwsAuroraDataAdapter, AwsElastiCacheDataAdapter, ServerDataAdapter, ContainerDataAdapter, AwsRdsTransport, AwsAuroraTransport, AwsElastiCacheTransport, BunDockerRuntime, DockerDataTransport, EncryptedDataSecretStore, SystemFleetSshTransport, SshFleetDriver, createFleetQueueHandlers, FleetStore, FleetService, zeroCapacity, capacity, PlacementStore, PlacementService, RemoteBuildService, createRemoteBuildQueueHandlers, TransportRemoteBuildDriver, createServerBuildDriver, createEcsBuildDriver, createAsgBuildDriver, RegionStore, RegionService, createRegionQueueHandlers, AwsRegionalDriver, UnavailableRegionalDriver, canonicalJson, manifestDigest, documentDigest, publicKeyFingerprint, verifyUpdateSignature, updateCompatibility, MaintenanceStore, maintenanceWindowOpen, MaintenanceService, createMaintenanceQueueHandlers, TransportPlatformMaintenanceDriver, ControlPlaneCleanupDriver, TransportDisasterRecoveryDriver, ensureDashboardActor, initializeDashboardControlPlane, synchronizeDashboardUsers, trackDashboardOperation, dashboardPageRoutes, resolveLegacyDashboardRoute, routesForDashboard, deploySite, createStaticApiOriginDependencies, deployStaticApiOrigin, verifyStaticApiOrigin, estimateStaticApiOriginMonthlyCost, createExistingStaticFullStackDependencies, generateExistingStaticFullStackTemplate, deployExistingStaticFullStack, estimateExistingStaticFullStackMonthlyCost, hashContainerContext, createContainerImageDependencies, buildAndPushContainerImage, infraEnvFromOutputs, buildFunctionEnv, deployServerlessApp, redeployServerlessApp, rollbackServerlessApp, setMaintenance, runRemoteCommand, resolveDashboardData, validateBackupDestination, BackupStore, encryptBackup, decryptBackup, backupCredentialStatus, S3BackupDestinationAdapter, BackupCoordinator, createBackupQueueHandlers, AwsDatabaseBackupSource, compressBackup, decompressBackup, BunFilesystemArchiveRuntime, FilesystemBackupSource, LogicalDatabaseBackupSource, BunDockerVolumeRuntime, DockerVolumeBackupSource, ControlPlaneBackupSource, AwsInfrastructureBackupSource, ConfigurationStore, parseDotenv, serializeDotenv, LocalEncryptedConfigurationBackend, AwsSecretsManagerConfigurationBackend, AwsSsmConfigurationBackend, ExternalConfigurationBackend, ConfigurationService, synchronizeConfiguredConfiguration, sanitizeCloudConfig, dashboardActions, resolveDashboardAction, startLocalDashboardServer };
50368
+ export { defaultConfig4 as defaultConfig, getConfig, loadCloudConfig, config5 as config, encodeBase32, decodeBase32, hotp, totp, verifyTotp, matchTotpCounter, totpUri, AUTH_SESSION_IDLE_TTL_MS, AUTH_SESSION_ABSOLUTE_TTL_MS, AUTH_ACTION_TOKEN_TTL_MS, AUTH_MFA_CHALLENGE_TTL_MS, AUTH_OIDC_TRANSACTION_TTL_MS, AuthenticationStore, sendAuthenticationEmail, authEncryptionKeyFile, resolveAuthEncryptionKey, sanitizeOidcReturnPath, discoverOidcProvider, beginOidcAuthorization, completeOidcAuthorization, CONTROL_PLANE_SCHEMA_VERSION, controlPlaneMigrations, AUTHORIZATION_CAPABILITIES, roleCapabilities, scopeContains, authorizeOrganization, effectiveCapabilities, OptimisticConcurrencyError, InvalidOperationTransitionError, UnsupportedSchemaVersionError, controlPlaneDatabaseFile, MAX_CONTROL_PLANE_JSON_BYTES, MAX_CONTROL_PLANE_ERROR_BYTES, sanitizeControlPlaneValue, ControlPlaneStore, searchControlPlane, API_TOKEN_DEFAULT_TTL_MS, API_TOKEN_MAX_TTL_MS, API_IDEMPOTENCY_TTL_MS, AutomationIdentityStore, TsCloudApiError, TsCloudClient, parseCompose, exportCompose, diffCompose, listComposeTemplates, getComposeTemplate, renderComposeTemplate, parseComposeCatalog, planComposeTemplateUpgrade, composeProjectName, buildComposeRuntimeCommand, buildComposeScaleCommand, buildComposeLogsCommand, buildComposeShellCommand, QueueCancellationError, QueueTimeoutError, RetryableOperationError, DurableOperationQueue, DurableQueueWorker, PreviewEnvironmentStore, PreviewEnvironmentService, DEPLOYMENT_QUEUE_KINDS, resolveQueuedDeploymentCommand, createDeploymentQueueHandlers, unsupportedVolumeCapabilities, volumeCapabilities, validateMountPath, validateAttachment, VolumeService, createVolumeQueueHandlers, DockerNamedVolumeDriver, ServerPathVolumeDriver, CloudBlockVolumeDriver, VolumeStore, synchronizeComposeVolumes, completeComposeVolumeDeletion, ComposeApplicationStore, ComposeApplicationService, releaseStrategyCapabilities, assertReleaseStrategy, ReleaseStore, ReleaseService, releaseTrafficPlan, activateImmutableRelease, rollbackImmutableRelease, RELEASE_QUEUE_KINDS, createReleaseQueueHandlers, SourceConnectionStore, SourceProviderError, GithubSourceAdapter, GitlabSourceAdapter, BitbucketSourceAdapter, GiteaSourceAdapter, createSourceAdapter, normalizeSourceEvent, processSourceWebhook, webhookEndpoint, discoverGitRefs, cloneSourceBinding, testSourceConnection, syncSourceRepositories, listSourceReferences, reconcileSourceWebhook, removeSourceWebhook, API_VERSION, openApiDocument, ApiServiceError, requestHash, AutomationApiService, createApiV1Handler, SecurityPostureStore, SECRET_PATTERNS, PreDeployScanner, scanForSecrets, formatScanResults, SecurityScannerRunner, SecretFindingScanner, TrivyImageScanner, AwsIamCapabilityScanner, generateCycloneDxSbom, generateImageSbom, attachSbomToRelease, attachVulnerabilitySummary, createReleaseProvenance, attachProvenanceToRelease, verifyArtifactSignature, securityScope, ensureDefaultSecurityPolicies, recordPreDeploySecretScan, recordSkippedSecretScan, recordDashboardHostPosture, productionChangeReview, secureContainerRelease, runtimeId2 as runtimeId, unsupportedCapabilities, capabilities2 as capabilities, normalizeRuntimeStatus, redactRuntimeConfig, ageSeconds, bytes, discoverRuntimeInventory, ecsWorkloads, lambdaWorkloads, parseDockerInspect, dockerWorkloads, DockerDiscoveryAdapter, parseSystemdRecords, systemdWorkloads, SystemdDiscoveryAdapter, EcsRuntimeAdapter, LambdaRuntimeAdapter, createRuntimeAdapters, resolveRuntimeInventory, authorizeRuntimePath, DIAGNOSTIC_PRESETS, RuntimeOperationService, RuntimeStreamRegistry, DEFAULT_TELEMETRY_POLICY, normalizeTelemetryPolicy, telemetryPolicyKey, loadTelemetryPolicy, saveTelemetryPolicy, telemetryEstimatedMonthlyCost, redactTelemetryText, redactTelemetryValue, pathTemplate, telemetryCursor, telemetryPercentile, telemetryBucketLabel, TelemetryStore, AlertStore, AlertEvaluator, isQuietHours, NotificationRouter, HealthCheckRunner, evaluateTelemetryAlertRules, normalizeScheduleExpression, nextScheduleRuns, previewSchedule, jobProviderCapability, renderServerCron, eventBridgeScheduleInput, reconcileJobObservation, desiredState, ServerCronJobAdapter, EventBridgeJobAdapter, JobProviderReconciler, synchronizeConfiguredJobs, JobService, createJobQueueHandlers, JobStore, dataServiceCapabilities, DataServiceStore, connectionGuidance, DataServiceLifecycle, createDataServiceQueueHandlers, AwsRdsDataAdapter, AwsAuroraDataAdapter, AwsElastiCacheDataAdapter, ServerDataAdapter, ContainerDataAdapter, AwsRdsTransport, AwsAuroraTransport, AwsElastiCacheTransport, BunDockerRuntime, DockerDataTransport, EncryptedDataSecretStore, SystemFleetSshTransport, SshFleetDriver, createFleetQueueHandlers, FleetStore, FleetService, zeroCapacity, capacity, PlacementStore, PlacementService, RemoteBuildService, createRemoteBuildQueueHandlers, TransportRemoteBuildDriver, createServerBuildDriver, createEcsBuildDriver, createAsgBuildDriver, RegionStore, RegionService, createRegionQueueHandlers, AwsRegionalDriver, UnavailableRegionalDriver, canonicalJson, manifestDigest, documentDigest, publicKeyFingerprint, verifyUpdateSignature, updateCompatibility, MaintenanceStore, maintenanceWindowOpen, MaintenanceService, createMaintenanceQueueHandlers, TransportPlatformMaintenanceDriver, ControlPlaneCleanupDriver, TransportDisasterRecoveryDriver, ensureDashboardActor, initializeDashboardControlPlane, synchronizeDashboardUsers, trackDashboardOperation, dashboardPageRoutes, resolveLegacyDashboardRoute, routesForDashboard, deploySite, createStaticApiOriginDependencies, deployStaticApiOrigin, verifyStaticApiOrigin, estimateStaticApiOriginMonthlyCost, createExistingStaticFullStackDependencies, generateExistingStaticFullStackTemplate, deployExistingStaticFullStack, estimateExistingStaticFullStackMonthlyCost, hashContainerContext, createContainerImageDependencies, buildAndPushContainerImage, infraEnvFromOutputs, buildFunctionEnv, deployServerlessApp, redeployServerlessApp, rollbackServerlessApp, setMaintenance, runRemoteCommand, buildServerTopology, buildServerlessTopology, buildTopology, resolveDashboardData, egressReportMetrics, fetchEgressReport, collectEgressMetrics, validateBackupDestination, BackupStore, encryptBackup, decryptBackup, backupCredentialStatus, S3BackupDestinationAdapter, BackupCoordinator, createBackupQueueHandlers, AwsDatabaseBackupSource, compressBackup, decompressBackup, BunFilesystemArchiveRuntime, FilesystemBackupSource, LogicalDatabaseBackupSource, BunDockerVolumeRuntime, DockerVolumeBackupSource, ControlPlaneBackupSource, AwsInfrastructureBackupSource, ConfigurationStore, parseDotenv, serializeDotenv, LocalEncryptedConfigurationBackend, AwsSecretsManagerConfigurationBackend, AwsSsmConfigurationBackend, ExternalConfigurationBackend, ConfigurationService, synchronizeConfiguredConfiguration, sanitizeCloudConfig, dashboardActions, resolveDashboardAction, startLocalDashboardServer };