@infracraft/pulumi 1.20.0 → 1.21.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/railway/domain.cjs +43 -5
- package/dist/railway/domain.cjs.map +1 -1
- package/dist/railway/domain.d.cts +22 -0
- package/dist/railway/domain.d.cts.map +1 -1
- package/dist/railway/domain.d.mts +22 -0
- package/dist/railway/domain.d.mts.map +1 -1
- package/dist/railway/domain.mjs +43 -5
- package/dist/railway/domain.mjs.map +1 -1
- package/package.json +1 -1
package/dist/railway/domain.cjs
CHANGED
|
@@ -12,9 +12,32 @@ _pulumi_pulumi = require_chunk.__toESM(_pulumi_pulumi, 1);
|
|
|
12
12
|
function extractCnameTarget(dnsRecords) {
|
|
13
13
|
return dnsRecords?.find((record) => record.recordType === "DNS_RECORD_TYPE_CNAME" && record.purpose === "DNS_RECORD_PURPOSE_TRAFFIC_ROUTE")?.requiredValue;
|
|
14
14
|
}
|
|
15
|
+
const RAILWAY_VERIFY_PREFIX = "railway-verify=";
|
|
16
|
+
/**
|
|
17
|
+
* Composes the ownership-verification TXT record value from Railway's token.
|
|
18
|
+
* Idempotent: Railway currently returns the token already prefixed, but this
|
|
19
|
+
* guards against a future API response returning the bare token instead.
|
|
20
|
+
*/
|
|
21
|
+
function composeVerificationTxtValue(token) {
|
|
22
|
+
return token.startsWith(RAILWAY_VERIFY_PREFIX) ? token : `${RAILWAY_VERIFY_PREFIX}${token}`;
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Extracts the ready-to-use ownership-verification TXT record (name + composed
|
|
26
|
+
* value) from a custom domain's status, or `undefined` if Railway hasn't assigned
|
|
27
|
+
* one (service domains, or custom domains needing no verification).
|
|
28
|
+
*/
|
|
29
|
+
function extractVerificationTxt(status) {
|
|
30
|
+
if (!status?.verificationDnsHost || !status.verificationToken) return;
|
|
31
|
+
return {
|
|
32
|
+
name: status.verificationDnsHost,
|
|
33
|
+
value: composeVerificationTxtValue(status.verificationToken)
|
|
34
|
+
};
|
|
35
|
+
}
|
|
15
36
|
const DOMAIN_STATUS_FIELDS = `
|
|
16
37
|
status {
|
|
17
38
|
dnsRecords { recordType purpose requiredValue }
|
|
39
|
+
verificationDnsHost
|
|
40
|
+
verificationToken
|
|
18
41
|
}
|
|
19
42
|
`;
|
|
20
43
|
const SERVICE_DOMAINS_QUERY = `
|
|
@@ -71,13 +94,16 @@ var RailwayDomainResourceProvider = class {
|
|
|
71
94
|
const found = existing.customDomains.find((d) => d.domain === inputs.customDomain);
|
|
72
95
|
if (found) {
|
|
73
96
|
_pulumi_pulumi.log.info(`Adopting existing custom domain "${found.domain}"`);
|
|
97
|
+
const verificationTxt = extractVerificationTxt(found.status);
|
|
74
98
|
return {
|
|
75
99
|
id: found.domain,
|
|
76
100
|
outs: {
|
|
77
101
|
...inputs,
|
|
78
102
|
domainId: found.id,
|
|
79
103
|
fqdn: found.domain,
|
|
80
|
-
cnameTarget: extractCnameTarget(found.status?.dnsRecords)
|
|
104
|
+
cnameTarget: extractCnameTarget(found.status?.dnsRecords),
|
|
105
|
+
verificationTxtName: verificationTxt?.name,
|
|
106
|
+
verificationTxtValue: verificationTxt?.value
|
|
81
107
|
}
|
|
82
108
|
};
|
|
83
109
|
}
|
|
@@ -87,13 +113,16 @@ var RailwayDomainResourceProvider = class {
|
|
|
87
113
|
environmentId: inputs.environmentId,
|
|
88
114
|
domain: inputs.customDomain
|
|
89
115
|
} });
|
|
116
|
+
const createdVerificationTxt = extractVerificationTxt(result.customDomainCreate.status);
|
|
90
117
|
return {
|
|
91
118
|
id: result.customDomainCreate.domain,
|
|
92
119
|
outs: {
|
|
93
120
|
...inputs,
|
|
94
121
|
domainId: result.customDomainCreate.id,
|
|
95
122
|
fqdn: result.customDomainCreate.domain,
|
|
96
|
-
cnameTarget: extractCnameTarget(result.customDomainCreate.status?.dnsRecords)
|
|
123
|
+
cnameTarget: extractCnameTarget(result.customDomainCreate.status?.dnsRecords),
|
|
124
|
+
verificationTxtName: createdVerificationTxt?.name,
|
|
125
|
+
verificationTxtValue: createdVerificationTxt?.value
|
|
97
126
|
}
|
|
98
127
|
};
|
|
99
128
|
}
|
|
@@ -127,13 +156,16 @@ var RailwayDomainResourceProvider = class {
|
|
|
127
156
|
if (props.customDomain) {
|
|
128
157
|
const found = existing.customDomains.find((d) => d.domain === props.customDomain);
|
|
129
158
|
if (!found) throw new Error(`Custom domain "${props.customDomain}" not found during refresh`);
|
|
159
|
+
const refreshedVerificationTxt = extractVerificationTxt(found.status);
|
|
130
160
|
return {
|
|
131
161
|
id: found.domain,
|
|
132
162
|
props: {
|
|
133
163
|
...props,
|
|
134
164
|
domainId: found.id,
|
|
135
165
|
fqdn: found.domain,
|
|
136
|
-
cnameTarget: extractCnameTarget(found.status?.dnsRecords)
|
|
166
|
+
cnameTarget: extractCnameTarget(found.status?.dnsRecords),
|
|
167
|
+
verificationTxtName: refreshedVerificationTxt?.name,
|
|
168
|
+
verificationTxtValue: refreshedVerificationTxt?.value
|
|
137
169
|
}
|
|
138
170
|
};
|
|
139
171
|
}
|
|
@@ -178,7 +210,9 @@ var RailwayDomainResource = class extends _pulumi_pulumi.dynamic.Resource {
|
|
|
178
210
|
...args,
|
|
179
211
|
domainId: void 0,
|
|
180
212
|
fqdn: void 0,
|
|
181
|
-
cnameTarget: void 0
|
|
213
|
+
cnameTarget: void 0,
|
|
214
|
+
verificationTxtName: void 0,
|
|
215
|
+
verificationTxtValue: void 0
|
|
182
216
|
}, opts);
|
|
183
217
|
}
|
|
184
218
|
};
|
|
@@ -215,9 +249,13 @@ var RailwayDomain = class extends _pulumi_pulumi.ComponentResource {
|
|
|
215
249
|
}, { parent: this });
|
|
216
250
|
this.fqdn = resource.fqdn;
|
|
217
251
|
this.cnameTarget = resource.cnameTarget;
|
|
252
|
+
this.verificationTxtName = resource.verificationTxtName;
|
|
253
|
+
this.verificationTxtValue = resource.verificationTxtValue;
|
|
218
254
|
this.registerOutputs({
|
|
219
255
|
fqdn: this.fqdn,
|
|
220
|
-
cnameTarget: this.cnameTarget
|
|
256
|
+
cnameTarget: this.cnameTarget,
|
|
257
|
+
verificationTxtName: this.verificationTxtName,
|
|
258
|
+
verificationTxtValue: this.verificationTxtValue
|
|
221
259
|
});
|
|
222
260
|
}
|
|
223
261
|
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"domain.cjs","names":["RailwayClient","pulumi"],"sources":["../../src/railway/domain.ts"],"sourcesContent":["import * as pulumi from \"@pulumi/pulumi\";\nimport { RailwayClient } from \"./client\";\nimport type { RailwayEnvironment } from \"./environment\";\nimport type { RailwayProject } from \"./project\";\nimport type { RailwayProvider } from \"./provider\";\nimport type { RailwayService } from \"./service\";\n\n/** Resolved inputs for the Railway domain dynamic provider. */\ninterface RailwayDomainInputs {\n\t/** Railway API bearer token. */\n\ttoken: string;\n\n\t/** Railway project UUID. */\n\tprojectId: string;\n\n\t/** Railway service UUID to attach the domain to. */\n\tserviceId: string;\n\n\t/** Railway environment UUID (e.g. production). */\n\tenvironmentId: string;\n\n\t/** Custom domain FQDN (e.g. `\"api.example.com\"`). Omit for auto-generated Railway domain. */\n\tcustomDomain?: string;\n}\n\n/** Persisted state for the Railway domain, extending inputs with Railway-assigned identifiers. */\ninterface RailwayDomainOutputs extends RailwayDomainInputs {\n\t/** Railway-assigned domain UUID (used for deletion API calls). */\n\tdomainId: string;\n\n\t/** Fully qualified domain name (e.g. `\"api-production-abc.up.railway.app\"`). */\n\tfqdn: string;\n\n\t/**\n\t * The value to CNAME `customDomain` to. Only present for custom domains — a\n\t * service's auto-generated `*.up.railway.app` domain needs no DNS record of its\n\t * own. `undefined` if Railway hasn't returned a traffic-routing CNAME record yet.\n\t */\n\tcnameTarget?: string;\n}\n\n/** A single DNS record Railway expects for a custom domain (CNAME target, ACME challenge, ...). */\ninterface DomainDnsRecord {\n\trecordType: string;\n\tpurpose: string;\n\trequiredValue: string;\n}\n\n/** Shape returned for a custom domain, including its required DNS records. */\ninterface CustomDomainEntry {\n\tid: string;\n\tdomain: string;\n\tstatus?: { dnsRecords: DomainDnsRecord[] };\n}\n\n/**\n * Picks the CNAME record Railway expects for routing traffic to a custom domain out\n * of its full DNS record list (which also includes e.g. an ACME challenge TXT record).\n */\nfunction extractCnameTarget(\n\tdnsRecords: DomainDnsRecord[] | undefined,\n): string | undefined {\n\treturn dnsRecords?.find(\n\t\t(record) =>\n\t\t\trecord.recordType === \"DNS_RECORD_TYPE_CNAME\" &&\n\t\t\trecord.purpose === \"DNS_RECORD_PURPOSE_TRAFFIC_ROUTE\",\n\t)?.requiredValue;\n}\n\nconst DOMAIN_STATUS_FIELDS = `\n status {\n dnsRecords { recordType purpose requiredValue }\n }\n`;\n\nconst SERVICE_DOMAINS_QUERY = `\n query($projectId: String!, $serviceId: String!, $environmentId: String!) {\n domains(\n projectId: $projectId\n serviceId: $serviceId\n environmentId: $environmentId\n ) {\n serviceDomains { id domain }\n customDomains { id domain ${DOMAIN_STATUS_FIELDS} }\n }\n }\n`;\n\nconst SERVICE_DOMAIN_CREATE = `\n mutation($input: ServiceDomainCreateInput!) {\n serviceDomainCreate(input: $input) { id domain }\n }\n`;\n\nconst CUSTOM_DOMAIN_CREATE = `\n mutation($input: CustomDomainCreateInput!) {\n customDomainCreate(input: $input) { id domain ${DOMAIN_STATUS_FIELDS} }\n }\n`;\n\nconst CUSTOM_DOMAIN_DELETE = `\n mutation($id: String!) { customDomainDelete(id: $id) }\n`;\n\nconst SERVICE_DOMAIN_DELETE = `\n mutation($id: String!) { serviceDomainDelete(id: $id) }\n`;\n\n/**\n * Queries all existing domains (service and custom) for a Railway service.\n */\nasync function findExistingDomains(\n\tclient: RailwayClient,\n\tprojectId: string,\n\tserviceId: string,\n\tenvironmentId: string,\n): Promise<{\n\tserviceDomains: Array<{ id: string; domain: string }>;\n\tcustomDomains: CustomDomainEntry[];\n}> {\n\tconst result = await client.query<{\n\t\tdomains: {\n\t\t\tserviceDomains: Array<{ id: string; domain: string }>;\n\t\t\tcustomDomains: CustomDomainEntry[];\n\t\t};\n\t}>(SERVICE_DOMAINS_QUERY, { projectId, serviceId, environmentId });\n\n\treturn result.domains;\n}\n\n/**\n * Dynamic provider implementing CRUD for Railway domains.\n *\n * Uses adopt-or-create: queries existing domains before creating new ones.\n * Uses the FQDN as the Pulumi resource ID.\n *\n * @internal Exported only for unit testing; not part of the public API surface.\n */\nexport class RailwayDomainResourceProvider\n\timplements pulumi.dynamic.ResourceProvider\n{\n\tasync create(\n\t\tinputs: RailwayDomainInputs,\n\t): Promise<pulumi.dynamic.CreateResult> {\n\t\tconst client = new RailwayClient(inputs.token);\n\n\t\tconst existing = await findExistingDomains(\n\t\t\tclient,\n\t\t\tinputs.projectId,\n\t\t\tinputs.serviceId,\n\t\t\tinputs.environmentId,\n\t\t);\n\n\t\tif (inputs.customDomain) {\n\t\t\tconst found = existing.customDomains.find(\n\t\t\t\t(d) => d.domain === inputs.customDomain,\n\t\t\t);\n\n\t\t\tif (found) {\n\t\t\t\tpulumi.log.info(`Adopting existing custom domain \"${found.domain}\"`);\n\n\t\t\t\treturn {\n\t\t\t\t\tid: found.domain,\n\t\t\t\t\touts: {\n\t\t\t\t\t\t...inputs,\n\t\t\t\t\t\tdomainId: found.id,\n\t\t\t\t\t\tfqdn: found.domain,\n\t\t\t\t\t\tcnameTarget: extractCnameTarget(found.status?.dnsRecords),\n\t\t\t\t\t},\n\t\t\t\t};\n\t\t\t}\n\n\t\t\tconst result = await client.query<{\n\t\t\t\tcustomDomainCreate: CustomDomainEntry;\n\t\t\t}>(CUSTOM_DOMAIN_CREATE, {\n\t\t\t\tinput: {\n\t\t\t\t\tprojectId: inputs.projectId,\n\t\t\t\t\tserviceId: inputs.serviceId,\n\t\t\t\t\tenvironmentId: inputs.environmentId,\n\t\t\t\t\tdomain: inputs.customDomain,\n\t\t\t\t},\n\t\t\t});\n\n\t\t\treturn {\n\t\t\t\tid: result.customDomainCreate.domain,\n\t\t\t\touts: {\n\t\t\t\t\t...inputs,\n\t\t\t\t\tdomainId: result.customDomainCreate.id,\n\t\t\t\t\tfqdn: result.customDomainCreate.domain,\n\t\t\t\t\tcnameTarget: extractCnameTarget(\n\t\t\t\t\t\tresult.customDomainCreate.status?.dnsRecords,\n\t\t\t\t\t),\n\t\t\t\t},\n\t\t\t};\n\t\t}\n\n\t\tif (existing.serviceDomains.length > 0) {\n\t\t\tconst found = existing.serviceDomains[0];\n\n\t\t\tpulumi.log.info(`Adopting existing service domain \"${found.domain}\"`);\n\n\t\t\treturn {\n\t\t\t\tid: found.domain,\n\t\t\t\touts: { ...inputs, domainId: found.id, fqdn: found.domain },\n\t\t\t};\n\t\t}\n\n\t\tconst result = await client.query<{\n\t\t\tserviceDomainCreate: { id: string; domain: string };\n\t\t}>(SERVICE_DOMAIN_CREATE, {\n\t\t\tinput: {\n\t\t\t\tserviceId: inputs.serviceId,\n\t\t\t\tenvironmentId: inputs.environmentId,\n\t\t\t},\n\t\t});\n\n\t\treturn {\n\t\t\tid: result.serviceDomainCreate.domain,\n\t\t\touts: {\n\t\t\t\t...inputs,\n\t\t\t\tdomainId: result.serviceDomainCreate.id,\n\t\t\t\tfqdn: result.serviceDomainCreate.domain,\n\t\t\t},\n\t\t};\n\t}\n\n\tasync read(\n\t\t_id: string,\n\t\tprops: RailwayDomainOutputs,\n\t): Promise<pulumi.dynamic.ReadResult> {\n\t\tconst client = new RailwayClient(props.token);\n\n\t\tconst existing = await findExistingDomains(\n\t\t\tclient,\n\t\t\tprops.projectId,\n\t\t\tprops.serviceId,\n\t\t\tprops.environmentId,\n\t\t);\n\n\t\tif (props.customDomain) {\n\t\t\tconst found = existing.customDomains.find(\n\t\t\t\t(d) => d.domain === props.customDomain,\n\t\t\t);\n\n\t\t\tif (!found) {\n\t\t\t\tthrow new Error(\n\t\t\t\t\t`Custom domain \"${props.customDomain}\" not found during refresh`,\n\t\t\t\t);\n\t\t\t}\n\n\t\t\treturn {\n\t\t\t\tid: found.domain,\n\t\t\t\tprops: {\n\t\t\t\t\t...props,\n\t\t\t\t\tdomainId: found.id,\n\t\t\t\t\tfqdn: found.domain,\n\t\t\t\t\tcnameTarget: extractCnameTarget(found.status?.dnsRecords),\n\t\t\t\t},\n\t\t\t};\n\t\t}\n\n\t\tif (existing.serviceDomains.length > 0) {\n\t\t\tconst found = existing.serviceDomains[0];\n\n\t\t\treturn {\n\t\t\t\tid: found.domain,\n\t\t\t\tprops: { ...props, domainId: found.id, fqdn: found.domain },\n\t\t\t};\n\t\t}\n\n\t\tthrow new Error(\"Railway domain not found during refresh\");\n\t}\n\n\tasync delete(_id: string, props: RailwayDomainOutputs): Promise<void> {\n\t\tconst client = new RailwayClient(props.token);\n\n\t\tconst mutation = props.customDomain\n\t\t\t? CUSTOM_DOMAIN_DELETE\n\t\t\t: SERVICE_DOMAIN_DELETE;\n\n\t\ttry {\n\t\t\tawait client.query(mutation, { id: props.domainId });\n\t\t} catch {\n\t\t\tpulumi.log.warn(\n\t\t\t\t\"Failed to delete Railway domain (may already be deleted)\",\n\t\t\t);\n\t\t}\n\t}\n\n\tasync diff(\n\t\t_id: string,\n\t\tolds: RailwayDomainOutputs,\n\t\tnews: RailwayDomainInputs,\n\t): Promise<pulumi.dynamic.DiffResult> {\n\t\tconst replaces: string[] = [];\n\n\t\tif (olds.serviceId !== news.serviceId) {\n\t\t\treplaces.push(\"serviceId\");\n\t\t}\n\n\t\tif (olds.customDomain !== news.customDomain) {\n\t\t\treplaces.push(\"customDomain\");\n\t\t}\n\n\t\tif (olds.environmentId !== news.environmentId) {\n\t\t\treplaces.push(\"environmentId\");\n\t\t}\n\n\t\treturn {\n\t\t\tchanges: replaces.length > 0,\n\t\t\treplaces,\n\t\t\tdeleteBeforeReplace: true,\n\t\t};\n\t}\n}\n\n/** Internal dynamic resource — not part of the public API. */\nclass RailwayDomainResource extends pulumi.dynamic.Resource {\n\tpublic declare readonly fqdn: pulumi.Output<string>;\n\tpublic declare readonly cnameTarget: pulumi.Output<string | undefined>;\n\n\tconstructor(\n\t\tname: string,\n\t\targs: {\n\t\t\ttoken: pulumi.Input<string>;\n\t\t\tprojectId: pulumi.Input<string>;\n\t\t\tserviceId: pulumi.Input<string>;\n\t\t\tenvironmentId: pulumi.Input<string>;\n\t\t\tcustomDomain?: pulumi.Input<string>;\n\t\t},\n\t\topts?: pulumi.CustomResourceOptions,\n\t) {\n\t\tsuper(\n\t\t\tnew RailwayDomainResourceProvider(),\n\t\t\tname,\n\t\t\t{ ...args, domainId: undefined, fqdn: undefined, cnameTarget: undefined },\n\t\t\topts,\n\t\t);\n\t}\n}\n\n/** Options type for RailwayDomain — replaces Pulumi's native `provider` field. */\ntype RailwayDomainOptions = Omit<\n\tpulumi.ComponentResourceOptions,\n\t\"provider\"\n> & {\n\t/** Railway authentication context. */\n\tprovider: RailwayProvider;\n\n\t/** Railway project context. */\n\tproject: RailwayProject;\n\n\t/** Railway environment context. */\n\tenvironment: RailwayEnvironment;\n\n\t/** Railway service context. */\n\tservice: RailwayService;\n};\n\n/** Args for RailwayDomain. */\nexport interface RailwayDomainArgs {\n\t/** Custom domain FQDN. Omit to create an auto-generated Railway service domain. */\n\tcustomDomain?: pulumi.Input<string>;\n}\n\n/**\n * Manages a Railway domain (service or custom) with adopt-or-create semantics.\n *\n * A service can carry more than one custom domain — declare one `RailwayDomain` per\n * domain; each instance adopts, creates, and deletes only its own domain.\n *\n * @example\n * ```typescript\n * const apiDomain = new RailwayDomain(\"api-domain\", { customDomain: \"api.example.com\" }, {\n * provider, project, environment, service,\n * });\n * const wwwDomain = new RailwayDomain(\"www-domain\", { customDomain: \"www.example.com\" }, {\n * provider, project, environment, service,\n * });\n *\n * // Point each domain's DNS CNAME at its own target.\n * const apiCnameTarget = apiDomain.cnameTarget;\n * const wwwCnameTarget = wwwDomain.cnameTarget;\n * ```\n */\nexport class RailwayDomain extends pulumi.ComponentResource {\n\t/** Fully qualified domain name. */\n\tpublic readonly fqdn: pulumi.Output<string>;\n\n\t/**\n\t * The value to CNAME `customDomain` to. Only set for custom domains — `undefined`\n\t * for a service's auto-generated `*.up.railway.app` domain, or if Railway hasn't\n\t * returned a traffic-routing CNAME record for it yet.\n\t */\n\tpublic readonly cnameTarget: pulumi.Output<string | undefined>;\n\n\tconstructor(\n\t\tname: string,\n\t\targs: RailwayDomainArgs,\n\t\topts: RailwayDomainOptions,\n\t) {\n\t\tconst { provider, project, environment, service, ...pulumiOpts } = opts;\n\n\t\tsuper(\"infracraft:railway:Domain\", name, {}, pulumiOpts);\n\n\t\tconst resource = new RailwayDomainResource(\n\t\t\t`${name}-resource`,\n\t\t\t{\n\t\t\t\ttoken: provider.token,\n\t\t\t\tprojectId: project.id,\n\t\t\t\tserviceId: service.id,\n\t\t\t\tenvironmentId: environment.id,\n\t\t\t\tcustomDomain: args.customDomain,\n\t\t\t},\n\t\t\t{ parent: this },\n\t\t);\n\n\t\tthis.fqdn = resource.fqdn;\n\t\tthis.cnameTarget = resource.cnameTarget;\n\n\t\tthis.registerOutputs({ fqdn: this.fqdn, cnameTarget: this.cnameTarget });\n\t}\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"domain.cjs","names":["RailwayClient","pulumi"],"sources":["../../src/railway/domain.ts"],"sourcesContent":["import * as pulumi from \"@pulumi/pulumi\";\nimport { RailwayClient } from \"./client\";\nimport type { RailwayEnvironment } from \"./environment\";\nimport type { RailwayProject } from \"./project\";\nimport type { RailwayProvider } from \"./provider\";\nimport type { RailwayService } from \"./service\";\n\n/** Resolved inputs for the Railway domain dynamic provider. */\ninterface RailwayDomainInputs {\n\t/** Railway API bearer token. */\n\ttoken: string;\n\n\t/** Railway project UUID. */\n\tprojectId: string;\n\n\t/** Railway service UUID to attach the domain to. */\n\tserviceId: string;\n\n\t/** Railway environment UUID (e.g. production). */\n\tenvironmentId: string;\n\n\t/** Custom domain FQDN (e.g. `\"api.example.com\"`). Omit for auto-generated Railway domain. */\n\tcustomDomain?: string;\n}\n\n/** Persisted state for the Railway domain, extending inputs with Railway-assigned identifiers. */\ninterface RailwayDomainOutputs extends RailwayDomainInputs {\n\t/** Railway-assigned domain UUID (used for deletion API calls). */\n\tdomainId: string;\n\n\t/** Fully qualified domain name (e.g. `\"api-production-abc.up.railway.app\"`). */\n\tfqdn: string;\n\n\t/**\n\t * The value to CNAME `customDomain` to. Only present for custom domains — a\n\t * service's auto-generated `*.up.railway.app` domain needs no DNS record of its\n\t * own. `undefined` if Railway hasn't returned a traffic-routing CNAME record yet.\n\t */\n\tcnameTarget?: string;\n\n\t/**\n\t * DNS record name for the ownership-verification TXT record (e.g.\n\t * `\"_railway-verify.staging.api\"`). `undefined` for service domains, and for\n\t * custom domains Railway doesn't require ownership verification for.\n\t */\n\tverificationTxtName?: string;\n\n\t/**\n\t * DNS record value for the ownership-verification TXT record, ready to write\n\t * as-is (the `railway-verify=` prefix is already composed — see\n\t * {@link composeVerificationTxtValue}). `undefined` under the same conditions as\n\t * {@link verificationTxtName}.\n\t */\n\tverificationTxtValue?: string;\n}\n\n/** A single DNS record Railway expects for a custom domain (CNAME target, ACME challenge, ...). */\ninterface DomainDnsRecord {\n\trecordType: string;\n\tpurpose: string;\n\trequiredValue: string;\n}\n\n/** Domain ownership-verification status, distinct from `dnsRecords` (see live API notes below). */\ninterface CustomDomainStatus {\n\tdnsRecords: DomainDnsRecord[];\n\t/**\n\t * DNS host for the ownership-verification TXT record. Verified live against\n\t * Railway's current API: this sits on `CustomDomain.status`, as a sibling of\n\t * `dnsRecords` rather than inside it — the two are populated independently\n\t * (`dnsRecords` can be non-empty with `verificationDnsHost` still null, and\n\t * vice versa), so both must be queried explicitly.\n\t */\n\tverificationDnsHost?: string | null;\n\t/**\n\t * Raw ownership-verification token. Verified live: Railway already returns this\n\t * pre-composed with the `railway-verify=` prefix (not a bare token) — see\n\t * {@link composeVerificationTxtValue}, which stays idempotent in case that ever\n\t * changes.\n\t */\n\tverificationToken?: string | null;\n}\n\n/** Shape returned for a custom domain, including its required DNS records. */\ninterface CustomDomainEntry {\n\tid: string;\n\tdomain: string;\n\tstatus?: CustomDomainStatus;\n}\n\n/**\n * Picks the CNAME record Railway expects for routing traffic to a custom domain out\n * of its full DNS record list (which also includes e.g. an ACME challenge TXT record).\n */\nfunction extractCnameTarget(\n\tdnsRecords: DomainDnsRecord[] | undefined,\n): string | undefined {\n\treturn dnsRecords?.find(\n\t\t(record) =>\n\t\t\trecord.recordType === \"DNS_RECORD_TYPE_CNAME\" &&\n\t\t\trecord.purpose === \"DNS_RECORD_PURPOSE_TRAFFIC_ROUTE\",\n\t)?.requiredValue;\n}\n\nconst RAILWAY_VERIFY_PREFIX = \"railway-verify=\";\n\n/**\n * Composes the ownership-verification TXT record value from Railway's token.\n * Idempotent: Railway currently returns the token already prefixed, but this\n * guards against a future API response returning the bare token instead.\n */\nfunction composeVerificationTxtValue(token: string): string {\n\treturn token.startsWith(RAILWAY_VERIFY_PREFIX)\n\t\t? token\n\t\t: `${RAILWAY_VERIFY_PREFIX}${token}`;\n}\n\n/**\n * Extracts the ready-to-use ownership-verification TXT record (name + composed\n * value) from a custom domain's status, or `undefined` if Railway hasn't assigned\n * one (service domains, or custom domains needing no verification).\n */\nfunction extractVerificationTxt(\n\tstatus: CustomDomainStatus | undefined,\n): { name: string; value: string } | undefined {\n\tif (!status?.verificationDnsHost || !status.verificationToken) {\n\t\treturn undefined;\n\t}\n\n\treturn {\n\t\tname: status.verificationDnsHost,\n\t\tvalue: composeVerificationTxtValue(status.verificationToken),\n\t};\n}\n\nconst DOMAIN_STATUS_FIELDS = `\n status {\n dnsRecords { recordType purpose requiredValue }\n verificationDnsHost\n verificationToken\n }\n`;\n\nconst SERVICE_DOMAINS_QUERY = `\n query($projectId: String!, $serviceId: String!, $environmentId: String!) {\n domains(\n projectId: $projectId\n serviceId: $serviceId\n environmentId: $environmentId\n ) {\n serviceDomains { id domain }\n customDomains { id domain ${DOMAIN_STATUS_FIELDS} }\n }\n }\n`;\n\nconst SERVICE_DOMAIN_CREATE = `\n mutation($input: ServiceDomainCreateInput!) {\n serviceDomainCreate(input: $input) { id domain }\n }\n`;\n\nconst CUSTOM_DOMAIN_CREATE = `\n mutation($input: CustomDomainCreateInput!) {\n customDomainCreate(input: $input) { id domain ${DOMAIN_STATUS_FIELDS} }\n }\n`;\n\nconst CUSTOM_DOMAIN_DELETE = `\n mutation($id: String!) { customDomainDelete(id: $id) }\n`;\n\nconst SERVICE_DOMAIN_DELETE = `\n mutation($id: String!) { serviceDomainDelete(id: $id) }\n`;\n\n/**\n * Queries all existing domains (service and custom) for a Railway service.\n */\nasync function findExistingDomains(\n\tclient: RailwayClient,\n\tprojectId: string,\n\tserviceId: string,\n\tenvironmentId: string,\n): Promise<{\n\tserviceDomains: Array<{ id: string; domain: string }>;\n\tcustomDomains: CustomDomainEntry[];\n}> {\n\tconst result = await client.query<{\n\t\tdomains: {\n\t\t\tserviceDomains: Array<{ id: string; domain: string }>;\n\t\t\tcustomDomains: CustomDomainEntry[];\n\t\t};\n\t}>(SERVICE_DOMAINS_QUERY, { projectId, serviceId, environmentId });\n\n\treturn result.domains;\n}\n\n/**\n * Dynamic provider implementing CRUD for Railway domains.\n *\n * Uses adopt-or-create: queries existing domains before creating new ones.\n * Uses the FQDN as the Pulumi resource ID.\n *\n * @internal Exported only for unit testing; not part of the public API surface.\n */\nexport class RailwayDomainResourceProvider\n\timplements pulumi.dynamic.ResourceProvider\n{\n\tasync create(\n\t\tinputs: RailwayDomainInputs,\n\t): Promise<pulumi.dynamic.CreateResult> {\n\t\tconst client = new RailwayClient(inputs.token);\n\n\t\tconst existing = await findExistingDomains(\n\t\t\tclient,\n\t\t\tinputs.projectId,\n\t\t\tinputs.serviceId,\n\t\t\tinputs.environmentId,\n\t\t);\n\n\t\tif (inputs.customDomain) {\n\t\t\tconst found = existing.customDomains.find(\n\t\t\t\t(d) => d.domain === inputs.customDomain,\n\t\t\t);\n\n\t\t\tif (found) {\n\t\t\t\tpulumi.log.info(`Adopting existing custom domain \"${found.domain}\"`);\n\n\t\t\t\tconst verificationTxt = extractVerificationTxt(found.status);\n\n\t\t\t\treturn {\n\t\t\t\t\tid: found.domain,\n\t\t\t\t\touts: {\n\t\t\t\t\t\t...inputs,\n\t\t\t\t\t\tdomainId: found.id,\n\t\t\t\t\t\tfqdn: found.domain,\n\t\t\t\t\t\tcnameTarget: extractCnameTarget(found.status?.dnsRecords),\n\t\t\t\t\t\tverificationTxtName: verificationTxt?.name,\n\t\t\t\t\t\tverificationTxtValue: verificationTxt?.value,\n\t\t\t\t\t},\n\t\t\t\t};\n\t\t\t}\n\n\t\t\tconst result = await client.query<{\n\t\t\t\tcustomDomainCreate: CustomDomainEntry;\n\t\t\t}>(CUSTOM_DOMAIN_CREATE, {\n\t\t\t\tinput: {\n\t\t\t\t\tprojectId: inputs.projectId,\n\t\t\t\t\tserviceId: inputs.serviceId,\n\t\t\t\t\tenvironmentId: inputs.environmentId,\n\t\t\t\t\tdomain: inputs.customDomain,\n\t\t\t\t},\n\t\t\t});\n\n\t\t\tconst createdVerificationTxt = extractVerificationTxt(\n\t\t\t\tresult.customDomainCreate.status,\n\t\t\t);\n\n\t\t\treturn {\n\t\t\t\tid: result.customDomainCreate.domain,\n\t\t\t\touts: {\n\t\t\t\t\t...inputs,\n\t\t\t\t\tdomainId: result.customDomainCreate.id,\n\t\t\t\t\tfqdn: result.customDomainCreate.domain,\n\t\t\t\t\tcnameTarget: extractCnameTarget(\n\t\t\t\t\t\tresult.customDomainCreate.status?.dnsRecords,\n\t\t\t\t\t),\n\t\t\t\t\tverificationTxtName: createdVerificationTxt?.name,\n\t\t\t\t\tverificationTxtValue: createdVerificationTxt?.value,\n\t\t\t\t},\n\t\t\t};\n\t\t}\n\n\t\tif (existing.serviceDomains.length > 0) {\n\t\t\tconst found = existing.serviceDomains[0];\n\n\t\t\tpulumi.log.info(`Adopting existing service domain \"${found.domain}\"`);\n\n\t\t\treturn {\n\t\t\t\tid: found.domain,\n\t\t\t\touts: { ...inputs, domainId: found.id, fqdn: found.domain },\n\t\t\t};\n\t\t}\n\n\t\tconst result = await client.query<{\n\t\t\tserviceDomainCreate: { id: string; domain: string };\n\t\t}>(SERVICE_DOMAIN_CREATE, {\n\t\t\tinput: {\n\t\t\t\tserviceId: inputs.serviceId,\n\t\t\t\tenvironmentId: inputs.environmentId,\n\t\t\t},\n\t\t});\n\n\t\treturn {\n\t\t\tid: result.serviceDomainCreate.domain,\n\t\t\touts: {\n\t\t\t\t...inputs,\n\t\t\t\tdomainId: result.serviceDomainCreate.id,\n\t\t\t\tfqdn: result.serviceDomainCreate.domain,\n\t\t\t},\n\t\t};\n\t}\n\n\tasync read(\n\t\t_id: string,\n\t\tprops: RailwayDomainOutputs,\n\t): Promise<pulumi.dynamic.ReadResult> {\n\t\tconst client = new RailwayClient(props.token);\n\n\t\tconst existing = await findExistingDomains(\n\t\t\tclient,\n\t\t\tprops.projectId,\n\t\t\tprops.serviceId,\n\t\t\tprops.environmentId,\n\t\t);\n\n\t\tif (props.customDomain) {\n\t\t\tconst found = existing.customDomains.find(\n\t\t\t\t(d) => d.domain === props.customDomain,\n\t\t\t);\n\n\t\t\tif (!found) {\n\t\t\t\tthrow new Error(\n\t\t\t\t\t`Custom domain \"${props.customDomain}\" not found during refresh`,\n\t\t\t\t);\n\t\t\t}\n\n\t\t\tconst refreshedVerificationTxt = extractVerificationTxt(found.status);\n\n\t\t\treturn {\n\t\t\t\tid: found.domain,\n\t\t\t\tprops: {\n\t\t\t\t\t...props,\n\t\t\t\t\tdomainId: found.id,\n\t\t\t\t\tfqdn: found.domain,\n\t\t\t\t\tcnameTarget: extractCnameTarget(found.status?.dnsRecords),\n\t\t\t\t\tverificationTxtName: refreshedVerificationTxt?.name,\n\t\t\t\t\tverificationTxtValue: refreshedVerificationTxt?.value,\n\t\t\t\t},\n\t\t\t};\n\t\t}\n\n\t\tif (existing.serviceDomains.length > 0) {\n\t\t\tconst found = existing.serviceDomains[0];\n\n\t\t\treturn {\n\t\t\t\tid: found.domain,\n\t\t\t\tprops: { ...props, domainId: found.id, fqdn: found.domain },\n\t\t\t};\n\t\t}\n\n\t\tthrow new Error(\"Railway domain not found during refresh\");\n\t}\n\n\tasync delete(_id: string, props: RailwayDomainOutputs): Promise<void> {\n\t\tconst client = new RailwayClient(props.token);\n\n\t\tconst mutation = props.customDomain\n\t\t\t? CUSTOM_DOMAIN_DELETE\n\t\t\t: SERVICE_DOMAIN_DELETE;\n\n\t\ttry {\n\t\t\tawait client.query(mutation, { id: props.domainId });\n\t\t} catch {\n\t\t\tpulumi.log.warn(\n\t\t\t\t\"Failed to delete Railway domain (may already be deleted)\",\n\t\t\t);\n\t\t}\n\t}\n\n\tasync diff(\n\t\t_id: string,\n\t\tolds: RailwayDomainOutputs,\n\t\tnews: RailwayDomainInputs,\n\t): Promise<pulumi.dynamic.DiffResult> {\n\t\tconst replaces: string[] = [];\n\n\t\tif (olds.serviceId !== news.serviceId) {\n\t\t\treplaces.push(\"serviceId\");\n\t\t}\n\n\t\tif (olds.customDomain !== news.customDomain) {\n\t\t\treplaces.push(\"customDomain\");\n\t\t}\n\n\t\tif (olds.environmentId !== news.environmentId) {\n\t\t\treplaces.push(\"environmentId\");\n\t\t}\n\n\t\treturn {\n\t\t\tchanges: replaces.length > 0,\n\t\t\treplaces,\n\t\t\tdeleteBeforeReplace: true,\n\t\t};\n\t}\n}\n\n/** Internal dynamic resource — not part of the public API. */\nclass RailwayDomainResource extends pulumi.dynamic.Resource {\n\tpublic declare readonly fqdn: pulumi.Output<string>;\n\tpublic declare readonly cnameTarget: pulumi.Output<string | undefined>;\n\tpublic declare readonly verificationTxtName: pulumi.Output<\n\t\tstring | undefined\n\t>;\n\tpublic declare readonly verificationTxtValue: pulumi.Output<\n\t\tstring | undefined\n\t>;\n\n\tconstructor(\n\t\tname: string,\n\t\targs: {\n\t\t\ttoken: pulumi.Input<string>;\n\t\t\tprojectId: pulumi.Input<string>;\n\t\t\tserviceId: pulumi.Input<string>;\n\t\t\tenvironmentId: pulumi.Input<string>;\n\t\t\tcustomDomain?: pulumi.Input<string>;\n\t\t},\n\t\topts?: pulumi.CustomResourceOptions,\n\t) {\n\t\tsuper(\n\t\t\tnew RailwayDomainResourceProvider(),\n\t\t\tname,\n\t\t\t{\n\t\t\t\t...args,\n\t\t\t\tdomainId: undefined,\n\t\t\t\tfqdn: undefined,\n\t\t\t\tcnameTarget: undefined,\n\t\t\t\tverificationTxtName: undefined,\n\t\t\t\tverificationTxtValue: undefined,\n\t\t\t},\n\t\t\topts,\n\t\t);\n\t}\n}\n\n/** Options type for RailwayDomain — replaces Pulumi's native `provider` field. */\ntype RailwayDomainOptions = Omit<\n\tpulumi.ComponentResourceOptions,\n\t\"provider\"\n> & {\n\t/** Railway authentication context. */\n\tprovider: RailwayProvider;\n\n\t/** Railway project context. */\n\tproject: RailwayProject;\n\n\t/** Railway environment context. */\n\tenvironment: RailwayEnvironment;\n\n\t/** Railway service context. */\n\tservice: RailwayService;\n};\n\n/** Args for RailwayDomain. */\nexport interface RailwayDomainArgs {\n\t/** Custom domain FQDN. Omit to create an auto-generated Railway service domain. */\n\tcustomDomain?: pulumi.Input<string>;\n}\n\n/**\n * Manages a Railway domain (service or custom) with adopt-or-create semantics.\n *\n * A service can carry more than one custom domain — declare one `RailwayDomain` per\n * domain; each instance adopts, creates, and deletes only its own domain.\n *\n * @example\n * ```typescript\n * const apiDomain = new RailwayDomain(\"api-domain\", { customDomain: \"api.example.com\" }, {\n * provider, project, environment, service,\n * });\n * const wwwDomain = new RailwayDomain(\"www-domain\", { customDomain: \"www.example.com\" }, {\n * provider, project, environment, service,\n * });\n *\n * // Point each domain's DNS CNAME at its own target.\n * const apiCnameTarget = apiDomain.cnameTarget;\n * const wwwCnameTarget = wwwDomain.cnameTarget;\n * ```\n */\nexport class RailwayDomain extends pulumi.ComponentResource {\n\t/** Fully qualified domain name. */\n\tpublic readonly fqdn: pulumi.Output<string>;\n\n\t/**\n\t * The value to CNAME `customDomain` to. Only set for custom domains — `undefined`\n\t * for a service's auto-generated `*.up.railway.app` domain, or if Railway hasn't\n\t * returned a traffic-routing CNAME record for it yet.\n\t */\n\tpublic readonly cnameTarget: pulumi.Output<string | undefined>;\n\n\t/**\n\t * DNS record name for the ownership-verification TXT record. `undefined` for\n\t * service domains, and for custom domains Railway doesn't require verification\n\t * for — write this record (alongside `cnameTarget`'s CNAME) to flip Railway's\n\t * domain status to verified and let its TLS certificate issue.\n\t */\n\tpublic readonly verificationTxtName: pulumi.Output<string | undefined>;\n\n\t/** DNS record value for the ownership-verification TXT record, ready to write as-is. */\n\tpublic readonly verificationTxtValue: pulumi.Output<string | undefined>;\n\n\tconstructor(\n\t\tname: string,\n\t\targs: RailwayDomainArgs,\n\t\topts: RailwayDomainOptions,\n\t) {\n\t\tconst { provider, project, environment, service, ...pulumiOpts } = opts;\n\n\t\tsuper(\"infracraft:railway:Domain\", name, {}, pulumiOpts);\n\n\t\tconst resource = new RailwayDomainResource(\n\t\t\t`${name}-resource`,\n\t\t\t{\n\t\t\t\ttoken: provider.token,\n\t\t\t\tprojectId: project.id,\n\t\t\t\tserviceId: service.id,\n\t\t\t\tenvironmentId: environment.id,\n\t\t\t\tcustomDomain: args.customDomain,\n\t\t\t},\n\t\t\t{ parent: this },\n\t\t);\n\n\t\tthis.fqdn = resource.fqdn;\n\t\tthis.cnameTarget = resource.cnameTarget;\n\t\tthis.verificationTxtName = resource.verificationTxtName;\n\t\tthis.verificationTxtValue = resource.verificationTxtValue;\n\n\t\tthis.registerOutputs({\n\t\t\tfqdn: this.fqdn,\n\t\t\tcnameTarget: this.cnameTarget,\n\t\t\tverificationTxtName: this.verificationTxtName,\n\t\t\tverificationTxtValue: this.verificationTxtValue,\n\t\t});\n\t}\n}\n"],"mappings":"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"}
|
|
@@ -31,6 +31,19 @@ interface RailwayDomainOutputs extends RailwayDomainInputs {
|
|
|
31
31
|
* own. `undefined` if Railway hasn't returned a traffic-routing CNAME record yet.
|
|
32
32
|
*/
|
|
33
33
|
cnameTarget?: string;
|
|
34
|
+
/**
|
|
35
|
+
* DNS record name for the ownership-verification TXT record (e.g.
|
|
36
|
+
* `"_railway-verify.staging.api"`). `undefined` for service domains, and for
|
|
37
|
+
* custom domains Railway doesn't require ownership verification for.
|
|
38
|
+
*/
|
|
39
|
+
verificationTxtName?: string;
|
|
40
|
+
/**
|
|
41
|
+
* DNS record value for the ownership-verification TXT record, ready to write
|
|
42
|
+
* as-is (the `railway-verify=` prefix is already composed — see
|
|
43
|
+
* {@link composeVerificationTxtValue}). `undefined` under the same conditions as
|
|
44
|
+
* {@link verificationTxtName}.
|
|
45
|
+
*/
|
|
46
|
+
verificationTxtValue?: string;
|
|
34
47
|
}
|
|
35
48
|
/**
|
|
36
49
|
* Dynamic provider implementing CRUD for Railway domains.
|
|
@@ -87,6 +100,15 @@ declare class RailwayDomain extends pulumi.ComponentResource {
|
|
|
87
100
|
* returned a traffic-routing CNAME record for it yet.
|
|
88
101
|
*/
|
|
89
102
|
readonly cnameTarget: pulumi.Output<string | undefined>;
|
|
103
|
+
/**
|
|
104
|
+
* DNS record name for the ownership-verification TXT record. `undefined` for
|
|
105
|
+
* service domains, and for custom domains Railway doesn't require verification
|
|
106
|
+
* for — write this record (alongside `cnameTarget`'s CNAME) to flip Railway's
|
|
107
|
+
* domain status to verified and let its TLS certificate issue.
|
|
108
|
+
*/
|
|
109
|
+
readonly verificationTxtName: pulumi.Output<string | undefined>;
|
|
110
|
+
/** DNS record value for the ownership-verification TXT record, ready to write as-is. */
|
|
111
|
+
readonly verificationTxtValue: pulumi.Output<string | undefined>;
|
|
90
112
|
constructor(name: string, args: RailwayDomainArgs, opts: RailwayDomainOptions);
|
|
91
113
|
}
|
|
92
114
|
//#endregion
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"domain.d.cts","names":[],"sources":["../../src/railway/domain.ts"],"mappings":";;;;;;;;;UAQU,mBAAA;;EAET,KAAA;EAF4B;EAK5B,SAAA;EAL4B;EAQ5B,SAAA;EAHA;EAMA,aAAA;EAAA;EAGA,YAAA;AAAA;AAAY;AAAA,UAIH,oBAAA,SAA6B,mBAAmB;EAA3B;EAE9B,QAAA;EAFyD;EAKzD,IAAA;EAHA;;;;
|
|
1
|
+
{"version":3,"file":"domain.d.cts","names":[],"sources":["../../src/railway/domain.ts"],"mappings":";;;;;;;;;UAQU,mBAAA;;EAET,KAAA;EAF4B;EAK5B,SAAA;EAL4B;EAQ5B,SAAA;EAHA;EAMA,aAAA;EAAA;EAGA,YAAA;AAAA;AAAY;AAAA,UAIH,oBAAA,SAA6B,mBAAmB;EAA3B;EAE9B,QAAA;EAFyD;EAKzD,IAAA;EAHA;;;;;EAUA,WAAA;EAeoB;AAyJrB;;;;EAjKC,mBAAA;EAsKG;;;;;;EA9JH,oBAAA;AAAA;;;;;;;;;cAyJY,6BAAA,YACD,MAAA,CAAO,OAAA,CAAQ,gBAAA;EAEpB,MAAA,CACL,MAAA,EAAQ,mBAAA,GACN,OAAA,CAAQ,MAAA,CAAO,OAAA,CAAQ,YAAA;EA6FpB,IAAA,CACL,GAAA,UACA,KAAA,EAAO,oBAAA,GACL,OAAA,CAAQ,MAAA,CAAO,OAAA,CAAQ,UAAA;EAgDpB,MAAA,CAAO,GAAA,UAAa,KAAA,EAAO,oBAAA,GAAuB,OAAA;EAgBlD,IAAA,CACL,GAAA,UACA,IAAA,EAAM,oBAAA,EACN,IAAA,EAAM,mBAAA,GACJ,OAAA,CAAQ,MAAA,CAAO,OAAA,CAAQ,UAAA;AAAA;;KA8DtB,oBAAA,GAAuB,IAAA,CAC3B,MAAA,CAAO,wBAAA;EArIN,sCAyID,QAAA,EAAU,eAAA,EAxIT;EA2ID,OAAA,EAAS,cAAA,EA1IE;EA6IX,WAAA,EAAa,kBAAA,EA7Ia;EAgJ1B,OAAA,EAAS,cAAA;AAAA;;UAIO,iBAAA;EApGwC;EAsGxD,YAAA,GAAe,MAAA,CAAO,KAAK;AAAA;;;;;;;;;;AAlFS;AAqBpC;;;;;;;;;;cAoFY,aAAA,SAAsB,MAAA,CAAO,iBAAA;EA3Cd;EAAA,SA6CX,IAAA,EAAM,MAAA,CAAO,MAAA;EA5CtB;;;;;EAAA,SAmDS,WAAA,EAAa,MAAA,CAAO,MAAA;EAzCvB;;;;AAGU;AAIxB;EAPc,SAiDG,mBAAA,EAAqB,MAAA,CAAO,MAAA;;WAG5B,oBAAA,EAAsB,MAAA,CAAO,MAAA;cAG5C,IAAA,UACA,IAAA,EAAM,iBAAA,EACN,IAAA,EAAM,oBAAA;AAAA"}
|
|
@@ -31,6 +31,19 @@ interface RailwayDomainOutputs extends RailwayDomainInputs {
|
|
|
31
31
|
* own. `undefined` if Railway hasn't returned a traffic-routing CNAME record yet.
|
|
32
32
|
*/
|
|
33
33
|
cnameTarget?: string;
|
|
34
|
+
/**
|
|
35
|
+
* DNS record name for the ownership-verification TXT record (e.g.
|
|
36
|
+
* `"_railway-verify.staging.api"`). `undefined` for service domains, and for
|
|
37
|
+
* custom domains Railway doesn't require ownership verification for.
|
|
38
|
+
*/
|
|
39
|
+
verificationTxtName?: string;
|
|
40
|
+
/**
|
|
41
|
+
* DNS record value for the ownership-verification TXT record, ready to write
|
|
42
|
+
* as-is (the `railway-verify=` prefix is already composed — see
|
|
43
|
+
* {@link composeVerificationTxtValue}). `undefined` under the same conditions as
|
|
44
|
+
* {@link verificationTxtName}.
|
|
45
|
+
*/
|
|
46
|
+
verificationTxtValue?: string;
|
|
34
47
|
}
|
|
35
48
|
/**
|
|
36
49
|
* Dynamic provider implementing CRUD for Railway domains.
|
|
@@ -87,6 +100,15 @@ declare class RailwayDomain extends pulumi.ComponentResource {
|
|
|
87
100
|
* returned a traffic-routing CNAME record for it yet.
|
|
88
101
|
*/
|
|
89
102
|
readonly cnameTarget: pulumi.Output<string | undefined>;
|
|
103
|
+
/**
|
|
104
|
+
* DNS record name for the ownership-verification TXT record. `undefined` for
|
|
105
|
+
* service domains, and for custom domains Railway doesn't require verification
|
|
106
|
+
* for — write this record (alongside `cnameTarget`'s CNAME) to flip Railway's
|
|
107
|
+
* domain status to verified and let its TLS certificate issue.
|
|
108
|
+
*/
|
|
109
|
+
readonly verificationTxtName: pulumi.Output<string | undefined>;
|
|
110
|
+
/** DNS record value for the ownership-verification TXT record, ready to write as-is. */
|
|
111
|
+
readonly verificationTxtValue: pulumi.Output<string | undefined>;
|
|
90
112
|
constructor(name: string, args: RailwayDomainArgs, opts: RailwayDomainOptions);
|
|
91
113
|
}
|
|
92
114
|
//#endregion
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"domain.d.mts","names":[],"sources":["../../src/railway/domain.ts"],"mappings":";;;;;;;;;UAQU,mBAAA;;EAET,KAAA;EAF4B;EAK5B,SAAA;EAL4B;EAQ5B,SAAA;EAHA;EAMA,aAAA;EAAA;EAGA,YAAA;AAAA;AAAY;AAAA,UAIH,oBAAA,SAA6B,mBAAmB;EAA3B;EAE9B,QAAA;EAFyD;EAKzD,IAAA;EAHA;;;;
|
|
1
|
+
{"version":3,"file":"domain.d.mts","names":[],"sources":["../../src/railway/domain.ts"],"mappings":";;;;;;;;;UAQU,mBAAA;;EAET,KAAA;EAF4B;EAK5B,SAAA;EAL4B;EAQ5B,SAAA;EAHA;EAMA,aAAA;EAAA;EAGA,YAAA;AAAA;AAAY;AAAA,UAIH,oBAAA,SAA6B,mBAAmB;EAA3B;EAE9B,QAAA;EAFyD;EAKzD,IAAA;EAHA;;;;;EAUA,WAAA;EAeoB;AAyJrB;;;;EAjKC,mBAAA;EAsKG;;;;;;EA9JH,oBAAA;AAAA;;;;;;;;;cAyJY,6BAAA,YACD,MAAA,CAAO,OAAA,CAAQ,gBAAA;EAEpB,MAAA,CACL,MAAA,EAAQ,mBAAA,GACN,OAAA,CAAQ,MAAA,CAAO,OAAA,CAAQ,YAAA;EA6FpB,IAAA,CACL,GAAA,UACA,KAAA,EAAO,oBAAA,GACL,OAAA,CAAQ,MAAA,CAAO,OAAA,CAAQ,UAAA;EAgDpB,MAAA,CAAO,GAAA,UAAa,KAAA,EAAO,oBAAA,GAAuB,OAAA;EAgBlD,IAAA,CACL,GAAA,UACA,IAAA,EAAM,oBAAA,EACN,IAAA,EAAM,mBAAA,GACJ,OAAA,CAAQ,MAAA,CAAO,OAAA,CAAQ,UAAA;AAAA;;KA8DtB,oBAAA,GAAuB,IAAA,CAC3B,MAAA,CAAO,wBAAA;EArIN,sCAyID,QAAA,EAAU,eAAA,EAxIT;EA2ID,OAAA,EAAS,cAAA,EA1IE;EA6IX,WAAA,EAAa,kBAAA,EA7Ia;EAgJ1B,OAAA,EAAS,cAAA;AAAA;;UAIO,iBAAA;EApGwC;EAsGxD,YAAA,GAAe,MAAA,CAAO,KAAK;AAAA;;;;;;;;;;AAlFS;AAqBpC;;;;;;;;;;cAoFY,aAAA,SAAsB,MAAA,CAAO,iBAAA;EA3Cd;EAAA,SA6CX,IAAA,EAAM,MAAA,CAAO,MAAA;EA5CtB;;;;;EAAA,SAmDS,WAAA,EAAa,MAAA,CAAO,MAAA;EAzCvB;;;;AAGU;AAIxB;EAPc,SAiDG,mBAAA,EAAqB,MAAA,CAAO,MAAA;;WAG5B,oBAAA,EAAsB,MAAA,CAAO,MAAA;cAG5C,IAAA,UACA,IAAA,EAAM,iBAAA,EACN,IAAA,EAAM,oBAAA;AAAA"}
|
package/dist/railway/domain.mjs
CHANGED
|
@@ -10,9 +10,32 @@ import * as pulumi from "@pulumi/pulumi";
|
|
|
10
10
|
function extractCnameTarget(dnsRecords) {
|
|
11
11
|
return dnsRecords?.find((record) => record.recordType === "DNS_RECORD_TYPE_CNAME" && record.purpose === "DNS_RECORD_PURPOSE_TRAFFIC_ROUTE")?.requiredValue;
|
|
12
12
|
}
|
|
13
|
+
const RAILWAY_VERIFY_PREFIX = "railway-verify=";
|
|
14
|
+
/**
|
|
15
|
+
* Composes the ownership-verification TXT record value from Railway's token.
|
|
16
|
+
* Idempotent: Railway currently returns the token already prefixed, but this
|
|
17
|
+
* guards against a future API response returning the bare token instead.
|
|
18
|
+
*/
|
|
19
|
+
function composeVerificationTxtValue(token) {
|
|
20
|
+
return token.startsWith(RAILWAY_VERIFY_PREFIX) ? token : `${RAILWAY_VERIFY_PREFIX}${token}`;
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Extracts the ready-to-use ownership-verification TXT record (name + composed
|
|
24
|
+
* value) from a custom domain's status, or `undefined` if Railway hasn't assigned
|
|
25
|
+
* one (service domains, or custom domains needing no verification).
|
|
26
|
+
*/
|
|
27
|
+
function extractVerificationTxt(status) {
|
|
28
|
+
if (!status?.verificationDnsHost || !status.verificationToken) return;
|
|
29
|
+
return {
|
|
30
|
+
name: status.verificationDnsHost,
|
|
31
|
+
value: composeVerificationTxtValue(status.verificationToken)
|
|
32
|
+
};
|
|
33
|
+
}
|
|
13
34
|
const DOMAIN_STATUS_FIELDS = `
|
|
14
35
|
status {
|
|
15
36
|
dnsRecords { recordType purpose requiredValue }
|
|
37
|
+
verificationDnsHost
|
|
38
|
+
verificationToken
|
|
16
39
|
}
|
|
17
40
|
`;
|
|
18
41
|
const SERVICE_DOMAINS_QUERY = `
|
|
@@ -69,13 +92,16 @@ var RailwayDomainResourceProvider = class {
|
|
|
69
92
|
const found = existing.customDomains.find((d) => d.domain === inputs.customDomain);
|
|
70
93
|
if (found) {
|
|
71
94
|
pulumi.log.info(`Adopting existing custom domain "${found.domain}"`);
|
|
95
|
+
const verificationTxt = extractVerificationTxt(found.status);
|
|
72
96
|
return {
|
|
73
97
|
id: found.domain,
|
|
74
98
|
outs: {
|
|
75
99
|
...inputs,
|
|
76
100
|
domainId: found.id,
|
|
77
101
|
fqdn: found.domain,
|
|
78
|
-
cnameTarget: extractCnameTarget(found.status?.dnsRecords)
|
|
102
|
+
cnameTarget: extractCnameTarget(found.status?.dnsRecords),
|
|
103
|
+
verificationTxtName: verificationTxt?.name,
|
|
104
|
+
verificationTxtValue: verificationTxt?.value
|
|
79
105
|
}
|
|
80
106
|
};
|
|
81
107
|
}
|
|
@@ -85,13 +111,16 @@ var RailwayDomainResourceProvider = class {
|
|
|
85
111
|
environmentId: inputs.environmentId,
|
|
86
112
|
domain: inputs.customDomain
|
|
87
113
|
} });
|
|
114
|
+
const createdVerificationTxt = extractVerificationTxt(result.customDomainCreate.status);
|
|
88
115
|
return {
|
|
89
116
|
id: result.customDomainCreate.domain,
|
|
90
117
|
outs: {
|
|
91
118
|
...inputs,
|
|
92
119
|
domainId: result.customDomainCreate.id,
|
|
93
120
|
fqdn: result.customDomainCreate.domain,
|
|
94
|
-
cnameTarget: extractCnameTarget(result.customDomainCreate.status?.dnsRecords)
|
|
121
|
+
cnameTarget: extractCnameTarget(result.customDomainCreate.status?.dnsRecords),
|
|
122
|
+
verificationTxtName: createdVerificationTxt?.name,
|
|
123
|
+
verificationTxtValue: createdVerificationTxt?.value
|
|
95
124
|
}
|
|
96
125
|
};
|
|
97
126
|
}
|
|
@@ -125,13 +154,16 @@ var RailwayDomainResourceProvider = class {
|
|
|
125
154
|
if (props.customDomain) {
|
|
126
155
|
const found = existing.customDomains.find((d) => d.domain === props.customDomain);
|
|
127
156
|
if (!found) throw new Error(`Custom domain "${props.customDomain}" not found during refresh`);
|
|
157
|
+
const refreshedVerificationTxt = extractVerificationTxt(found.status);
|
|
128
158
|
return {
|
|
129
159
|
id: found.domain,
|
|
130
160
|
props: {
|
|
131
161
|
...props,
|
|
132
162
|
domainId: found.id,
|
|
133
163
|
fqdn: found.domain,
|
|
134
|
-
cnameTarget: extractCnameTarget(found.status?.dnsRecords)
|
|
164
|
+
cnameTarget: extractCnameTarget(found.status?.dnsRecords),
|
|
165
|
+
verificationTxtName: refreshedVerificationTxt?.name,
|
|
166
|
+
verificationTxtValue: refreshedVerificationTxt?.value
|
|
135
167
|
}
|
|
136
168
|
};
|
|
137
169
|
}
|
|
@@ -176,7 +208,9 @@ var RailwayDomainResource = class extends pulumi.dynamic.Resource {
|
|
|
176
208
|
...args,
|
|
177
209
|
domainId: void 0,
|
|
178
210
|
fqdn: void 0,
|
|
179
|
-
cnameTarget: void 0
|
|
211
|
+
cnameTarget: void 0,
|
|
212
|
+
verificationTxtName: void 0,
|
|
213
|
+
verificationTxtValue: void 0
|
|
180
214
|
}, opts);
|
|
181
215
|
}
|
|
182
216
|
};
|
|
@@ -213,9 +247,13 @@ var RailwayDomain = class extends pulumi.ComponentResource {
|
|
|
213
247
|
}, { parent: this });
|
|
214
248
|
this.fqdn = resource.fqdn;
|
|
215
249
|
this.cnameTarget = resource.cnameTarget;
|
|
250
|
+
this.verificationTxtName = resource.verificationTxtName;
|
|
251
|
+
this.verificationTxtValue = resource.verificationTxtValue;
|
|
216
252
|
this.registerOutputs({
|
|
217
253
|
fqdn: this.fqdn,
|
|
218
|
-
cnameTarget: this.cnameTarget
|
|
254
|
+
cnameTarget: this.cnameTarget,
|
|
255
|
+
verificationTxtName: this.verificationTxtName,
|
|
256
|
+
verificationTxtValue: this.verificationTxtValue
|
|
219
257
|
});
|
|
220
258
|
}
|
|
221
259
|
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"domain.mjs","names":[],"sources":["../../src/railway/domain.ts"],"sourcesContent":["import * as pulumi from \"@pulumi/pulumi\";\nimport { RailwayClient } from \"./client\";\nimport type { RailwayEnvironment } from \"./environment\";\nimport type { RailwayProject } from \"./project\";\nimport type { RailwayProvider } from \"./provider\";\nimport type { RailwayService } from \"./service\";\n\n/** Resolved inputs for the Railway domain dynamic provider. */\ninterface RailwayDomainInputs {\n\t/** Railway API bearer token. */\n\ttoken: string;\n\n\t/** Railway project UUID. */\n\tprojectId: string;\n\n\t/** Railway service UUID to attach the domain to. */\n\tserviceId: string;\n\n\t/** Railway environment UUID (e.g. production). */\n\tenvironmentId: string;\n\n\t/** Custom domain FQDN (e.g. `\"api.example.com\"`). Omit for auto-generated Railway domain. */\n\tcustomDomain?: string;\n}\n\n/** Persisted state for the Railway domain, extending inputs with Railway-assigned identifiers. */\ninterface RailwayDomainOutputs extends RailwayDomainInputs {\n\t/** Railway-assigned domain UUID (used for deletion API calls). */\n\tdomainId: string;\n\n\t/** Fully qualified domain name (e.g. `\"api-production-abc.up.railway.app\"`). */\n\tfqdn: string;\n\n\t/**\n\t * The value to CNAME `customDomain` to. Only present for custom domains — a\n\t * service's auto-generated `*.up.railway.app` domain needs no DNS record of its\n\t * own. `undefined` if Railway hasn't returned a traffic-routing CNAME record yet.\n\t */\n\tcnameTarget?: string;\n}\n\n/** A single DNS record Railway expects for a custom domain (CNAME target, ACME challenge, ...). */\ninterface DomainDnsRecord {\n\trecordType: string;\n\tpurpose: string;\n\trequiredValue: string;\n}\n\n/** Shape returned for a custom domain, including its required DNS records. */\ninterface CustomDomainEntry {\n\tid: string;\n\tdomain: string;\n\tstatus?: { dnsRecords: DomainDnsRecord[] };\n}\n\n/**\n * Picks the CNAME record Railway expects for routing traffic to a custom domain out\n * of its full DNS record list (which also includes e.g. an ACME challenge TXT record).\n */\nfunction extractCnameTarget(\n\tdnsRecords: DomainDnsRecord[] | undefined,\n): string | undefined {\n\treturn dnsRecords?.find(\n\t\t(record) =>\n\t\t\trecord.recordType === \"DNS_RECORD_TYPE_CNAME\" &&\n\t\t\trecord.purpose === \"DNS_RECORD_PURPOSE_TRAFFIC_ROUTE\",\n\t)?.requiredValue;\n}\n\nconst DOMAIN_STATUS_FIELDS = `\n status {\n dnsRecords { recordType purpose requiredValue }\n }\n`;\n\nconst SERVICE_DOMAINS_QUERY = `\n query($projectId: String!, $serviceId: String!, $environmentId: String!) {\n domains(\n projectId: $projectId\n serviceId: $serviceId\n environmentId: $environmentId\n ) {\n serviceDomains { id domain }\n customDomains { id domain ${DOMAIN_STATUS_FIELDS} }\n }\n }\n`;\n\nconst SERVICE_DOMAIN_CREATE = `\n mutation($input: ServiceDomainCreateInput!) {\n serviceDomainCreate(input: $input) { id domain }\n }\n`;\n\nconst CUSTOM_DOMAIN_CREATE = `\n mutation($input: CustomDomainCreateInput!) {\n customDomainCreate(input: $input) { id domain ${DOMAIN_STATUS_FIELDS} }\n }\n`;\n\nconst CUSTOM_DOMAIN_DELETE = `\n mutation($id: String!) { customDomainDelete(id: $id) }\n`;\n\nconst SERVICE_DOMAIN_DELETE = `\n mutation($id: String!) { serviceDomainDelete(id: $id) }\n`;\n\n/**\n * Queries all existing domains (service and custom) for a Railway service.\n */\nasync function findExistingDomains(\n\tclient: RailwayClient,\n\tprojectId: string,\n\tserviceId: string,\n\tenvironmentId: string,\n): Promise<{\n\tserviceDomains: Array<{ id: string; domain: string }>;\n\tcustomDomains: CustomDomainEntry[];\n}> {\n\tconst result = await client.query<{\n\t\tdomains: {\n\t\t\tserviceDomains: Array<{ id: string; domain: string }>;\n\t\t\tcustomDomains: CustomDomainEntry[];\n\t\t};\n\t}>(SERVICE_DOMAINS_QUERY, { projectId, serviceId, environmentId });\n\n\treturn result.domains;\n}\n\n/**\n * Dynamic provider implementing CRUD for Railway domains.\n *\n * Uses adopt-or-create: queries existing domains before creating new ones.\n * Uses the FQDN as the Pulumi resource ID.\n *\n * @internal Exported only for unit testing; not part of the public API surface.\n */\nexport class RailwayDomainResourceProvider\n\timplements pulumi.dynamic.ResourceProvider\n{\n\tasync create(\n\t\tinputs: RailwayDomainInputs,\n\t): Promise<pulumi.dynamic.CreateResult> {\n\t\tconst client = new RailwayClient(inputs.token);\n\n\t\tconst existing = await findExistingDomains(\n\t\t\tclient,\n\t\t\tinputs.projectId,\n\t\t\tinputs.serviceId,\n\t\t\tinputs.environmentId,\n\t\t);\n\n\t\tif (inputs.customDomain) {\n\t\t\tconst found = existing.customDomains.find(\n\t\t\t\t(d) => d.domain === inputs.customDomain,\n\t\t\t);\n\n\t\t\tif (found) {\n\t\t\t\tpulumi.log.info(`Adopting existing custom domain \"${found.domain}\"`);\n\n\t\t\t\treturn {\n\t\t\t\t\tid: found.domain,\n\t\t\t\t\touts: {\n\t\t\t\t\t\t...inputs,\n\t\t\t\t\t\tdomainId: found.id,\n\t\t\t\t\t\tfqdn: found.domain,\n\t\t\t\t\t\tcnameTarget: extractCnameTarget(found.status?.dnsRecords),\n\t\t\t\t\t},\n\t\t\t\t};\n\t\t\t}\n\n\t\t\tconst result = await client.query<{\n\t\t\t\tcustomDomainCreate: CustomDomainEntry;\n\t\t\t}>(CUSTOM_DOMAIN_CREATE, {\n\t\t\t\tinput: {\n\t\t\t\t\tprojectId: inputs.projectId,\n\t\t\t\t\tserviceId: inputs.serviceId,\n\t\t\t\t\tenvironmentId: inputs.environmentId,\n\t\t\t\t\tdomain: inputs.customDomain,\n\t\t\t\t},\n\t\t\t});\n\n\t\t\treturn {\n\t\t\t\tid: result.customDomainCreate.domain,\n\t\t\t\touts: {\n\t\t\t\t\t...inputs,\n\t\t\t\t\tdomainId: result.customDomainCreate.id,\n\t\t\t\t\tfqdn: result.customDomainCreate.domain,\n\t\t\t\t\tcnameTarget: extractCnameTarget(\n\t\t\t\t\t\tresult.customDomainCreate.status?.dnsRecords,\n\t\t\t\t\t),\n\t\t\t\t},\n\t\t\t};\n\t\t}\n\n\t\tif (existing.serviceDomains.length > 0) {\n\t\t\tconst found = existing.serviceDomains[0];\n\n\t\t\tpulumi.log.info(`Adopting existing service domain \"${found.domain}\"`);\n\n\t\t\treturn {\n\t\t\t\tid: found.domain,\n\t\t\t\touts: { ...inputs, domainId: found.id, fqdn: found.domain },\n\t\t\t};\n\t\t}\n\n\t\tconst result = await client.query<{\n\t\t\tserviceDomainCreate: { id: string; domain: string };\n\t\t}>(SERVICE_DOMAIN_CREATE, {\n\t\t\tinput: {\n\t\t\t\tserviceId: inputs.serviceId,\n\t\t\t\tenvironmentId: inputs.environmentId,\n\t\t\t},\n\t\t});\n\n\t\treturn {\n\t\t\tid: result.serviceDomainCreate.domain,\n\t\t\touts: {\n\t\t\t\t...inputs,\n\t\t\t\tdomainId: result.serviceDomainCreate.id,\n\t\t\t\tfqdn: result.serviceDomainCreate.domain,\n\t\t\t},\n\t\t};\n\t}\n\n\tasync read(\n\t\t_id: string,\n\t\tprops: RailwayDomainOutputs,\n\t): Promise<pulumi.dynamic.ReadResult> {\n\t\tconst client = new RailwayClient(props.token);\n\n\t\tconst existing = await findExistingDomains(\n\t\t\tclient,\n\t\t\tprops.projectId,\n\t\t\tprops.serviceId,\n\t\t\tprops.environmentId,\n\t\t);\n\n\t\tif (props.customDomain) {\n\t\t\tconst found = existing.customDomains.find(\n\t\t\t\t(d) => d.domain === props.customDomain,\n\t\t\t);\n\n\t\t\tif (!found) {\n\t\t\t\tthrow new Error(\n\t\t\t\t\t`Custom domain \"${props.customDomain}\" not found during refresh`,\n\t\t\t\t);\n\t\t\t}\n\n\t\t\treturn {\n\t\t\t\tid: found.domain,\n\t\t\t\tprops: {\n\t\t\t\t\t...props,\n\t\t\t\t\tdomainId: found.id,\n\t\t\t\t\tfqdn: found.domain,\n\t\t\t\t\tcnameTarget: extractCnameTarget(found.status?.dnsRecords),\n\t\t\t\t},\n\t\t\t};\n\t\t}\n\n\t\tif (existing.serviceDomains.length > 0) {\n\t\t\tconst found = existing.serviceDomains[0];\n\n\t\t\treturn {\n\t\t\t\tid: found.domain,\n\t\t\t\tprops: { ...props, domainId: found.id, fqdn: found.domain },\n\t\t\t};\n\t\t}\n\n\t\tthrow new Error(\"Railway domain not found during refresh\");\n\t}\n\n\tasync delete(_id: string, props: RailwayDomainOutputs): Promise<void> {\n\t\tconst client = new RailwayClient(props.token);\n\n\t\tconst mutation = props.customDomain\n\t\t\t? CUSTOM_DOMAIN_DELETE\n\t\t\t: SERVICE_DOMAIN_DELETE;\n\n\t\ttry {\n\t\t\tawait client.query(mutation, { id: props.domainId });\n\t\t} catch {\n\t\t\tpulumi.log.warn(\n\t\t\t\t\"Failed to delete Railway domain (may already be deleted)\",\n\t\t\t);\n\t\t}\n\t}\n\n\tasync diff(\n\t\t_id: string,\n\t\tolds: RailwayDomainOutputs,\n\t\tnews: RailwayDomainInputs,\n\t): Promise<pulumi.dynamic.DiffResult> {\n\t\tconst replaces: string[] = [];\n\n\t\tif (olds.serviceId !== news.serviceId) {\n\t\t\treplaces.push(\"serviceId\");\n\t\t}\n\n\t\tif (olds.customDomain !== news.customDomain) {\n\t\t\treplaces.push(\"customDomain\");\n\t\t}\n\n\t\tif (olds.environmentId !== news.environmentId) {\n\t\t\treplaces.push(\"environmentId\");\n\t\t}\n\n\t\treturn {\n\t\t\tchanges: replaces.length > 0,\n\t\t\treplaces,\n\t\t\tdeleteBeforeReplace: true,\n\t\t};\n\t}\n}\n\n/** Internal dynamic resource — not part of the public API. */\nclass RailwayDomainResource extends pulumi.dynamic.Resource {\n\tpublic declare readonly fqdn: pulumi.Output<string>;\n\tpublic declare readonly cnameTarget: pulumi.Output<string | undefined>;\n\n\tconstructor(\n\t\tname: string,\n\t\targs: {\n\t\t\ttoken: pulumi.Input<string>;\n\t\t\tprojectId: pulumi.Input<string>;\n\t\t\tserviceId: pulumi.Input<string>;\n\t\t\tenvironmentId: pulumi.Input<string>;\n\t\t\tcustomDomain?: pulumi.Input<string>;\n\t\t},\n\t\topts?: pulumi.CustomResourceOptions,\n\t) {\n\t\tsuper(\n\t\t\tnew RailwayDomainResourceProvider(),\n\t\t\tname,\n\t\t\t{ ...args, domainId: undefined, fqdn: undefined, cnameTarget: undefined },\n\t\t\topts,\n\t\t);\n\t}\n}\n\n/** Options type for RailwayDomain — replaces Pulumi's native `provider` field. */\ntype RailwayDomainOptions = Omit<\n\tpulumi.ComponentResourceOptions,\n\t\"provider\"\n> & {\n\t/** Railway authentication context. */\n\tprovider: RailwayProvider;\n\n\t/** Railway project context. */\n\tproject: RailwayProject;\n\n\t/** Railway environment context. */\n\tenvironment: RailwayEnvironment;\n\n\t/** Railway service context. */\n\tservice: RailwayService;\n};\n\n/** Args for RailwayDomain. */\nexport interface RailwayDomainArgs {\n\t/** Custom domain FQDN. Omit to create an auto-generated Railway service domain. */\n\tcustomDomain?: pulumi.Input<string>;\n}\n\n/**\n * Manages a Railway domain (service or custom) with adopt-or-create semantics.\n *\n * A service can carry more than one custom domain — declare one `RailwayDomain` per\n * domain; each instance adopts, creates, and deletes only its own domain.\n *\n * @example\n * ```typescript\n * const apiDomain = new RailwayDomain(\"api-domain\", { customDomain: \"api.example.com\" }, {\n * provider, project, environment, service,\n * });\n * const wwwDomain = new RailwayDomain(\"www-domain\", { customDomain: \"www.example.com\" }, {\n * provider, project, environment, service,\n * });\n *\n * // Point each domain's DNS CNAME at its own target.\n * const apiCnameTarget = apiDomain.cnameTarget;\n * const wwwCnameTarget = wwwDomain.cnameTarget;\n * ```\n */\nexport class RailwayDomain extends pulumi.ComponentResource {\n\t/** Fully qualified domain name. */\n\tpublic readonly fqdn: pulumi.Output<string>;\n\n\t/**\n\t * The value to CNAME `customDomain` to. Only set for custom domains — `undefined`\n\t * for a service's auto-generated `*.up.railway.app` domain, or if Railway hasn't\n\t * returned a traffic-routing CNAME record for it yet.\n\t */\n\tpublic readonly cnameTarget: pulumi.Output<string | undefined>;\n\n\tconstructor(\n\t\tname: string,\n\t\targs: RailwayDomainArgs,\n\t\topts: RailwayDomainOptions,\n\t) {\n\t\tconst { provider, project, environment, service, ...pulumiOpts } = opts;\n\n\t\tsuper(\"infracraft:railway:Domain\", name, {}, pulumiOpts);\n\n\t\tconst resource = new RailwayDomainResource(\n\t\t\t`${name}-resource`,\n\t\t\t{\n\t\t\t\ttoken: provider.token,\n\t\t\t\tprojectId: project.id,\n\t\t\t\tserviceId: service.id,\n\t\t\t\tenvironmentId: environment.id,\n\t\t\t\tcustomDomain: args.customDomain,\n\t\t\t},\n\t\t\t{ parent: this },\n\t\t);\n\n\t\tthis.fqdn = resource.fqdn;\n\t\tthis.cnameTarget = resource.cnameTarget;\n\n\t\tthis.registerOutputs({ fqdn: this.fqdn, cnameTarget: this.cnameTarget });\n\t}\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"domain.mjs","names":[],"sources":["../../src/railway/domain.ts"],"sourcesContent":["import * as pulumi from \"@pulumi/pulumi\";\nimport { RailwayClient } from \"./client\";\nimport type { RailwayEnvironment } from \"./environment\";\nimport type { RailwayProject } from \"./project\";\nimport type { RailwayProvider } from \"./provider\";\nimport type { RailwayService } from \"./service\";\n\n/** Resolved inputs for the Railway domain dynamic provider. */\ninterface RailwayDomainInputs {\n\t/** Railway API bearer token. */\n\ttoken: string;\n\n\t/** Railway project UUID. */\n\tprojectId: string;\n\n\t/** Railway service UUID to attach the domain to. */\n\tserviceId: string;\n\n\t/** Railway environment UUID (e.g. production). */\n\tenvironmentId: string;\n\n\t/** Custom domain FQDN (e.g. `\"api.example.com\"`). Omit for auto-generated Railway domain. */\n\tcustomDomain?: string;\n}\n\n/** Persisted state for the Railway domain, extending inputs with Railway-assigned identifiers. */\ninterface RailwayDomainOutputs extends RailwayDomainInputs {\n\t/** Railway-assigned domain UUID (used for deletion API calls). */\n\tdomainId: string;\n\n\t/** Fully qualified domain name (e.g. `\"api-production-abc.up.railway.app\"`). */\n\tfqdn: string;\n\n\t/**\n\t * The value to CNAME `customDomain` to. Only present for custom domains — a\n\t * service's auto-generated `*.up.railway.app` domain needs no DNS record of its\n\t * own. `undefined` if Railway hasn't returned a traffic-routing CNAME record yet.\n\t */\n\tcnameTarget?: string;\n\n\t/**\n\t * DNS record name for the ownership-verification TXT record (e.g.\n\t * `\"_railway-verify.staging.api\"`). `undefined` for service domains, and for\n\t * custom domains Railway doesn't require ownership verification for.\n\t */\n\tverificationTxtName?: string;\n\n\t/**\n\t * DNS record value for the ownership-verification TXT record, ready to write\n\t * as-is (the `railway-verify=` prefix is already composed — see\n\t * {@link composeVerificationTxtValue}). `undefined` under the same conditions as\n\t * {@link verificationTxtName}.\n\t */\n\tverificationTxtValue?: string;\n}\n\n/** A single DNS record Railway expects for a custom domain (CNAME target, ACME challenge, ...). */\ninterface DomainDnsRecord {\n\trecordType: string;\n\tpurpose: string;\n\trequiredValue: string;\n}\n\n/** Domain ownership-verification status, distinct from `dnsRecords` (see live API notes below). */\ninterface CustomDomainStatus {\n\tdnsRecords: DomainDnsRecord[];\n\t/**\n\t * DNS host for the ownership-verification TXT record. Verified live against\n\t * Railway's current API: this sits on `CustomDomain.status`, as a sibling of\n\t * `dnsRecords` rather than inside it — the two are populated independently\n\t * (`dnsRecords` can be non-empty with `verificationDnsHost` still null, and\n\t * vice versa), so both must be queried explicitly.\n\t */\n\tverificationDnsHost?: string | null;\n\t/**\n\t * Raw ownership-verification token. Verified live: Railway already returns this\n\t * pre-composed with the `railway-verify=` prefix (not a bare token) — see\n\t * {@link composeVerificationTxtValue}, which stays idempotent in case that ever\n\t * changes.\n\t */\n\tverificationToken?: string | null;\n}\n\n/** Shape returned for a custom domain, including its required DNS records. */\ninterface CustomDomainEntry {\n\tid: string;\n\tdomain: string;\n\tstatus?: CustomDomainStatus;\n}\n\n/**\n * Picks the CNAME record Railway expects for routing traffic to a custom domain out\n * of its full DNS record list (which also includes e.g. an ACME challenge TXT record).\n */\nfunction extractCnameTarget(\n\tdnsRecords: DomainDnsRecord[] | undefined,\n): string | undefined {\n\treturn dnsRecords?.find(\n\t\t(record) =>\n\t\t\trecord.recordType === \"DNS_RECORD_TYPE_CNAME\" &&\n\t\t\trecord.purpose === \"DNS_RECORD_PURPOSE_TRAFFIC_ROUTE\",\n\t)?.requiredValue;\n}\n\nconst RAILWAY_VERIFY_PREFIX = \"railway-verify=\";\n\n/**\n * Composes the ownership-verification TXT record value from Railway's token.\n * Idempotent: Railway currently returns the token already prefixed, but this\n * guards against a future API response returning the bare token instead.\n */\nfunction composeVerificationTxtValue(token: string): string {\n\treturn token.startsWith(RAILWAY_VERIFY_PREFIX)\n\t\t? token\n\t\t: `${RAILWAY_VERIFY_PREFIX}${token}`;\n}\n\n/**\n * Extracts the ready-to-use ownership-verification TXT record (name + composed\n * value) from a custom domain's status, or `undefined` if Railway hasn't assigned\n * one (service domains, or custom domains needing no verification).\n */\nfunction extractVerificationTxt(\n\tstatus: CustomDomainStatus | undefined,\n): { name: string; value: string } | undefined {\n\tif (!status?.verificationDnsHost || !status.verificationToken) {\n\t\treturn undefined;\n\t}\n\n\treturn {\n\t\tname: status.verificationDnsHost,\n\t\tvalue: composeVerificationTxtValue(status.verificationToken),\n\t};\n}\n\nconst DOMAIN_STATUS_FIELDS = `\n status {\n dnsRecords { recordType purpose requiredValue }\n verificationDnsHost\n verificationToken\n }\n`;\n\nconst SERVICE_DOMAINS_QUERY = `\n query($projectId: String!, $serviceId: String!, $environmentId: String!) {\n domains(\n projectId: $projectId\n serviceId: $serviceId\n environmentId: $environmentId\n ) {\n serviceDomains { id domain }\n customDomains { id domain ${DOMAIN_STATUS_FIELDS} }\n }\n }\n`;\n\nconst SERVICE_DOMAIN_CREATE = `\n mutation($input: ServiceDomainCreateInput!) {\n serviceDomainCreate(input: $input) { id domain }\n }\n`;\n\nconst CUSTOM_DOMAIN_CREATE = `\n mutation($input: CustomDomainCreateInput!) {\n customDomainCreate(input: $input) { id domain ${DOMAIN_STATUS_FIELDS} }\n }\n`;\n\nconst CUSTOM_DOMAIN_DELETE = `\n mutation($id: String!) { customDomainDelete(id: $id) }\n`;\n\nconst SERVICE_DOMAIN_DELETE = `\n mutation($id: String!) { serviceDomainDelete(id: $id) }\n`;\n\n/**\n * Queries all existing domains (service and custom) for a Railway service.\n */\nasync function findExistingDomains(\n\tclient: RailwayClient,\n\tprojectId: string,\n\tserviceId: string,\n\tenvironmentId: string,\n): Promise<{\n\tserviceDomains: Array<{ id: string; domain: string }>;\n\tcustomDomains: CustomDomainEntry[];\n}> {\n\tconst result = await client.query<{\n\t\tdomains: {\n\t\t\tserviceDomains: Array<{ id: string; domain: string }>;\n\t\t\tcustomDomains: CustomDomainEntry[];\n\t\t};\n\t}>(SERVICE_DOMAINS_QUERY, { projectId, serviceId, environmentId });\n\n\treturn result.domains;\n}\n\n/**\n * Dynamic provider implementing CRUD for Railway domains.\n *\n * Uses adopt-or-create: queries existing domains before creating new ones.\n * Uses the FQDN as the Pulumi resource ID.\n *\n * @internal Exported only for unit testing; not part of the public API surface.\n */\nexport class RailwayDomainResourceProvider\n\timplements pulumi.dynamic.ResourceProvider\n{\n\tasync create(\n\t\tinputs: RailwayDomainInputs,\n\t): Promise<pulumi.dynamic.CreateResult> {\n\t\tconst client = new RailwayClient(inputs.token);\n\n\t\tconst existing = await findExistingDomains(\n\t\t\tclient,\n\t\t\tinputs.projectId,\n\t\t\tinputs.serviceId,\n\t\t\tinputs.environmentId,\n\t\t);\n\n\t\tif (inputs.customDomain) {\n\t\t\tconst found = existing.customDomains.find(\n\t\t\t\t(d) => d.domain === inputs.customDomain,\n\t\t\t);\n\n\t\t\tif (found) {\n\t\t\t\tpulumi.log.info(`Adopting existing custom domain \"${found.domain}\"`);\n\n\t\t\t\tconst verificationTxt = extractVerificationTxt(found.status);\n\n\t\t\t\treturn {\n\t\t\t\t\tid: found.domain,\n\t\t\t\t\touts: {\n\t\t\t\t\t\t...inputs,\n\t\t\t\t\t\tdomainId: found.id,\n\t\t\t\t\t\tfqdn: found.domain,\n\t\t\t\t\t\tcnameTarget: extractCnameTarget(found.status?.dnsRecords),\n\t\t\t\t\t\tverificationTxtName: verificationTxt?.name,\n\t\t\t\t\t\tverificationTxtValue: verificationTxt?.value,\n\t\t\t\t\t},\n\t\t\t\t};\n\t\t\t}\n\n\t\t\tconst result = await client.query<{\n\t\t\t\tcustomDomainCreate: CustomDomainEntry;\n\t\t\t}>(CUSTOM_DOMAIN_CREATE, {\n\t\t\t\tinput: {\n\t\t\t\t\tprojectId: inputs.projectId,\n\t\t\t\t\tserviceId: inputs.serviceId,\n\t\t\t\t\tenvironmentId: inputs.environmentId,\n\t\t\t\t\tdomain: inputs.customDomain,\n\t\t\t\t},\n\t\t\t});\n\n\t\t\tconst createdVerificationTxt = extractVerificationTxt(\n\t\t\t\tresult.customDomainCreate.status,\n\t\t\t);\n\n\t\t\treturn {\n\t\t\t\tid: result.customDomainCreate.domain,\n\t\t\t\touts: {\n\t\t\t\t\t...inputs,\n\t\t\t\t\tdomainId: result.customDomainCreate.id,\n\t\t\t\t\tfqdn: result.customDomainCreate.domain,\n\t\t\t\t\tcnameTarget: extractCnameTarget(\n\t\t\t\t\t\tresult.customDomainCreate.status?.dnsRecords,\n\t\t\t\t\t),\n\t\t\t\t\tverificationTxtName: createdVerificationTxt?.name,\n\t\t\t\t\tverificationTxtValue: createdVerificationTxt?.value,\n\t\t\t\t},\n\t\t\t};\n\t\t}\n\n\t\tif (existing.serviceDomains.length > 0) {\n\t\t\tconst found = existing.serviceDomains[0];\n\n\t\t\tpulumi.log.info(`Adopting existing service domain \"${found.domain}\"`);\n\n\t\t\treturn {\n\t\t\t\tid: found.domain,\n\t\t\t\touts: { ...inputs, domainId: found.id, fqdn: found.domain },\n\t\t\t};\n\t\t}\n\n\t\tconst result = await client.query<{\n\t\t\tserviceDomainCreate: { id: string; domain: string };\n\t\t}>(SERVICE_DOMAIN_CREATE, {\n\t\t\tinput: {\n\t\t\t\tserviceId: inputs.serviceId,\n\t\t\t\tenvironmentId: inputs.environmentId,\n\t\t\t},\n\t\t});\n\n\t\treturn {\n\t\t\tid: result.serviceDomainCreate.domain,\n\t\t\touts: {\n\t\t\t\t...inputs,\n\t\t\t\tdomainId: result.serviceDomainCreate.id,\n\t\t\t\tfqdn: result.serviceDomainCreate.domain,\n\t\t\t},\n\t\t};\n\t}\n\n\tasync read(\n\t\t_id: string,\n\t\tprops: RailwayDomainOutputs,\n\t): Promise<pulumi.dynamic.ReadResult> {\n\t\tconst client = new RailwayClient(props.token);\n\n\t\tconst existing = await findExistingDomains(\n\t\t\tclient,\n\t\t\tprops.projectId,\n\t\t\tprops.serviceId,\n\t\t\tprops.environmentId,\n\t\t);\n\n\t\tif (props.customDomain) {\n\t\t\tconst found = existing.customDomains.find(\n\t\t\t\t(d) => d.domain === props.customDomain,\n\t\t\t);\n\n\t\t\tif (!found) {\n\t\t\t\tthrow new Error(\n\t\t\t\t\t`Custom domain \"${props.customDomain}\" not found during refresh`,\n\t\t\t\t);\n\t\t\t}\n\n\t\t\tconst refreshedVerificationTxt = extractVerificationTxt(found.status);\n\n\t\t\treturn {\n\t\t\t\tid: found.domain,\n\t\t\t\tprops: {\n\t\t\t\t\t...props,\n\t\t\t\t\tdomainId: found.id,\n\t\t\t\t\tfqdn: found.domain,\n\t\t\t\t\tcnameTarget: extractCnameTarget(found.status?.dnsRecords),\n\t\t\t\t\tverificationTxtName: refreshedVerificationTxt?.name,\n\t\t\t\t\tverificationTxtValue: refreshedVerificationTxt?.value,\n\t\t\t\t},\n\t\t\t};\n\t\t}\n\n\t\tif (existing.serviceDomains.length > 0) {\n\t\t\tconst found = existing.serviceDomains[0];\n\n\t\t\treturn {\n\t\t\t\tid: found.domain,\n\t\t\t\tprops: { ...props, domainId: found.id, fqdn: found.domain },\n\t\t\t};\n\t\t}\n\n\t\tthrow new Error(\"Railway domain not found during refresh\");\n\t}\n\n\tasync delete(_id: string, props: RailwayDomainOutputs): Promise<void> {\n\t\tconst client = new RailwayClient(props.token);\n\n\t\tconst mutation = props.customDomain\n\t\t\t? CUSTOM_DOMAIN_DELETE\n\t\t\t: SERVICE_DOMAIN_DELETE;\n\n\t\ttry {\n\t\t\tawait client.query(mutation, { id: props.domainId });\n\t\t} catch {\n\t\t\tpulumi.log.warn(\n\t\t\t\t\"Failed to delete Railway domain (may already be deleted)\",\n\t\t\t);\n\t\t}\n\t}\n\n\tasync diff(\n\t\t_id: string,\n\t\tolds: RailwayDomainOutputs,\n\t\tnews: RailwayDomainInputs,\n\t): Promise<pulumi.dynamic.DiffResult> {\n\t\tconst replaces: string[] = [];\n\n\t\tif (olds.serviceId !== news.serviceId) {\n\t\t\treplaces.push(\"serviceId\");\n\t\t}\n\n\t\tif (olds.customDomain !== news.customDomain) {\n\t\t\treplaces.push(\"customDomain\");\n\t\t}\n\n\t\tif (olds.environmentId !== news.environmentId) {\n\t\t\treplaces.push(\"environmentId\");\n\t\t}\n\n\t\treturn {\n\t\t\tchanges: replaces.length > 0,\n\t\t\treplaces,\n\t\t\tdeleteBeforeReplace: true,\n\t\t};\n\t}\n}\n\n/** Internal dynamic resource — not part of the public API. */\nclass RailwayDomainResource extends pulumi.dynamic.Resource {\n\tpublic declare readonly fqdn: pulumi.Output<string>;\n\tpublic declare readonly cnameTarget: pulumi.Output<string | undefined>;\n\tpublic declare readonly verificationTxtName: pulumi.Output<\n\t\tstring | undefined\n\t>;\n\tpublic declare readonly verificationTxtValue: pulumi.Output<\n\t\tstring | undefined\n\t>;\n\n\tconstructor(\n\t\tname: string,\n\t\targs: {\n\t\t\ttoken: pulumi.Input<string>;\n\t\t\tprojectId: pulumi.Input<string>;\n\t\t\tserviceId: pulumi.Input<string>;\n\t\t\tenvironmentId: pulumi.Input<string>;\n\t\t\tcustomDomain?: pulumi.Input<string>;\n\t\t},\n\t\topts?: pulumi.CustomResourceOptions,\n\t) {\n\t\tsuper(\n\t\t\tnew RailwayDomainResourceProvider(),\n\t\t\tname,\n\t\t\t{\n\t\t\t\t...args,\n\t\t\t\tdomainId: undefined,\n\t\t\t\tfqdn: undefined,\n\t\t\t\tcnameTarget: undefined,\n\t\t\t\tverificationTxtName: undefined,\n\t\t\t\tverificationTxtValue: undefined,\n\t\t\t},\n\t\t\topts,\n\t\t);\n\t}\n}\n\n/** Options type for RailwayDomain — replaces Pulumi's native `provider` field. */\ntype RailwayDomainOptions = Omit<\n\tpulumi.ComponentResourceOptions,\n\t\"provider\"\n> & {\n\t/** Railway authentication context. */\n\tprovider: RailwayProvider;\n\n\t/** Railway project context. */\n\tproject: RailwayProject;\n\n\t/** Railway environment context. */\n\tenvironment: RailwayEnvironment;\n\n\t/** Railway service context. */\n\tservice: RailwayService;\n};\n\n/** Args for RailwayDomain. */\nexport interface RailwayDomainArgs {\n\t/** Custom domain FQDN. Omit to create an auto-generated Railway service domain. */\n\tcustomDomain?: pulumi.Input<string>;\n}\n\n/**\n * Manages a Railway domain (service or custom) with adopt-or-create semantics.\n *\n * A service can carry more than one custom domain — declare one `RailwayDomain` per\n * domain; each instance adopts, creates, and deletes only its own domain.\n *\n * @example\n * ```typescript\n * const apiDomain = new RailwayDomain(\"api-domain\", { customDomain: \"api.example.com\" }, {\n * provider, project, environment, service,\n * });\n * const wwwDomain = new RailwayDomain(\"www-domain\", { customDomain: \"www.example.com\" }, {\n * provider, project, environment, service,\n * });\n *\n * // Point each domain's DNS CNAME at its own target.\n * const apiCnameTarget = apiDomain.cnameTarget;\n * const wwwCnameTarget = wwwDomain.cnameTarget;\n * ```\n */\nexport class RailwayDomain extends pulumi.ComponentResource {\n\t/** Fully qualified domain name. */\n\tpublic readonly fqdn: pulumi.Output<string>;\n\n\t/**\n\t * The value to CNAME `customDomain` to. Only set for custom domains — `undefined`\n\t * for a service's auto-generated `*.up.railway.app` domain, or if Railway hasn't\n\t * returned a traffic-routing CNAME record for it yet.\n\t */\n\tpublic readonly cnameTarget: pulumi.Output<string | undefined>;\n\n\t/**\n\t * DNS record name for the ownership-verification TXT record. `undefined` for\n\t * service domains, and for custom domains Railway doesn't require verification\n\t * for — write this record (alongside `cnameTarget`'s CNAME) to flip Railway's\n\t * domain status to verified and let its TLS certificate issue.\n\t */\n\tpublic readonly verificationTxtName: pulumi.Output<string | undefined>;\n\n\t/** DNS record value for the ownership-verification TXT record, ready to write as-is. */\n\tpublic readonly verificationTxtValue: pulumi.Output<string | undefined>;\n\n\tconstructor(\n\t\tname: string,\n\t\targs: RailwayDomainArgs,\n\t\topts: RailwayDomainOptions,\n\t) {\n\t\tconst { provider, project, environment, service, ...pulumiOpts } = opts;\n\n\t\tsuper(\"infracraft:railway:Domain\", name, {}, pulumiOpts);\n\n\t\tconst resource = new RailwayDomainResource(\n\t\t\t`${name}-resource`,\n\t\t\t{\n\t\t\t\ttoken: provider.token,\n\t\t\t\tprojectId: project.id,\n\t\t\t\tserviceId: service.id,\n\t\t\t\tenvironmentId: environment.id,\n\t\t\t\tcustomDomain: args.customDomain,\n\t\t\t},\n\t\t\t{ parent: this },\n\t\t);\n\n\t\tthis.fqdn = resource.fqdn;\n\t\tthis.cnameTarget = resource.cnameTarget;\n\t\tthis.verificationTxtName = resource.verificationTxtName;\n\t\tthis.verificationTxtValue = resource.verificationTxtValue;\n\n\t\tthis.registerOutputs({\n\t\t\tfqdn: this.fqdn,\n\t\t\tcnameTarget: this.cnameTarget,\n\t\t\tverificationTxtName: this.verificationTxtName,\n\t\t\tverificationTxtValue: this.verificationTxtValue,\n\t\t});\n\t}\n}\n"],"mappings":";;;;;;;;;AA8FA,SAAS,mBACR,YACqB;CACrB,OAAO,YAAY,MACjB,WACA,OAAO,eAAe,2BACtB,OAAO,YAAY,kCACrB,GAAG;AACJ;AAEA,MAAM,wBAAwB;;;;;;AAO9B,SAAS,4BAA4B,OAAuB;CAC3D,OAAO,MAAM,WAAW,qBAAqB,IAC1C,QACA,GAAG,wBAAwB;AAC/B;;;;;;AAOA,SAAS,uBACR,QAC8C;CAC9C,IAAI,CAAC,QAAQ,uBAAuB,CAAC,OAAO,mBAC3C;CAGD,OAAO;EACN,MAAM,OAAO;EACb,OAAO,4BAA4B,OAAO,iBAAiB;CAC5D;AACD;AAEA,MAAM,uBAAuB;;;;;;;AAQ7B,MAAM,wBAAwB;;;;;;;;kCAQI,qBAAqB;;;;AAKvD,MAAM,wBAAwB;;;;;AAM9B,MAAM,uBAAuB;;oDAEuB,qBAAqB;;;AAIzE,MAAM,uBAAuB;;;AAI7B,MAAM,wBAAwB;;;;;;AAO9B,eAAe,oBACd,QACA,WACA,WACA,eAIE;CAQF,QAAO,MAPc,OAAO,MAKzB,uBAAuB;EAAE;EAAW;EAAW;CAAc,CAAC,GAEnD;AACf;;;;;;;;;AAUA,IAAa,gCAAb,MAEA;CACC,MAAM,OACL,QACuC;EACvC,MAAM,SAAS,IAAI,cAAc,OAAO,KAAK;EAE7C,MAAM,WAAW,MAAM,oBACtB,QACA,OAAO,WACP,OAAO,WACP,OAAO,aACR;EAEA,IAAI,OAAO,cAAc;GACxB,MAAM,QAAQ,SAAS,cAAc,MACnC,MAAM,EAAE,WAAW,OAAO,YAC5B;GAEA,IAAI,OAAO;IACV,OAAO,IAAI,KAAK,oCAAoC,MAAM,OAAO,EAAE;IAEnE,MAAM,kBAAkB,uBAAuB,MAAM,MAAM;IAE3D,OAAO;KACN,IAAI,MAAM;KACV,MAAM;MACL,GAAG;MACH,UAAU,MAAM;MAChB,MAAM,MAAM;MACZ,aAAa,mBAAmB,MAAM,QAAQ,UAAU;MACxD,qBAAqB,iBAAiB;MACtC,sBAAsB,iBAAiB;KACxC;IACD;GACD;GAEA,MAAM,SAAS,MAAM,OAAO,MAEzB,sBAAsB,EACxB,OAAO;IACN,WAAW,OAAO;IAClB,WAAW,OAAO;IAClB,eAAe,OAAO;IACtB,QAAQ,OAAO;GAChB,EACD,CAAC;GAED,MAAM,yBAAyB,uBAC9B,OAAO,mBAAmB,MAC3B;GAEA,OAAO;IACN,IAAI,OAAO,mBAAmB;IAC9B,MAAM;KACL,GAAG;KACH,UAAU,OAAO,mBAAmB;KACpC,MAAM,OAAO,mBAAmB;KAChC,aAAa,mBACZ,OAAO,mBAAmB,QAAQ,UACnC;KACA,qBAAqB,wBAAwB;KAC7C,sBAAsB,wBAAwB;IAC/C;GACD;EACD;EAEA,IAAI,SAAS,eAAe,SAAS,GAAG;GACvC,MAAM,QAAQ,SAAS,eAAe;GAEtC,OAAO,IAAI,KAAK,qCAAqC,MAAM,OAAO,EAAE;GAEpE,OAAO;IACN,IAAI,MAAM;IACV,MAAM;KAAE,GAAG;KAAQ,UAAU,MAAM;KAAI,MAAM,MAAM;IAAO;GAC3D;EACD;EAEA,MAAM,SAAS,MAAM,OAAO,MAEzB,uBAAuB,EACzB,OAAO;GACN,WAAW,OAAO;GAClB,eAAe,OAAO;EACvB,EACD,CAAC;EAED,OAAO;GACN,IAAI,OAAO,oBAAoB;GAC/B,MAAM;IACL,GAAG;IACH,UAAU,OAAO,oBAAoB;IACrC,MAAM,OAAO,oBAAoB;GAClC;EACD;CACD;CAEA,MAAM,KACL,KACA,OACqC;EAGrC,MAAM,WAAW,MAAM,oBACtB,IAHkB,cAAc,MAAM,KAGjC,GACL,MAAM,WACN,MAAM,WACN,MAAM,aACP;EAEA,IAAI,MAAM,cAAc;GACvB,MAAM,QAAQ,SAAS,cAAc,MACnC,MAAM,EAAE,WAAW,MAAM,YAC3B;GAEA,IAAI,CAAC,OACJ,MAAM,IAAI,MACT,kBAAkB,MAAM,aAAa,2BACtC;GAGD,MAAM,2BAA2B,uBAAuB,MAAM,MAAM;GAEpE,OAAO;IACN,IAAI,MAAM;IACV,OAAO;KACN,GAAG;KACH,UAAU,MAAM;KAChB,MAAM,MAAM;KACZ,aAAa,mBAAmB,MAAM,QAAQ,UAAU;KACxD,qBAAqB,0BAA0B;KAC/C,sBAAsB,0BAA0B;IACjD;GACD;EACD;EAEA,IAAI,SAAS,eAAe,SAAS,GAAG;GACvC,MAAM,QAAQ,SAAS,eAAe;GAEtC,OAAO;IACN,IAAI,MAAM;IACV,OAAO;KAAE,GAAG;KAAO,UAAU,MAAM;KAAI,MAAM,MAAM;IAAO;GAC3D;EACD;EAEA,MAAM,IAAI,MAAM,yCAAyC;CAC1D;CAEA,MAAM,OAAO,KAAa,OAA4C;EACrE,MAAM,SAAS,IAAI,cAAc,MAAM,KAAK;EAE5C,MAAM,WAAW,MAAM,eACpB,uBACA;EAEH,IAAI;GACH,MAAM,OAAO,MAAM,UAAU,EAAE,IAAI,MAAM,SAAS,CAAC;EACpD,QAAQ;GACP,OAAO,IAAI,KACV,0DACD;EACD;CACD;CAEA,MAAM,KACL,KACA,MACA,MACqC;EACrC,MAAM,WAAqB,CAAC;EAE5B,IAAI,KAAK,cAAc,KAAK,WAC3B,SAAS,KAAK,WAAW;EAG1B,IAAI,KAAK,iBAAiB,KAAK,cAC9B,SAAS,KAAK,cAAc;EAG7B,IAAI,KAAK,kBAAkB,KAAK,eAC/B,SAAS,KAAK,eAAe;EAG9B,OAAO;GACN,SAAS,SAAS,SAAS;GAC3B;GACA,qBAAqB;EACtB;CACD;AACD;;AAGA,IAAM,wBAAN,cAAoC,OAAO,QAAQ,SAAS;CAU3D,YACC,MACA,MAOA,MACC;EACD,MACC,IAAI,8BAA8B,GAClC,MACA;GACC,GAAG;GACH,UAAU;GACV,MAAM;GACN,aAAa;GACb,qBAAqB;GACrB,sBAAsB;EACvB,GACA,IACD;CACD;AACD;;;;;;;;;;;;;;;;;;;;;AA8CA,IAAa,gBAAb,cAAmC,OAAO,kBAAkB;CAsB3D,YACC,MACA,MACA,MACC;EACD,MAAM,EAAE,UAAU,SAAS,aAAa,SAAS,GAAG,eAAe;EAEnE,MAAM,6BAA6B,MAAM,CAAC,GAAG,UAAU;EAEvD,MAAM,WAAW,IAAI,sBACpB,GAAG,KAAK,YACR;GACC,OAAO,SAAS;GAChB,WAAW,QAAQ;GACnB,WAAW,QAAQ;GACnB,eAAe,YAAY;GAC3B,cAAc,KAAK;EACpB,GACA,EAAE,QAAQ,KAAK,CAChB;EAEA,KAAK,OAAO,SAAS;EACrB,KAAK,cAAc,SAAS;EAC5B,KAAK,sBAAsB,SAAS;EACpC,KAAK,uBAAuB,SAAS;EAErC,KAAK,gBAAgB;GACpB,MAAM,KAAK;GACX,aAAa,KAAK;GAClB,qBAAqB,KAAK;GAC1B,sBAAsB,KAAK;EAC5B,CAAC;CACF;AACD"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@infracraft/pulumi",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.21.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"description": "Native Pulumi providers for Railway, Neon, Vercel, and Fly.io with adopt-or-create semantics and deploy orchestration. No Terraform bridge.",
|