@absolutejs/deploy 0.7.0 → 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/README.md +27 -3
- package/dist/cloudTarget.d.ts +8 -8
- package/dist/digitalocean.d.ts +1 -1
- package/dist/digitalocean.js.map +3 -3
- package/dist/digitaloceanDns.d.ts +22 -0
- package/dist/digitaloceanDns.js +526 -0
- package/dist/digitaloceanDns.js.map +13 -0
- package/dist/hetzner.js.map +2 -2
- package/dist/hetznerDns.d.ts +43 -0
- package/dist/hetznerDns.js +169 -0
- package/dist/hetznerDns.js.map +10 -0
- package/dist/linode.d.ts +99 -0
- package/dist/linode.js +428 -0
- package/dist/linode.js.map +12 -0
- package/dist/route53.d.ts +96 -0
- package/dist/route53.js +170 -0
- package/dist/route53.js.map +10 -0
- package/dist/vultr.d.ts +95 -0
- package/dist/vultr.js +420 -0
- package/dist/vultr.js.map +12 -0
- package/package.json +27 -2
package/dist/route53.js
ADDED
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
// @bun
|
|
2
|
+
// src/route53.ts
|
|
3
|
+
var ensureFqdn = (name, zoneName) => {
|
|
4
|
+
if (name === "@")
|
|
5
|
+
return ensureTrailingDot(zoneName);
|
|
6
|
+
if (name === zoneName)
|
|
7
|
+
return ensureTrailingDot(name);
|
|
8
|
+
if (name.endsWith("."))
|
|
9
|
+
return name;
|
|
10
|
+
return `${name}.`;
|
|
11
|
+
};
|
|
12
|
+
var ensureTrailingDot = (s) => s.endsWith(".") ? s : `${s}.`;
|
|
13
|
+
var stripTrailingDot = (s) => s.endsWith(".") ? s.slice(0, -1) : s;
|
|
14
|
+
var encodeTxtValue = (raw) => {
|
|
15
|
+
if (raw.startsWith('"') && raw.endsWith('"'))
|
|
16
|
+
return raw;
|
|
17
|
+
const CHUNK = 255;
|
|
18
|
+
const chunks = [];
|
|
19
|
+
for (let i = 0;i < raw.length; i += CHUNK) {
|
|
20
|
+
chunks.push(`"${raw.slice(i, i + CHUNK).replaceAll('"', "\\\"")}"`);
|
|
21
|
+
}
|
|
22
|
+
return chunks.join(" ");
|
|
23
|
+
};
|
|
24
|
+
var decodeTxtValue = (wire) => {
|
|
25
|
+
const parts = wire.match(/"((?:\\.|[^"])*)"/g);
|
|
26
|
+
if (parts === null)
|
|
27
|
+
return wire;
|
|
28
|
+
return parts.map((part) => part.slice(1, -1).replaceAll("\\\"", '"')).join("");
|
|
29
|
+
};
|
|
30
|
+
var encodeId = (payload) => btoa(JSON.stringify(payload)).replaceAll("+", "-").replaceAll("/", "_").replaceAll("=", "");
|
|
31
|
+
var decodeId = (id) => {
|
|
32
|
+
const padded = id.replaceAll("-", "+").replaceAll("_", "/").padEnd(Math.ceil(id.length / 4) * 4, "=");
|
|
33
|
+
return JSON.parse(atob(padded));
|
|
34
|
+
};
|
|
35
|
+
var toDnsRecord = (set, zoneName) => {
|
|
36
|
+
const values = set.ResourceRecords?.map((r) => r.Value) ?? [];
|
|
37
|
+
if (values.length === 0)
|
|
38
|
+
return;
|
|
39
|
+
const content = set.Type === "TXT" ? decodeTxtValue(values.join(" ")) : values[0];
|
|
40
|
+
const idPayload = {
|
|
41
|
+
n: set.Name,
|
|
42
|
+
t: set.Type,
|
|
43
|
+
v: values,
|
|
44
|
+
...set.TTL !== undefined ? { l: set.TTL } : {}
|
|
45
|
+
};
|
|
46
|
+
const fqdn = stripTrailingDot(set.Name);
|
|
47
|
+
const name = fqdn === zoneName ? zoneName : fqdn;
|
|
48
|
+
return {
|
|
49
|
+
content,
|
|
50
|
+
id: encodeId(idPayload),
|
|
51
|
+
name,
|
|
52
|
+
type: set.Type,
|
|
53
|
+
...set.TTL !== undefined ? { ttl: set.TTL } : {}
|
|
54
|
+
};
|
|
55
|
+
};
|
|
56
|
+
var buildRecordSet = (spec, zoneName, defaultTtl) => {
|
|
57
|
+
const Name = ensureFqdn(spec.name, zoneName);
|
|
58
|
+
const ttl = spec.ttl ?? defaultTtl;
|
|
59
|
+
const value = spec.type === "TXT" ? encodeTxtValue(spec.content) : spec.content;
|
|
60
|
+
return {
|
|
61
|
+
Name,
|
|
62
|
+
ResourceRecords: [{ Value: value }],
|
|
63
|
+
TTL: ttl,
|
|
64
|
+
Type: spec.type
|
|
65
|
+
};
|
|
66
|
+
};
|
|
67
|
+
var route53DnsProvider = (options) => {
|
|
68
|
+
const { client, hostedZoneId, zoneName } = options;
|
|
69
|
+
const defaultTtl = options.defaultTtl ?? 300;
|
|
70
|
+
const zoneLabel = zoneName;
|
|
71
|
+
const fetchAll = async (filter) => {
|
|
72
|
+
const out = [];
|
|
73
|
+
let startName;
|
|
74
|
+
let startType;
|
|
75
|
+
for (;; ) {
|
|
76
|
+
const params = {
|
|
77
|
+
HostedZoneId: hostedZoneId,
|
|
78
|
+
MaxItems: "500",
|
|
79
|
+
...startName !== undefined ? { StartRecordName: startName } : {},
|
|
80
|
+
...startType !== undefined ? { StartRecordType: startType } : {}
|
|
81
|
+
};
|
|
82
|
+
const page = await client.listResourceRecordSets(params);
|
|
83
|
+
for (const set of page.ResourceRecordSets) {
|
|
84
|
+
const dnsRecord = toDnsRecord(set, zoneName);
|
|
85
|
+
if (dnsRecord === undefined)
|
|
86
|
+
continue;
|
|
87
|
+
if (filter?.name !== undefined && dnsRecord.name !== filter.name) {
|
|
88
|
+
continue;
|
|
89
|
+
}
|
|
90
|
+
if (filter?.type !== undefined && dnsRecord.type !== filter.type) {
|
|
91
|
+
continue;
|
|
92
|
+
}
|
|
93
|
+
out.push(dnsRecord);
|
|
94
|
+
}
|
|
95
|
+
if (page.IsTruncated !== true)
|
|
96
|
+
break;
|
|
97
|
+
startName = page.NextRecordName;
|
|
98
|
+
startType = page.NextRecordType;
|
|
99
|
+
}
|
|
100
|
+
return out;
|
|
101
|
+
};
|
|
102
|
+
const list = (filter) => fetchAll(filter);
|
|
103
|
+
const find = async (key) => {
|
|
104
|
+
const matches = await fetchAll(key);
|
|
105
|
+
if (matches.length === 0)
|
|
106
|
+
return;
|
|
107
|
+
if (matches.length > 1) {
|
|
108
|
+
throw new Error(`[deploy/route53] multiple ${key.type} records for "${key.name}" in zone ${zoneLabel} \u2014 drifted state; resolve manually before upsert.`);
|
|
109
|
+
}
|
|
110
|
+
return matches[0];
|
|
111
|
+
};
|
|
112
|
+
const submitChange = async (change) => {
|
|
113
|
+
await client.changeResourceRecordSets({
|
|
114
|
+
ChangeBatch: { Changes: [change] },
|
|
115
|
+
HostedZoneId: hostedZoneId
|
|
116
|
+
});
|
|
117
|
+
};
|
|
118
|
+
const create = async (spec) => {
|
|
119
|
+
const set = buildRecordSet(spec, zoneName, defaultTtl);
|
|
120
|
+
await submitChange({ Action: "CREATE", ResourceRecordSet: set });
|
|
121
|
+
const synthesized = toDnsRecord(set, zoneName);
|
|
122
|
+
if (synthesized === undefined) {
|
|
123
|
+
throw new Error("[deploy/route53] internal: built record had no ResourceRecords");
|
|
124
|
+
}
|
|
125
|
+
return synthesized;
|
|
126
|
+
};
|
|
127
|
+
const upsert = async (spec) => {
|
|
128
|
+
const set = buildRecordSet(spec, zoneName, defaultTtl);
|
|
129
|
+
await submitChange({ Action: "UPSERT", ResourceRecordSet: set });
|
|
130
|
+
const synthesized = toDnsRecord(set, zoneName);
|
|
131
|
+
if (synthesized === undefined) {
|
|
132
|
+
throw new Error("[deploy/route53] internal: built record had no ResourceRecords");
|
|
133
|
+
}
|
|
134
|
+
return synthesized;
|
|
135
|
+
};
|
|
136
|
+
const update = async (_id, spec) => upsert(spec);
|
|
137
|
+
const deleteRecord = async (id) => {
|
|
138
|
+
const payload = decodeId(id);
|
|
139
|
+
const set = {
|
|
140
|
+
Name: payload.n,
|
|
141
|
+
ResourceRecords: payload.v.map((Value) => ({ Value })),
|
|
142
|
+
Type: payload.t,
|
|
143
|
+
...payload.l !== undefined ? { TTL: payload.l } : {}
|
|
144
|
+
};
|
|
145
|
+
try {
|
|
146
|
+
await submitChange({ Action: "DELETE", ResourceRecordSet: set });
|
|
147
|
+
} catch (error) {
|
|
148
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
149
|
+
if (message.includes("not found") || message.includes("NoSuchHostedZone") || message.includes("but it was not found")) {
|
|
150
|
+
return;
|
|
151
|
+
}
|
|
152
|
+
throw error;
|
|
153
|
+
}
|
|
154
|
+
};
|
|
155
|
+
return {
|
|
156
|
+
create,
|
|
157
|
+
delete: deleteRecord,
|
|
158
|
+
description: `route53 zone "${zoneLabel}"`,
|
|
159
|
+
find,
|
|
160
|
+
list,
|
|
161
|
+
update,
|
|
162
|
+
upsert
|
|
163
|
+
};
|
|
164
|
+
};
|
|
165
|
+
export {
|
|
166
|
+
route53DnsProvider
|
|
167
|
+
};
|
|
168
|
+
|
|
169
|
+
//# debugId=A9C653578AEC27D364756E2164756E21
|
|
170
|
+
//# sourceMappingURL=route53.js.map
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../src/route53.ts"],
|
|
4
|
+
"sourcesContent": [
|
|
5
|
+
"/**\n * @absolutejs/deploy/route53 — AWS Route 53 DNS implementing the\n * {@link DnsProvider} contract from `./dns`.\n *\n * Different shape from Cloudflare/DO/Hetzner DNS: Route 53 has no\n * per-record id. Records are identified by their (Name, Type)\n * composite, and mutations are done via `ChangeResourceRecordSets`\n * with a batch of CREATE / UPSERT / DELETE actions. We exploit\n * Route 53's native `UPSERT` action — one API call replaces the\n * find-then-create-or-update dance the other providers need.\n *\n * Narrow `Route53ClientLike` interface so the package stays free\n * of an `@aws-sdk/client-route-53` dependency. Wire your existing\n * AWS SDK client with a 4-line shim — see README.\n */\n\nimport type {\n\tDnsProvider,\n\tDnsRecord,\n\tDnsRecordFilter,\n\tDnsRecordSpec,\n\tDnsRecordType\n} from './dns';\n\n// =============================================================================\n// AWS API shapes — narrowed to what we use\n// =============================================================================\n\nexport type Route53ResourceRecord = { Value: string };\n\nexport type Route53ResourceRecordSet = {\n\tName: string;\n\tType: string;\n\tTTL?: number;\n\tResourceRecords?: Route53ResourceRecord[];\n\tAliasTarget?: {\n\t\tHostedZoneId: string;\n\t\tDNSName: string;\n\t\tEvaluateTargetHealth: boolean;\n\t};\n\tSetIdentifier?: string;\n};\n\nexport type Route53Change = {\n\tAction: 'CREATE' | 'DELETE' | 'UPSERT';\n\tResourceRecordSet: Route53ResourceRecordSet;\n};\n\nexport type Route53ListInput = {\n\tHostedZoneId: string;\n\tStartRecordName?: string;\n\tStartRecordType?: string;\n\tMaxItems?: string;\n};\n\nexport type Route53ListOutput = {\n\tResourceRecordSets: Route53ResourceRecordSet[];\n\tIsTruncated?: boolean;\n\tNextRecordName?: string;\n\tNextRecordType?: string;\n};\n\nexport type Route53ChangeInput = {\n\tHostedZoneId: string;\n\tChangeBatch: { Changes: Route53Change[]; Comment?: string };\n};\n\nexport type Route53ChangeOutput = {\n\tChangeInfo: { Id: string; Status: string };\n};\n\n/**\n * Minimal subset of the Route 53 client we need. Wire your\n * `@aws-sdk/client-route-53` `Route53Client` with a shim:\n *\n * ```ts\n * import {\n * Route53Client,\n * ListResourceRecordSetsCommand,\n * ChangeResourceRecordSetsCommand,\n * } from '@aws-sdk/client-route-53';\n *\n * const aws = new Route53Client({ region: 'us-east-1' });\n * const client: Route53ClientLike = {\n * listResourceRecordSets: (input) =>\n * aws.send(new ListResourceRecordSetsCommand(input)) as any,\n * changeResourceRecordSets: (input) =>\n * aws.send(new ChangeResourceRecordSetsCommand(input)) as any,\n * };\n * ```\n *\n * Or hand-roll a SigV4-signed fetch client if you want zero deps.\n */\nexport type Route53ClientLike = {\n\tlistResourceRecordSets: (\n\t\tinput: Route53ListInput\n\t) => Promise<Route53ListOutput>;\n\tchangeResourceRecordSets: (\n\t\tinput: Route53ChangeInput\n\t) => Promise<Route53ChangeOutput>;\n};\n\nexport type Route53ProviderOptions = {\n\tclient: Route53ClientLike;\n\t/** Hosted zone id, e.g. `'Z2FDTNDATAQYW2'`. */\n\thostedZoneId: string;\n\t/** Zone name for log + name conversion, e.g. `'example.com'`. */\n\tzoneName: string;\n\t/** Default TTL when `spec.ttl` is omitted. Default 300. */\n\tdefaultTtl?: number;\n};\n\n// =============================================================================\n// Name + TXT-value normalization\n// =============================================================================\n\nconst ensureFqdn = (name: string, zoneName: string): string => {\n\tif (name === '@') return ensureTrailingDot(zoneName);\n\tif (name === zoneName) return ensureTrailingDot(name);\n\tif (name.endsWith('.')) return name;\n\treturn `${name}.`;\n};\n\nconst ensureTrailingDot = (s: string): string => (s.endsWith('.') ? s : `${s}.`);\n\nconst stripTrailingDot = (s: string): string =>\n\ts.endsWith('.') ? s.slice(0, -1) : s;\n\n/**\n * Route 53 TXT values MUST be enclosed in double quotes in the wire\n * format. Strings > 255 chars get split into multiple quoted chunks.\n * Bare unquoted input → wrap. Already-quoted input → pass through.\n */\nconst encodeTxtValue = (raw: string): string => {\n\tif (raw.startsWith('\"') && raw.endsWith('\"')) return raw;\n\tconst CHUNK = 255;\n\tconst chunks: string[] = [];\n\tfor (let i = 0; i < raw.length; i += CHUNK) {\n\t\tchunks.push(`\"${raw.slice(i, i + CHUNK).replaceAll('\"', '\\\\\"')}\"`);\n\t}\n\treturn chunks.join(' ');\n};\n\n/** Inverse of encodeTxtValue: unwrap \" ... \" \" ... \" back to raw. */\nconst decodeTxtValue = (wire: string): string => {\n\tconst parts = wire.match(/\"((?:\\\\.|[^\"])*)\"/g);\n\tif (parts === null) return wire;\n\treturn parts\n\t\t.map((part) => part.slice(1, -1).replaceAll('\\\\\"', '\"'))\n\t\t.join('');\n};\n\n// =============================================================================\n// Synthetic-id encoding — Route 53 has no per-record id\n// =============================================================================\n\n/**\n * Route 53 deletes require the CURRENT record state (Name + Type +\n * TTL + Values), not an opaque id. We encode that state as a\n * base64-JSON synthetic id so the DnsProvider contract (which\n * expects `delete(id)`) works without a second API round-trip.\n */\ntype Route53IdPayload = {\n\tn: string; // Name (FQDN with trailing dot)\n\tt: string; // Type\n\tv: string[]; // Values (as written to ResourceRecords[])\n\tl?: number; // TTL\n};\n\nconst encodeId = (payload: Route53IdPayload): string =>\n\tbtoa(JSON.stringify(payload))\n\t\t.replaceAll('+', '-')\n\t\t.replaceAll('/', '_')\n\t\t.replaceAll('=', '');\n\nconst decodeId = (id: string): Route53IdPayload => {\n\tconst padded = id\n\t\t.replaceAll('-', '+')\n\t\t.replaceAll('_', '/')\n\t\t.padEnd(Math.ceil(id.length / 4) * 4, '=');\n\treturn JSON.parse(atob(padded)) as Route53IdPayload;\n};\n\n// =============================================================================\n// Conversion: AWS RecordSet ↔ DnsRecord\n// =============================================================================\n\nconst toDnsRecord = (\n\tset: Route53ResourceRecordSet,\n\tzoneName: string\n): DnsRecord | undefined => {\n\t// Alias records (no ResourceRecords) don't fit our string-content model.\n\tconst values = set.ResourceRecords?.map((r) => r.Value) ?? [];\n\tif (values.length === 0) return undefined;\n\t// For TXT, decode the wire format back to raw plaintext.\n\tconst content =\n\t\tset.Type === 'TXT' ? decodeTxtValue(values.join(' ')) : (values[0] as string);\n\tconst idPayload: Route53IdPayload = {\n\t\tn: set.Name,\n\t\tt: set.Type,\n\t\tv: values,\n\t\t...(set.TTL !== undefined ? { l: set.TTL } : {})\n\t};\n\tconst fqdn = stripTrailingDot(set.Name);\n\tconst name = fqdn === zoneName ? zoneName : fqdn;\n\treturn {\n\t\tcontent,\n\t\tid: encodeId(idPayload),\n\t\tname,\n\t\ttype: set.Type as DnsRecordType,\n\t\t...(set.TTL !== undefined ? { ttl: set.TTL } : {})\n\t};\n};\n\nconst buildRecordSet = (\n\tspec: DnsRecordSpec,\n\tzoneName: string,\n\tdefaultTtl: number\n): Route53ResourceRecordSet => {\n\tconst Name = ensureFqdn(spec.name, zoneName);\n\tconst ttl = spec.ttl ?? defaultTtl;\n\tconst value = spec.type === 'TXT' ? encodeTxtValue(spec.content) : spec.content;\n\treturn {\n\t\tName,\n\t\tResourceRecords: [{ Value: value }],\n\t\tTTL: ttl,\n\t\tType: spec.type\n\t};\n};\n\n// =============================================================================\n// Provider factory\n// =============================================================================\n\nexport const route53DnsProvider = (\n\toptions: Route53ProviderOptions\n): DnsProvider => {\n\tconst { client, hostedZoneId, zoneName } = options;\n\tconst defaultTtl = options.defaultTtl ?? 300;\n\tconst zoneLabel = zoneName;\n\n\tconst fetchAll = async (\n\t\tfilter?: DnsRecordFilter\n\t): Promise<DnsRecord[]> => {\n\t\tconst out: DnsRecord[] = [];\n\t\tlet startName: string | undefined;\n\t\tlet startType: string | undefined;\n\t\tfor (;;) {\n\t\t\tconst params: Route53ListInput = {\n\t\t\t\tHostedZoneId: hostedZoneId,\n\t\t\t\tMaxItems: '500',\n\t\t\t\t...(startName !== undefined ? { StartRecordName: startName } : {}),\n\t\t\t\t...(startType !== undefined ? { StartRecordType: startType } : {})\n\t\t\t};\n\t\t\tconst page = await client.listResourceRecordSets(params);\n\t\t\tfor (const set of page.ResourceRecordSets) {\n\t\t\t\tconst dnsRecord = toDnsRecord(set, zoneName);\n\t\t\t\tif (dnsRecord === undefined) continue;\n\t\t\t\tif (\n\t\t\t\t\tfilter?.name !== undefined &&\n\t\t\t\t\tdnsRecord.name !== filter.name\n\t\t\t\t) {\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\t\t\t\tif (\n\t\t\t\t\tfilter?.type !== undefined &&\n\t\t\t\t\tdnsRecord.type !== filter.type\n\t\t\t\t) {\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\t\t\t\tout.push(dnsRecord);\n\t\t\t}\n\t\t\tif (page.IsTruncated !== true) break;\n\t\t\tstartName = page.NextRecordName;\n\t\t\tstartType = page.NextRecordType;\n\t\t}\n\t\treturn out;\n\t};\n\n\tconst list = (filter?: DnsRecordFilter): Promise<DnsRecord[]> =>\n\t\tfetchAll(filter);\n\n\tconst find = async (key: {\n\t\tname: string;\n\t\ttype: DnsRecordType;\n\t}): Promise<DnsRecord | undefined> => {\n\t\tconst matches = await fetchAll(key);\n\t\tif (matches.length === 0) return undefined;\n\t\tif (matches.length > 1) {\n\t\t\tthrow new Error(\n\t\t\t\t`[deploy/route53] multiple ${key.type} records for \"${key.name}\" in zone ${zoneLabel} — drifted state; resolve manually before upsert.`\n\t\t\t);\n\t\t}\n\t\treturn matches[0];\n\t};\n\n\tconst submitChange = async (change: Route53Change): Promise<void> => {\n\t\tawait client.changeResourceRecordSets({\n\t\t\tChangeBatch: { Changes: [change] },\n\t\t\tHostedZoneId: hostedZoneId\n\t\t});\n\t};\n\n\tconst create = async (spec: DnsRecordSpec): Promise<DnsRecord> => {\n\t\tconst set = buildRecordSet(spec, zoneName, defaultTtl);\n\t\tawait submitChange({ Action: 'CREATE', ResourceRecordSet: set });\n\t\tconst synthesized = toDnsRecord(set, zoneName);\n\t\tif (synthesized === undefined) {\n\t\t\tthrow new Error(\n\t\t\t\t'[deploy/route53] internal: built record had no ResourceRecords'\n\t\t\t);\n\t\t}\n\t\treturn synthesized;\n\t};\n\n\tconst upsert = async (spec: DnsRecordSpec): Promise<DnsRecord> => {\n\t\tconst set = buildRecordSet(spec, zoneName, defaultTtl);\n\t\tawait submitChange({ Action: 'UPSERT', ResourceRecordSet: set });\n\t\tconst synthesized = toDnsRecord(set, zoneName);\n\t\tif (synthesized === undefined) {\n\t\t\tthrow new Error(\n\t\t\t\t'[deploy/route53] internal: built record had no ResourceRecords'\n\t\t\t);\n\t\t}\n\t\treturn synthesized;\n\t};\n\n\tconst update = async (\n\t\t_id: string,\n\t\tspec: DnsRecordSpec\n\t): Promise<DnsRecord> => upsert(spec);\n\n\tconst deleteRecord = async (id: string): Promise<void> => {\n\t\t// Route 53's DELETE requires the exact current state. We reconstruct\n\t\t// it from the synthetic id we issued at read/write time.\n\t\tconst payload = decodeId(id);\n\t\tconst set: Route53ResourceRecordSet = {\n\t\t\tName: payload.n,\n\t\t\tResourceRecords: payload.v.map((Value) => ({ Value })),\n\t\t\tType: payload.t,\n\t\t\t...(payload.l !== undefined ? { TTL: payload.l } : {})\n\t\t};\n\t\ttry {\n\t\t\tawait submitChange({ Action: 'DELETE', ResourceRecordSet: set });\n\t\t} catch (error) {\n\t\t\t// Route 53 throws InvalidChangeBatch when the record doesn't exist\n\t\t\t// (or its state has drifted from what we encoded). Treat the\n\t\t\t// \"doesn't exist\" subset as idempotent success.\n\t\t\tconst message =\n\t\t\t\terror instanceof Error ? error.message : String(error);\n\t\t\tif (\n\t\t\t\tmessage.includes('not found') ||\n\t\t\t\tmessage.includes('NoSuchHostedZone') ||\n\t\t\t\tmessage.includes(\n\t\t\t\t\t'but it was not found'\n\t\t\t\t)\n\t\t\t) {\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tthrow error;\n\t\t}\n\t};\n\n\treturn {\n\t\tcreate,\n\t\tdelete: deleteRecord,\n\t\tdescription: `route53 zone \"${zoneLabel}\"`,\n\t\tfind,\n\t\tlist,\n\t\tupdate,\n\t\tupsert\n\t};\n};\n"
|
|
6
|
+
],
|
|
7
|
+
"mappings": ";;AAoHA,IAAM,aAAa,CAAC,MAAc,aAA6B;AAAA,EAC9D,IAAI,SAAS;AAAA,IAAK,OAAO,kBAAkB,QAAQ;AAAA,EACnD,IAAI,SAAS;AAAA,IAAU,OAAO,kBAAkB,IAAI;AAAA,EACpD,IAAI,KAAK,SAAS,GAAG;AAAA,IAAG,OAAO;AAAA,EAC/B,OAAO,GAAG;AAAA;AAGX,IAAM,oBAAoB,CAAC,MAAuB,EAAE,SAAS,GAAG,IAAI,IAAI,GAAG;AAE3E,IAAM,mBAAmB,CAAC,MACzB,EAAE,SAAS,GAAG,IAAI,EAAE,MAAM,GAAG,EAAE,IAAI;AAOpC,IAAM,iBAAiB,CAAC,QAAwB;AAAA,EAC/C,IAAI,IAAI,WAAW,GAAG,KAAK,IAAI,SAAS,GAAG;AAAA,IAAG,OAAO;AAAA,EACrD,MAAM,QAAQ;AAAA,EACd,MAAM,SAAmB,CAAC;AAAA,EAC1B,SAAS,IAAI,EAAG,IAAI,IAAI,QAAQ,KAAK,OAAO;AAAA,IAC3C,OAAO,KAAK,IAAI,IAAI,MAAM,GAAG,IAAI,KAAK,EAAE,WAAW,KAAK,MAAK,IAAI;AAAA,EAClE;AAAA,EACA,OAAO,OAAO,KAAK,GAAG;AAAA;AAIvB,IAAM,iBAAiB,CAAC,SAAyB;AAAA,EAChD,MAAM,QAAQ,KAAK,MAAM,oBAAoB;AAAA,EAC7C,IAAI,UAAU;AAAA,IAAM,OAAO;AAAA,EAC3B,OAAO,MACL,IAAI,CAAC,SAAS,KAAK,MAAM,GAAG,EAAE,EAAE,WAAW,QAAO,GAAG,CAAC,EACtD,KAAK,EAAE;AAAA;AAoBV,IAAM,WAAW,CAAC,YACjB,KAAK,KAAK,UAAU,OAAO,CAAC,EAC1B,WAAW,KAAK,GAAG,EACnB,WAAW,KAAK,GAAG,EACnB,WAAW,KAAK,EAAE;AAErB,IAAM,WAAW,CAAC,OAAiC;AAAA,EAClD,MAAM,SAAS,GACb,WAAW,KAAK,GAAG,EACnB,WAAW,KAAK,GAAG,EACnB,OAAO,KAAK,KAAK,GAAG,SAAS,CAAC,IAAI,GAAG,GAAG;AAAA,EAC1C,OAAO,KAAK,MAAM,KAAK,MAAM,CAAC;AAAA;AAO/B,IAAM,cAAc,CACnB,KACA,aAC2B;AAAA,EAE3B,MAAM,SAAS,IAAI,iBAAiB,IAAI,CAAC,MAAM,EAAE,KAAK,KAAK,CAAC;AAAA,EAC5D,IAAI,OAAO,WAAW;AAAA,IAAG;AAAA,EAEzB,MAAM,UACL,IAAI,SAAS,QAAQ,eAAe,OAAO,KAAK,GAAG,CAAC,IAAK,OAAO;AAAA,EACjE,MAAM,YAA8B;AAAA,IACnC,GAAG,IAAI;AAAA,IACP,GAAG,IAAI;AAAA,IACP,GAAG;AAAA,OACC,IAAI,QAAQ,YAAY,EAAE,GAAG,IAAI,IAAI,IAAI,CAAC;AAAA,EAC/C;AAAA,EACA,MAAM,OAAO,iBAAiB,IAAI,IAAI;AAAA,EACtC,MAAM,OAAO,SAAS,WAAW,WAAW;AAAA,EAC5C,OAAO;AAAA,IACN;AAAA,IACA,IAAI,SAAS,SAAS;AAAA,IACtB;AAAA,IACA,MAAM,IAAI;AAAA,OACN,IAAI,QAAQ,YAAY,EAAE,KAAK,IAAI,IAAI,IAAI,CAAC;AAAA,EACjD;AAAA;AAGD,IAAM,iBAAiB,CACtB,MACA,UACA,eAC8B;AAAA,EAC9B,MAAM,OAAO,WAAW,KAAK,MAAM,QAAQ;AAAA,EAC3C,MAAM,MAAM,KAAK,OAAO;AAAA,EACxB,MAAM,QAAQ,KAAK,SAAS,QAAQ,eAAe,KAAK,OAAO,IAAI,KAAK;AAAA,EACxE,OAAO;AAAA,IACN;AAAA,IACA,iBAAiB,CAAC,EAAE,OAAO,MAAM,CAAC;AAAA,IAClC,KAAK;AAAA,IACL,MAAM,KAAK;AAAA,EACZ;AAAA;AAOM,IAAM,qBAAqB,CACjC,YACiB;AAAA,EACjB,QAAQ,QAAQ,cAAc,aAAa;AAAA,EAC3C,MAAM,aAAa,QAAQ,cAAc;AAAA,EACzC,MAAM,YAAY;AAAA,EAElB,MAAM,WAAW,OAChB,WAC0B;AAAA,IAC1B,MAAM,MAAmB,CAAC;AAAA,IAC1B,IAAI;AAAA,IACJ,IAAI;AAAA,IACJ,UAAS;AAAA,MACR,MAAM,SAA2B;AAAA,QAChC,cAAc;AAAA,QACd,UAAU;AAAA,WACN,cAAc,YAAY,EAAE,iBAAiB,UAAU,IAAI,CAAC;AAAA,WAC5D,cAAc,YAAY,EAAE,iBAAiB,UAAU,IAAI,CAAC;AAAA,MACjE;AAAA,MACA,MAAM,OAAO,MAAM,OAAO,uBAAuB,MAAM;AAAA,MACvD,WAAW,OAAO,KAAK,oBAAoB;AAAA,QAC1C,MAAM,YAAY,YAAY,KAAK,QAAQ;AAAA,QAC3C,IAAI,cAAc;AAAA,UAAW;AAAA,QAC7B,IACC,QAAQ,SAAS,aACjB,UAAU,SAAS,OAAO,MACzB;AAAA,UACD;AAAA,QACD;AAAA,QACA,IACC,QAAQ,SAAS,aACjB,UAAU,SAAS,OAAO,MACzB;AAAA,UACD;AAAA,QACD;AAAA,QACA,IAAI,KAAK,SAAS;AAAA,MACnB;AAAA,MACA,IAAI,KAAK,gBAAgB;AAAA,QAAM;AAAA,MAC/B,YAAY,KAAK;AAAA,MACjB,YAAY,KAAK;AAAA,IAClB;AAAA,IACA,OAAO;AAAA;AAAA,EAGR,MAAM,OAAO,CAAC,WACb,SAAS,MAAM;AAAA,EAEhB,MAAM,OAAO,OAAO,QAGkB;AAAA,IACrC,MAAM,UAAU,MAAM,SAAS,GAAG;AAAA,IAClC,IAAI,QAAQ,WAAW;AAAA,MAAG;AAAA,IAC1B,IAAI,QAAQ,SAAS,GAAG;AAAA,MACvB,MAAM,IAAI,MACT,6BAA6B,IAAI,qBAAqB,IAAI,iBAAiB,iEAC5E;AAAA,IACD;AAAA,IACA,OAAO,QAAQ;AAAA;AAAA,EAGhB,MAAM,eAAe,OAAO,WAAyC;AAAA,IACpE,MAAM,OAAO,yBAAyB;AAAA,MACrC,aAAa,EAAE,SAAS,CAAC,MAAM,EAAE;AAAA,MACjC,cAAc;AAAA,IACf,CAAC;AAAA;AAAA,EAGF,MAAM,SAAS,OAAO,SAA4C;AAAA,IACjE,MAAM,MAAM,eAAe,MAAM,UAAU,UAAU;AAAA,IACrD,MAAM,aAAa,EAAE,QAAQ,UAAU,mBAAmB,IAAI,CAAC;AAAA,IAC/D,MAAM,cAAc,YAAY,KAAK,QAAQ;AAAA,IAC7C,IAAI,gBAAgB,WAAW;AAAA,MAC9B,MAAM,IAAI,MACT,gEACD;AAAA,IACD;AAAA,IACA,OAAO;AAAA;AAAA,EAGR,MAAM,SAAS,OAAO,SAA4C;AAAA,IACjE,MAAM,MAAM,eAAe,MAAM,UAAU,UAAU;AAAA,IACrD,MAAM,aAAa,EAAE,QAAQ,UAAU,mBAAmB,IAAI,CAAC;AAAA,IAC/D,MAAM,cAAc,YAAY,KAAK,QAAQ;AAAA,IAC7C,IAAI,gBAAgB,WAAW;AAAA,MAC9B,MAAM,IAAI,MACT,gEACD;AAAA,IACD;AAAA,IACA,OAAO;AAAA;AAAA,EAGR,MAAM,SAAS,OACd,KACA,SACwB,OAAO,IAAI;AAAA,EAEpC,MAAM,eAAe,OAAO,OAA8B;AAAA,IAGzD,MAAM,UAAU,SAAS,EAAE;AAAA,IAC3B,MAAM,MAAgC;AAAA,MACrC,MAAM,QAAQ;AAAA,MACd,iBAAiB,QAAQ,EAAE,IAAI,CAAC,WAAW,EAAE,MAAM,EAAE;AAAA,MACrD,MAAM,QAAQ;AAAA,SACV,QAAQ,MAAM,YAAY,EAAE,KAAK,QAAQ,EAAE,IAAI,CAAC;AAAA,IACrD;AAAA,IACA,IAAI;AAAA,MACH,MAAM,aAAa,EAAE,QAAQ,UAAU,mBAAmB,IAAI,CAAC;AAAA,MAC9D,OAAO,OAAO;AAAA,MAIf,MAAM,UACL,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK;AAAA,MACtD,IACC,QAAQ,SAAS,WAAW,KAC5B,QAAQ,SAAS,kBAAkB,KACnC,QAAQ,SACP,sBACD,GACC;AAAA,QACD;AAAA,MACD;AAAA,MACA,MAAM;AAAA;AAAA;AAAA,EAIR,OAAO;AAAA,IACN;AAAA,IACA,QAAQ;AAAA,IACR,aAAa,iBAAiB;AAAA,IAC9B;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACD;AAAA;",
|
|
8
|
+
"debugId": "A9C653578AEC27D364756E2164756E21",
|
|
9
|
+
"names": []
|
|
10
|
+
}
|
package/dist/vultr.d.ts
ADDED
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @absolutejs/deploy/vultr — provision-or-reuse Target adapter for
|
|
3
|
+
* Vultr instances. Sibling to digitalOceanTarget + hetznerTarget +
|
|
4
|
+
* linodeTarget; same shape, Vultr v2 API mappings.
|
|
5
|
+
*
|
|
6
|
+
* Idempotent by label. Vultr stores the public IP as `main_ip`
|
|
7
|
+
* (single string, not an array). SSH keys are pre-registered with
|
|
8
|
+
* Vultr and referenced by UUID — different from Linode (raw keys)
|
|
9
|
+
* and DO/Hetzner (fingerprints/ids/names).
|
|
10
|
+
*/
|
|
11
|
+
import type { Target } from './targets';
|
|
12
|
+
export type VultrClientLike = {
|
|
13
|
+
request: <T = unknown>(method: 'GET' | 'POST' | 'DELETE' | 'PATCH', path: string, body?: unknown) => Promise<T>;
|
|
14
|
+
};
|
|
15
|
+
/** A Vultr instance, narrowed to what we inspect. */
|
|
16
|
+
export type VultrInstance = {
|
|
17
|
+
id: string;
|
|
18
|
+
label: string;
|
|
19
|
+
status: 'active' | 'pending' | 'suspended' | 'resizing';
|
|
20
|
+
power_status?: 'running' | 'stopped' | 'starting';
|
|
21
|
+
server_status?: string;
|
|
22
|
+
main_ip: string;
|
|
23
|
+
internal_ip?: string;
|
|
24
|
+
region?: string;
|
|
25
|
+
plan?: string;
|
|
26
|
+
tags?: string[];
|
|
27
|
+
};
|
|
28
|
+
export type VultrTargetOptions = {
|
|
29
|
+
/** Vultr API key. Required unless `client` is set. */
|
|
30
|
+
token?: string;
|
|
31
|
+
client?: VultrClientLike;
|
|
32
|
+
/** Instance label. Idempotency key. */
|
|
33
|
+
name: string;
|
|
34
|
+
/** Region slug — `'ewr'`, `'lax'`, `'sgp'`, etc. */
|
|
35
|
+
region: string;
|
|
36
|
+
/** Plan slug — `'vc2-1c-1gb'`, `'vc2-2c-4gb'`, etc. */
|
|
37
|
+
plan: string;
|
|
38
|
+
/** OS id. Numeric — e.g. `1743` for Ubuntu 22.04. */
|
|
39
|
+
osId: number;
|
|
40
|
+
/**
|
|
41
|
+
* SSH key UUIDs already registered in your Vultr account
|
|
42
|
+
* (https://my.vultr.com/settings/#ssh-keys). Vultr does NOT accept
|
|
43
|
+
* raw key strings — you upload them once, then reference the UUIDs.
|
|
44
|
+
*/
|
|
45
|
+
sshKeys: ReadonlyArray<string>;
|
|
46
|
+
/** Tags applied to the instance. */
|
|
47
|
+
tags?: ReadonlyArray<string>;
|
|
48
|
+
/** Cloud-init user data (will be base64-encoded by us). */
|
|
49
|
+
userData?: string;
|
|
50
|
+
/** Enable IPv6. Default false. */
|
|
51
|
+
enableIpv6?: boolean;
|
|
52
|
+
/** Enable backups. Default false. */
|
|
53
|
+
backups?: boolean;
|
|
54
|
+
/** Enable DDoS protection. Default false. */
|
|
55
|
+
ddosProtection?: boolean;
|
|
56
|
+
/** Hostname (Vultr distinguishes label from hostname). Default = name. */
|
|
57
|
+
hostname?: string;
|
|
58
|
+
user?: string;
|
|
59
|
+
identity?: string;
|
|
60
|
+
port?: number;
|
|
61
|
+
provisionTimeoutMs?: number;
|
|
62
|
+
sshReadinessTimeoutMs?: number;
|
|
63
|
+
pollIntervalMs?: number;
|
|
64
|
+
onLog?: (line: string) => void;
|
|
65
|
+
probeSsh?: (host: string, port: number) => Promise<boolean>;
|
|
66
|
+
sleep?: (ms: number) => Promise<void>;
|
|
67
|
+
now?: () => number;
|
|
68
|
+
};
|
|
69
|
+
export type VultrTarget = Target & {
|
|
70
|
+
readonly instanceId: string;
|
|
71
|
+
readonly ipv4: string;
|
|
72
|
+
destroy: () => Promise<void>;
|
|
73
|
+
};
|
|
74
|
+
export declare class VultrError extends Error {
|
|
75
|
+
readonly status: number;
|
|
76
|
+
readonly body: unknown;
|
|
77
|
+
constructor(message: string, status: number, body: unknown);
|
|
78
|
+
}
|
|
79
|
+
export declare const createVultrClient: (token: string, options?: {
|
|
80
|
+
baseUrl?: string;
|
|
81
|
+
fetch?: typeof fetch;
|
|
82
|
+
}) => VultrClientLike;
|
|
83
|
+
/** Find an instance by label. Throws on ambiguous duplicates. */
|
|
84
|
+
export declare const findVultrInstance: (client: VultrClientLike, name: string) => Promise<VultrInstance | undefined>;
|
|
85
|
+
export declare const listVultrInstances: (options: {
|
|
86
|
+
token?: string;
|
|
87
|
+
client?: VultrClientLike;
|
|
88
|
+
tag?: string;
|
|
89
|
+
}) => Promise<VultrInstance[]>;
|
|
90
|
+
export declare const destroyVultrInstance: (options: {
|
|
91
|
+
token?: string;
|
|
92
|
+
client?: VultrClientLike;
|
|
93
|
+
id: string;
|
|
94
|
+
}) => Promise<void>;
|
|
95
|
+
export declare const vultrTarget: (options: VultrTargetOptions) => Promise<VultrTarget>;
|