@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
|
@@ -4,6 +4,9 @@ import {
|
|
|
4
4
|
import {
|
|
5
5
|
AWSClient
|
|
6
6
|
} from "./chunk-zqtpg06c.js";
|
|
7
|
+
|
|
8
|
+
// src/dns/types.ts
|
|
9
|
+
var PROXIABLE_RECORD_TYPES = new Set(["A", "AAAA", "CNAME"]);
|
|
7
10
|
// src/dns/porkbun.ts
|
|
8
11
|
var PORKBUN_API_URL = "https://api.porkbun.com/api/json/v3";
|
|
9
12
|
var PORKBUN_MAX_ATTEMPTS = 8;
|
|
@@ -544,13 +547,19 @@ class GoDaddyProvider {
|
|
|
544
547
|
}
|
|
545
548
|
// src/dns/cloudflare.ts
|
|
546
549
|
var CLOUDFLARE_API_URL = "https://api.cloudflare.com/client/v4";
|
|
550
|
+
var CLOUDFLARE_MANAGED_RULE_PREFIX = "[ts-cloud]";
|
|
547
551
|
|
|
548
552
|
class CloudflareProvider {
|
|
549
553
|
name = "cloudflare";
|
|
550
554
|
apiToken;
|
|
551
555
|
zoneCache = new Map;
|
|
552
|
-
|
|
556
|
+
configuredZoneId;
|
|
557
|
+
accountId;
|
|
558
|
+
configuredZoneName;
|
|
559
|
+
constructor(apiToken, options = {}) {
|
|
553
560
|
this.apiToken = apiToken;
|
|
561
|
+
this.configuredZoneId = options.zoneId;
|
|
562
|
+
this.accountId = options.accountId;
|
|
554
563
|
}
|
|
555
564
|
async request(method, endpoint, body) {
|
|
556
565
|
const url = `${CLOUDFLARE_API_URL}${endpoint}`;
|
|
@@ -580,38 +589,70 @@ class CloudflareProvider {
|
|
|
580
589
|
}
|
|
581
590
|
return domain;
|
|
582
591
|
}
|
|
583
|
-
async
|
|
584
|
-
const
|
|
592
|
+
async resolveZone(domain) {
|
|
593
|
+
const hostname = domain.replace(/\.$/, "").toLowerCase();
|
|
594
|
+
if (this.configuredZoneId) {
|
|
595
|
+
const name = await this.resolveConfiguredZoneName();
|
|
596
|
+
if (name && (hostname === name || hostname.endsWith(`.${name}`))) {
|
|
597
|
+
return { id: this.configuredZoneId, name };
|
|
598
|
+
}
|
|
599
|
+
}
|
|
600
|
+
const rootDomain = this.getRootDomain(hostname);
|
|
585
601
|
const cached = this.zoneCache.get(rootDomain);
|
|
586
602
|
if (cached) {
|
|
587
|
-
return cached;
|
|
603
|
+
return { id: cached, name: rootDomain };
|
|
588
604
|
}
|
|
589
605
|
const response = await this.request("GET", `/zones?name=${encodeURIComponent(rootDomain)}`);
|
|
590
606
|
if (!response.result || response.result.length === 0) {
|
|
591
|
-
throw new Error(`Zone not found for domain: ${rootDomain}`);
|
|
607
|
+
throw new Error(`Zone not found for domain: ${rootDomain}. ` + `If the API token is scoped to a single zone, set its zone id (CLOUDFLARE_ZONE_ID) — ` + `zone lookup by name needs account-wide Zone:Read.`);
|
|
592
608
|
}
|
|
593
609
|
const zoneId = response.result[0].id;
|
|
594
610
|
this.zoneCache.set(rootDomain, zoneId);
|
|
595
|
-
return zoneId;
|
|
611
|
+
return { id: zoneId, name: response.result[0].name || rootDomain };
|
|
596
612
|
}
|
|
597
|
-
|
|
598
|
-
|
|
613
|
+
async resolveConfiguredZoneName() {
|
|
614
|
+
if (!this.configuredZoneId)
|
|
615
|
+
return;
|
|
616
|
+
if (this.configuredZoneName)
|
|
617
|
+
return this.configuredZoneName;
|
|
618
|
+
try {
|
|
619
|
+
const response = await this.request("GET", `/zones/${this.configuredZoneId}`);
|
|
620
|
+
const name = response.result?.name?.toLowerCase();
|
|
621
|
+
if (!name)
|
|
622
|
+
return;
|
|
623
|
+
this.configuredZoneName = name;
|
|
624
|
+
this.zoneCache.set(name, this.configuredZoneId);
|
|
625
|
+
return name;
|
|
626
|
+
} catch {
|
|
627
|
+
return;
|
|
628
|
+
}
|
|
629
|
+
}
|
|
630
|
+
async getZoneId(domain) {
|
|
631
|
+
return (await this.resolveZone(domain)).id;
|
|
632
|
+
}
|
|
633
|
+
getFullRecordName(name, zoneName) {
|
|
599
634
|
const cleanName = name.replace(/\.$/, "");
|
|
600
|
-
if (!cleanName || cleanName ===
|
|
601
|
-
return
|
|
635
|
+
if (!cleanName || cleanName === zoneName || cleanName === "@") {
|
|
636
|
+
return zoneName;
|
|
602
637
|
}
|
|
603
|
-
if (cleanName.endsWith(`.${
|
|
638
|
+
if (cleanName.endsWith(`.${zoneName}`)) {
|
|
604
639
|
return cleanName;
|
|
605
640
|
}
|
|
606
|
-
return `${cleanName}.${
|
|
641
|
+
return `${cleanName}.${zoneName}`;
|
|
607
642
|
}
|
|
608
|
-
toCloudflareRecord(record,
|
|
643
|
+
toCloudflareRecord(record, zoneName, proxied) {
|
|
609
644
|
const cfRecord = {
|
|
610
645
|
type: record.type,
|
|
611
|
-
name: this.getFullRecordName(record.name,
|
|
646
|
+
name: this.getFullRecordName(record.name, zoneName),
|
|
612
647
|
content: record.content || record.value || "",
|
|
613
648
|
ttl: record.ttl || 1
|
|
614
649
|
};
|
|
650
|
+
const desiredProxy = proxied ?? record.proxied;
|
|
651
|
+
if (desiredProxy !== undefined && PROXIABLE_RECORD_TYPES.has(record.type)) {
|
|
652
|
+
cfRecord.proxied = desiredProxy;
|
|
653
|
+
if (desiredProxy)
|
|
654
|
+
cfRecord.ttl = 1;
|
|
655
|
+
}
|
|
615
656
|
if (record.type === "MX" && record.priority !== undefined) {
|
|
616
657
|
cfRecord.priority = record.priority;
|
|
617
658
|
}
|
|
@@ -629,14 +670,15 @@ class CloudflareProvider {
|
|
|
629
670
|
type: record.type,
|
|
630
671
|
content: record.content,
|
|
631
672
|
ttl: record.ttl,
|
|
632
|
-
priority: record.priority
|
|
673
|
+
priority: record.priority,
|
|
674
|
+
proxied: record.proxied
|
|
633
675
|
};
|
|
634
676
|
}
|
|
635
677
|
async createRecord(domain, record) {
|
|
636
678
|
try {
|
|
637
|
-
const
|
|
638
|
-
const cfRecord = this.toCloudflareRecord(record,
|
|
639
|
-
const response = await this.request("POST", `/zones/${
|
|
679
|
+
const zone = await this.resolveZone(domain);
|
|
680
|
+
const cfRecord = this.toCloudflareRecord(record, zone.name);
|
|
681
|
+
const response = await this.request("POST", `/zones/${zone.id}/dns_records`, cfRecord);
|
|
640
682
|
return {
|
|
641
683
|
success: true,
|
|
642
684
|
id: response.result.id,
|
|
@@ -651,20 +693,21 @@ class CloudflareProvider {
|
|
|
651
693
|
}
|
|
652
694
|
async upsertRecord(domain, record) {
|
|
653
695
|
try {
|
|
654
|
-
const
|
|
655
|
-
const fullName = this.getFullRecordName(record.name,
|
|
656
|
-
const existingResponse = await this.request("GET", `/zones/${
|
|
657
|
-
const
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
696
|
+
const zone = await this.resolveZone(domain);
|
|
697
|
+
const fullName = this.getFullRecordName(record.name, zone.name);
|
|
698
|
+
const existingResponse = await this.request("GET", `/zones/${zone.id}/dns_records?type=${record.type}&name=${encodeURIComponent(fullName)}`);
|
|
699
|
+
const existing = existingResponse.result?.[0];
|
|
700
|
+
const proxied = record.proxied ?? existing?.proxied;
|
|
701
|
+
const cfRecord = this.toCloudflareRecord(record, zone.name, proxied);
|
|
702
|
+
if (existing) {
|
|
703
|
+
const response2 = await this.request("PUT", `/zones/${zone.id}/dns_records/${existing.id}`, cfRecord);
|
|
661
704
|
return {
|
|
662
705
|
success: true,
|
|
663
706
|
id: response2.result.id,
|
|
664
707
|
message: "Record updated successfully"
|
|
665
708
|
};
|
|
666
709
|
}
|
|
667
|
-
const response = await this.request("POST", `/zones/${
|
|
710
|
+
const response = await this.request("POST", `/zones/${zone.id}/dns_records`, cfRecord);
|
|
668
711
|
return {
|
|
669
712
|
success: true,
|
|
670
713
|
id: response.result.id,
|
|
@@ -676,8 +719,9 @@ class CloudflareProvider {
|
|
|
676
719
|
}
|
|
677
720
|
async deleteRecord(domain, record) {
|
|
678
721
|
try {
|
|
679
|
-
const
|
|
680
|
-
const
|
|
722
|
+
const zone = await this.resolveZone(domain);
|
|
723
|
+
const zoneId = zone.id;
|
|
724
|
+
const fullName = this.getFullRecordName(record.name, zone.name);
|
|
681
725
|
const existingResponse = await this.request("GET", `/zones/${zoneId}/dns_records?type=${record.type}&name=${encodeURIComponent(fullName)}`);
|
|
682
726
|
if (!existingResponse.result || existingResponse.result.length === 0) {
|
|
683
727
|
return {
|
|
@@ -804,8 +848,9 @@ class CloudflareProvider {
|
|
|
804
848
|
}
|
|
805
849
|
async getRecordProxyStatus(domain, record) {
|
|
806
850
|
try {
|
|
807
|
-
const
|
|
808
|
-
const
|
|
851
|
+
const zone = await this.resolveZone(domain);
|
|
852
|
+
const zoneId = zone.id;
|
|
853
|
+
const fullName = this.getFullRecordName(record.name, zone.name);
|
|
809
854
|
const response = await this.request("GET", `/zones/${zoneId}/dns_records?type=${record.type}&name=${encodeURIComponent(fullName)}`);
|
|
810
855
|
if (response.result && response.result.length > 0) {
|
|
811
856
|
const matchingRecord = response.result.find((r) => r.content === record.content);
|
|
@@ -818,8 +863,9 @@ class CloudflareProvider {
|
|
|
818
863
|
}
|
|
819
864
|
async setRecordProxyStatus(domain, record, proxied) {
|
|
820
865
|
try {
|
|
821
|
-
const
|
|
822
|
-
const
|
|
866
|
+
const zone = await this.resolveZone(domain);
|
|
867
|
+
const zoneId = zone.id;
|
|
868
|
+
const fullName = this.getFullRecordName(record.name, zone.name);
|
|
823
869
|
const response = await this.request("GET", `/zones/${zoneId}/dns_records?type=${record.type}&name=${encodeURIComponent(fullName)}`);
|
|
824
870
|
if (!response.result || response.result.length === 0) {
|
|
825
871
|
return false;
|
|
@@ -834,6 +880,81 @@ class CloudflareProvider {
|
|
|
834
880
|
return false;
|
|
835
881
|
}
|
|
836
882
|
}
|
|
883
|
+
async zoneIdFor(domain) {
|
|
884
|
+
return this.getZoneId(domain);
|
|
885
|
+
}
|
|
886
|
+
get account() {
|
|
887
|
+
return this.accountId;
|
|
888
|
+
}
|
|
889
|
+
async getZoneSettings(domain) {
|
|
890
|
+
const zoneId = await this.getZoneId(domain);
|
|
891
|
+
const response = await this.request("GET", `/zones/${zoneId}/settings`);
|
|
892
|
+
const settings = {};
|
|
893
|
+
for (const setting of response.result || [])
|
|
894
|
+
settings[setting.id] = setting.value;
|
|
895
|
+
return settings;
|
|
896
|
+
}
|
|
897
|
+
async applyZoneSettings(domain, desired) {
|
|
898
|
+
const zoneId = await this.getZoneId(domain);
|
|
899
|
+
const current = await this.getZoneSettings(domain).catch(() => ({}));
|
|
900
|
+
const changed = [];
|
|
901
|
+
const failed = [];
|
|
902
|
+
for (const [id, value] of Object.entries(desired)) {
|
|
903
|
+
if (value === undefined)
|
|
904
|
+
continue;
|
|
905
|
+
const existing = current[id];
|
|
906
|
+
if (existing !== undefined && deepEqual(existing, value))
|
|
907
|
+
continue;
|
|
908
|
+
try {
|
|
909
|
+
await this.request("PATCH", `/zones/${zoneId}/settings/${id}`, { value });
|
|
910
|
+
changed.push({ id, from: existing, to: value });
|
|
911
|
+
} catch (error) {
|
|
912
|
+
failed.push({ id, error: error instanceof Error ? error.message : String(error) });
|
|
913
|
+
}
|
|
914
|
+
}
|
|
915
|
+
return { changed, failed };
|
|
916
|
+
}
|
|
917
|
+
async getPhaseRules(domain, phase) {
|
|
918
|
+
const zoneId = await this.getZoneId(domain);
|
|
919
|
+
try {
|
|
920
|
+
const response = await this.request("GET", `/zones/${zoneId}/rulesets/phases/${phase}/entrypoint`);
|
|
921
|
+
return response.result?.rules || [];
|
|
922
|
+
} catch {
|
|
923
|
+
return [];
|
|
924
|
+
}
|
|
925
|
+
}
|
|
926
|
+
async putManagedPhaseRules(domain, phase, rules) {
|
|
927
|
+
try {
|
|
928
|
+
const zoneId = await this.getZoneId(domain);
|
|
929
|
+
const existing = await this.getPhaseRules(domain, phase);
|
|
930
|
+
const foreign = existing.filter((rule) => !(rule.description || "").startsWith(CLOUDFLARE_MANAGED_RULE_PREFIX));
|
|
931
|
+
const managed = rules.map((rule) => ({
|
|
932
|
+
...rule,
|
|
933
|
+
description: rule.description?.startsWith(CLOUDFLARE_MANAGED_RULE_PREFIX) ? rule.description : `${CLOUDFLARE_MANAGED_RULE_PREFIX} ${rule.description || phase}`
|
|
934
|
+
}));
|
|
935
|
+
const payload = [...foreign, ...managed].map(({ id: _id, ...rule }) => rule);
|
|
936
|
+
await this.request("PUT", `/zones/${zoneId}/rulesets/phases/${phase}/entrypoint`, { rules: payload });
|
|
937
|
+
return { success: true };
|
|
938
|
+
} catch (error) {
|
|
939
|
+
return { success: false, message: error instanceof Error ? error.message : String(error) };
|
|
940
|
+
}
|
|
941
|
+
}
|
|
942
|
+
}
|
|
943
|
+
function deepEqual(a, b) {
|
|
944
|
+
if (a === b)
|
|
945
|
+
return true;
|
|
946
|
+
if (typeof a !== typeof b || a === null || b === null)
|
|
947
|
+
return false;
|
|
948
|
+
if (typeof a !== "object")
|
|
949
|
+
return false;
|
|
950
|
+
if (Array.isArray(a) || Array.isArray(b)) {
|
|
951
|
+
if (!Array.isArray(a) || !Array.isArray(b) || a.length !== b.length)
|
|
952
|
+
return false;
|
|
953
|
+
return a.every((item, index) => deepEqual(item, b[index]));
|
|
954
|
+
}
|
|
955
|
+
const aRecord = a;
|
|
956
|
+
const bRecord = b;
|
|
957
|
+
return Object.keys(bRecord).every((key) => deepEqual(aRecord[key], bRecord[key]));
|
|
837
958
|
}
|
|
838
959
|
// src/dns/route53-adapter.ts
|
|
839
960
|
class Route53Provider {
|
|
@@ -1779,7 +1900,7 @@ function createDnsProvider(config) {
|
|
|
1779
1900
|
case "godaddy":
|
|
1780
1901
|
return new GoDaddyProvider(config.apiKey, config.apiSecret, config.environment);
|
|
1781
1902
|
case "cloudflare":
|
|
1782
|
-
return new CloudflareProvider(config.apiToken);
|
|
1903
|
+
return new CloudflareProvider(config.apiToken, { zoneId: config.zoneId, accountId: config.accountId });
|
|
1783
1904
|
default:
|
|
1784
1905
|
throw new Error(`Unknown DNS provider: ${config.provider}`);
|
|
1785
1906
|
}
|
|
@@ -1826,10 +1947,12 @@ class DnsProviderFactory {
|
|
|
1826
1947
|
});
|
|
1827
1948
|
return this;
|
|
1828
1949
|
}
|
|
1829
|
-
addCloudflare(apiToken) {
|
|
1950
|
+
addCloudflare(apiToken, options = {}) {
|
|
1830
1951
|
this.configs.push({
|
|
1831
1952
|
provider: "cloudflare",
|
|
1832
|
-
apiToken
|
|
1953
|
+
apiToken,
|
|
1954
|
+
zoneId: options.zoneId,
|
|
1955
|
+
accountId: options.accountId
|
|
1833
1956
|
});
|
|
1834
1957
|
return this;
|
|
1835
1958
|
}
|
|
@@ -1850,7 +1973,10 @@ class DnsProviderFactory {
|
|
|
1850
1973
|
}
|
|
1851
1974
|
const cloudflareApiToken = process.env.CLOUDFLARE_API_TOKEN;
|
|
1852
1975
|
if (cloudflareApiToken) {
|
|
1853
|
-
this.addCloudflare(cloudflareApiToken
|
|
1976
|
+
this.addCloudflare(cloudflareApiToken, {
|
|
1977
|
+
zoneId: process.env.CLOUDFLARE_ZONE_ID,
|
|
1978
|
+
accountId: process.env.CLOUDFLARE_ACCOUNT_ID
|
|
1979
|
+
});
|
|
1854
1980
|
}
|
|
1855
1981
|
return this;
|
|
1856
1982
|
}
|
|
@@ -1882,4 +2008,4 @@ class DnsProviderFactory {
|
|
|
1882
2008
|
}
|
|
1883
2009
|
var dnsProviders = new DnsProviderFactory;
|
|
1884
2010
|
|
|
1885
|
-
export { PorkbunProvider, GoDaddyProvider, CloudflareProvider, Route53Provider, UnifiedDnsValidator, createPorkbunValidator, createGoDaddyValidator, createRoute53Validator, createDnsProvider, detectDnsProvider, DnsProviderFactory, dnsProviders, ACMClient, ACMDnsValidator };
|
|
2011
|
+
export { PROXIABLE_RECORD_TYPES, PorkbunProvider, GoDaddyProvider, CLOUDFLARE_MANAGED_RULE_PREFIX, CloudflareProvider, Route53Provider, UnifiedDnsValidator, createPorkbunValidator, createGoDaddyValidator, createRoute53Validator, createDnsProvider, detectDnsProvider, DnsProviderFactory, dnsProviders, ACMClient, ACMDnsValidator };
|
|
@@ -1519,6 +1519,7 @@ function gatewayHostnames(sites, options = {}) {
|
|
|
1519
1519
|
}
|
|
1520
1520
|
return [...hosts];
|
|
1521
1521
|
}
|
|
1522
|
+
var PUBLIC_DNS_RESOLVERS = ["1.1.1.1", "8.8.8.8"];
|
|
1522
1523
|
var DEFAULT_ACME_WEBROOT = "/var/www/acme-challenge";
|
|
1523
1524
|
function normalizeSiteRedirect(input) {
|
|
1524
1525
|
if (typeof input === "string")
|
|
@@ -1665,11 +1666,12 @@ function buildRpxConfigInternal(sites, options, appBoxes) {
|
|
|
1665
1666
|
config.acmeChallengeWebroot = options.proxy.acmeWebroot ?? DEFAULT_ACME_WEBROOT;
|
|
1666
1667
|
}
|
|
1667
1668
|
const cdn = options.proxy.cdn;
|
|
1668
|
-
|
|
1669
|
+
const guardedHosts = cdn?.frontedHosts?.length ? cdn.frontedHosts : [...domains];
|
|
1670
|
+
if (cdn?.secret && guardedHosts.length > 0) {
|
|
1669
1671
|
config.originGuard = {
|
|
1670
1672
|
header: cdn.secretHeader ?? "X-Origin-Verify",
|
|
1671
1673
|
value: cdn.secret,
|
|
1672
|
-
hosts:
|
|
1674
|
+
hosts: guardedHosts
|
|
1673
1675
|
};
|
|
1674
1676
|
}
|
|
1675
1677
|
return config;
|
|
@@ -1751,8 +1753,18 @@ for (const f of files) {
|
|
|
1751
1753
|
if (frag.productionCerts?.certsDir) certsDir = frag.productionCerts.certsDir
|
|
1752
1754
|
acmeChallengeWebroot ??= frag.acmeChallengeWebroot
|
|
1753
1755
|
if (frag.originGuard) {
|
|
1756
|
+
// One gateway means one origin-guard secret (rpx takes a single
|
|
1757
|
+
// header/value). A tenant whose secret disagrees with the adopted one must
|
|
1758
|
+
// NOT have its hosts guarded: its CDN would send a different value, the
|
|
1759
|
+
// gateway would reject every request, and a working host would go dark.
|
|
1760
|
+
// Unguarded is weaker but serving, and the mismatch is logged.
|
|
1754
1761
|
guard ??= { header: frag.originGuard.header, value: frag.originGuard.value }
|
|
1755
|
-
|
|
1762
|
+
if (frag.originGuard.header === guard.header && frag.originGuard.value === guard.value) {
|
|
1763
|
+
for (const h of frag.originGuard.hosts ?? []) guardHosts.add(h)
|
|
1764
|
+
}
|
|
1765
|
+
else {
|
|
1766
|
+
console.warn('[rpx-assembler] origin-guard secret in ' + f + ' differs from the one already in force; its hosts stay unguarded rather than rejecting all traffic')
|
|
1767
|
+
}
|
|
1756
1768
|
}
|
|
1757
1769
|
}
|
|
1758
1770
|
const config = {
|
|
@@ -1861,13 +1873,24 @@ function buildCertManagementCommands(options) {
|
|
|
1861
1873
|
`WEBROOT='${webroot}'`,
|
|
1862
1874
|
`EMAIL='${email}'`,
|
|
1863
1875
|
`TLSX="${tlsxCli}"`,
|
|
1876
|
+
`BUN='${bunBin}'`,
|
|
1864
1877
|
`DOMAINS='${csv}'`,
|
|
1865
1878
|
"DNS_ATTEMPTS='24'",
|
|
1866
1879
|
"DNS_DELAY_SECONDS='5'",
|
|
1880
|
+
`DNS_RESOLVERS='${PUBLIC_DNS_RESOLVERS.join(" ")}'`,
|
|
1881
|
+
"dns_resolves() {",
|
|
1882
|
+
' d="$1"',
|
|
1883
|
+
" for ns in $DNS_RESOLVERS; do",
|
|
1884
|
+
` if $BUN --eval "const{Resolver}=require('node:dns');const r=new Resolver();r.setServers([process.argv[1]]);r.resolve4(process.argv[2],e=>process.exit(e?1:0))" "$ns" "$d" >/dev/null 2>&1; then`,
|
|
1885
|
+
" return 0",
|
|
1886
|
+
" fi",
|
|
1887
|
+
" done",
|
|
1888
|
+
' getent ahosts "$d" >/dev/null 2>&1',
|
|
1889
|
+
"}",
|
|
1867
1890
|
"wait_for_dns() {",
|
|
1868
1891
|
' d="$1"',
|
|
1869
1892
|
" attempt=1",
|
|
1870
|
-
' while !
|
|
1893
|
+
' while ! dns_resolves "$d"; do',
|
|
1871
1894
|
' if [ "$attempt" -ge "$DNS_ATTEMPTS" ]; then',
|
|
1872
1895
|
' echo "DNS for $d did not become resolvable after $DNS_ATTEMPTS attempts" >&2',
|
|
1873
1896
|
" return 1",
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import {
|
|
2
2
|
deployStaticSiteWithExternalDnsFull
|
|
3
|
-
} from "./chunk-
|
|
3
|
+
} from "./chunk-5nr95wpj.js";
|
|
4
4
|
import {
|
|
5
5
|
box,
|
|
6
6
|
error,
|
|
@@ -50,7 +50,7 @@ import {
|
|
|
50
50
|
resolveSiteKind,
|
|
51
51
|
resolveUiSource,
|
|
52
52
|
siteInstallBase
|
|
53
|
-
} from "./chunk-
|
|
53
|
+
} from "./chunk-hg7rhrgd.js";
|
|
54
54
|
import {
|
|
55
55
|
SSMClient
|
|
56
56
|
} from "./chunk-7m60qnc8.js";
|
|
@@ -5,9 +5,9 @@ import {
|
|
|
5
5
|
generateStaticSiteTemplate,
|
|
6
6
|
invalidateCache,
|
|
7
7
|
uploadStaticFiles
|
|
8
|
-
} from "./chunk-
|
|
8
|
+
} from "./chunk-5nr95wpj.js";
|
|
9
9
|
import"./chunk-01d86gt1.js";
|
|
10
|
-
import"./chunk-
|
|
10
|
+
import"./chunk-atbgd8wz.js";
|
|
11
11
|
import"./chunk-32e7ya18.js";
|
|
12
12
|
import"./chunk-4cjrg98a.js";
|
|
13
13
|
import"./chunk-rds0sy87.js";
|
package/dist/deploy/index.js
CHANGED
|
@@ -36,7 +36,7 @@ import {
|
|
|
36
36
|
synchronizeDashboardUsers,
|
|
37
37
|
trackDashboardOperation,
|
|
38
38
|
verifyStaticApiOrigin
|
|
39
|
-
} from "../chunk-
|
|
39
|
+
} from "../chunk-hygwq9y2.js";
|
|
40
40
|
import {
|
|
41
41
|
deleteStaticSite,
|
|
42
42
|
deployStaticSite,
|
|
@@ -47,7 +47,7 @@ import {
|
|
|
47
47
|
generateStaticSiteTemplate,
|
|
48
48
|
invalidateCache,
|
|
49
49
|
uploadStaticFiles
|
|
50
|
-
} from "../chunk-
|
|
50
|
+
} from "../chunk-5nr95wpj.js";
|
|
51
51
|
import {
|
|
52
52
|
buildAndPushServerlessImage
|
|
53
53
|
} from "../chunk-fgk8cqew.js";
|
|
@@ -56,7 +56,7 @@ import"../chunk-nsgqsmrh.js";
|
|
|
56
56
|
import"../chunk-01d86gt1.js";
|
|
57
57
|
import"../chunk-50jpda9q.js";
|
|
58
58
|
import"../chunk-he9a874b.js";
|
|
59
|
-
import"../chunk-
|
|
59
|
+
import"../chunk-atbgd8wz.js";
|
|
60
60
|
import"../chunk-32e7ya18.js";
|
|
61
61
|
import {
|
|
62
62
|
IPV6_EXCLUDED_HOST_LABELS,
|
|
@@ -83,7 +83,7 @@ import {
|
|
|
83
83
|
siteInstallBase,
|
|
84
84
|
validateDeploymentConfig,
|
|
85
85
|
verifyAddressRecord
|
|
86
|
-
} from "../chunk-
|
|
86
|
+
} from "../chunk-hg7rhrgd.js";
|
|
87
87
|
import"../chunk-7m60qnc8.js";
|
|
88
88
|
import"../chunk-4cjrg98a.js";
|
|
89
89
|
import"../chunk-rds0sy87.js";
|
package/dist/dns/cloudflare.d.ts
CHANGED
|
@@ -3,11 +3,53 @@
|
|
|
3
3
|
* API documentation: https://developers.cloudflare.com/api/resources/dns/subresources/records/
|
|
4
4
|
*/
|
|
5
5
|
import type { CreateRecordResult, DeleteRecordResult, DnsProvider, DnsRecord, DnsRecordType, ListRecordsResult } from './types';
|
|
6
|
+
/** One entry of `GET /zones/{id}/settings` (and the body of a per-setting PATCH). */
|
|
7
|
+
export interface CloudflareZoneSetting {
|
|
8
|
+
id: string;
|
|
9
|
+
value: unknown;
|
|
10
|
+
editable?: boolean;
|
|
11
|
+
modified_on?: string | null;
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* A rule inside a phase entrypoint ruleset (cache rules, transform rules, …).
|
|
15
|
+
* Only the fields ts-cloud writes or has to preserve are modelled.
|
|
16
|
+
*/
|
|
17
|
+
export interface CloudflareRule {
|
|
18
|
+
id?: string;
|
|
19
|
+
action: string;
|
|
20
|
+
expression: string;
|
|
21
|
+
description?: string;
|
|
22
|
+
enabled?: boolean;
|
|
23
|
+
action_parameters?: Record<string, unknown>;
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Marker that identifies a rule as ts-cloud's, carried in the rule description.
|
|
27
|
+
*
|
|
28
|
+
* Cloudflare's phase entrypoints are replace-only — `PUT .../entrypoint` sets
|
|
29
|
+
* the WHOLE rule list — so writing our rules naively would delete every rule a
|
|
30
|
+
* human added in the dashboard. Tagging ours lets a reconcile rewrite only what
|
|
31
|
+
* it owns and carry everything else through untouched.
|
|
32
|
+
*/
|
|
33
|
+
export declare const CLOUDFLARE_MANAGED_RULE_PREFIX = "[ts-cloud]";
|
|
34
|
+
/** Ruleset phases ts-cloud manages rules in. */
|
|
35
|
+
export type CloudflareRulesetPhase = 'http_request_cache_settings' | 'http_request_late_transform';
|
|
36
|
+
export interface CloudflareProviderOptions {
|
|
37
|
+
/** Zone id, so the API token can be scoped to a single zone. */
|
|
38
|
+
zoneId?: string;
|
|
39
|
+
/** Account id, for account-scoped resources. */
|
|
40
|
+
accountId?: string;
|
|
41
|
+
}
|
|
6
42
|
export declare class CloudflareProvider implements DnsProvider {
|
|
7
43
|
readonly name = "cloudflare";
|
|
8
44
|
private apiToken;
|
|
45
|
+
/** Zone name → zone id. */
|
|
9
46
|
private zoneCache;
|
|
10
|
-
|
|
47
|
+
/** Zone id configured up front (single-zone token); resolved to a name on first use. */
|
|
48
|
+
private readonly configuredZoneId?;
|
|
49
|
+
private readonly accountId?;
|
|
50
|
+
/** Name of {@link configuredZoneId}, learned once from the API. */
|
|
51
|
+
private configuredZoneName?;
|
|
52
|
+
constructor(apiToken: string, options?: CloudflareProviderOptions);
|
|
11
53
|
/**
|
|
12
54
|
* Make an authenticated API request to Cloudflare
|
|
13
55
|
*/
|
|
@@ -17,6 +59,22 @@ export declare class CloudflareProvider implements DnsProvider {
|
|
|
17
59
|
* e.g., "api.example.com" -> "example.com"
|
|
18
60
|
*/
|
|
19
61
|
private getRootDomain;
|
|
62
|
+
/**
|
|
63
|
+
* Resolve the zone a hostname belongs to, as both id and name.
|
|
64
|
+
*
|
|
65
|
+
* A configured {@link CloudflareProviderOptions.zoneId} is tried first and is
|
|
66
|
+
* the only path that works with a single-zone API token: the by-name fallback
|
|
67
|
+
* calls `GET /zones?name=…`, an ACCOUNT-level listing that a zone-scoped token
|
|
68
|
+
* cannot read — it returns an empty list, which is indistinguishable from "the
|
|
69
|
+
* domain isn't here" unless you already know the token is narrow.
|
|
70
|
+
*
|
|
71
|
+
* Resolving the configured zone also yields its real name, which is what makes
|
|
72
|
+
* record naming correct for multi-label suffixes (`example.co.uk`). The
|
|
73
|
+
* fallback can only guess the apex from the last two labels.
|
|
74
|
+
*/
|
|
75
|
+
private resolveZone;
|
|
76
|
+
/** Name of the configured zone, fetched once. Undefined if it can't be read. */
|
|
77
|
+
private resolveConfiguredZoneName;
|
|
20
78
|
/**
|
|
21
79
|
* Get Zone ID for a domain (with caching)
|
|
22
80
|
*/
|
|
@@ -24,6 +82,9 @@ export declare class CloudflareProvider implements DnsProvider {
|
|
|
24
82
|
/**
|
|
25
83
|
* Get the full record name
|
|
26
84
|
* Cloudflare stores records with full domain names
|
|
85
|
+
*
|
|
86
|
+
* `zoneName` is the zone's actual name as Cloudflare reports it, not a guess
|
|
87
|
+
* from the requested domain — see {@link resolveZone}.
|
|
27
88
|
*/
|
|
28
89
|
private getFullRecordName;
|
|
29
90
|
/**
|
|
@@ -71,4 +132,61 @@ export declare class CloudflareProvider implements DnsProvider {
|
|
|
71
132
|
* Update proxy status for a record (Cloudflare-specific)
|
|
72
133
|
*/
|
|
73
134
|
setRecordProxyStatus(domain: string, record: DnsRecord, proxied: boolean): Promise<boolean>;
|
|
135
|
+
/**
|
|
136
|
+
* Public zone id for a hostname — the entry point for the Cloudflare-specific
|
|
137
|
+
* work (settings, rulesets, purge) that sits outside the DnsProvider contract.
|
|
138
|
+
*/
|
|
139
|
+
zoneIdFor(domain: string): Promise<string>;
|
|
140
|
+
/** Account id this provider was configured with, if any. */
|
|
141
|
+
get account(): string | undefined;
|
|
142
|
+
/**
|
|
143
|
+
* Read every zone setting, keyed by setting id.
|
|
144
|
+
*
|
|
145
|
+
* Used to make {@link applyZoneSettings} a real reconcile: settings that
|
|
146
|
+
* already hold the desired value are left alone, so a deploy reports what it
|
|
147
|
+
* actually changed instead of rewriting the whole zone every time.
|
|
148
|
+
*/
|
|
149
|
+
getZoneSettings(domain: string): Promise<Record<string, unknown>>;
|
|
150
|
+
/**
|
|
151
|
+
* Apply zone settings, skipping any that already match.
|
|
152
|
+
*
|
|
153
|
+
* Returns one entry per setting that was actually written, plus the failures.
|
|
154
|
+
* Failures are collected rather than thrown: zone settings are largely
|
|
155
|
+
* independent, several are plan-gated (a Free zone rejects `min_tls_version`
|
|
156
|
+
* above 1.0 on some plans, `http3` needs the feature enabled), and one
|
|
157
|
+
* unavailable toggle must not abort a deploy that has already shipped code.
|
|
158
|
+
*/
|
|
159
|
+
applyZoneSettings(domain: string, desired: Record<string, unknown>): Promise<{
|
|
160
|
+
changed: Array<{
|
|
161
|
+
id: string;
|
|
162
|
+
from: unknown;
|
|
163
|
+
to: unknown;
|
|
164
|
+
}>;
|
|
165
|
+
failed: Array<{
|
|
166
|
+
id: string;
|
|
167
|
+
error: string;
|
|
168
|
+
}>;
|
|
169
|
+
}>;
|
|
170
|
+
/**
|
|
171
|
+
* Read the rules currently in a phase entrypoint ruleset.
|
|
172
|
+
*
|
|
173
|
+
* A zone that has never had a rule in this phase has no entrypoint at all and
|
|
174
|
+
* the API answers 404, which is a normal empty state rather than a failure.
|
|
175
|
+
*/
|
|
176
|
+
getPhaseRules(domain: string, phase: CloudflareRulesetPhase): Promise<CloudflareRule[]>;
|
|
177
|
+
/**
|
|
178
|
+
* Replace ts-cloud's rules in a phase while preserving everyone else's.
|
|
179
|
+
*
|
|
180
|
+
* Cloudflare only exposes a whole-list PUT for a phase entrypoint, so a naive
|
|
181
|
+
* write deletes every rule someone added in the dashboard. Rules ts-cloud owns
|
|
182
|
+
* are tagged in their description with {@link CLOUDFLARE_MANAGED_RULE_PREFIX};
|
|
183
|
+
* this drops exactly those, appends the new ones, and carries the rest through
|
|
184
|
+
* in their original order.
|
|
185
|
+
*
|
|
186
|
+
* Passing an empty `rules` removes ts-cloud's rules from the phase.
|
|
187
|
+
*/
|
|
188
|
+
putManagedPhaseRules(domain: string, phase: CloudflareRulesetPhase, rules: CloudflareRule[]): Promise<{
|
|
189
|
+
success: boolean;
|
|
190
|
+
message?: string;
|
|
191
|
+
}>;
|
|
74
192
|
}
|
package/dist/dns/index.d.ts
CHANGED
|
@@ -43,7 +43,10 @@ export declare class DnsProviderFactory {
|
|
|
43
43
|
/**
|
|
44
44
|
* Add Cloudflare provider
|
|
45
45
|
*/
|
|
46
|
-
addCloudflare(apiToken: string
|
|
46
|
+
addCloudflare(apiToken: string, options?: {
|
|
47
|
+
zoneId?: string;
|
|
48
|
+
accountId?: string;
|
|
49
|
+
}): this;
|
|
47
50
|
/**
|
|
48
51
|
* Load providers from environment variables
|
|
49
52
|
*/
|
package/dist/dns/index.js
CHANGED
|
@@ -2,6 +2,7 @@ import {
|
|
|
2
2
|
CloudflareProvider,
|
|
3
3
|
DnsProviderFactory,
|
|
4
4
|
GoDaddyProvider,
|
|
5
|
+
PROXIABLE_RECORD_TYPES,
|
|
5
6
|
PorkbunProvider,
|
|
6
7
|
Route53Provider,
|
|
7
8
|
UnifiedDnsValidator,
|
|
@@ -11,7 +12,7 @@ import {
|
|
|
11
12
|
createRoute53Validator,
|
|
12
13
|
detectDnsProvider,
|
|
13
14
|
dnsProviders
|
|
14
|
-
} from "../chunk-
|
|
15
|
+
} from "../chunk-atbgd8wz.js";
|
|
15
16
|
import"../chunk-32e7ya18.js";
|
|
16
17
|
import"../chunk-zqtpg06c.js";
|
|
17
18
|
import"../chunk-v0bahtg2.js";
|
|
@@ -25,6 +26,7 @@ export {
|
|
|
25
26
|
UnifiedDnsValidator,
|
|
26
27
|
Route53Provider,
|
|
27
28
|
PorkbunProvider,
|
|
29
|
+
PROXIABLE_RECORD_TYPES,
|
|
28
30
|
GoDaddyProvider,
|
|
29
31
|
DnsProviderFactory,
|
|
30
32
|
CloudflareProvider
|