@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.
- 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 +367 -4
- package/dist/ui/access-denied.html +2 -2
- package/dist/ui/account/automation.html +4 -4
- 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 +3 -3
- 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 +3 -3
- 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 +3 -3
- package/dist/ui/server/security.html +2 -2
- package/dist/ui/server/services.html +2 -2
- package/dist/ui/server/sites.html +3 -3
- package/dist/ui/server/ssh-keys.html +4 -4
- package/dist/ui/server/team.html +3 -3
- 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/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 +4 -4
- 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
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-
|
|
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,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-
|
|
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-
|
|
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,
|