@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.
- package/dist/aws/index.js +3 -1
- package/dist/bin/cli.js +575 -565
- package/dist/bin/dashboard-server.js +334 -324
- package/dist/cdn/cloudflare-plan.d.ts +61 -0
- package/dist/cdn/cloudflare-rules.d.ts +77 -0
- package/dist/cdn/cloudflare-settings.d.ts +100 -0
- package/dist/cdn/cloudflare.d.ts +108 -0
- package/dist/cdn/cloudflare.test.d.ts +1 -0
- package/dist/cdn/index.d.ts +17 -0
- package/dist/cdn/origin-tls.d.ts +36 -0
- package/dist/{chunk-v0vpamnf.js → chunk-5nr95wpj.js} +3 -3
- package/dist/{chunk-gttvakpv.js → chunk-atbgd8wz.js} +163 -37
- package/dist/{chunk-x6137d59.js → chunk-hg7rhrgd.js} +27 -4
- package/dist/{chunk-wxhrr03r.js → chunk-hygwq9y2.js} +2 -2
- package/dist/{chunk-kpeb5rcs.js → chunk-ws47smxr.js} +2 -2
- package/dist/deploy/index.js +4 -4
- package/dist/dns/cloudflare.d.ts +119 -1
- package/dist/dns/index.d.ts +4 -1
- package/dist/dns/index.js +3 -1
- package/dist/dns/types.d.ts +43 -0
- package/dist/drivers/index.js +1 -1
- package/dist/drivers/shared/rpx-gateway.d.ts +11 -0
- package/dist/index.d.ts +5 -1
- package/dist/index.js +369 -4
- package/dist/ui/account/automation.html +3 -3
- package/dist/ui/account/security.html +2 -2
- package/dist/ui/applications/compose.html +4 -4
- package/dist/ui/applications/new.html +2 -2
- package/dist/ui/data/backups.html +4 -4
- package/dist/ui/data/services.html +4 -4
- package/dist/ui/data/volumes.html +4 -4
- package/dist/ui/index.html +4 -4
- package/dist/ui/infrastructure/topology.html +4 -4
- package/dist/ui/integrations.html +2 -2
- package/dist/ui/operations/alerts.html +4 -4
- package/dist/ui/operations/configuration.html +4 -4
- package/dist/ui/operations/jobs.html +4 -4
- package/dist/ui/operations/maintenance.html +4 -4
- package/dist/ui/operations/observability.html +4 -4
- package/dist/ui/operations/previews.html +4 -4
- package/dist/ui/operations/queue.html +4 -4
- package/dist/ui/operations/regions.html +4 -4
- package/dist/ui/operations/releases.html +4 -4
- package/dist/ui/operations/spend.html +4 -4
- package/dist/ui/operations/workloads.html +4 -4
- package/dist/ui/security.html +2 -2
- package/dist/ui/server/actions.html +4 -4
- package/dist/ui/server/activity.html +2 -2
- package/dist/ui/server/capacity.html +4 -4
- package/dist/ui/server/database.html +4 -4
- package/dist/ui/server/deployments.html +4 -4
- package/dist/ui/server/diagnostics.html +2 -2
- package/dist/ui/server/firewall.html +4 -4
- package/dist/ui/server/fleet.html +4 -4
- package/dist/ui/server/logs.html +3 -3
- package/dist/ui/server/metrics.html +4 -4
- package/dist/ui/server/security.html +2 -2
- package/dist/ui/server/services.html +2 -2
- package/dist/ui/server/sites.html +4 -4
- package/dist/ui/server/ssh-keys.html +4 -4
- package/dist/ui/server/team.html +4 -4
- package/dist/ui/server/terminal.html +2 -2
- package/dist/ui/serverless/alarms.html +4 -4
- package/dist/ui/serverless/assets.html +2 -2
- package/dist/ui/serverless/data.html +4 -4
- package/dist/ui/serverless/deployments.html +2 -2
- package/dist/ui/serverless/firewall.html +2 -2
- package/dist/ui/serverless/functions.html +4 -4
- package/dist/ui/serverless/logs.html +4 -4
- package/dist/ui/serverless/metrics.html +2 -2
- package/dist/ui/serverless/queues.html +4 -4
- package/dist/ui/serverless/secrets.html +4 -4
- package/dist/ui/serverless/traces.html +3 -3
- package/dist/ui/serverless.html +4 -4
- package/package.json +3 -3
package/dist/dns/types.d.ts
CHANGED
|
@@ -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
|
/**
|
package/dist/drivers/index.js
CHANGED
|
@@ -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-
|
|
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-
|
|
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-
|
|
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-
|
|
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,
|