@stacksjs/ts-cloud 0.8.2 → 0.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (75) hide show
  1. package/dist/aws/index.js +3 -1
  2. package/dist/bin/cli.js +575 -565
  3. package/dist/bin/dashboard-server.js +334 -324
  4. package/dist/cdn/cloudflare-plan.d.ts +61 -0
  5. package/dist/cdn/cloudflare-rules.d.ts +77 -0
  6. package/dist/cdn/cloudflare-settings.d.ts +100 -0
  7. package/dist/cdn/cloudflare.d.ts +108 -0
  8. package/dist/cdn/cloudflare.test.d.ts +1 -0
  9. package/dist/cdn/index.d.ts +17 -0
  10. package/dist/cdn/origin-tls.d.ts +36 -0
  11. package/dist/{chunk-v0vpamnf.js → chunk-5nr95wpj.js} +3 -3
  12. package/dist/{chunk-gttvakpv.js → chunk-atbgd8wz.js} +163 -37
  13. package/dist/{chunk-x6137d59.js → chunk-hg7rhrgd.js} +27 -4
  14. package/dist/{chunk-wxhrr03r.js → chunk-hygwq9y2.js} +2 -2
  15. package/dist/{chunk-kpeb5rcs.js → chunk-ws47smxr.js} +2 -2
  16. package/dist/deploy/index.js +4 -4
  17. package/dist/dns/cloudflare.d.ts +119 -1
  18. package/dist/dns/index.d.ts +4 -1
  19. package/dist/dns/index.js +3 -1
  20. package/dist/dns/types.d.ts +43 -0
  21. package/dist/drivers/index.js +1 -1
  22. package/dist/drivers/shared/rpx-gateway.d.ts +11 -0
  23. package/dist/index.d.ts +5 -1
  24. package/dist/index.js +369 -4
  25. package/dist/ui/account/automation.html +3 -3
  26. package/dist/ui/account/security.html +2 -2
  27. package/dist/ui/applications/compose.html +4 -4
  28. package/dist/ui/applications/new.html +2 -2
  29. package/dist/ui/data/backups.html +4 -4
  30. package/dist/ui/data/services.html +4 -4
  31. package/dist/ui/data/volumes.html +4 -4
  32. package/dist/ui/index.html +4 -4
  33. package/dist/ui/infrastructure/topology.html +4 -4
  34. package/dist/ui/integrations.html +2 -2
  35. package/dist/ui/operations/alerts.html +4 -4
  36. package/dist/ui/operations/configuration.html +4 -4
  37. package/dist/ui/operations/jobs.html +4 -4
  38. package/dist/ui/operations/maintenance.html +4 -4
  39. package/dist/ui/operations/observability.html +4 -4
  40. package/dist/ui/operations/previews.html +4 -4
  41. package/dist/ui/operations/queue.html +4 -4
  42. package/dist/ui/operations/regions.html +4 -4
  43. package/dist/ui/operations/releases.html +4 -4
  44. package/dist/ui/operations/spend.html +4 -4
  45. package/dist/ui/operations/workloads.html +4 -4
  46. package/dist/ui/security.html +2 -2
  47. package/dist/ui/server/actions.html +4 -4
  48. package/dist/ui/server/activity.html +2 -2
  49. package/dist/ui/server/capacity.html +4 -4
  50. package/dist/ui/server/database.html +4 -4
  51. package/dist/ui/server/deployments.html +4 -4
  52. package/dist/ui/server/diagnostics.html +2 -2
  53. package/dist/ui/server/firewall.html +4 -4
  54. package/dist/ui/server/fleet.html +4 -4
  55. package/dist/ui/server/logs.html +3 -3
  56. package/dist/ui/server/metrics.html +4 -4
  57. package/dist/ui/server/security.html +2 -2
  58. package/dist/ui/server/services.html +2 -2
  59. package/dist/ui/server/sites.html +4 -4
  60. package/dist/ui/server/ssh-keys.html +4 -4
  61. package/dist/ui/server/team.html +4 -4
  62. package/dist/ui/server/terminal.html +2 -2
  63. package/dist/ui/serverless/alarms.html +4 -4
  64. package/dist/ui/serverless/assets.html +2 -2
  65. package/dist/ui/serverless/data.html +4 -4
  66. package/dist/ui/serverless/deployments.html +2 -2
  67. package/dist/ui/serverless/firewall.html +2 -2
  68. package/dist/ui/serverless/functions.html +4 -4
  69. package/dist/ui/serverless/logs.html +4 -4
  70. package/dist/ui/serverless/metrics.html +2 -2
  71. package/dist/ui/serverless/queues.html +4 -4
  72. package/dist/ui/serverless/secrets.html +4 -4
  73. package/dist/ui/serverless/traces.html +3 -3
  74. package/dist/ui/serverless.html +4 -4
  75. package/package.json +3 -3
@@ -20,7 +20,32 @@ export interface DnsRecord {
20
20
  priority?: number;
21
21
  weight?: number;
22
22
  port?: number;
23
+ /**
24
+ * Cloudflare only: serve this record through Cloudflare's reverse proxy (the
25
+ * "orange cloud") instead of publishing the origin address to the world.
26
+ *
27
+ * This is what turns a plain A/AAAA/CNAME into a CDN-fronted host — the
28
+ * public answer becomes a Cloudflare anycast address and requests reach the
29
+ * origin over Cloudflare's network, picking up its edge cache, TLS
30
+ * termination and DDoS protection on the way.
31
+ *
32
+ * Leave it `undefined` on an upsert to mean "don't care": the provider then
33
+ * PRESERVES whatever proxy state the record already has. That distinction
34
+ * matters because every deploy rewrites these records, and a `false` default
35
+ * would silently grey-cloud a proxied site on the next deploy.
36
+ *
37
+ * Ignored by every non-Cloudflare provider, and by Cloudflare itself for
38
+ * record types it cannot proxy (see {@link PROXIABLE_RECORD_TYPES}).
39
+ */
40
+ proxied?: boolean;
23
41
  }
42
+ /**
43
+ * Record types Cloudflare is able to put behind its proxy. Anything else (TXT,
44
+ * MX, NS, CAA, SRV) is always served as plain DNS, and sending `proxied` for
45
+ * one is an API error rather than a no-op — so the provider drops the field for
46
+ * those types instead of forwarding it.
47
+ */
48
+ export declare const PROXIABLE_RECORD_TYPES: ReadonlySet<DnsRecordType>;
24
49
  export interface DnsRecordResult extends DnsRecord {
25
50
  id?: string;
26
51
  }
@@ -95,6 +120,24 @@ export interface GoDaddyProviderConfig {
95
120
  export interface CloudflareProviderConfig {
96
121
  provider: 'cloudflare';
97
122
  apiToken: string;
123
+ /**
124
+ * Zone id for the domain being managed.
125
+ *
126
+ * Supplying it is what lets the API token be scoped to a SINGLE zone. Without
127
+ * it the provider has to find the zone by name through `GET /zones?name=…`,
128
+ * and that endpoint is an account-level listing: a token restricted to one
129
+ * zone gets an empty result back and every operation fails with "Zone not
130
+ * found", which reads like a missing domain rather than a missing permission.
131
+ *
132
+ * Only applies to the zone it belongs to; requests for any other domain fall
133
+ * back to the by-name lookup.
134
+ */
135
+ zoneId?: string;
136
+ /**
137
+ * Account id. Only needed for account-scoped resources (account-level
138
+ * rulesets); zone-scoped work never uses it.
139
+ */
140
+ accountId?: string;
98
141
  }
99
142
  export type DnsProviderConfig = Route53ProviderConfig | PorkbunProviderConfig | GoDaddyProviderConfig | CloudflareProviderConfig;
100
143
  /**
@@ -70,7 +70,7 @@ import {
70
70
  waitForSsh,
71
71
  wrapCloudInitUserData,
72
72
  writeResizeCheckpoint
73
- } from "../chunk-x6137d59.js";
73
+ } from "../chunk-hg7rhrgd.js";
74
74
  import"../chunk-7m60qnc8.js";
75
75
  import"../chunk-4cjrg98a.js";
76
76
  import"../chunk-rds0sy87.js";
@@ -48,6 +48,17 @@ export declare function hasAutoWwwVariant(domain: string): boolean;
48
48
  export declare function gatewayHostnames(sites: Record<string, SiteConfig | undefined>, options?: {
49
49
  autoWww?: boolean;
50
50
  }): string[];
51
+ /**
52
+ * Public recursive resolvers used to decide whether a hostname is ready for an
53
+ * ACME http-01 challenge.
54
+ *
55
+ * The question the check has to answer is "can Let's Encrypt see this record
56
+ * yet", and LE resolves from the public internet — so the box's own resolver is
57
+ * the wrong vantage point, and a stale negative entry there blocks issuance for
58
+ * a record the rest of the world can already resolve. Two independent operators
59
+ * so one being unreachable does not stall a deploy.
60
+ */
61
+ export declare const PUBLIC_DNS_RESOLVERS: readonly string[];
51
62
  /** Default webroot the gateway serves ACME http-01 challenges from on `:80`. */
52
63
  export declare const DEFAULT_ACME_WEBROOT = "/var/www/acme-challenge";
53
64
  /** A normalized redirect target on an {@link RpxRoute} (see rpx's `redirect`). */
package/dist/index.d.ts CHANGED
@@ -35,16 +35,20 @@ export * from './ssl';
35
35
  export { deployStaticSite, deployStaticSiteFull, uploadStaticFiles, invalidateCache, deleteStaticSite, generateStaticSiteTemplate, deployStaticSiteWithExternalDns, deployStaticSiteWithExternalDnsFull, generateExternalDnsStaticSiteTemplate, deploySite, hasProxyUpstream, resolveProxyUpstreams, resolveSiteDeployTarget, resolveSiteKind, shipsARelease, validateDeploymentConfig, buildAndPushServerlessImage, buildFunctionEnv, deployServerlessApp, infraEnvFromOutputs, redeployServerlessApp, rollbackServerlessApp, runRemoteCommand, setMaintenance, buildManagementDashboardArtifact, dashboardCredentialsFile, ensureManagementDashboard, MANAGEMENT_DASHBOARD_SITE, resolveDashboardAuth, resolveUiSource, } from './deploy';
36
36
  export { collectServerDnsDomains, hostAcceptsIpv6, IPV6_EXCLUDED_HOST_LABELS, normalizePublicIpv6, reconcileAddressRecords, removeStaleServerAddressRecords, verifyAddressRecord, } from './deploy/server-dns';
37
37
  export type { AddressRecordReport } from './deploy/server-dns';
38
+ export * from './cdn';
38
39
  export type { EnsureDashboardLogger, ResolvedDashboardAuth, StaticSiteConfig, DeployResult, UploadOptions, ExternalDnsStaticSiteConfig, ExternalDnsDeployResult, DeploySiteConfig, DeploySiteResult, StaticSiteDnsProvider, SiteDeployKind, DeploymentValidationResult, BuildImageOptions, BuiltImage, CodeSource, DeployServerlessOptions, ResolvedContext, } from './deploy';
39
- export { createCloudDriver, CloudDriverFactory, cloudDrivers, AwsDriver, HetznerDriver, HetznerClient, resolveHetznerApiToken, normalizeSshPublicKey, ensureFirewall, ensureServer, ensureSshKey, serverPublicIpv4, sshExec, sshExecOrThrow, scpUpload, waitForSsh, waitForCloudInit, buildSshArgs, generateUbuntuAppCloudInit, wrapCloudInitUserData, buildHostCleanupScript, buildSiteDeployScript, buildStaticSiteDeployScript, resolveExecStart, deployAllComputeSites, deploySiteRelease, reloadRpxGateway, renewRpxCertificates, } from './drivers';
40
+ export { createCloudDriver, CloudDriverFactory, cloudDrivers, AwsDriver, HetznerDriver, HetznerClient, resolveHetznerApiToken, normalizeSshPublicKey, ensureFirewall, ensureServer, ensureSshKey, serverPublicIpv4, sshExec, sshExecOrThrow, scpUpload, waitForSsh, waitForCloudInit, buildSshArgs, generateUbuntuAppCloudInit, wrapCloudInitUserData, buildHostCleanupScript, buildSiteDeployScript, buildStaticSiteDeployScript, resolveExecStart, deployAllComputeSites, deploySiteRelease, reloadRpxGateway, renewRpxCertificates, gatewayHostnames, hasAutoWwwVariant, } from './drivers';
40
41
  export type { CreateCloudDriverOptions } from './drivers/factory';
41
42
  export { dashboardActions, resolveDashboardAction, sanitizeCloudConfig, startLocalDashboardServer, } from './deploy/local-dashboard-server';
42
43
  export type { DashboardAction, LocalDashboardServer, LocalDashboardServerOptions, } from './deploy/local-dashboard-server';
43
44
  export { createDnsProvider, detectDnsProvider, DnsProviderFactory, dnsProviders, PorkbunProvider, GoDaddyProvider, Route53Provider, UnifiedDnsValidator, createPorkbunValidator, createGoDaddyValidator, createRoute53Validator, } from './dns';
45
+ export type { CloudflareProviderOptions, CloudflareRule, CloudflareRulesetPhase, CloudflareZoneSetting, } from './dns/cloudflare';
46
+ export { CLOUDFLARE_MANAGED_RULE_PREFIX } from './dns/cloudflare';
44
47
  export * from './source';
45
48
  export * from './queue';
46
49
  export * from './backups';
47
50
  export * from './configuration';
48
51
  export type { DnsProvider, DnsProviderConfig, DnsRecord, DnsRecordType, DnsRecordResult, CreateRecordResult, DeleteRecordResult, ListRecordsResult, } from './dns';
52
+ export { PROXIABLE_RECORD_TYPES } from './dns';
49
53
  export * from '@ts-cloud/core';
50
54
  export type { S3Bucket, S3BucketPolicy, CloudFrontDistribution, CloudFrontOriginAccessControl, CloudFrontCacheBehavior, CloudFrontOrigin, } from '@ts-cloud/aws-types';
package/dist/index.js CHANGED
@@ -208,7 +208,7 @@ import {
208
208
  verifyUpdateSignature,
209
209
  webhookEndpoint,
210
210
  zeroCapacity
211
- } from "./chunk-wxhrr03r.js";
211
+ } from "./chunk-hygwq9y2.js";
212
212
  import {
213
213
  deleteStaticSite,
214
214
  deployStaticSite,
@@ -219,7 +219,7 @@ import {
219
219
  generateStaticSiteTemplate,
220
220
  invalidateCache,
221
221
  uploadStaticFiles
222
- } from "./chunk-v0vpamnf.js";
222
+ } from "./chunk-5nr95wpj.js";
223
223
  import {
224
224
  buildAndPushServerlessImage
225
225
  } from "./chunk-fgk8cqew.js";
@@ -278,8 +278,10 @@ import {
278
278
  import {
279
279
  ACMClient,
280
280
  ACMDnsValidator,
281
+ CLOUDFLARE_MANAGED_RULE_PREFIX,
281
282
  DnsProviderFactory,
282
283
  GoDaddyProvider,
284
+ PROXIABLE_RECORD_TYPES,
283
285
  PorkbunProvider,
284
286
  Route53Provider,
285
287
  UnifiedDnsValidator,
@@ -289,7 +291,7 @@ import {
289
291
  createRoute53Validator,
290
292
  detectDnsProvider,
291
293
  dnsProviders
292
- } from "./chunk-gttvakpv.js";
294
+ } from "./chunk-atbgd8wz.js";
293
295
  import {
294
296
  Route53Client
295
297
  } from "./chunk-32e7ya18.js";
@@ -316,6 +318,8 @@ import {
316
318
  ensureManagementDashboard,
317
319
  ensureServer,
318
320
  ensureSshKey,
321
+ gatewayHostnames,
322
+ hasAutoWwwVariant,
319
323
  hasProxyUpstream,
320
324
  hostAcceptsIpv6,
321
325
  normalizePublicIpv6,
@@ -341,7 +345,7 @@ import {
341
345
  waitForCloudInit,
342
346
  waitForSsh,
343
347
  wrapCloudInitUserData
344
- } from "./chunk-x6137d59.js";
348
+ } from "./chunk-hg7rhrgd.js";
345
349
  import {
346
350
  EC2Client,
347
351
  SSMClient
@@ -5282,6 +5286,353 @@ class AcmeClient {
5282
5286
  return this.accountKey;
5283
5287
  }
5284
5288
  }
5289
+ // src/cdn/cloudflare-rules.ts
5290
+ var DEFAULT_STATIC_ASSET_EXTENSIONS = [
5291
+ "js",
5292
+ "mjs",
5293
+ "cjs",
5294
+ "css",
5295
+ "map",
5296
+ "woff",
5297
+ "woff2",
5298
+ "ttf",
5299
+ "otf",
5300
+ "eot",
5301
+ "png",
5302
+ "jpg",
5303
+ "jpeg",
5304
+ "gif",
5305
+ "webp",
5306
+ "avif",
5307
+ "svg",
5308
+ "ico",
5309
+ "mp4",
5310
+ "webm",
5311
+ "ogg",
5312
+ "mp3",
5313
+ "wav",
5314
+ "pdf",
5315
+ "wasm"
5316
+ ];
5317
+ function quote(value) {
5318
+ return `"${value.replace(/\\/g, "\\\\").replace(/"/g, "\\\"")}"`;
5319
+ }
5320
+ function set(values) {
5321
+ return `{${values.map(quote).join(" ")}}`;
5322
+ }
5323
+ function hostCondition(hosts) {
5324
+ if (hosts.length === 1)
5325
+ return `http.host eq ${quote(hosts[0])}`;
5326
+ return `http.host in ${set(hosts)}`;
5327
+ }
5328
+ function buildStaticSiteCacheRules(hosts, settings = {}) {
5329
+ if (settings.enabled === false || hosts.length === 0)
5330
+ return [];
5331
+ const host = hostCondition(hosts);
5332
+ const rules = [];
5333
+ const bypassPaths = settings.bypassPaths ?? [];
5334
+ const bypassClause = bypassPaths.length > 0 ? `(${bypassPaths.map((path) => `starts_with(http.request.uri.path, ${quote(path)})`).join(" or ")})` : undefined;
5335
+ if (bypassClause) {
5336
+ rules.push({
5337
+ action: "set_cache_settings",
5338
+ description: "bypass cache",
5339
+ expression: `(${host} and ${bypassClause})`,
5340
+ enabled: true,
5341
+ action_parameters: { cache: false }
5342
+ });
5343
+ }
5344
+ const cacheable = bypassClause ? `${host} and not ${bypassClause}` : host;
5345
+ const extensions = settings.assetExtensions ?? DEFAULT_STATIC_ASSET_EXTENSIONS;
5346
+ const assetClause = extensions.length > 0 ? `lower(http.request.uri.path.extension) in ${set([...extensions])}` : undefined;
5347
+ if (assetClause) {
5348
+ rules.push({
5349
+ action: "set_cache_settings",
5350
+ description: "cache fingerprinted assets",
5351
+ expression: `(${cacheable} and ${assetClause})`,
5352
+ enabled: true,
5353
+ action_parameters: {
5354
+ cache: true,
5355
+ edge_ttl: { mode: "override_origin", default: settings.assetEdgeTtl ?? 2592000 },
5356
+ browser_ttl: { mode: "override_origin", default: settings.assetBrowserTtl ?? 31536000 }
5357
+ }
5358
+ });
5359
+ }
5360
+ rules.push({
5361
+ action: "set_cache_settings",
5362
+ description: "cache documents",
5363
+ expression: assetClause ? `(${cacheable} and not ${assetClause})` : `(${cacheable})`,
5364
+ enabled: true,
5365
+ action_parameters: {
5366
+ cache: true,
5367
+ edge_ttl: { mode: "override_origin", default: settings.documentEdgeTtl ?? 3600 },
5368
+ browser_ttl: { mode: "override_origin", default: settings.documentBrowserTtl ?? 0 }
5369
+ }
5370
+ });
5371
+ return rules;
5372
+ }
5373
+ function buildOriginGuardRule(hosts, header, value) {
5374
+ if (hosts.length === 0 || !header || !value)
5375
+ return [];
5376
+ return [
5377
+ {
5378
+ action: "rewrite",
5379
+ description: "origin guard header",
5380
+ expression: `(${hostCondition(hosts)})`,
5381
+ enabled: true,
5382
+ action_parameters: {
5383
+ headers: {
5384
+ [header]: { operation: "set", value }
5385
+ }
5386
+ }
5387
+ }
5388
+ ];
5389
+ }
5390
+
5391
+ // src/cdn/cloudflare-settings.ts
5392
+ var onOff = (value) => value === undefined ? undefined : value ? "on" : "off";
5393
+ function toCloudflareZoneSettings(settings) {
5394
+ const mapped = {
5395
+ ssl: settings.ssl,
5396
+ always_use_https: onOff(settings.alwaysUseHttps),
5397
+ automatic_https_rewrites: onOff(settings.automaticHttpsRewrites),
5398
+ min_tls_version: settings.minTlsVersion,
5399
+ tls_1_3: onOff(settings.tls13),
5400
+ brotli: onOff(settings.brotli),
5401
+ http3: onOff(settings.http3),
5402
+ "0rtt": onOff(settings.zeroRtt),
5403
+ early_hints: onOff(settings.earlyHints),
5404
+ browser_cache_ttl: settings.browserCacheTtl,
5405
+ always_online: onOff(settings.alwaysOnline),
5406
+ websockets: onOff(settings.websockets)
5407
+ };
5408
+ if (settings.hsts) {
5409
+ mapped.security_header = {
5410
+ strict_transport_security: {
5411
+ enabled: settings.hsts.enabled,
5412
+ max_age: settings.hsts.maxAge ?? 31536000,
5413
+ include_subdomains: settings.hsts.includeSubdomains ?? false,
5414
+ preload: settings.hsts.preload ?? false,
5415
+ nosniff: settings.hsts.noSniff ?? true
5416
+ }
5417
+ };
5418
+ }
5419
+ for (const key of Object.keys(mapped)) {
5420
+ if (mapped[key] === undefined)
5421
+ delete mapped[key];
5422
+ }
5423
+ return { ...mapped, ...settings.raw ?? {} };
5424
+ }
5425
+ var STATIC_SITE_ZONE_SETTINGS = {
5426
+ ssl: "strict",
5427
+ alwaysUseHttps: true,
5428
+ automaticHttpsRewrites: true,
5429
+ minTlsVersion: "1.2",
5430
+ tls13: true,
5431
+ brotli: true,
5432
+ http3: true,
5433
+ earlyHints: true,
5434
+ alwaysOnline: true,
5435
+ browserCacheTtl: 0
5436
+ };
5437
+
5438
+ // src/cdn/origin-tls.ts
5439
+ import { connect } from "node:tls";
5440
+ async function probeOriginTls(options) {
5441
+ const { address, serverName, port = 443, timeoutMs = 5000 } = options;
5442
+ return new Promise((resolve) => {
5443
+ let settled = false;
5444
+ const finish = (result) => {
5445
+ if (settled)
5446
+ return;
5447
+ settled = true;
5448
+ try {
5449
+ socket.destroy();
5450
+ } catch {}
5451
+ resolve(result);
5452
+ };
5453
+ const socket = connect({
5454
+ host: address,
5455
+ port,
5456
+ servername: serverName,
5457
+ rejectUnauthorized: false,
5458
+ timeout: timeoutMs
5459
+ });
5460
+ socket.once("secureConnect", () => {
5461
+ if (socket.authorized) {
5462
+ finish({ reachable: true, trusted: true });
5463
+ return;
5464
+ }
5465
+ finish({
5466
+ reachable: true,
5467
+ trusted: false,
5468
+ reason: socket.authorizationError ? String(socket.authorizationError) : "origin certificate is not trusted for this hostname"
5469
+ });
5470
+ });
5471
+ socket.once("timeout", () => {
5472
+ finish({ reachable: false, trusted: false, reason: `no TLS handshake within ${timeoutMs}ms` });
5473
+ });
5474
+ socket.once("error", (error) => {
5475
+ finish({ reachable: false, trusted: false, reason: error.message });
5476
+ });
5477
+ });
5478
+ }
5479
+
5480
+ // src/cdn/cloudflare.ts
5481
+ async function reconcileCloudflareCdn(options) {
5482
+ const {
5483
+ provider,
5484
+ zone,
5485
+ hosts,
5486
+ ipv4,
5487
+ ipv6,
5488
+ proxied = true,
5489
+ settings = STATIC_SITE_ZONE_SETTINGS,
5490
+ cache,
5491
+ originGuard,
5492
+ purge = true,
5493
+ ttl = 300,
5494
+ skipOriginProbe = false
5495
+ } = options;
5496
+ const report = {
5497
+ records: [],
5498
+ settingsChanged: [],
5499
+ cacheRules: 0,
5500
+ originGuard: false,
5501
+ purged: false,
5502
+ deferredProxy: [],
5503
+ warnings: []
5504
+ };
5505
+ if (hosts.length === 0)
5506
+ return report;
5507
+ const proxiedHosts = [];
5508
+ for (const host of hosts) {
5509
+ let hostProxied = proxied;
5510
+ if (proxied && !skipOriginProbe) {
5511
+ const probe = await probeOriginTls({ address: ipv4, serverName: host });
5512
+ if (!probe.trusted) {
5513
+ hostProxied = false;
5514
+ const reason = probe.reason || "origin is not serving a trusted certificate";
5515
+ report.deferredProxy.push({ host, reason });
5516
+ report.warnings.push(`${host}: publishing DNS-only for now — the origin does not yet serve a certificate this proxy can verify (${reason}). ` + `The origin issues certificates over HTTP-01, which needs the name to reach it directly; re-run the deploy once it has one and the record will be proxied.`);
5517
+ }
5518
+ }
5519
+ if (hostProxied)
5520
+ proxiedHosts.push(host);
5521
+ const families = [{ type: "A", content: ipv4 }];
5522
+ if (ipv6 && hostAcceptsIpv6(host))
5523
+ families.push({ type: "AAAA", content: ipv6 });
5524
+ for (const { type, content } of families) {
5525
+ const result = await provider.upsertRecord(zone, {
5526
+ name: host,
5527
+ type,
5528
+ content,
5529
+ ttl,
5530
+ proxied: hostProxied
5531
+ });
5532
+ if (result.success)
5533
+ report.records.push({ host, type, content, proxied: hostProxied });
5534
+ else
5535
+ report.warnings.push(`${host} ${type} → ${content} failed: ${result.message || "unknown provider error"}`);
5536
+ }
5537
+ }
5538
+ if (proxiedHosts.length === 0) {
5539
+ if (proxied) {
5540
+ report.warnings.push("No host is proxied yet, so zone settings, cache rules and purge were skipped — they only take effect through the proxy.");
5541
+ }
5542
+ return report;
5543
+ }
5544
+ try {
5545
+ const desired = toCloudflareZoneSettings(settings);
5546
+ const applied = await provider.applyZoneSettings(zone, desired);
5547
+ report.settingsChanged = applied.changed;
5548
+ for (const failure of applied.failed) {
5549
+ report.warnings.push(`zone setting '${failure.id}' could not be applied: ${failure.error}`);
5550
+ }
5551
+ } catch (error) {
5552
+ report.warnings.push(`zone settings could not be read: ${error instanceof Error ? error.message : String(error)}`);
5553
+ }
5554
+ const cacheRules = buildStaticSiteCacheRules(proxiedHosts, cache);
5555
+ if (cacheRules.length > 0) {
5556
+ const written = await provider.putManagedPhaseRules(zone, "http_request_cache_settings", cacheRules);
5557
+ if (written.success)
5558
+ report.cacheRules = cacheRules.length;
5559
+ else
5560
+ report.warnings.push(`cache rules could not be written: ${written.message}`);
5561
+ }
5562
+ if (originGuard) {
5563
+ const rules = buildOriginGuardRule(proxiedHosts, originGuard.header, originGuard.value);
5564
+ if (rules.length > 0) {
5565
+ const written = await provider.putManagedPhaseRules(zone, "http_request_late_transform", rules);
5566
+ if (written.success)
5567
+ report.originGuard = true;
5568
+ else {
5569
+ report.warnings.push(`origin guard header could not be written: ${written.message}. ` + `The gateway will reject requests that lack it — disable the guard or grant the token Transform Rules: Edit.`);
5570
+ }
5571
+ }
5572
+ }
5573
+ if (purge) {
5574
+ report.purged = await provider.purgeCache(zone, { hosts: proxiedHosts });
5575
+ if (!report.purged)
5576
+ report.warnings.push("edge cache purge failed — new content may take until TTL expiry to appear.");
5577
+ }
5578
+ return report;
5579
+ }
5580
+ // src/cdn/cloudflare-plan.ts
5581
+ var defaultEnv = (key) => process.env[key];
5582
+ function resolveZoneApex(host, configuredDomain) {
5583
+ const hostname = host.replace(/\.$/, "").toLowerCase();
5584
+ if (configuredDomain) {
5585
+ const domain = configuredDomain.replace(/\.$/, "").toLowerCase();
5586
+ if (hostname === domain || hostname.endsWith(`.${domain}`))
5587
+ return domain;
5588
+ }
5589
+ return hostname.split(".").slice(-2).join(".");
5590
+ }
5591
+ function resolveCloudflareCdnPlan(config2, options = {}) {
5592
+ const env = options.env ?? defaultEnv;
5593
+ const cdn = config2.infrastructure?.compute?.proxy?.cdn;
5594
+ const errors = [];
5595
+ if (!cdn || cdn.provider !== "cloudflare")
5596
+ return { plan: null, errors };
5597
+ if (cdn.originDomain) {
5598
+ errors.push(`compute.proxy.cdn.originDomain (${cdn.originDomain}) is ignored for Cloudflare: the proxy forwards to the address in the record it fronts, ` + `so no separate origin hostname is needed — and publishing one gives clients a documented way around the edge.`);
5599
+ }
5600
+ const apiToken = env("CLOUDFLARE_API_TOKEN");
5601
+ if (!apiToken) {
5602
+ errors.push("CLOUDFLARE_API_TOKEN is not set — the Cloudflare CDN cannot be reconciled.");
5603
+ return { plan: null, errors };
5604
+ }
5605
+ const sites = config2.sites ?? {};
5606
+ const hosts = cdn.frontedHosts?.length ? [...new Set(cdn.frontedHosts.map((host) => host.toLowerCase()))] : gatewayHostnames(sites, { autoWww: config2.infrastructure?.compute?.proxy?.autoWww !== false });
5607
+ if (hosts.length === 0) {
5608
+ errors.push("No hostnames to front — set compute.proxy.cdn.frontedHosts, or give a site a domain.");
5609
+ return { plan: null, errors };
5610
+ }
5611
+ const configuredDomain = config2.infrastructure?.dns?.domain;
5612
+ const zones = new Set(hosts.map((host) => resolveZoneApex(host, configuredDomain)));
5613
+ if (zones.size > 1) {
5614
+ errors.push(`Fronted hosts span more than one zone (${[...zones].join(", ")}). ` + `A Cloudflare zone is configured as a unit, so run one CDN config per zone.`);
5615
+ return { plan: null, errors };
5616
+ }
5617
+ const cf = cdn.cloudflare ?? {};
5618
+ const guard = cdn.secret ? { header: cdn.secretHeader ?? "X-Origin-Verify", value: cdn.secret } : undefined;
5619
+ return {
5620
+ plan: {
5621
+ zone: [...zones][0],
5622
+ hosts,
5623
+ apiToken,
5624
+ zoneId: cf.zoneId ?? env("CLOUDFLARE_ZONE_ID"),
5625
+ accountId: cf.accountId ?? env("CLOUDFLARE_ACCOUNT_ID"),
5626
+ proxied: cf.proxied !== false,
5627
+ settings: cf.settings ?? STATIC_SITE_ZONE_SETTINGS,
5628
+ cache: cf.cache,
5629
+ originGuard: guard,
5630
+ purge: cf.purgeOnDeploy !== false,
5631
+ skipOriginProbe: cf.skipOriginProbe === true
5632
+ },
5633
+ errors
5634
+ };
5635
+ }
5285
5636
  export {
5286
5637
  zonedParts,
5287
5638
  zoneOffsetMs,
@@ -5329,6 +5680,7 @@ export {
5329
5680
  totp,
5330
5681
  toFocusRecords,
5331
5682
  toFocusJsonl,
5683
+ toCloudflareZoneSettings,
5332
5684
  tieredMicroCents,
5333
5685
  testSourceConnection,
5334
5686
  templateCache,
@@ -5414,6 +5766,7 @@ export {
5414
5766
  robustZScore,
5415
5767
  responseToResult,
5416
5768
  resourceManagementManager,
5769
+ resolveZoneApex,
5417
5770
  resolveUiSource,
5418
5771
  resolveStorageBucketName,
5419
5772
  resolveStatePath,
@@ -5446,6 +5799,7 @@ export {
5446
5799
  resolveDashboardAuth,
5447
5800
  resolveDashboardAction,
5448
5801
  resolveCredentials,
5802
+ resolveCloudflareCdnPlan,
5449
5803
  resolveCloudProvider,
5450
5804
  resolveAuthEncryptionKey,
5451
5805
  resolveAppDatabase,
@@ -5479,6 +5833,7 @@ export {
5479
5833
  recordDashboardHostPosture,
5480
5834
  reconcileSourceWebhook,
5481
5835
  reconcileJobObservation,
5836
+ reconcileCloudflareCdn,
5482
5837
  reconcileAddressRecords,
5483
5838
  rateLimitKey,
5484
5839
  rateLimitHeaders,
@@ -5492,6 +5847,7 @@ export {
5492
5847
  productionChangeReview,
5493
5848
  processSourceWebhook,
5494
5849
  processInChunks,
5850
+ probeOriginTls,
5495
5851
  priceUsageLines,
5496
5852
  priceUsage,
5497
5853
  previousBudgetWindow,
@@ -5588,6 +5944,7 @@ export {
5588
5944
  hourBucketsBetween,
5589
5945
  hourBucket,
5590
5946
  hotp,
5947
+ hostCondition,
5591
5948
  hostAcceptsIpv6,
5592
5949
  healthCheckManager,
5593
5950
  hashString,
@@ -5597,6 +5954,7 @@ export {
5597
5954
  hashBuffer,
5598
5955
  hasProxyUpstream,
5599
5956
  hasManagementDashboardSite,
5957
+ hasAutoWwwVariant,
5600
5958
  handleSpendRequest,
5601
5959
  guardDutyManager,
5602
5960
  governingLimitCents,
@@ -5661,6 +6019,7 @@ export {
5661
6019
  generateBootstrap,
5662
6020
  generateApprovalConfig,
5663
6021
  generateAppImageDockerfile,
6022
+ gatewayHostnames,
5664
6023
  functionFingerprint,
5665
6024
  fromWebIdentity,
5666
6025
  fromSharedCredentials,
@@ -5836,9 +6195,11 @@ export {
5836
6195
  canaryManager,
5837
6196
  bytes,
5838
6197
  buildStaticSiteDeployScript,
6198
+ buildStaticSiteCacheRules,
5839
6199
  buildSshArgs,
5840
6200
  buildSiteDeployScript,
5841
6201
  buildPhpRuntimeLayerZip,
6202
+ buildOriginGuardRule,
5842
6203
  buildOptimizationManager,
5843
6204
  buildNodeRuntimeLayerZip,
5844
6205
  buildMediaCdnPlan,
@@ -5948,6 +6309,7 @@ export {
5948
6309
  Search,
5949
6310
  SchedulerClient,
5950
6311
  STSClient,
6312
+ STATIC_SITE_ZONE_SETTINGS,
5951
6313
  STATE_DIR_ENV_VAR,
5952
6314
  SSMClient,
5953
6315
  SQSClient,
@@ -6018,6 +6380,7 @@ export {
6018
6380
  Permissions,
6019
6381
  PerformanceManager,
6020
6382
  ParameterStore,
6383
+ PROXIABLE_RECORD_TYPES,
6021
6384
  PHP_LAYER_EXTENSIONS,
6022
6385
  PHP_DEFAULT_EXCLUDES,
6023
6386
  OrganizationManager,
@@ -6137,6 +6500,7 @@ export {
6137
6500
  DEPLOYMENT_QUEUE_KINDS,
6138
6501
  DEFAULT_THRESHOLDS,
6139
6502
  DEFAULT_TELEMETRY_POLICY,
6503
+ DEFAULT_STATIC_ASSET_EXTENSIONS,
6140
6504
  DEFAULT_STATE_DIR,
6141
6505
  DEFAULT_SERVICE_LIMITS,
6142
6506
  DEFAULT_RECURSION_LIMITS,
@@ -6180,6 +6544,7 @@ export {
6180
6544
  Cache,
6181
6545
  CONTROL_PLANE_SCHEMA_VERSION,
6182
6546
  COMMON_CROSS_ACCOUNT_ROLES,
6547
+ CLOUDFLARE_MANAGED_RULE_PREFIX,
6183
6548
  CHAIN_HEADER,
6184
6549
  CDN,
6185
6550
  BunFilesystemArchiveRuntime,