@stacksjs/ts-cloud 0.8.3 → 0.9.1

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 (73) 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 +367 -4
  25. package/dist/ui/access-denied.html +2 -2
  26. package/dist/ui/account/automation.html +4 -4
  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 +3 -3
  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 +3 -3
  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 +3 -3
  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 +3 -3
  60. package/dist/ui/server/ssh-keys.html +4 -4
  61. package/dist/ui/server/team.html +3 -3
  62. package/dist/ui/serverless/alarms.html +4 -4
  63. package/dist/ui/serverless/assets.html +2 -2
  64. package/dist/ui/serverless/data.html +4 -4
  65. package/dist/ui/serverless/deployments.html +2 -2
  66. package/dist/ui/serverless/functions.html +4 -4
  67. package/dist/ui/serverless/logs.html +4 -4
  68. package/dist/ui/serverless/metrics.html +2 -2
  69. package/dist/ui/serverless/queues.html +4 -4
  70. package/dist/ui/serverless/secrets.html +4 -4
  71. package/dist/ui/serverless/traces.html +4 -4
  72. package/dist/ui/serverless.html +4 -4
  73. 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
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
- export { createDnsProvider, detectDnsProvider, DnsProviderFactory, dnsProviders, PorkbunProvider, GoDaddyProvider, Route53Provider, UnifiedDnsValidator, createPorkbunValidator, createGoDaddyValidator, createRoute53Validator, } from './dns';
44
+ export { createDnsProvider, detectDnsProvider, DnsProviderFactory, dnsProviders, PorkbunProvider, GoDaddyProvider, Route53Provider, CloudflareProvider, 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,11 @@ import {
278
278
  import {
279
279
  ACMClient,
280
280
  ACMDnsValidator,
281
+ CLOUDFLARE_MANAGED_RULE_PREFIX,
282
+ CloudflareProvider,
281
283
  DnsProviderFactory,
282
284
  GoDaddyProvider,
285
+ PROXIABLE_RECORD_TYPES,
283
286
  PorkbunProvider,
284
287
  Route53Provider,
285
288
  UnifiedDnsValidator,
@@ -289,7 +292,7 @@ import {
289
292
  createRoute53Validator,
290
293
  detectDnsProvider,
291
294
  dnsProviders
292
- } from "./chunk-gttvakpv.js";
295
+ } from "./chunk-atbgd8wz.js";
293
296
  import {
294
297
  Route53Client
295
298
  } from "./chunk-32e7ya18.js";
@@ -343,7 +346,7 @@ import {
343
346
  waitForCloudInit,
344
347
  waitForSsh,
345
348
  wrapCloudInitUserData
346
- } from "./chunk-x6137d59.js";
349
+ } from "./chunk-hg7rhrgd.js";
347
350
  import {
348
351
  EC2Client,
349
352
  SSMClient
@@ -5284,6 +5287,353 @@ class AcmeClient {
5284
5287
  return this.accountKey;
5285
5288
  }
5286
5289
  }
5290
+ // src/cdn/cloudflare-rules.ts
5291
+ var DEFAULT_STATIC_ASSET_EXTENSIONS = [
5292
+ "js",
5293
+ "mjs",
5294
+ "cjs",
5295
+ "css",
5296
+ "map",
5297
+ "woff",
5298
+ "woff2",
5299
+ "ttf",
5300
+ "otf",
5301
+ "eot",
5302
+ "png",
5303
+ "jpg",
5304
+ "jpeg",
5305
+ "gif",
5306
+ "webp",
5307
+ "avif",
5308
+ "svg",
5309
+ "ico",
5310
+ "mp4",
5311
+ "webm",
5312
+ "ogg",
5313
+ "mp3",
5314
+ "wav",
5315
+ "pdf",
5316
+ "wasm"
5317
+ ];
5318
+ function quote(value) {
5319
+ return `"${value.replace(/\\/g, "\\\\").replace(/"/g, "\\\"")}"`;
5320
+ }
5321
+ function set(values) {
5322
+ return `{${values.map(quote).join(" ")}}`;
5323
+ }
5324
+ function hostCondition(hosts) {
5325
+ if (hosts.length === 1)
5326
+ return `http.host eq ${quote(hosts[0])}`;
5327
+ return `http.host in ${set(hosts)}`;
5328
+ }
5329
+ function buildStaticSiteCacheRules(hosts, settings = {}) {
5330
+ if (settings.enabled === false || hosts.length === 0)
5331
+ return [];
5332
+ const host = hostCondition(hosts);
5333
+ const rules = [];
5334
+ const bypassPaths = settings.bypassPaths ?? [];
5335
+ const bypassClause = bypassPaths.length > 0 ? `(${bypassPaths.map((path) => `starts_with(http.request.uri.path, ${quote(path)})`).join(" or ")})` : undefined;
5336
+ if (bypassClause) {
5337
+ rules.push({
5338
+ action: "set_cache_settings",
5339
+ description: "bypass cache",
5340
+ expression: `(${host} and ${bypassClause})`,
5341
+ enabled: true,
5342
+ action_parameters: { cache: false }
5343
+ });
5344
+ }
5345
+ const cacheable = bypassClause ? `${host} and not ${bypassClause}` : host;
5346
+ const extensions = settings.assetExtensions ?? DEFAULT_STATIC_ASSET_EXTENSIONS;
5347
+ const assetClause = extensions.length > 0 ? `lower(http.request.uri.path.extension) in ${set([...extensions])}` : undefined;
5348
+ if (assetClause) {
5349
+ rules.push({
5350
+ action: "set_cache_settings",
5351
+ description: "cache fingerprinted assets",
5352
+ expression: `(${cacheable} and ${assetClause})`,
5353
+ enabled: true,
5354
+ action_parameters: {
5355
+ cache: true,
5356
+ edge_ttl: { mode: "override_origin", default: settings.assetEdgeTtl ?? 2592000 },
5357
+ browser_ttl: { mode: "override_origin", default: settings.assetBrowserTtl ?? 31536000 }
5358
+ }
5359
+ });
5360
+ }
5361
+ rules.push({
5362
+ action: "set_cache_settings",
5363
+ description: "cache documents",
5364
+ expression: assetClause ? `(${cacheable} and not ${assetClause})` : `(${cacheable})`,
5365
+ enabled: true,
5366
+ action_parameters: {
5367
+ cache: true,
5368
+ edge_ttl: { mode: "override_origin", default: settings.documentEdgeTtl ?? 3600 },
5369
+ browser_ttl: { mode: "override_origin", default: settings.documentBrowserTtl ?? 0 }
5370
+ }
5371
+ });
5372
+ return rules;
5373
+ }
5374
+ function buildOriginGuardRule(hosts, header, value) {
5375
+ if (hosts.length === 0 || !header || !value)
5376
+ return [];
5377
+ return [
5378
+ {
5379
+ action: "rewrite",
5380
+ description: "origin guard header",
5381
+ expression: `(${hostCondition(hosts)})`,
5382
+ enabled: true,
5383
+ action_parameters: {
5384
+ headers: {
5385
+ [header]: { operation: "set", value }
5386
+ }
5387
+ }
5388
+ }
5389
+ ];
5390
+ }
5391
+
5392
+ // src/cdn/cloudflare-settings.ts
5393
+ var onOff = (value) => value === undefined ? undefined : value ? "on" : "off";
5394
+ function toCloudflareZoneSettings(settings) {
5395
+ const mapped = {
5396
+ ssl: settings.ssl,
5397
+ always_use_https: onOff(settings.alwaysUseHttps),
5398
+ automatic_https_rewrites: onOff(settings.automaticHttpsRewrites),
5399
+ min_tls_version: settings.minTlsVersion,
5400
+ tls_1_3: onOff(settings.tls13),
5401
+ brotli: onOff(settings.brotli),
5402
+ http3: onOff(settings.http3),
5403
+ "0rtt": onOff(settings.zeroRtt),
5404
+ early_hints: onOff(settings.earlyHints),
5405
+ browser_cache_ttl: settings.browserCacheTtl,
5406
+ always_online: onOff(settings.alwaysOnline),
5407
+ websockets: onOff(settings.websockets)
5408
+ };
5409
+ if (settings.hsts) {
5410
+ mapped.security_header = {
5411
+ strict_transport_security: {
5412
+ enabled: settings.hsts.enabled,
5413
+ max_age: settings.hsts.maxAge ?? 31536000,
5414
+ include_subdomains: settings.hsts.includeSubdomains ?? false,
5415
+ preload: settings.hsts.preload ?? false,
5416
+ nosniff: settings.hsts.noSniff ?? true
5417
+ }
5418
+ };
5419
+ }
5420
+ for (const key of Object.keys(mapped)) {
5421
+ if (mapped[key] === undefined)
5422
+ delete mapped[key];
5423
+ }
5424
+ return { ...mapped, ...settings.raw ?? {} };
5425
+ }
5426
+ var STATIC_SITE_ZONE_SETTINGS = {
5427
+ ssl: "strict",
5428
+ alwaysUseHttps: true,
5429
+ automaticHttpsRewrites: true,
5430
+ minTlsVersion: "1.2",
5431
+ tls13: true,
5432
+ brotli: true,
5433
+ http3: true,
5434
+ earlyHints: true,
5435
+ alwaysOnline: true,
5436
+ browserCacheTtl: 0
5437
+ };
5438
+
5439
+ // src/cdn/origin-tls.ts
5440
+ import { connect } from "node:tls";
5441
+ async function probeOriginTls(options) {
5442
+ const { address, serverName, port = 443, timeoutMs = 5000 } = options;
5443
+ return new Promise((resolve) => {
5444
+ let settled = false;
5445
+ const finish = (result) => {
5446
+ if (settled)
5447
+ return;
5448
+ settled = true;
5449
+ try {
5450
+ socket.destroy();
5451
+ } catch {}
5452
+ resolve(result);
5453
+ };
5454
+ const socket = connect({
5455
+ host: address,
5456
+ port,
5457
+ servername: serverName,
5458
+ rejectUnauthorized: false,
5459
+ timeout: timeoutMs
5460
+ });
5461
+ socket.once("secureConnect", () => {
5462
+ if (socket.authorized) {
5463
+ finish({ reachable: true, trusted: true });
5464
+ return;
5465
+ }
5466
+ finish({
5467
+ reachable: true,
5468
+ trusted: false,
5469
+ reason: socket.authorizationError ? String(socket.authorizationError) : "origin certificate is not trusted for this hostname"
5470
+ });
5471
+ });
5472
+ socket.once("timeout", () => {
5473
+ finish({ reachable: false, trusted: false, reason: `no TLS handshake within ${timeoutMs}ms` });
5474
+ });
5475
+ socket.once("error", (error) => {
5476
+ finish({ reachable: false, trusted: false, reason: error.message });
5477
+ });
5478
+ });
5479
+ }
5480
+
5481
+ // src/cdn/cloudflare.ts
5482
+ async function reconcileCloudflareCdn(options) {
5483
+ const {
5484
+ provider,
5485
+ zone,
5486
+ hosts,
5487
+ ipv4,
5488
+ ipv6,
5489
+ proxied = true,
5490
+ settings = STATIC_SITE_ZONE_SETTINGS,
5491
+ cache,
5492
+ originGuard,
5493
+ purge = true,
5494
+ ttl = 300,
5495
+ skipOriginProbe = false
5496
+ } = options;
5497
+ const report = {
5498
+ records: [],
5499
+ settingsChanged: [],
5500
+ cacheRules: 0,
5501
+ originGuard: false,
5502
+ purged: false,
5503
+ deferredProxy: [],
5504
+ warnings: []
5505
+ };
5506
+ if (hosts.length === 0)
5507
+ return report;
5508
+ const proxiedHosts = [];
5509
+ for (const host of hosts) {
5510
+ let hostProxied = proxied;
5511
+ if (proxied && !skipOriginProbe) {
5512
+ const probe = await probeOriginTls({ address: ipv4, serverName: host });
5513
+ if (!probe.trusted) {
5514
+ hostProxied = false;
5515
+ const reason = probe.reason || "origin is not serving a trusted certificate";
5516
+ report.deferredProxy.push({ host, reason });
5517
+ 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.`);
5518
+ }
5519
+ }
5520
+ if (hostProxied)
5521
+ proxiedHosts.push(host);
5522
+ const families = [{ type: "A", content: ipv4 }];
5523
+ if (ipv6 && hostAcceptsIpv6(host))
5524
+ families.push({ type: "AAAA", content: ipv6 });
5525
+ for (const { type, content } of families) {
5526
+ const result = await provider.upsertRecord(zone, {
5527
+ name: host,
5528
+ type,
5529
+ content,
5530
+ ttl,
5531
+ proxied: hostProxied
5532
+ });
5533
+ if (result.success)
5534
+ report.records.push({ host, type, content, proxied: hostProxied });
5535
+ else
5536
+ report.warnings.push(`${host} ${type} → ${content} failed: ${result.message || "unknown provider error"}`);
5537
+ }
5538
+ }
5539
+ if (proxiedHosts.length === 0) {
5540
+ if (proxied) {
5541
+ report.warnings.push("No host is proxied yet, so zone settings, cache rules and purge were skipped — they only take effect through the proxy.");
5542
+ }
5543
+ return report;
5544
+ }
5545
+ try {
5546
+ const desired = toCloudflareZoneSettings(settings);
5547
+ const applied = await provider.applyZoneSettings(zone, desired);
5548
+ report.settingsChanged = applied.changed;
5549
+ for (const failure of applied.failed) {
5550
+ report.warnings.push(`zone setting '${failure.id}' could not be applied: ${failure.error}`);
5551
+ }
5552
+ } catch (error) {
5553
+ report.warnings.push(`zone settings could not be read: ${error instanceof Error ? error.message : String(error)}`);
5554
+ }
5555
+ const cacheRules = buildStaticSiteCacheRules(proxiedHosts, cache);
5556
+ if (cacheRules.length > 0) {
5557
+ const written = await provider.putManagedPhaseRules(zone, "http_request_cache_settings", cacheRules);
5558
+ if (written.success)
5559
+ report.cacheRules = cacheRules.length;
5560
+ else
5561
+ report.warnings.push(`cache rules could not be written: ${written.message}`);
5562
+ }
5563
+ if (originGuard) {
5564
+ const rules = buildOriginGuardRule(proxiedHosts, originGuard.header, originGuard.value);
5565
+ if (rules.length > 0) {
5566
+ const written = await provider.putManagedPhaseRules(zone, "http_request_late_transform", rules);
5567
+ if (written.success)
5568
+ report.originGuard = true;
5569
+ else {
5570
+ 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.`);
5571
+ }
5572
+ }
5573
+ }
5574
+ if (purge) {
5575
+ report.purged = await provider.purgeCache(zone, { hosts: proxiedHosts });
5576
+ if (!report.purged)
5577
+ report.warnings.push("edge cache purge failed — new content may take until TTL expiry to appear.");
5578
+ }
5579
+ return report;
5580
+ }
5581
+ // src/cdn/cloudflare-plan.ts
5582
+ var defaultEnv = (key) => process.env[key];
5583
+ function resolveZoneApex(host, configuredDomain) {
5584
+ const hostname = host.replace(/\.$/, "").toLowerCase();
5585
+ if (configuredDomain) {
5586
+ const domain = configuredDomain.replace(/\.$/, "").toLowerCase();
5587
+ if (hostname === domain || hostname.endsWith(`.${domain}`))
5588
+ return domain;
5589
+ }
5590
+ return hostname.split(".").slice(-2).join(".");
5591
+ }
5592
+ function resolveCloudflareCdnPlan(config2, options = {}) {
5593
+ const env = options.env ?? defaultEnv;
5594
+ const cdn = config2.infrastructure?.compute?.proxy?.cdn;
5595
+ const errors = [];
5596
+ if (!cdn || cdn.provider !== "cloudflare")
5597
+ return { plan: null, errors };
5598
+ if (cdn.originDomain) {
5599
+ 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.`);
5600
+ }
5601
+ const apiToken = env("CLOUDFLARE_API_TOKEN");
5602
+ if (!apiToken) {
5603
+ errors.push("CLOUDFLARE_API_TOKEN is not set — the Cloudflare CDN cannot be reconciled.");
5604
+ return { plan: null, errors };
5605
+ }
5606
+ const sites = config2.sites ?? {};
5607
+ const hosts = cdn.frontedHosts?.length ? [...new Set(cdn.frontedHosts.map((host) => host.toLowerCase()))] : gatewayHostnames(sites, { autoWww: config2.infrastructure?.compute?.proxy?.autoWww !== false });
5608
+ if (hosts.length === 0) {
5609
+ errors.push("No hostnames to front — set compute.proxy.cdn.frontedHosts, or give a site a domain.");
5610
+ return { plan: null, errors };
5611
+ }
5612
+ const configuredDomain = config2.infrastructure?.dns?.domain;
5613
+ const zones = new Set(hosts.map((host) => resolveZoneApex(host, configuredDomain)));
5614
+ if (zones.size > 1) {
5615
+ 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.`);
5616
+ return { plan: null, errors };
5617
+ }
5618
+ const cf = cdn.cloudflare ?? {};
5619
+ const guard = cdn.secret ? { header: cdn.secretHeader ?? "X-Origin-Verify", value: cdn.secret } : undefined;
5620
+ return {
5621
+ plan: {
5622
+ zone: [...zones][0],
5623
+ hosts,
5624
+ apiToken,
5625
+ zoneId: cf.zoneId ?? env("CLOUDFLARE_ZONE_ID"),
5626
+ accountId: cf.accountId ?? env("CLOUDFLARE_ACCOUNT_ID"),
5627
+ proxied: cf.proxied !== false,
5628
+ settings: cf.settings ?? STATIC_SITE_ZONE_SETTINGS,
5629
+ cache: cf.cache,
5630
+ originGuard: guard,
5631
+ purge: cf.purgeOnDeploy !== false,
5632
+ skipOriginProbe: cf.skipOriginProbe === true
5633
+ },
5634
+ errors
5635
+ };
5636
+ }
5287
5637
  export {
5288
5638
  zonedParts,
5289
5639
  zoneOffsetMs,
@@ -5331,6 +5681,7 @@ export {
5331
5681
  totp,
5332
5682
  toFocusRecords,
5333
5683
  toFocusJsonl,
5684
+ toCloudflareZoneSettings,
5334
5685
  tieredMicroCents,
5335
5686
  testSourceConnection,
5336
5687
  templateCache,
@@ -5416,6 +5767,7 @@ export {
5416
5767
  robustZScore,
5417
5768
  responseToResult,
5418
5769
  resourceManagementManager,
5770
+ resolveZoneApex,
5419
5771
  resolveUiSource,
5420
5772
  resolveStorageBucketName,
5421
5773
  resolveStatePath,
@@ -5448,6 +5800,7 @@ export {
5448
5800
  resolveDashboardAuth,
5449
5801
  resolveDashboardAction,
5450
5802
  resolveCredentials,
5803
+ resolveCloudflareCdnPlan,
5451
5804
  resolveCloudProvider,
5452
5805
  resolveAuthEncryptionKey,
5453
5806
  resolveAppDatabase,
@@ -5481,6 +5834,7 @@ export {
5481
5834
  recordDashboardHostPosture,
5482
5835
  reconcileSourceWebhook,
5483
5836
  reconcileJobObservation,
5837
+ reconcileCloudflareCdn,
5484
5838
  reconcileAddressRecords,
5485
5839
  rateLimitKey,
5486
5840
  rateLimitHeaders,
@@ -5494,6 +5848,7 @@ export {
5494
5848
  productionChangeReview,
5495
5849
  processSourceWebhook,
5496
5850
  processInChunks,
5851
+ probeOriginTls,
5497
5852
  priceUsageLines,
5498
5853
  priceUsage,
5499
5854
  previousBudgetWindow,
@@ -5590,6 +5945,7 @@ export {
5590
5945
  hourBucketsBetween,
5591
5946
  hourBucket,
5592
5947
  hotp,
5948
+ hostCondition,
5593
5949
  hostAcceptsIpv6,
5594
5950
  healthCheckManager,
5595
5951
  hashString,
@@ -5840,9 +6196,11 @@ export {
5840
6196
  canaryManager,
5841
6197
  bytes,
5842
6198
  buildStaticSiteDeployScript,
6199
+ buildStaticSiteCacheRules,
5843
6200
  buildSshArgs,
5844
6201
  buildSiteDeployScript,
5845
6202
  buildPhpRuntimeLayerZip,
6203
+ buildOriginGuardRule,
5846
6204
  buildOptimizationManager,
5847
6205
  buildNodeRuntimeLayerZip,
5848
6206
  buildMediaCdnPlan,
@@ -5952,6 +6310,7 @@ export {
5952
6310
  Search,
5953
6311
  SchedulerClient,
5954
6312
  STSClient,
6313
+ STATIC_SITE_ZONE_SETTINGS,
5955
6314
  STATE_DIR_ENV_VAR,
5956
6315
  SSMClient,
5957
6316
  SQSClient,
@@ -6022,6 +6381,7 @@ export {
6022
6381
  Permissions,
6023
6382
  PerformanceManager,
6024
6383
  ParameterStore,
6384
+ PROXIABLE_RECORD_TYPES,
6025
6385
  PHP_LAYER_EXTENSIONS,
6026
6386
  PHP_DEFAULT_EXCLUDES,
6027
6387
  OrganizationManager,
@@ -6141,6 +6501,7 @@ export {
6141
6501
  DEPLOYMENT_QUEUE_KINDS,
6142
6502
  DEFAULT_THRESHOLDS,
6143
6503
  DEFAULT_TELEMETRY_POLICY,
6504
+ DEFAULT_STATIC_ASSET_EXTENSIONS,
6144
6505
  DEFAULT_STATE_DIR,
6145
6506
  DEFAULT_SERVICE_LIMITS,
6146
6507
  DEFAULT_RECURSION_LIMITS,
@@ -6171,6 +6532,7 @@ export {
6171
6532
  ComposeApplicationService,
6172
6533
  Communication,
6173
6534
  CommandHistory,
6535
+ CloudflareProvider,
6174
6536
  CloudWatchLogsClient,
6175
6537
  CloudTrailManager,
6176
6538
  CloudFrontClient,
@@ -6184,6 +6546,7 @@ export {
6184
6546
  Cache,
6185
6547
  CONTROL_PLANE_SCHEMA_VERSION,
6186
6548
  COMMON_CROSS_ACCOUNT_ROLES,
6549
+ CLOUDFLARE_MANAGED_RULE_PREFIX,
6187
6550
  CHAIN_HEADER,
6188
6551
  CDN,
6189
6552
  BunFilesystemArchiveRuntime,