@infracraft/pulumi 1.30.1 → 1.30.3
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/fly/certificate.cjs +1 -1
- package/dist/fly/certificate.mjs +1 -1
- package/dist/fly/secret.cjs +1 -1
- package/dist/fly/secret.mjs +1 -1
- package/dist/fly/volume.cjs +1 -1
- package/dist/fly/volume.mjs +1 -1
- package/dist/neon/branch.cjs +1 -1
- package/dist/neon/branch.mjs +1 -1
- package/dist/neon/client.cjs +34 -8
- package/dist/neon/client.cjs.map +1 -1
- package/dist/neon/client.d.cts.map +1 -1
- package/dist/neon/client.d.mts.map +1 -1
- package/dist/neon/client.mjs +33 -8
- package/dist/neon/client.mjs.map +1 -1
- package/dist/neon/database.cjs +1 -1
- package/dist/neon/database.mjs +1 -1
- package/dist/neon/endpoint.cjs +1 -1
- package/dist/neon/endpoint.mjs +1 -1
- package/dist/neon/project.cjs +1 -1
- package/dist/neon/project.mjs +1 -1
- package/dist/neon/role.cjs +1 -1
- package/dist/neon/role.mjs +1 -1
- package/dist/railway/service.cjs +4 -0
- package/dist/railway/service.cjs.map +1 -1
- package/dist/railway/service.d.cts.map +1 -1
- package/dist/railway/service.d.mts.map +1 -1
- package/dist/railway/service.mjs +4 -0
- package/dist/railway/service.mjs.map +1 -1
- package/dist/vercel/domain.cjs +1 -1
- package/dist/vercel/domain.mjs +1 -1
- package/dist/vercel/project.cjs +1 -1
- package/dist/vercel/project.mjs +1 -1
- package/dist/vercel/variable.cjs +1 -1
- package/dist/vercel/variable.mjs +1 -1
- package/package.json +1 -1
package/dist/fly/certificate.cjs
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
|
|
2
2
|
const require_chunk = require('../chunk-BVYJZCqc.cjs');
|
|
3
|
-
const require_errors_api_not_found_error = require('../errors/api-not-found-error.cjs');
|
|
4
3
|
const require_dynamic_resolve_credential = require('../dynamic/resolve-credential.cjs');
|
|
4
|
+
const require_errors_api_not_found_error = require('../errors/api-not-found-error.cjs');
|
|
5
5
|
const require_fly_client = require('./client.cjs');
|
|
6
6
|
let _pulumi_pulumi = require("@pulumi/pulumi");
|
|
7
7
|
_pulumi_pulumi = require_chunk.__toESM(_pulumi_pulumi, 1);
|
package/dist/fly/certificate.mjs
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { t as __name } from "../chunk-OPjESj5l.mjs";
|
|
2
|
-
import { ApiNotFoundError } from "../errors/api-not-found-error.mjs";
|
|
3
2
|
import { resolveCredential } from "../dynamic/resolve-credential.mjs";
|
|
3
|
+
import { ApiNotFoundError } from "../errors/api-not-found-error.mjs";
|
|
4
4
|
import { FlyClient } from "./client.mjs";
|
|
5
5
|
import * as pulumi from "@pulumi/pulumi";
|
|
6
6
|
|
package/dist/fly/secret.cjs
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
|
|
2
2
|
const require_chunk = require('../chunk-BVYJZCqc.cjs');
|
|
3
|
-
const require_errors_api_not_found_error = require('../errors/api-not-found-error.cjs');
|
|
4
3
|
const require_dynamic_resolve_credential = require('../dynamic/resolve-credential.cjs');
|
|
4
|
+
const require_errors_api_not_found_error = require('../errors/api-not-found-error.cjs');
|
|
5
5
|
const require_fly_client = require('./client.cjs');
|
|
6
6
|
let _pulumi_pulumi = require("@pulumi/pulumi");
|
|
7
7
|
_pulumi_pulumi = require_chunk.__toESM(_pulumi_pulumi, 1);
|
package/dist/fly/secret.mjs
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { t as __name } from "../chunk-OPjESj5l.mjs";
|
|
2
|
-
import { ApiNotFoundError } from "../errors/api-not-found-error.mjs";
|
|
3
2
|
import { resolveCredential } from "../dynamic/resolve-credential.mjs";
|
|
3
|
+
import { ApiNotFoundError } from "../errors/api-not-found-error.mjs";
|
|
4
4
|
import { FlyClient } from "./client.mjs";
|
|
5
5
|
import * as pulumi from "@pulumi/pulumi";
|
|
6
6
|
|
package/dist/fly/volume.cjs
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
|
|
2
2
|
const require_chunk = require('../chunk-BVYJZCqc.cjs');
|
|
3
|
-
const require_errors_api_not_found_error = require('../errors/api-not-found-error.cjs');
|
|
4
3
|
const require_dynamic_resolve_credential = require('../dynamic/resolve-credential.cjs');
|
|
4
|
+
const require_errors_api_not_found_error = require('../errors/api-not-found-error.cjs');
|
|
5
5
|
const require_fly_client = require('./client.cjs');
|
|
6
6
|
let _pulumi_pulumi = require("@pulumi/pulumi");
|
|
7
7
|
_pulumi_pulumi = require_chunk.__toESM(_pulumi_pulumi, 1);
|
package/dist/fly/volume.mjs
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { t as __name } from "../chunk-OPjESj5l.mjs";
|
|
2
|
-
import { ApiNotFoundError } from "../errors/api-not-found-error.mjs";
|
|
3
2
|
import { resolveCredential } from "../dynamic/resolve-credential.mjs";
|
|
3
|
+
import { ApiNotFoundError } from "../errors/api-not-found-error.mjs";
|
|
4
4
|
import { FlyClient } from "./client.mjs";
|
|
5
5
|
import * as pulumi from "@pulumi/pulumi";
|
|
6
6
|
|
package/dist/neon/branch.cjs
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
|
|
2
2
|
const require_chunk = require('../chunk-BVYJZCqc.cjs');
|
|
3
|
-
const require_errors_api_not_found_error = require('../errors/api-not-found-error.cjs');
|
|
4
3
|
const require_dynamic_is_resolved_string = require('../dynamic/is-resolved-string.cjs');
|
|
5
4
|
const require_dynamic_resolve_credential = require('../dynamic/resolve-credential.cjs');
|
|
5
|
+
const require_errors_api_not_found_error = require('../errors/api-not-found-error.cjs');
|
|
6
6
|
const require_neon_client = require('./client.cjs');
|
|
7
7
|
let _pulumi_pulumi = require("@pulumi/pulumi");
|
|
8
8
|
_pulumi_pulumi = require_chunk.__toESM(_pulumi_pulumi, 1);
|
package/dist/neon/branch.mjs
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { t as __name } from "../chunk-OPjESj5l.mjs";
|
|
2
|
-
import { ApiNotFoundError } from "../errors/api-not-found-error.mjs";
|
|
3
2
|
import { isResolvedString } from "../dynamic/is-resolved-string.mjs";
|
|
4
3
|
import { resolveCredential } from "../dynamic/resolve-credential.mjs";
|
|
4
|
+
import { ApiNotFoundError } from "../errors/api-not-found-error.mjs";
|
|
5
5
|
import { NeonClient } from "./client.mjs";
|
|
6
6
|
import * as pulumi from "@pulumi/pulumi";
|
|
7
7
|
|
package/dist/neon/client.cjs
CHANGED
|
@@ -2,10 +2,30 @@ Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
|
|
|
2
2
|
const require_chunk = require('../chunk-BVYJZCqc.cjs');
|
|
3
3
|
const require_errors_api_not_found_error = require('../errors/api-not-found-error.cjs');
|
|
4
4
|
const require_http_resilient_fetch = require('../http/resilient-fetch.cjs');
|
|
5
|
+
let _pulumi_pulumi = require("@pulumi/pulumi");
|
|
6
|
+
_pulumi_pulumi = require_chunk.__toESM(_pulumi_pulumi, 1);
|
|
5
7
|
|
|
6
8
|
//#region src/neon/client.ts
|
|
7
9
|
const NEON_API_URL = "https://console.neon.tech/api/v2";
|
|
8
10
|
/**
|
|
11
|
+
* Neon runs project mutations as ASYNC operations and answers 423 ("project
|
|
12
|
+
* already has running conflicting operations, scheduling of new ones is
|
|
13
|
+
* prohibited") while earlier ones settle — its docs prescribe waiting for
|
|
14
|
+
* completion before the next request. A destroy immediately followed by an
|
|
15
|
+
* `up` on the same project hits this deterministically (live: branch/endpoint
|
|
16
|
+
* deletion still settling 30s later). Waiting until the lock clears is the
|
|
17
|
+
* documented protocol, bounded so a genuinely wedged project still fails.
|
|
18
|
+
*/
|
|
19
|
+
const OPERATION_LOCK_WAIT = {
|
|
20
|
+
/** Delay between lock probes. */
|
|
21
|
+
intervalMs: 5e3,
|
|
22
|
+
/** Upper bound on probes (~2 minutes total). */
|
|
23
|
+
maxAttempts: 24
|
|
24
|
+
};
|
|
25
|
+
function sleep(ms) {
|
|
26
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
9
29
|
* REST client for the Neon API.
|
|
10
30
|
*
|
|
11
31
|
* @example
|
|
@@ -69,14 +89,20 @@ var NeonClient = class {
|
|
|
69
89
|
await this.request("DELETE", path);
|
|
70
90
|
}
|
|
71
91
|
async request(method, path, body) {
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
92
|
+
let response;
|
|
93
|
+
for (let attempt = 1;; attempt++) {
|
|
94
|
+
response = await require_http_resilient_fetch.resilientFetch(`${NEON_API_URL}${path}`, {
|
|
95
|
+
method,
|
|
96
|
+
headers: {
|
|
97
|
+
"Content-Type": "application/json",
|
|
98
|
+
Authorization: `Bearer ${this.apiKey}`
|
|
99
|
+
},
|
|
100
|
+
body: body ? JSON.stringify(body) : void 0
|
|
101
|
+
});
|
|
102
|
+
if (response.status !== 423 || attempt >= OPERATION_LOCK_WAIT.maxAttempts) break;
|
|
103
|
+
_pulumi_pulumi.log.info(`[infracraft] Neon project operations still settling (423) — waiting ${OPERATION_LOCK_WAIT.intervalMs / 1e3}s (attempt ${attempt}/${OPERATION_LOCK_WAIT.maxAttempts})`);
|
|
104
|
+
await sleep(OPERATION_LOCK_WAIT.intervalMs);
|
|
105
|
+
}
|
|
80
106
|
if (response.status === 404) throw new require_errors_api_not_found_error.ApiNotFoundError("neon", path);
|
|
81
107
|
if (!response.ok) {
|
|
82
108
|
const errorText = await response.text();
|
package/dist/neon/client.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"client.cjs","names":["resilientFetch","ApiNotFoundError"],"sources":["../../src/neon/client.ts"],"sourcesContent":["import { ApiNotFoundError } from \"../errors/api-not-found-error\";\nimport { resilientFetch } from \"../http/resilient-fetch\";\n\nconst NEON_API_URL = \"https://console.neon.tech/api/v2\";\n\n/**\n * REST client for the Neon API.\n *\n * @example\n * ```typescript\n * const client = new NeonClient(apiKey);\n * const branch = await client.get<{ branch: { id: string } }>(\n * `/projects/abc/branches/br-xyz`\n * );\n * ```\n */\nexport class NeonClient {\n\t/** Neon API key for authentication. */\n\tprivate readonly apiKey: string;\n\n\t/**\n\t * @param apiKey Neon API key (project-scoped or account-scoped)\n\t */\n\tconstructor(apiKey: string) {\n\t\tthis.apiKey = apiKey;\n\t}\n\n\t/**\n\t * Performs a GET request against the Neon API.\n\t *\n\t * @param path API path (e.g. `/projects/abc/branches`)\n\t * @returns Typed JSON response body\n\t * @throws {ApiNotFoundError} On 404\n\t * @throws {Error} On any other non-2xx HTTP status\n\t */\n\tasync get<T>(path: string): Promise<T> {\n\t\treturn this.request<T>(\"GET\", path);\n\t}\n\n\t/**\n\t * Performs a POST request against the Neon API.\n\t *\n\t * @param path API path\n\t * @param body Request body (will be JSON-serialized)\n\t * @returns Typed JSON response body\n\t * @throws {ApiNotFoundError} On 404\n\t * @throws {Error} On any other non-2xx HTTP status\n\t */\n\tasync post<T>(path: string, body?: unknown): Promise<T> {\n\t\treturn this.request<T>(\"POST\", path, body);\n\t}\n\n\t/**\n\t * Performs a PATCH request against the Neon API.\n\t *\n\t * @param path API path\n\t * @param body Request body (will be JSON-serialized)\n\t * @returns Typed JSON response body\n\t * @throws {ApiNotFoundError} On 404\n\t * @throws {Error} On any other non-2xx HTTP status\n\t */\n\tasync patch<T>(path: string, body?: unknown): Promise<T> {\n\t\treturn this.request<T>(\"PATCH\", path, body);\n\t}\n\n\t/**\n\t * Performs a DELETE request against the Neon API.\n\t *\n\t * @param path API path\n\t * @throws {ApiNotFoundError} On 404\n\t * @throws {Error} On any other non-2xx HTTP status\n\t */\n\tasync delete(path: string): Promise<void> {\n\t\tawait this.request<void>(\"DELETE\", path);\n\t}\n\n\tprivate async request<T>(\n\t\tmethod: string,\n\t\tpath: string,\n\t\tbody?: unknown,\n\t): Promise<T> {\n\t\
|
|
1
|
+
{"version":3,"file":"client.cjs","names":["resilientFetch","ApiNotFoundError"],"sources":["../../src/neon/client.ts"],"sourcesContent":["import * as pulumi from \"@pulumi/pulumi\";\nimport { ApiNotFoundError } from \"../errors/api-not-found-error\";\nimport { resilientFetch } from \"../http/resilient-fetch\";\n\nconst NEON_API_URL = \"https://console.neon.tech/api/v2\";\n\n/**\n * Neon runs project mutations as ASYNC operations and answers 423 (\"project\n * already has running conflicting operations, scheduling of new ones is\n * prohibited\") while earlier ones settle — its docs prescribe waiting for\n * completion before the next request. A destroy immediately followed by an\n * `up` on the same project hits this deterministically (live: branch/endpoint\n * deletion still settling 30s later). Waiting until the lock clears is the\n * documented protocol, bounded so a genuinely wedged project still fails.\n */\nconst OPERATION_LOCK_WAIT = {\n\t/** Delay between lock probes. */\n\tintervalMs: 5_000,\n\t/** Upper bound on probes (~2 minutes total). */\n\tmaxAttempts: 24,\n} as const;\n\nfunction sleep(ms: number): Promise<void> {\n\treturn new Promise((resolve) => setTimeout(resolve, ms));\n}\n\n/**\n * REST client for the Neon API.\n *\n * @example\n * ```typescript\n * const client = new NeonClient(apiKey);\n * const branch = await client.get<{ branch: { id: string } }>(\n * `/projects/abc/branches/br-xyz`\n * );\n * ```\n */\nexport class NeonClient {\n\t/** Neon API key for authentication. */\n\tprivate readonly apiKey: string;\n\n\t/**\n\t * @param apiKey Neon API key (project-scoped or account-scoped)\n\t */\n\tconstructor(apiKey: string) {\n\t\tthis.apiKey = apiKey;\n\t}\n\n\t/**\n\t * Performs a GET request against the Neon API.\n\t *\n\t * @param path API path (e.g. `/projects/abc/branches`)\n\t * @returns Typed JSON response body\n\t * @throws {ApiNotFoundError} On 404\n\t * @throws {Error} On any other non-2xx HTTP status\n\t */\n\tasync get<T>(path: string): Promise<T> {\n\t\treturn this.request<T>(\"GET\", path);\n\t}\n\n\t/**\n\t * Performs a POST request against the Neon API.\n\t *\n\t * @param path API path\n\t * @param body Request body (will be JSON-serialized)\n\t * @returns Typed JSON response body\n\t * @throws {ApiNotFoundError} On 404\n\t * @throws {Error} On any other non-2xx HTTP status\n\t */\n\tasync post<T>(path: string, body?: unknown): Promise<T> {\n\t\treturn this.request<T>(\"POST\", path, body);\n\t}\n\n\t/**\n\t * Performs a PATCH request against the Neon API.\n\t *\n\t * @param path API path\n\t * @param body Request body (will be JSON-serialized)\n\t * @returns Typed JSON response body\n\t * @throws {ApiNotFoundError} On 404\n\t * @throws {Error} On any other non-2xx HTTP status\n\t */\n\tasync patch<T>(path: string, body?: unknown): Promise<T> {\n\t\treturn this.request<T>(\"PATCH\", path, body);\n\t}\n\n\t/**\n\t * Performs a DELETE request against the Neon API.\n\t *\n\t * @param path API path\n\t * @throws {ApiNotFoundError} On 404\n\t * @throws {Error} On any other non-2xx HTTP status\n\t */\n\tasync delete(path: string): Promise<void> {\n\t\tawait this.request<void>(\"DELETE\", path);\n\t}\n\n\tprivate async request<T>(\n\t\tmethod: string,\n\t\tpath: string,\n\t\tbody?: unknown,\n\t): Promise<T> {\n\t\tlet response: Response;\n\n\t\tfor (let attempt = 1; ; attempt++) {\n\t\t\tresponse = await resilientFetch(`${NEON_API_URL}${path}`, {\n\t\t\t\tmethod,\n\t\t\t\theaders: {\n\t\t\t\t\t\"Content-Type\": \"application/json\",\n\t\t\t\t\tAuthorization: `Bearer ${this.apiKey}`,\n\t\t\t\t},\n\t\t\t\tbody: body ? JSON.stringify(body) : undefined,\n\t\t\t});\n\n\t\t\tif (\n\t\t\t\tresponse.status !== 423 ||\n\t\t\t\tattempt >= OPERATION_LOCK_WAIT.maxAttempts\n\t\t\t) {\n\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\tpulumi.log.info(\n\t\t\t\t`[infracraft] Neon project operations still settling (423) — waiting ${OPERATION_LOCK_WAIT.intervalMs / 1000}s (attempt ${attempt}/${OPERATION_LOCK_WAIT.maxAttempts})`,\n\t\t\t);\n\n\t\t\tawait sleep(OPERATION_LOCK_WAIT.intervalMs);\n\t\t}\n\n\t\tif (response.status === 404) {\n\t\t\tthrow new ApiNotFoundError(\"neon\", path);\n\t\t}\n\n\t\tif (!response.ok) {\n\t\t\tconst errorText = await response.text();\n\n\t\t\tthrow new Error(`Neon API error (${response.status}): ${errorText}`);\n\t\t}\n\n\t\tif (method === \"DELETE\") {\n\t\t\treturn undefined as T;\n\t\t}\n\n\t\treturn (await response.json()) as T;\n\t}\n}\n"],"mappings":";;;;;;;;AAIA,MAAM,eAAe;;;;;;;;;;AAWrB,MAAM,sBAAsB;;CAE3B,YAAY;;CAEZ,aAAa;AACd;AAEA,SAAS,MAAM,IAA2B;CACzC,OAAO,IAAI,SAAS,YAAY,WAAW,SAAS,EAAE,CAAC;AACxD;;;;;;;;;;;;AAaA,IAAa,aAAb,MAAwB;;;;CAOvB,YAAY,QAAgB;EAC3B,KAAK,SAAS;CACf;;;;;;;;;CAUA,MAAM,IAAO,MAA0B;EACtC,OAAO,KAAK,QAAW,OAAO,IAAI;CACnC;;;;;;;;;;CAWA,MAAM,KAAQ,MAAc,MAA4B;EACvD,OAAO,KAAK,QAAW,QAAQ,MAAM,IAAI;CAC1C;;;;;;;;;;CAWA,MAAM,MAAS,MAAc,MAA4B;EACxD,OAAO,KAAK,QAAW,SAAS,MAAM,IAAI;CAC3C;;;;;;;;CASA,MAAM,OAAO,MAA6B;EACzC,MAAM,KAAK,QAAc,UAAU,IAAI;CACxC;CAEA,MAAc,QACb,QACA,MACA,MACa;EACb,IAAI;EAEJ,KAAK,IAAI,UAAU,IAAK,WAAW;GAClC,WAAW,MAAMA,4CAAe,GAAG,eAAe,QAAQ;IACzD;IACA,SAAS;KACR,gBAAgB;KAChB,eAAe,UAAU,KAAK;IAC/B;IACA,MAAM,OAAO,KAAK,UAAU,IAAI,IAAI;GACrC,CAAC;GAED,IACC,SAAS,WAAW,OACpB,WAAW,oBAAoB,aAE/B;GAGD,eAAO,IAAI,KACV,uEAAuE,oBAAoB,aAAa,IAAK,aAAa,QAAQ,GAAG,oBAAoB,YAAY,EACtK;GAEA,MAAM,MAAM,oBAAoB,UAAU;EAC3C;EAEA,IAAI,SAAS,WAAW,KACvB,MAAM,IAAIC,oDAAiB,QAAQ,IAAI;EAGxC,IAAI,CAAC,SAAS,IAAI;GACjB,MAAM,YAAY,MAAM,SAAS,KAAK;GAEtC,MAAM,IAAI,MAAM,mBAAmB,SAAS,OAAO,KAAK,WAAW;EACpE;EAEA,IAAI,WAAW,UACd;EAGD,OAAQ,MAAM,SAAS,KAAK;CAC7B;AACD"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"client.d.cts","names":[],"sources":["../../src/neon/client.ts"],"mappings":";;;;;;
|
|
1
|
+
{"version":3,"file":"client.d.cts","names":[],"sources":["../../src/neon/client.ts"],"mappings":";;;;;;AAqCA;;;;;;;;cAAa,UAAA;EAwDgB;EAAA,iBAtDX,MAAA;EAsDkB;;;cAjDvB,MAAA;EAYN;;;;;;;;EAAA,GAAA,GAAA,CAAO,IAAA,WAAe,OAAA,CAAQ,CAAA;EAaS;;;;;;;;;EAAvC,IAAA,GAAA,CAAQ,IAAA,UAAc,IAAA,aAAiB,OAAA,CAAQ,CAAA;EAwBzB;;;AAIP;;;;;;EAff,KAAA,GAAA,CAAS,IAAA,UAAc,IAAA,aAAiB,OAAA,CAAQ,CAAA;;;;;;;;EAWhD,MAAA,CAAO,IAAA,WAAe,OAAA;EAAA,QAId,OAAA;AAAA"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"client.d.mts","names":[],"sources":["../../src/neon/client.ts"],"mappings":";;;;;;
|
|
1
|
+
{"version":3,"file":"client.d.mts","names":[],"sources":["../../src/neon/client.ts"],"mappings":";;;;;;AAqCA;;;;;;;;cAAa,UAAA;EAwDgB;EAAA,iBAtDX,MAAA;EAsDkB;;;cAjDvB,MAAA;EAYN;;;;;;;;EAAA,GAAA,GAAA,CAAO,IAAA,WAAe,OAAA,CAAQ,CAAA;EAaS;;;;;;;;;EAAvC,IAAA,GAAA,CAAQ,IAAA,UAAc,IAAA,aAAiB,OAAA,CAAQ,CAAA;EAwBzB;;;AAIP;;;;;;EAff,KAAA,GAAA,CAAS,IAAA,UAAc,IAAA,aAAiB,OAAA,CAAQ,CAAA;;;;;;;;EAWhD,MAAA,CAAO,IAAA,WAAe,OAAA;EAAA,QAId,OAAA;AAAA"}
|
package/dist/neon/client.mjs
CHANGED
|
@@ -1,10 +1,29 @@
|
|
|
1
1
|
import { t as __name } from "../chunk-OPjESj5l.mjs";
|
|
2
2
|
import { ApiNotFoundError } from "../errors/api-not-found-error.mjs";
|
|
3
3
|
import { resilientFetch } from "../http/resilient-fetch.mjs";
|
|
4
|
+
import * as pulumi from "@pulumi/pulumi";
|
|
4
5
|
|
|
5
6
|
//#region src/neon/client.ts
|
|
6
7
|
const NEON_API_URL = "https://console.neon.tech/api/v2";
|
|
7
8
|
/**
|
|
9
|
+
* Neon runs project mutations as ASYNC operations and answers 423 ("project
|
|
10
|
+
* already has running conflicting operations, scheduling of new ones is
|
|
11
|
+
* prohibited") while earlier ones settle — its docs prescribe waiting for
|
|
12
|
+
* completion before the next request. A destroy immediately followed by an
|
|
13
|
+
* `up` on the same project hits this deterministically (live: branch/endpoint
|
|
14
|
+
* deletion still settling 30s later). Waiting until the lock clears is the
|
|
15
|
+
* documented protocol, bounded so a genuinely wedged project still fails.
|
|
16
|
+
*/
|
|
17
|
+
const OPERATION_LOCK_WAIT = {
|
|
18
|
+
/** Delay between lock probes. */
|
|
19
|
+
intervalMs: 5e3,
|
|
20
|
+
/** Upper bound on probes (~2 minutes total). */
|
|
21
|
+
maxAttempts: 24
|
|
22
|
+
};
|
|
23
|
+
function sleep(ms) {
|
|
24
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
8
27
|
* REST client for the Neon API.
|
|
9
28
|
*
|
|
10
29
|
* @example
|
|
@@ -68,14 +87,20 @@ var NeonClient = class {
|
|
|
68
87
|
await this.request("DELETE", path);
|
|
69
88
|
}
|
|
70
89
|
async request(method, path, body) {
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
90
|
+
let response;
|
|
91
|
+
for (let attempt = 1;; attempt++) {
|
|
92
|
+
response = await resilientFetch(`${NEON_API_URL}${path}`, {
|
|
93
|
+
method,
|
|
94
|
+
headers: {
|
|
95
|
+
"Content-Type": "application/json",
|
|
96
|
+
Authorization: `Bearer ${this.apiKey}`
|
|
97
|
+
},
|
|
98
|
+
body: body ? JSON.stringify(body) : void 0
|
|
99
|
+
});
|
|
100
|
+
if (response.status !== 423 || attempt >= OPERATION_LOCK_WAIT.maxAttempts) break;
|
|
101
|
+
pulumi.log.info(`[infracraft] Neon project operations still settling (423) — waiting ${OPERATION_LOCK_WAIT.intervalMs / 1e3}s (attempt ${attempt}/${OPERATION_LOCK_WAIT.maxAttempts})`);
|
|
102
|
+
await sleep(OPERATION_LOCK_WAIT.intervalMs);
|
|
103
|
+
}
|
|
79
104
|
if (response.status === 404) throw new ApiNotFoundError("neon", path);
|
|
80
105
|
if (!response.ok) {
|
|
81
106
|
const errorText = await response.text();
|
package/dist/neon/client.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"client.mjs","names":[],"sources":["../../src/neon/client.ts"],"sourcesContent":["import { ApiNotFoundError } from \"../errors/api-not-found-error\";\nimport { resilientFetch } from \"../http/resilient-fetch\";\n\nconst NEON_API_URL = \"https://console.neon.tech/api/v2\";\n\n/**\n * REST client for the Neon API.\n *\n * @example\n * ```typescript\n * const client = new NeonClient(apiKey);\n * const branch = await client.get<{ branch: { id: string } }>(\n * `/projects/abc/branches/br-xyz`\n * );\n * ```\n */\nexport class NeonClient {\n\t/** Neon API key for authentication. */\n\tprivate readonly apiKey: string;\n\n\t/**\n\t * @param apiKey Neon API key (project-scoped or account-scoped)\n\t */\n\tconstructor(apiKey: string) {\n\t\tthis.apiKey = apiKey;\n\t}\n\n\t/**\n\t * Performs a GET request against the Neon API.\n\t *\n\t * @param path API path (e.g. `/projects/abc/branches`)\n\t * @returns Typed JSON response body\n\t * @throws {ApiNotFoundError} On 404\n\t * @throws {Error} On any other non-2xx HTTP status\n\t */\n\tasync get<T>(path: string): Promise<T> {\n\t\treturn this.request<T>(\"GET\", path);\n\t}\n\n\t/**\n\t * Performs a POST request against the Neon API.\n\t *\n\t * @param path API path\n\t * @param body Request body (will be JSON-serialized)\n\t * @returns Typed JSON response body\n\t * @throws {ApiNotFoundError} On 404\n\t * @throws {Error} On any other non-2xx HTTP status\n\t */\n\tasync post<T>(path: string, body?: unknown): Promise<T> {\n\t\treturn this.request<T>(\"POST\", path, body);\n\t}\n\n\t/**\n\t * Performs a PATCH request against the Neon API.\n\t *\n\t * @param path API path\n\t * @param body Request body (will be JSON-serialized)\n\t * @returns Typed JSON response body\n\t * @throws {ApiNotFoundError} On 404\n\t * @throws {Error} On any other non-2xx HTTP status\n\t */\n\tasync patch<T>(path: string, body?: unknown): Promise<T> {\n\t\treturn this.request<T>(\"PATCH\", path, body);\n\t}\n\n\t/**\n\t * Performs a DELETE request against the Neon API.\n\t *\n\t * @param path API path\n\t * @throws {ApiNotFoundError} On 404\n\t * @throws {Error} On any other non-2xx HTTP status\n\t */\n\tasync delete(path: string): Promise<void> {\n\t\tawait this.request<void>(\"DELETE\", path);\n\t}\n\n\tprivate async request<T>(\n\t\tmethod: string,\n\t\tpath: string,\n\t\tbody?: unknown,\n\t): Promise<T> {\n\t\
|
|
1
|
+
{"version":3,"file":"client.mjs","names":[],"sources":["../../src/neon/client.ts"],"sourcesContent":["import * as pulumi from \"@pulumi/pulumi\";\nimport { ApiNotFoundError } from \"../errors/api-not-found-error\";\nimport { resilientFetch } from \"../http/resilient-fetch\";\n\nconst NEON_API_URL = \"https://console.neon.tech/api/v2\";\n\n/**\n * Neon runs project mutations as ASYNC operations and answers 423 (\"project\n * already has running conflicting operations, scheduling of new ones is\n * prohibited\") while earlier ones settle — its docs prescribe waiting for\n * completion before the next request. A destroy immediately followed by an\n * `up` on the same project hits this deterministically (live: branch/endpoint\n * deletion still settling 30s later). Waiting until the lock clears is the\n * documented protocol, bounded so a genuinely wedged project still fails.\n */\nconst OPERATION_LOCK_WAIT = {\n\t/** Delay between lock probes. */\n\tintervalMs: 5_000,\n\t/** Upper bound on probes (~2 minutes total). */\n\tmaxAttempts: 24,\n} as const;\n\nfunction sleep(ms: number): Promise<void> {\n\treturn new Promise((resolve) => setTimeout(resolve, ms));\n}\n\n/**\n * REST client for the Neon API.\n *\n * @example\n * ```typescript\n * const client = new NeonClient(apiKey);\n * const branch = await client.get<{ branch: { id: string } }>(\n * `/projects/abc/branches/br-xyz`\n * );\n * ```\n */\nexport class NeonClient {\n\t/** Neon API key for authentication. */\n\tprivate readonly apiKey: string;\n\n\t/**\n\t * @param apiKey Neon API key (project-scoped or account-scoped)\n\t */\n\tconstructor(apiKey: string) {\n\t\tthis.apiKey = apiKey;\n\t}\n\n\t/**\n\t * Performs a GET request against the Neon API.\n\t *\n\t * @param path API path (e.g. `/projects/abc/branches`)\n\t * @returns Typed JSON response body\n\t * @throws {ApiNotFoundError} On 404\n\t * @throws {Error} On any other non-2xx HTTP status\n\t */\n\tasync get<T>(path: string): Promise<T> {\n\t\treturn this.request<T>(\"GET\", path);\n\t}\n\n\t/**\n\t * Performs a POST request against the Neon API.\n\t *\n\t * @param path API path\n\t * @param body Request body (will be JSON-serialized)\n\t * @returns Typed JSON response body\n\t * @throws {ApiNotFoundError} On 404\n\t * @throws {Error} On any other non-2xx HTTP status\n\t */\n\tasync post<T>(path: string, body?: unknown): Promise<T> {\n\t\treturn this.request<T>(\"POST\", path, body);\n\t}\n\n\t/**\n\t * Performs a PATCH request against the Neon API.\n\t *\n\t * @param path API path\n\t * @param body Request body (will be JSON-serialized)\n\t * @returns Typed JSON response body\n\t * @throws {ApiNotFoundError} On 404\n\t * @throws {Error} On any other non-2xx HTTP status\n\t */\n\tasync patch<T>(path: string, body?: unknown): Promise<T> {\n\t\treturn this.request<T>(\"PATCH\", path, body);\n\t}\n\n\t/**\n\t * Performs a DELETE request against the Neon API.\n\t *\n\t * @param path API path\n\t * @throws {ApiNotFoundError} On 404\n\t * @throws {Error} On any other non-2xx HTTP status\n\t */\n\tasync delete(path: string): Promise<void> {\n\t\tawait this.request<void>(\"DELETE\", path);\n\t}\n\n\tprivate async request<T>(\n\t\tmethod: string,\n\t\tpath: string,\n\t\tbody?: unknown,\n\t): Promise<T> {\n\t\tlet response: Response;\n\n\t\tfor (let attempt = 1; ; attempt++) {\n\t\t\tresponse = await resilientFetch(`${NEON_API_URL}${path}`, {\n\t\t\t\tmethod,\n\t\t\t\theaders: {\n\t\t\t\t\t\"Content-Type\": \"application/json\",\n\t\t\t\t\tAuthorization: `Bearer ${this.apiKey}`,\n\t\t\t\t},\n\t\t\t\tbody: body ? JSON.stringify(body) : undefined,\n\t\t\t});\n\n\t\t\tif (\n\t\t\t\tresponse.status !== 423 ||\n\t\t\t\tattempt >= OPERATION_LOCK_WAIT.maxAttempts\n\t\t\t) {\n\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\tpulumi.log.info(\n\t\t\t\t`[infracraft] Neon project operations still settling (423) — waiting ${OPERATION_LOCK_WAIT.intervalMs / 1000}s (attempt ${attempt}/${OPERATION_LOCK_WAIT.maxAttempts})`,\n\t\t\t);\n\n\t\t\tawait sleep(OPERATION_LOCK_WAIT.intervalMs);\n\t\t}\n\n\t\tif (response.status === 404) {\n\t\t\tthrow new ApiNotFoundError(\"neon\", path);\n\t\t}\n\n\t\tif (!response.ok) {\n\t\t\tconst errorText = await response.text();\n\n\t\t\tthrow new Error(`Neon API error (${response.status}): ${errorText}`);\n\t\t}\n\n\t\tif (method === \"DELETE\") {\n\t\t\treturn undefined as T;\n\t\t}\n\n\t\treturn (await response.json()) as T;\n\t}\n}\n"],"mappings":";;;;;;AAIA,MAAM,eAAe;;;;;;;;;;AAWrB,MAAM,sBAAsB;;CAE3B,YAAY;;CAEZ,aAAa;AACd;AAEA,SAAS,MAAM,IAA2B;CACzC,OAAO,IAAI,SAAS,YAAY,WAAW,SAAS,EAAE,CAAC;AACxD;;;;;;;;;;;;AAaA,IAAa,aAAb,MAAwB;;;;CAOvB,YAAY,QAAgB;EAC3B,KAAK,SAAS;CACf;;;;;;;;;CAUA,MAAM,IAAO,MAA0B;EACtC,OAAO,KAAK,QAAW,OAAO,IAAI;CACnC;;;;;;;;;;CAWA,MAAM,KAAQ,MAAc,MAA4B;EACvD,OAAO,KAAK,QAAW,QAAQ,MAAM,IAAI;CAC1C;;;;;;;;;;CAWA,MAAM,MAAS,MAAc,MAA4B;EACxD,OAAO,KAAK,QAAW,SAAS,MAAM,IAAI;CAC3C;;;;;;;;CASA,MAAM,OAAO,MAA6B;EACzC,MAAM,KAAK,QAAc,UAAU,IAAI;CACxC;CAEA,MAAc,QACb,QACA,MACA,MACa;EACb,IAAI;EAEJ,KAAK,IAAI,UAAU,IAAK,WAAW;GAClC,WAAW,MAAM,eAAe,GAAG,eAAe,QAAQ;IACzD;IACA,SAAS;KACR,gBAAgB;KAChB,eAAe,UAAU,KAAK;IAC/B;IACA,MAAM,OAAO,KAAK,UAAU,IAAI,IAAI;GACrC,CAAC;GAED,IACC,SAAS,WAAW,OACpB,WAAW,oBAAoB,aAE/B;GAGD,OAAO,IAAI,KACV,uEAAuE,oBAAoB,aAAa,IAAK,aAAa,QAAQ,GAAG,oBAAoB,YAAY,EACtK;GAEA,MAAM,MAAM,oBAAoB,UAAU;EAC3C;EAEA,IAAI,SAAS,WAAW,KACvB,MAAM,IAAI,iBAAiB,QAAQ,IAAI;EAGxC,IAAI,CAAC,SAAS,IAAI;GACjB,MAAM,YAAY,MAAM,SAAS,KAAK;GAEtC,MAAM,IAAI,MAAM,mBAAmB,SAAS,OAAO,KAAK,WAAW;EACpE;EAEA,IAAI,WAAW,UACd;EAGD,OAAQ,MAAM,SAAS,KAAK;CAC7B;AACD"}
|
package/dist/neon/database.cjs
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
|
|
2
2
|
const require_chunk = require('../chunk-BVYJZCqc.cjs');
|
|
3
|
-
const require_errors_api_not_found_error = require('../errors/api-not-found-error.cjs');
|
|
4
3
|
const require_dynamic_resolve_credential = require('../dynamic/resolve-credential.cjs');
|
|
4
|
+
const require_errors_api_not_found_error = require('../errors/api-not-found-error.cjs');
|
|
5
5
|
const require_neon_client = require('./client.cjs');
|
|
6
6
|
let _pulumi_pulumi = require("@pulumi/pulumi");
|
|
7
7
|
_pulumi_pulumi = require_chunk.__toESM(_pulumi_pulumi, 1);
|
package/dist/neon/database.mjs
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { t as __name } from "../chunk-OPjESj5l.mjs";
|
|
2
|
-
import { ApiNotFoundError } from "../errors/api-not-found-error.mjs";
|
|
3
2
|
import { resolveCredential } from "../dynamic/resolve-credential.mjs";
|
|
3
|
+
import { ApiNotFoundError } from "../errors/api-not-found-error.mjs";
|
|
4
4
|
import { NeonClient } from "./client.mjs";
|
|
5
5
|
import * as pulumi from "@pulumi/pulumi";
|
|
6
6
|
|
package/dist/neon/endpoint.cjs
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
|
|
2
2
|
const require_chunk = require('../chunk-BVYJZCqc.cjs');
|
|
3
|
-
const require_errors_api_not_found_error = require('../errors/api-not-found-error.cjs');
|
|
4
3
|
const require_dynamic_resolve_credential = require('../dynamic/resolve-credential.cjs');
|
|
4
|
+
const require_errors_api_not_found_error = require('../errors/api-not-found-error.cjs');
|
|
5
5
|
const require_neon_client = require('./client.cjs');
|
|
6
6
|
let _pulumi_pulumi = require("@pulumi/pulumi");
|
|
7
7
|
_pulumi_pulumi = require_chunk.__toESM(_pulumi_pulumi, 1);
|
package/dist/neon/endpoint.mjs
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { t as __name } from "../chunk-OPjESj5l.mjs";
|
|
2
|
-
import { ApiNotFoundError } from "../errors/api-not-found-error.mjs";
|
|
3
2
|
import { resolveCredential } from "../dynamic/resolve-credential.mjs";
|
|
3
|
+
import { ApiNotFoundError } from "../errors/api-not-found-error.mjs";
|
|
4
4
|
import { NeonClient } from "./client.mjs";
|
|
5
5
|
import * as pulumi from "@pulumi/pulumi";
|
|
6
6
|
|
package/dist/neon/project.cjs
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
|
|
2
2
|
const require_chunk = require('../chunk-BVYJZCqc.cjs');
|
|
3
|
-
const require_errors_api_not_found_error = require('../errors/api-not-found-error.cjs');
|
|
4
3
|
const require_dynamic_resolve_credential = require('../dynamic/resolve-credential.cjs');
|
|
4
|
+
const require_errors_api_not_found_error = require('../errors/api-not-found-error.cjs');
|
|
5
5
|
const require_neon_client = require('./client.cjs');
|
|
6
6
|
let _pulumi_pulumi = require("@pulumi/pulumi");
|
|
7
7
|
_pulumi_pulumi = require_chunk.__toESM(_pulumi_pulumi, 1);
|
package/dist/neon/project.mjs
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { t as __name } from "../chunk-OPjESj5l.mjs";
|
|
2
|
-
import { ApiNotFoundError } from "../errors/api-not-found-error.mjs";
|
|
3
2
|
import { resolveCredential } from "../dynamic/resolve-credential.mjs";
|
|
3
|
+
import { ApiNotFoundError } from "../errors/api-not-found-error.mjs";
|
|
4
4
|
import { NeonClient } from "./client.mjs";
|
|
5
5
|
import * as pulumi from "@pulumi/pulumi";
|
|
6
6
|
|
package/dist/neon/role.cjs
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
|
|
2
2
|
const require_chunk = require('../chunk-BVYJZCqc.cjs');
|
|
3
|
-
const require_errors_api_not_found_error = require('../errors/api-not-found-error.cjs');
|
|
4
3
|
const require_dynamic_is_resolved_string = require('../dynamic/is-resolved-string.cjs');
|
|
5
4
|
const require_dynamic_resolve_credential = require('../dynamic/resolve-credential.cjs');
|
|
5
|
+
const require_errors_api_not_found_error = require('../errors/api-not-found-error.cjs');
|
|
6
6
|
const require_neon_client = require('./client.cjs');
|
|
7
7
|
let _pulumi_pulumi = require("@pulumi/pulumi");
|
|
8
8
|
_pulumi_pulumi = require_chunk.__toESM(_pulumi_pulumi, 1);
|
package/dist/neon/role.mjs
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { t as __name } from "../chunk-OPjESj5l.mjs";
|
|
2
|
-
import { ApiNotFoundError } from "../errors/api-not-found-error.mjs";
|
|
3
2
|
import { isResolvedString } from "../dynamic/is-resolved-string.mjs";
|
|
4
3
|
import { resolveCredential } from "../dynamic/resolve-credential.mjs";
|
|
4
|
+
import { ApiNotFoundError } from "../errors/api-not-found-error.mjs";
|
|
5
5
|
import { NeonClient } from "./client.mjs";
|
|
6
6
|
import * as pulumi from "@pulumi/pulumi";
|
|
7
7
|
|
package/dist/railway/service.cjs
CHANGED
|
@@ -245,6 +245,10 @@ var RailwayServiceResourceProvider = class {
|
|
|
245
245
|
property: "source.image",
|
|
246
246
|
reason: "source.image must be a non-empty Docker image reference (e.g. \"redis:8-alpine\")"
|
|
247
247
|
});
|
|
248
|
+
if (require_dynamic_is_resolved_string.isResolvedString(news.healthcheckPath) && news.healthcheckPath.includes("-")) failures.push({
|
|
249
|
+
property: "healthcheckPath",
|
|
250
|
+
reason: "Railway rejects any healthcheckPath containing a hyphen with \"Invalid input\" (undocumented; proven by live probe matrix 2026-07-06) — use a hyphen-free path like \"/healthcheck\""
|
|
251
|
+
});
|
|
248
252
|
return {
|
|
249
253
|
inputs: news,
|
|
250
254
|
failures
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"service.cjs","names":["isResolvedString","RailwayClient","resolveCredential","pulumi"],"sources":["../../src/railway/service.ts"],"sourcesContent":["import * as pulumi from \"@pulumi/pulumi\";\nimport { isResolvedString } from \"../dynamic/is-resolved-string\";\nimport { resolveCredential } from \"../dynamic/resolve-credential\";\nimport { RailwayClient } from \"./client\";\nimport type { RailwayEnvironment } from \"./environment\";\nimport type { RailwayProject } from \"./project\";\nimport type { RailwayProvider } from \"./provider\";\n\n/**\n * Railway build system. Enum keys are UPPERCASE per convention; values are\n * Railway's required UPPERCASE wire literals.\n * Note: HEROKU and PAKETO were deprecated Feb 21 2025 and auto-migrated to\n * NIXPACKS by Railway, but remain in the schema and are accepted by the API.\n */\nexport enum RailwayBuilder {\n\tRAILPACK = \"RAILPACK\",\n\tNIXPACKS = \"NIXPACKS\",\n\tDOCKERFILE = \"DOCKERFILE\",\n\tHEROKU = \"HEROKU\",\n\tPAKETO = \"PAKETO\",\n}\n\n/**\n * Railway service restart policy. Controls when Railway restarts the service\n * container after it exits. Default is ON_FAILURE.\n */\nexport enum RailwayRestartPolicy {\n\tON_FAILURE = \"ON_FAILURE\",\n\tALWAYS = \"ALWAYS\",\n\tNEVER = \"NEVER\",\n}\n\n/** Docker image source for a Railway service (e.g. `redis:8-alpine`). */\ninterface RailwayServiceSource {\n\t/** Full Docker image reference including tag. */\n\timage: string;\n}\n\n/** Resolved inputs for the Railway service dynamic provider. */\ninterface RailwayServiceInputs {\n\t/** Railway API bearer token. Absent when `tokenEnvVar` is used instead. */\n\ttoken?: string;\n\n\t/** Env var name resolved to the token when `token` is absent (see `RailwayProviderArgs.tokenEnvVar`). */\n\ttokenEnvVar?: string;\n\n\t/** Railway project UUID. */\n\tprojectId: string;\n\n\t/** Railway environment UUID (e.g. production). */\n\tenvironmentId: string;\n\n\t/** Human-readable service name used for adopt-or-create matching. */\n\tname: string;\n\n\t/** SVG icon URL displayed in the Railway dashboard. */\n\ticon?: string;\n\n\t/** Docker image source for image-based services. */\n\tsource?: RailwayServiceSource;\n\n\t/** Build system to use when building the service. */\n\tbuilder?: RailwayBuilder;\n\n\t/** Shell command executed during the build phase. */\n\tbuildCommand?: string;\n\n\t/** Shell command executed to start the service at runtime. */\n\tstartCommand?: string;\n\n\t/** Restart behavior for the service container. */\n\trestartPolicyType?: RailwayRestartPolicy;\n\n\t/** HTTP path polled for health checks (e.g. `\"/health-check\"`). */\n\thealthcheckPath?: string;\n\n\t/** Seconds to wait for a healthy response before marking unhealthy. */\n\thealthcheckTimeout?: number;\n\n\t/** Shell command executed before the main deploy (e.g. migrations). */\n\tpreDeployCommand?: string;\n}\n\n/** Persisted state for the Railway service, extending inputs with the Railway-assigned ID. */\ninterface RailwayServiceOutputs extends RailwayServiceInputs {\n\t/** Railway-assigned service UUID. */\n\tserviceId: string;\n}\n\nconst SERVICES_QUERY = `\n query($projectId: String!) {\n project(id: $projectId) {\n services {\n edges {\n node {\n id\n name\n }\n }\n }\n }\n }\n`;\n\nconst SERVICE_QUERY = `\n query($serviceId: String!) {\n service(id: $serviceId) {\n id\n name\n }\n }\n`;\n\nconst SERVICE_CREATE = `\n mutation($input: ServiceCreateInput!) {\n serviceCreate(input: $input) {\n id\n name\n }\n }\n`;\n\nconst SERVICE_UPDATE = `\n mutation($id: String!, $input: ServiceUpdateInput!) {\n serviceUpdate(id: $id, input: $input) {\n id\n name\n }\n }\n`;\n\nconst SERVICE_INSTANCE_UPDATE = `\n mutation(\n $serviceId: String!\n $environmentId: String!\n $input: ServiceInstanceUpdateInput!\n ) {\n serviceInstanceUpdate(\n serviceId: $serviceId\n environmentId: $environmentId\n input: $input\n )\n }\n`;\n\nconst SERVICE_CONNECT = `\n mutation($id: String!, $input: ServiceConnectInput!) {\n serviceConnect(id: $id, input: $input) {\n id\n }\n }\n`;\n\nconst SERVICE_INSTANCE_DEPLOY = `\n mutation($serviceId: String!, $environmentId: String!) {\n serviceInstanceDeployV2(serviceId: $serviceId, environmentId: $environmentId)\n }\n`;\n\n/**\n * Triggers a deployment of the service instance in the target environment.\n * Image-sourced services never deploy there on their own: `serviceCreate`'s\n * auto-deploy only reaches the project's DEFAULT environment, and\n * `serviceInstanceUpdate` applies config without redeploying — so a service\n * in any other environment stays undeployed forever and its private DNS name\n * never registers. `environmentTriggersDeploy` is no alternative: it returns\n * success without creating anything for a service that has never deployed in\n * that environment (proven live, 2026-07-06).\n */\nasync function deployServiceInstance(\n\tclient: RailwayClient,\n\tserviceId: string,\n\tenvironmentId: string,\n): Promise<void> {\n\tconst result = await client.query<{ serviceInstanceDeployV2: string }>(\n\t\tSERVICE_INSTANCE_DEPLOY,\n\t\t{ serviceId, environmentId },\n\t);\n\n\tpulumi.log.info(\n\t\t`[infracraft] serviceInstanceDeployV2 created deployment ${result.serviceInstanceDeployV2}`,\n\t);\n}\n\nconst SERVICE_INSTANCE_QUERY = `\n query($serviceId: String!, $environmentId: String!) {\n serviceInstance(serviceId: $serviceId, environmentId: $environmentId) {\n id\n }\n }\n`;\n\nconst ENVIRONMENT_PATCH_COMMIT = `\n mutation($environmentId: String!, $patch: EnvironmentConfig!, $message: String!) {\n environmentPatchCommit(environmentId: $environmentId, patch: $patch, commitMessage: $message)\n }\n`;\n\n/**\n * Guarantees the service has an instance in the target environment.\n *\n * `serviceCreate` materializes an instance ONLY in the environment passed at\n * create time; in every other environment the service is \"skipped\" — no\n * instance exists there, `serviceInstanceUpdate` returns true while silently\n * doing nothing, and `railway up` fails with UPLOAD_FAILED 404 (live incident:\n * first-ever mesh deploy to production). Materialization goes through the\n * staged-changes flow — committing a config patch that keys the service in\n * `services` — which is the documented path for NAMED environments;\n * `environmentUnskipService` is rejected there (\"Can only unskip services in\n * PR environments\", proven live). The commit's return is not trusted: the\n * instance is re-queried afterward and a still-missing instance is a loud\n * error rather than another silent no-op.\n */\nasync function ensureServiceInstance(\n\tclient: RailwayClient,\n\tserviceId: string,\n\tenvironmentId: string,\n): Promise<void> {\n\tconst exists = async (): Promise<boolean> => {\n\t\ttry {\n\t\t\tconst result = await client.query<{\n\t\t\t\tserviceInstance: { id: string } | null;\n\t\t\t}>(SERVICE_INSTANCE_QUERY, { serviceId, environmentId });\n\n\t\t\treturn Boolean(result.serviceInstance);\n\t\t} catch (error) {\n\t\t\tif (error instanceof Error && /not found/i.test(error.message)) {\n\t\t\t\treturn false;\n\t\t\t}\n\n\t\t\tthrow error;\n\t\t}\n\t};\n\n\tif (await exists()) {\n\t\treturn;\n\t}\n\n\tpulumi.log.info(\n\t\t`[infracraft] service ${serviceId} has no instance in environment ${environmentId} — committing a config patch to materialize it`,\n\t);\n\n\tawait client.query(ENVIRONMENT_PATCH_COMMIT, {\n\t\tenvironmentId,\n\t\tpatch: { services: { [serviceId]: {} } },\n\t\tmessage: `[infracraft] add service ${serviceId} to environment`,\n\t});\n\n\tif (!(await exists())) {\n\t\tthrow new Error(\n\t\t\t`Railway service ${serviceId} still has no instance in environment ${environmentId} after the config-patch commit — cannot configure or deploy it`,\n\t\t);\n\t}\n}\n\n/** What `applyInstanceConfig` had to leave out to get the update accepted. */\ninterface InstanceConfigResult {\n\t/** True when the healthcheck fields were dropped from a failed first attempt. */\n\tdroppedHealthcheck: boolean;\n}\n\n/**\n * Applies service instance configuration (builder, commands, healthcheck).\n *\n * The healthcheck fields ride on the FIRST attempt — an instance with existing\n * deployments accepts them, so steady-state applies stay one call. A FRESH\n * instance with no deployment rejects `healthcheckPath` with \"Invalid input\"\n * (live drill finding), so on failure the call is retried without the\n * healthcheck fields and the drop is reported to the caller — which MUST\n * re-apply them after the first deployment exists (the provider itself for\n * image services, the RailwayDeploy monitor for code services). Silent loss is\n * not an option.\n */\nasync function applyInstanceConfig(\n\tclient: RailwayClient,\n\tserviceId: string,\n\tenvironmentId: string,\n\tinputs: RailwayServiceInputs,\n): Promise<InstanceConfigResult> {\n\tconst instanceInput: Record<string, unknown> = {};\n\n\t// Source must be applied PER ENVIRONMENT: `ServiceCreateInput.source` only\n\t// configures the instance of the environment passed at create time, and every\n\t// other environment's instance is born with source=null — deploy triggers\n\t// then no-op silently because there is nothing to deploy.\n\tif (inputs.source) {\n\t\tinstanceInput.source = { image: inputs.source.image };\n\t}\n\n\tif (inputs.builder) {\n\t\tinstanceInput.builder = inputs.builder;\n\t}\n\n\tif (inputs.buildCommand) {\n\t\tinstanceInput.buildCommand = inputs.buildCommand;\n\t}\n\n\tif (inputs.startCommand) {\n\t\tinstanceInput.startCommand = inputs.startCommand;\n\t}\n\n\tif (inputs.restartPolicyType) {\n\t\tinstanceInput.restartPolicyType = inputs.restartPolicyType;\n\t}\n\n\tif (inputs.healthcheckPath) {\n\t\tinstanceInput.healthcheckPath = inputs.healthcheckPath;\n\t}\n\n\tif (inputs.healthcheckTimeout) {\n\t\tinstanceInput.healthcheckTimeout = inputs.healthcheckTimeout;\n\t}\n\n\tif (inputs.preDeployCommand) {\n\t\tinstanceInput.preDeployCommand = inputs.preDeployCommand;\n\t}\n\n\tif (Object.keys(instanceInput).length === 0) {\n\t\treturn { droppedHealthcheck: false };\n\t}\n\n\tconst hasHealthcheck =\n\t\tinstanceInput.healthcheckPath !== undefined ||\n\t\tinstanceInput.healthcheckTimeout !== undefined;\n\n\ttry {\n\t\tawait client.query(SERVICE_INSTANCE_UPDATE, {\n\t\t\tserviceId,\n\t\t\tenvironmentId,\n\t\t\tinput: instanceInput,\n\t\t});\n\n\t\treturn { droppedHealthcheck: false };\n\t} catch (error) {\n\t\tif (!hasHealthcheck) {\n\t\t\tthrow error;\n\t\t}\n\n\t\tpulumi.log.warn(\n\t\t\t`serviceInstanceUpdate failed, retrying without healthcheck fields: ${error}`,\n\t\t);\n\n\t\tdelete instanceInput.healthcheckPath;\n\t\tdelete instanceInput.healthcheckTimeout;\n\n\t\tif (Object.keys(instanceInput).length > 0) {\n\t\t\tawait client.query(SERVICE_INSTANCE_UPDATE, {\n\t\t\t\tserviceId,\n\t\t\t\tenvironmentId,\n\t\t\t\tinput: instanceInput,\n\t\t\t});\n\t\t}\n\n\t\treturn { droppedHealthcheck: true };\n\t}\n}\n\n/**\n * Re-applies ONLY the healthcheck fields after the instance has a deployment.\n * Called when `applyInstanceConfig` had to drop them (fresh instance, no\n * deployment); a failure here would silently lose the healthcheck config\n * forever, so it throws loudly instead.\n */\nasync function applyHealthcheckConfig(\n\tclient: RailwayClient,\n\tserviceId: string,\n\tenvironmentId: string,\n\tinputs: RailwayServiceInputs,\n): Promise<void> {\n\tconst healthcheckInput: Record<string, unknown> = {};\n\n\tif (inputs.healthcheckPath) {\n\t\thealthcheckInput.healthcheckPath = inputs.healthcheckPath;\n\t}\n\n\tif (inputs.healthcheckTimeout) {\n\t\thealthcheckInput.healthcheckTimeout = inputs.healthcheckTimeout;\n\t}\n\n\tif (Object.keys(healthcheckInput).length === 0) {\n\t\treturn;\n\t}\n\n\ttry {\n\t\tawait client.query(SERVICE_INSTANCE_UPDATE, {\n\t\t\tserviceId,\n\t\t\tenvironmentId,\n\t\t\tinput: healthcheckInput,\n\t\t});\n\t} catch (error) {\n\t\tthrow new Error(\n\t\t\t`Railway healthcheck config ${JSON.stringify(healthcheckInput)} could not be applied to service ${serviceId} in environment ${environmentId} even after its deployment was created — failing instead of silently dropping it: ${error}`,\n\t\t);\n\t}\n}\n\n/**\n * Dynamic provider implementing CRUD for Railway services.\n *\n * Uses adopt-or-create on `create()`: queries services by project ID and name\n * before creating a new one. Service instance configuration (builder, commands,\n * healthcheck) is applied via `serviceInstanceUpdate` after create or update.\n *\n * @internal Exported only for unit testing; not part of the public API surface.\n */\nexport class RailwayServiceResourceProvider\n\timplements pulumi.dynamic.ResourceProvider\n{\n\t/**\n\t * Validates inputs at plan time. An empty `source.image` would otherwise\n\t * fail deep inside `serviceInstanceUpdate` with an opaque GraphQL error.\n\t */\n\tasync check(\n\t\t_olds: RailwayServiceInputs,\n\t\tnews: RailwayServiceInputs,\n\t): Promise<pulumi.dynamic.CheckResult<RailwayServiceInputs>> {\n\t\tconst failures: pulumi.dynamic.CheckFailure[] = [];\n\n\t\tif (\n\t\t\tnews.source &&\n\t\t\tisResolvedString(news.source.image) &&\n\t\t\tnews.source.image.trim().length === 0\n\t\t) {\n\t\t\tfailures.push({\n\t\t\t\tproperty: \"source.image\",\n\t\t\t\treason:\n\t\t\t\t\t'source.image must be a non-empty Docker image reference (e.g. \"redis:8-alpine\")',\n\t\t\t});\n\t\t}\n\n\t\treturn { inputs: news, failures };\n\t}\n\n\t/**\n\t * Creates or adopts a Railway service by name, then applies instance config.\n\t */\n\tasync create(\n\t\tinputs: RailwayServiceInputs,\n\t): Promise<pulumi.dynamic.CreateResult> {\n\t\tconst client = new RailwayClient(\n\t\t\tresolveCredential(inputs.token, inputs.tokenEnvVar),\n\t\t);\n\n\t\tconst result = await client.query<{\n\t\t\tproject: {\n\t\t\t\tservices: { edges: Array<{ node: { id: string; name: string } }> };\n\t\t\t};\n\t\t}>(SERVICES_QUERY, { projectId: inputs.projectId });\n\n\t\tlet serviceId = result.project.services.edges.find(\n\t\t\t(edge) => edge.node.name === inputs.name,\n\t\t)?.node.id;\n\n\t\tif (serviceId) {\n\t\t\tpulumi.log.info(\n\t\t\t\t`Adopted existing Railway service \"${inputs.name}\" (${serviceId})`,\n\t\t\t);\n\t\t} else {\n\t\t\tconst createInput: Record<string, unknown> = {\n\t\t\t\tprojectId: inputs.projectId,\n\t\t\t\tenvironmentId: inputs.environmentId,\n\t\t\t\tname: inputs.name,\n\t\t\t};\n\n\t\t\tif (inputs.source) {\n\t\t\t\tcreateInput.source = { image: inputs.source.image };\n\t\t\t}\n\n\t\t\tconst created = await client.query<{\n\t\t\t\tserviceCreate: { id: string; name: string };\n\t\t\t}>(SERVICE_CREATE, { input: createInput });\n\n\t\t\tserviceId = created.serviceCreate.id;\n\n\t\t\tpulumi.log.info(\n\t\t\t\t`Created Railway service \"${inputs.name}\" (${serviceId})`,\n\t\t\t);\n\n\t\t\tif (inputs.source) {\n\t\t\t\tawait client.query(SERVICE_CONNECT, {\n\t\t\t\t\tid: serviceId,\n\t\t\t\t\tinput: { image: inputs.source.image },\n\t\t\t\t});\n\t\t\t}\n\n\t\t\tif (inputs.icon) {\n\t\t\t\tawait client.query(SERVICE_UPDATE, {\n\t\t\t\t\tid: serviceId,\n\t\t\t\t\tinput: { icon: inputs.icon },\n\t\t\t\t});\n\t\t\t}\n\t\t}\n\n\t\tawait ensureServiceInstance(client, serviceId, inputs.environmentId);\n\n\t\tconst { droppedHealthcheck } = await applyInstanceConfig(\n\t\t\tclient,\n\t\t\tserviceId,\n\t\t\tinputs.environmentId,\n\t\t\tinputs,\n\t\t);\n\n\t\t// Image services have no `railway up` step (see RailwayDeploy for code\n\t\t// services) — the provider owns their deploy, and with it the post-deploy\n\t\t// re-apply of any dropped healthcheck fields. Code services get theirs\n\t\t// from the RailwayDeploy monitor instead.\n\t\tif (inputs.source) {\n\t\t\tawait deployServiceInstance(client, serviceId, inputs.environmentId);\n\n\t\t\tif (droppedHealthcheck) {\n\t\t\t\tawait applyHealthcheckConfig(\n\t\t\t\t\tclient,\n\t\t\t\t\tserviceId,\n\t\t\t\t\tinputs.environmentId,\n\t\t\t\t\tinputs,\n\t\t\t\t);\n\t\t\t}\n\t\t}\n\n\t\tconst outs: RailwayServiceOutputs = { ...inputs, serviceId };\n\n\t\treturn { id: serviceId, outs };\n\t}\n\n\t/**\n\t * Updates service name/icon and re-applies instance configuration.\n\t */\n\tasync update(\n\t\tid: string,\n\t\tolds: RailwayServiceOutputs,\n\t\tnews: RailwayServiceInputs,\n\t): Promise<pulumi.dynamic.UpdateResult> {\n\t\tconst client = new RailwayClient(\n\t\t\tresolveCredential(news.token, news.tokenEnvVar),\n\t\t);\n\n\t\tconst updateInput: Record<string, unknown> = {};\n\n\t\tif (olds.name !== news.name) {\n\t\t\tupdateInput.name = news.name;\n\t\t}\n\n\t\tif (news.icon && olds.icon !== news.icon) {\n\t\t\tupdateInput.icon = news.icon;\n\t\t}\n\n\t\tif (Object.keys(updateInput).length > 0) {\n\t\t\tawait client.query(SERVICE_UPDATE, { id, input: updateInput });\n\t\t}\n\n\t\tawait ensureServiceInstance(client, id, news.environmentId);\n\n\t\tconst { droppedHealthcheck } = await applyInstanceConfig(\n\t\t\tclient,\n\t\t\tid,\n\t\t\tnews.environmentId,\n\t\t\tnews,\n\t\t);\n\n\t\t// Instance config changes (source, startCommand, …) only take effect on\n\t\t// the next deployment; image services get none unless the provider\n\t\t// triggers it. Dropped healthcheck fields are re-applied once that\n\t\t// deployment exists (see applyHealthcheckConfig).\n\t\tif (news.source) {\n\t\t\tawait deployServiceInstance(client, id, news.environmentId);\n\n\t\t\tif (droppedHealthcheck) {\n\t\t\t\tawait applyHealthcheckConfig(client, id, news.environmentId, news);\n\t\t\t}\n\t\t}\n\n\t\tconst outs: RailwayServiceOutputs = { ...news, serviceId: id };\n\n\t\treturn { outs };\n\t}\n\n\t/**\n\t * Reads current state for `pulumi refresh` by querying the service by ID.\n\t */\n\tasync read(\n\t\tid: string,\n\t\tprops: RailwayServiceOutputs,\n\t): Promise<pulumi.dynamic.ReadResult> {\n\t\tconst client = new RailwayClient(\n\t\t\tresolveCredential(props.token, props.tokenEnvVar),\n\t\t);\n\n\t\ttry {\n\t\t\tawait client.query<{ service: { id: string; name: string } }>(\n\t\t\t\tSERVICE_QUERY,\n\t\t\t\t{ serviceId: id },\n\t\t\t);\n\t\t} catch {\n\t\t\t// Resource gone → blank id lets refresh reconcile the deletion.\n\t\t\treturn {};\n\t\t}\n\n\t\treturn { id, props: { ...props, serviceId: id } };\n\t}\n\n\t/**\n\t * Deletion is a no-op. A Railway service is a project-level resource shared\n\t * across environments (forked environments adopt it by name), so a single\n\t * environment's destroy must never delete it. Deleting the *environment*\n\t * removes that environment's service instances instead.\n\t */\n\tasync delete(): Promise<void> {\n\t\tpulumi.log.warn(\n\t\t\t\"Railway service deletion skipped — services are project-level; delete the environment to remove its instances\",\n\t\t);\n\t}\n\n\t/**\n\t * Compares old and new inputs to determine what changed.\n\t *\n\t * ProjectId and environmentId changes trigger replacement.\n\t */\n\tasync diff(\n\t\t_id: string,\n\t\tolds: RailwayServiceOutputs,\n\t\tnews: RailwayServiceInputs,\n\t): Promise<pulumi.dynamic.DiffResult> {\n\t\tconst replaces: string[] = [];\n\t\tconst changes: string[] = [];\n\n\t\tif (olds.name !== news.name) {\n\t\t\tchanges.push(\"name\");\n\t\t}\n\n\t\tif (olds.projectId !== news.projectId) {\n\t\t\treplaces.push(\"projectId\");\n\t\t}\n\n\t\tif (olds.environmentId !== news.environmentId) {\n\t\t\treplaces.push(\"environmentId\");\n\t\t}\n\n\t\tif (olds.builder !== news.builder) {\n\t\t\tchanges.push(\"builder\");\n\t\t}\n\n\t\tif (olds.buildCommand !== news.buildCommand) {\n\t\t\tchanges.push(\"buildCommand\");\n\t\t}\n\n\t\tif (olds.startCommand !== news.startCommand) {\n\t\t\tchanges.push(\"startCommand\");\n\t\t}\n\n\t\tif (olds.restartPolicyType !== news.restartPolicyType) {\n\t\t\tchanges.push(\"restartPolicyType\");\n\t\t}\n\n\t\tif (olds.healthcheckPath !== news.healthcheckPath) {\n\t\t\tchanges.push(\"healthcheckPath\");\n\t\t}\n\n\t\tif (olds.healthcheckTimeout !== news.healthcheckTimeout) {\n\t\t\tchanges.push(\"healthcheckTimeout\");\n\t\t}\n\n\t\tif (olds.preDeployCommand !== news.preDeployCommand) {\n\t\t\tchanges.push(\"preDeployCommand\");\n\t\t}\n\n\t\tif (olds.icon !== news.icon) {\n\t\t\tchanges.push(\"icon\");\n\t\t}\n\n\t\treturn {\n\t\t\tchanges: replaces.length > 0 || changes.length > 0,\n\t\t\treplaces,\n\t\t\t// serviceId survives every in-place update (only projectId/environmentId\n\t\t\t// changes replace), so declaring it stable keeps it known during preview —\n\t\t\t// dependents (e.g. RailwayVolume) no longer see an unknown serviceId and\n\t\t\t// stop showing phantom replaces.\n\t\t\tstables: replaces.length === 0 ? [\"serviceId\"] : [],\n\t\t\tdeleteBeforeReplace: true,\n\t\t};\n\t}\n}\n\n/** Internal dynamic resource — not part of the public API. */\nclass RailwayServiceResource extends pulumi.dynamic.Resource {\n\tpublic declare readonly serviceId: pulumi.Output<string>;\n\n\tconstructor(\n\t\tname: string,\n\t\targs: {\n\t\t\ttoken?: pulumi.Input<string>;\n\t\t\ttokenEnvVar?: pulumi.Input<string>;\n\t\t\tprojectId: pulumi.Input<string>;\n\t\t\tenvironmentId: pulumi.Input<string>;\n\t\t\tname: pulumi.Input<string>;\n\t\t\ticon?: pulumi.Input<string>;\n\t\t\tsource?: pulumi.Input<{ image: pulumi.Input<string> }>;\n\t\t\tbuilder?: pulumi.Input<RailwayBuilder>;\n\t\t\tbuildCommand?: pulumi.Input<string>;\n\t\t\tstartCommand?: pulumi.Input<string>;\n\t\t\trestartPolicyType?: pulumi.Input<RailwayRestartPolicy>;\n\t\t\thealthcheckPath?: pulumi.Input<string>;\n\t\t\thealthcheckTimeout?: pulumi.Input<number>;\n\t\t\tpreDeployCommand?: pulumi.Input<string>;\n\t\t},\n\t\topts?: pulumi.CustomResourceOptions,\n\t) {\n\t\tsuper(\n\t\t\tnew RailwayServiceResourceProvider(),\n\t\t\tname,\n\t\t\t{ ...args, serviceId: undefined },\n\t\t\t// The API token flows into dynamic-provider state with the outputs — mark it secret there.\n\t\t\t{ ...opts, additionalSecretOutputs: [\"token\"] },\n\t\t);\n\t}\n}\n\n/** Options type for RailwayService — replaces Pulumi's native `provider` field. */\ntype RailwayServiceOptions = 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\n/** Args for RailwayService. */\nexport interface RailwayServiceArgs {\n\t/** Human-readable service name used for adopt-or-create matching. */\n\tname: pulumi.Input<string>;\n\n\t/** SVG icon URL displayed in the Railway dashboard. */\n\ticon?: pulumi.Input<string>;\n\n\t/** Docker image source for image-based services. */\n\tsource?: pulumi.Input<{ image: pulumi.Input<string> }>;\n\n\t/** Build system to use when building the service. */\n\tbuilder?: pulumi.Input<RailwayBuilder>;\n\n\t/** Shell command executed during the build phase. */\n\tbuildCommand?: pulumi.Input<string>;\n\n\t/** Shell command executed to start the service at runtime. */\n\tstartCommand?: pulumi.Input<string>;\n\n\t/** Restart behavior for the service container. */\n\trestartPolicyType?: pulumi.Input<RailwayRestartPolicy>;\n\n\t/** HTTP path polled for health checks (e.g. `\"/health-check\"`). */\n\thealthcheckPath?: pulumi.Input<string>;\n\n\t/** Seconds to wait for a healthy response before marking unhealthy. */\n\thealthcheckTimeout?: pulumi.Input<number>;\n\n\t/** Shell command executed before the main deploy (e.g. migrations). */\n\tpreDeployCommand?: pulumi.Input<string>;\n}\n\n/**\n * Manages a Railway service with adopt-or-create semantics.\n *\n * @example\n * ```typescript\n * const service = new RailwayService(\"api\", {\n * name: \"api\",\n * builder: RailwayBuilder.RAILPACK,\n * startCommand: \"node dist/index.js\",\n * healthcheckPath: \"/health\",\n * }, { provider, project, environment });\n *\n * // Use serviceId downstream\n * new RailwayVariable(\"api-vars\", {\n * variables: { DATABASE_URL: dbUrl },\n * }, { provider, project, environment, service });\n * ```\n */\nexport class RailwayService extends pulumi.ComponentResource {\n\t/** Railway service UUID. */\n\tpublic readonly id: pulumi.Output<string>;\n\n\tconstructor(\n\t\tname: string,\n\t\targs: RailwayServiceArgs,\n\t\topts: RailwayServiceOptions,\n\t) {\n\t\tconst { provider, project, environment, ...pulumiOpts } = opts;\n\n\t\tsuper(\"infracraft:railway:Service\", name, {}, pulumiOpts);\n\n\t\tconst resource = new RailwayServiceResource(\n\t\t\t`${name}-resource`,\n\t\t\t{\n\t\t\t\ttoken: provider.token,\n\t\t\t\ttokenEnvVar: provider.tokenEnvVar,\n\t\t\t\tprojectId: project.id,\n\t\t\t\tenvironmentId: environment.id,\n\t\t\t\t...args,\n\t\t\t},\n\t\t\t{ parent: this },\n\t\t);\n\n\t\tthis.id = resource.serviceId;\n\n\t\tthis.registerOutputs({ id: this.id });\n\t}\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"service.cjs","names":["isResolvedString","RailwayClient","resolveCredential","pulumi"],"sources":["../../src/railway/service.ts"],"sourcesContent":["import * as pulumi from \"@pulumi/pulumi\";\nimport { isResolvedString } from \"../dynamic/is-resolved-string\";\nimport { resolveCredential } from \"../dynamic/resolve-credential\";\nimport { RailwayClient } from \"./client\";\nimport type { RailwayEnvironment } from \"./environment\";\nimport type { RailwayProject } from \"./project\";\nimport type { RailwayProvider } from \"./provider\";\n\n/**\n * Railway build system. Enum keys are UPPERCASE per convention; values are\n * Railway's required UPPERCASE wire literals.\n * Note: HEROKU and PAKETO were deprecated Feb 21 2025 and auto-migrated to\n * NIXPACKS by Railway, but remain in the schema and are accepted by the API.\n */\nexport enum RailwayBuilder {\n\tRAILPACK = \"RAILPACK\",\n\tNIXPACKS = \"NIXPACKS\",\n\tDOCKERFILE = \"DOCKERFILE\",\n\tHEROKU = \"HEROKU\",\n\tPAKETO = \"PAKETO\",\n}\n\n/**\n * Railway service restart policy. Controls when Railway restarts the service\n * container after it exits. Default is ON_FAILURE.\n */\nexport enum RailwayRestartPolicy {\n\tON_FAILURE = \"ON_FAILURE\",\n\tALWAYS = \"ALWAYS\",\n\tNEVER = \"NEVER\",\n}\n\n/** Docker image source for a Railway service (e.g. `redis:8-alpine`). */\ninterface RailwayServiceSource {\n\t/** Full Docker image reference including tag. */\n\timage: string;\n}\n\n/** Resolved inputs for the Railway service dynamic provider. */\ninterface RailwayServiceInputs {\n\t/** Railway API bearer token. Absent when `tokenEnvVar` is used instead. */\n\ttoken?: string;\n\n\t/** Env var name resolved to the token when `token` is absent (see `RailwayProviderArgs.tokenEnvVar`). */\n\ttokenEnvVar?: string;\n\n\t/** Railway project UUID. */\n\tprojectId: string;\n\n\t/** Railway environment UUID (e.g. production). */\n\tenvironmentId: string;\n\n\t/** Human-readable service name used for adopt-or-create matching. */\n\tname: string;\n\n\t/** SVG icon URL displayed in the Railway dashboard. */\n\ticon?: string;\n\n\t/** Docker image source for image-based services. */\n\tsource?: RailwayServiceSource;\n\n\t/** Build system to use when building the service. */\n\tbuilder?: RailwayBuilder;\n\n\t/** Shell command executed during the build phase. */\n\tbuildCommand?: string;\n\n\t/** Shell command executed to start the service at runtime. */\n\tstartCommand?: string;\n\n\t/** Restart behavior for the service container. */\n\trestartPolicyType?: RailwayRestartPolicy;\n\n\t/** HTTP path polled for health checks (e.g. `\"/health-check\"`). */\n\thealthcheckPath?: string;\n\n\t/** Seconds to wait for a healthy response before marking unhealthy. */\n\thealthcheckTimeout?: number;\n\n\t/** Shell command executed before the main deploy (e.g. migrations). */\n\tpreDeployCommand?: string;\n}\n\n/** Persisted state for the Railway service, extending inputs with the Railway-assigned ID. */\ninterface RailwayServiceOutputs extends RailwayServiceInputs {\n\t/** Railway-assigned service UUID. */\n\tserviceId: string;\n}\n\nconst SERVICES_QUERY = `\n query($projectId: String!) {\n project(id: $projectId) {\n services {\n edges {\n node {\n id\n name\n }\n }\n }\n }\n }\n`;\n\nconst SERVICE_QUERY = `\n query($serviceId: String!) {\n service(id: $serviceId) {\n id\n name\n }\n }\n`;\n\nconst SERVICE_CREATE = `\n mutation($input: ServiceCreateInput!) {\n serviceCreate(input: $input) {\n id\n name\n }\n }\n`;\n\nconst SERVICE_UPDATE = `\n mutation($id: String!, $input: ServiceUpdateInput!) {\n serviceUpdate(id: $id, input: $input) {\n id\n name\n }\n }\n`;\n\nconst SERVICE_INSTANCE_UPDATE = `\n mutation(\n $serviceId: String!\n $environmentId: String!\n $input: ServiceInstanceUpdateInput!\n ) {\n serviceInstanceUpdate(\n serviceId: $serviceId\n environmentId: $environmentId\n input: $input\n )\n }\n`;\n\nconst SERVICE_CONNECT = `\n mutation($id: String!, $input: ServiceConnectInput!) {\n serviceConnect(id: $id, input: $input) {\n id\n }\n }\n`;\n\nconst SERVICE_INSTANCE_DEPLOY = `\n mutation($serviceId: String!, $environmentId: String!) {\n serviceInstanceDeployV2(serviceId: $serviceId, environmentId: $environmentId)\n }\n`;\n\n/**\n * Triggers a deployment of the service instance in the target environment.\n * Image-sourced services never deploy there on their own: `serviceCreate`'s\n * auto-deploy only reaches the project's DEFAULT environment, and\n * `serviceInstanceUpdate` applies config without redeploying — so a service\n * in any other environment stays undeployed forever and its private DNS name\n * never registers. `environmentTriggersDeploy` is no alternative: it returns\n * success without creating anything for a service that has never deployed in\n * that environment (proven live, 2026-07-06).\n */\nasync function deployServiceInstance(\n\tclient: RailwayClient,\n\tserviceId: string,\n\tenvironmentId: string,\n): Promise<void> {\n\tconst result = await client.query<{ serviceInstanceDeployV2: string }>(\n\t\tSERVICE_INSTANCE_DEPLOY,\n\t\t{ serviceId, environmentId },\n\t);\n\n\tpulumi.log.info(\n\t\t`[infracraft] serviceInstanceDeployV2 created deployment ${result.serviceInstanceDeployV2}`,\n\t);\n}\n\nconst SERVICE_INSTANCE_QUERY = `\n query($serviceId: String!, $environmentId: String!) {\n serviceInstance(serviceId: $serviceId, environmentId: $environmentId) {\n id\n }\n }\n`;\n\nconst ENVIRONMENT_PATCH_COMMIT = `\n mutation($environmentId: String!, $patch: EnvironmentConfig!, $message: String!) {\n environmentPatchCommit(environmentId: $environmentId, patch: $patch, commitMessage: $message)\n }\n`;\n\n/**\n * Guarantees the service has an instance in the target environment.\n *\n * `serviceCreate` materializes an instance ONLY in the environment passed at\n * create time; in every other environment the service is \"skipped\" — no\n * instance exists there, `serviceInstanceUpdate` returns true while silently\n * doing nothing, and `railway up` fails with UPLOAD_FAILED 404 (live incident:\n * first-ever mesh deploy to production). Materialization goes through the\n * staged-changes flow — committing a config patch that keys the service in\n * `services` — which is the documented path for NAMED environments;\n * `environmentUnskipService` is rejected there (\"Can only unskip services in\n * PR environments\", proven live). The commit's return is not trusted: the\n * instance is re-queried afterward and a still-missing instance is a loud\n * error rather than another silent no-op.\n */\nasync function ensureServiceInstance(\n\tclient: RailwayClient,\n\tserviceId: string,\n\tenvironmentId: string,\n): Promise<void> {\n\tconst exists = async (): Promise<boolean> => {\n\t\ttry {\n\t\t\tconst result = await client.query<{\n\t\t\t\tserviceInstance: { id: string } | null;\n\t\t\t}>(SERVICE_INSTANCE_QUERY, { serviceId, environmentId });\n\n\t\t\treturn Boolean(result.serviceInstance);\n\t\t} catch (error) {\n\t\t\tif (error instanceof Error && /not found/i.test(error.message)) {\n\t\t\t\treturn false;\n\t\t\t}\n\n\t\t\tthrow error;\n\t\t}\n\t};\n\n\tif (await exists()) {\n\t\treturn;\n\t}\n\n\tpulumi.log.info(\n\t\t`[infracraft] service ${serviceId} has no instance in environment ${environmentId} — committing a config patch to materialize it`,\n\t);\n\n\tawait client.query(ENVIRONMENT_PATCH_COMMIT, {\n\t\tenvironmentId,\n\t\tpatch: { services: { [serviceId]: {} } },\n\t\tmessage: `[infracraft] add service ${serviceId} to environment`,\n\t});\n\n\tif (!(await exists())) {\n\t\tthrow new Error(\n\t\t\t`Railway service ${serviceId} still has no instance in environment ${environmentId} after the config-patch commit — cannot configure or deploy it`,\n\t\t);\n\t}\n}\n\n/** What `applyInstanceConfig` had to leave out to get the update accepted. */\ninterface InstanceConfigResult {\n\t/** True when the healthcheck fields were dropped from a failed first attempt. */\n\tdroppedHealthcheck: boolean;\n}\n\n/**\n * Applies service instance configuration (builder, commands, healthcheck).\n *\n * The healthcheck fields ride on the FIRST attempt — an instance with existing\n * deployments accepts them, so steady-state applies stay one call. A FRESH\n * instance with no deployment rejects `healthcheckPath` with \"Invalid input\"\n * (live drill finding), so on failure the call is retried without the\n * healthcheck fields and the drop is reported to the caller — which MUST\n * re-apply them after the first deployment exists (the provider itself for\n * image services, the RailwayDeploy monitor for code services). Silent loss is\n * not an option.\n */\nasync function applyInstanceConfig(\n\tclient: RailwayClient,\n\tserviceId: string,\n\tenvironmentId: string,\n\tinputs: RailwayServiceInputs,\n): Promise<InstanceConfigResult> {\n\tconst instanceInput: Record<string, unknown> = {};\n\n\t// Source must be applied PER ENVIRONMENT: `ServiceCreateInput.source` only\n\t// configures the instance of the environment passed at create time, and every\n\t// other environment's instance is born with source=null — deploy triggers\n\t// then no-op silently because there is nothing to deploy.\n\tif (inputs.source) {\n\t\tinstanceInput.source = { image: inputs.source.image };\n\t}\n\n\tif (inputs.builder) {\n\t\tinstanceInput.builder = inputs.builder;\n\t}\n\n\tif (inputs.buildCommand) {\n\t\tinstanceInput.buildCommand = inputs.buildCommand;\n\t}\n\n\tif (inputs.startCommand) {\n\t\tinstanceInput.startCommand = inputs.startCommand;\n\t}\n\n\tif (inputs.restartPolicyType) {\n\t\tinstanceInput.restartPolicyType = inputs.restartPolicyType;\n\t}\n\n\tif (inputs.healthcheckPath) {\n\t\tinstanceInput.healthcheckPath = inputs.healthcheckPath;\n\t}\n\n\tif (inputs.healthcheckTimeout) {\n\t\tinstanceInput.healthcheckTimeout = inputs.healthcheckTimeout;\n\t}\n\n\tif (inputs.preDeployCommand) {\n\t\tinstanceInput.preDeployCommand = inputs.preDeployCommand;\n\t}\n\n\tif (Object.keys(instanceInput).length === 0) {\n\t\treturn { droppedHealthcheck: false };\n\t}\n\n\tconst hasHealthcheck =\n\t\tinstanceInput.healthcheckPath !== undefined ||\n\t\tinstanceInput.healthcheckTimeout !== undefined;\n\n\ttry {\n\t\tawait client.query(SERVICE_INSTANCE_UPDATE, {\n\t\t\tserviceId,\n\t\t\tenvironmentId,\n\t\t\tinput: instanceInput,\n\t\t});\n\n\t\treturn { droppedHealthcheck: false };\n\t} catch (error) {\n\t\tif (!hasHealthcheck) {\n\t\t\tthrow error;\n\t\t}\n\n\t\tpulumi.log.warn(\n\t\t\t`serviceInstanceUpdate failed, retrying without healthcheck fields: ${error}`,\n\t\t);\n\n\t\tdelete instanceInput.healthcheckPath;\n\t\tdelete instanceInput.healthcheckTimeout;\n\n\t\tif (Object.keys(instanceInput).length > 0) {\n\t\t\tawait client.query(SERVICE_INSTANCE_UPDATE, {\n\t\t\t\tserviceId,\n\t\t\t\tenvironmentId,\n\t\t\t\tinput: instanceInput,\n\t\t\t});\n\t\t}\n\n\t\treturn { droppedHealthcheck: true };\n\t}\n}\n\n/**\n * Re-applies ONLY the healthcheck fields after the instance has a deployment.\n * Called when `applyInstanceConfig` had to drop them (fresh instance, no\n * deployment); a failure here would silently lose the healthcheck config\n * forever, so it throws loudly instead.\n */\nasync function applyHealthcheckConfig(\n\tclient: RailwayClient,\n\tserviceId: string,\n\tenvironmentId: string,\n\tinputs: RailwayServiceInputs,\n): Promise<void> {\n\tconst healthcheckInput: Record<string, unknown> = {};\n\n\tif (inputs.healthcheckPath) {\n\t\thealthcheckInput.healthcheckPath = inputs.healthcheckPath;\n\t}\n\n\tif (inputs.healthcheckTimeout) {\n\t\thealthcheckInput.healthcheckTimeout = inputs.healthcheckTimeout;\n\t}\n\n\tif (Object.keys(healthcheckInput).length === 0) {\n\t\treturn;\n\t}\n\n\ttry {\n\t\tawait client.query(SERVICE_INSTANCE_UPDATE, {\n\t\t\tserviceId,\n\t\t\tenvironmentId,\n\t\t\tinput: healthcheckInput,\n\t\t});\n\t} catch (error) {\n\t\tthrow new Error(\n\t\t\t`Railway healthcheck config ${JSON.stringify(healthcheckInput)} could not be applied to service ${serviceId} in environment ${environmentId} even after its deployment was created — failing instead of silently dropping it: ${error}`,\n\t\t);\n\t}\n}\n\n/**\n * Dynamic provider implementing CRUD for Railway services.\n *\n * Uses adopt-or-create on `create()`: queries services by project ID and name\n * before creating a new one. Service instance configuration (builder, commands,\n * healthcheck) is applied via `serviceInstanceUpdate` after create or update.\n *\n * @internal Exported only for unit testing; not part of the public API surface.\n */\nexport class RailwayServiceResourceProvider\n\timplements pulumi.dynamic.ResourceProvider\n{\n\t/**\n\t * Validates inputs at plan time. An empty `source.image` would otherwise\n\t * fail deep inside `serviceInstanceUpdate` with an opaque GraphQL error.\n\t */\n\tasync check(\n\t\t_olds: RailwayServiceInputs,\n\t\tnews: RailwayServiceInputs,\n\t): Promise<pulumi.dynamic.CheckResult<RailwayServiceInputs>> {\n\t\tconst failures: pulumi.dynamic.CheckFailure[] = [];\n\n\t\tif (\n\t\t\tnews.source &&\n\t\t\tisResolvedString(news.source.image) &&\n\t\t\tnews.source.image.trim().length === 0\n\t\t) {\n\t\t\tfailures.push({\n\t\t\t\tproperty: \"source.image\",\n\t\t\t\treason:\n\t\t\t\t\t'source.image must be a non-empty Docker image reference (e.g. \"redis:8-alpine\")',\n\t\t\t});\n\t\t}\n\n\t\tif (\n\t\t\tisResolvedString(news.healthcheckPath) &&\n\t\t\tnews.healthcheckPath.includes(\"-\")\n\t\t) {\n\t\t\tfailures.push({\n\t\t\t\tproperty: \"healthcheckPath\",\n\t\t\t\treason:\n\t\t\t\t\t'Railway rejects any healthcheckPath containing a hyphen with \"Invalid input\" (undocumented; proven by live probe matrix 2026-07-06) — use a hyphen-free path like \"/healthcheck\"',\n\t\t\t});\n\t\t}\n\n\t\treturn { inputs: news, failures };\n\t}\n\n\t/**\n\t * Creates or adopts a Railway service by name, then applies instance config.\n\t */\n\tasync create(\n\t\tinputs: RailwayServiceInputs,\n\t): Promise<pulumi.dynamic.CreateResult> {\n\t\tconst client = new RailwayClient(\n\t\t\tresolveCredential(inputs.token, inputs.tokenEnvVar),\n\t\t);\n\n\t\tconst result = await client.query<{\n\t\t\tproject: {\n\t\t\t\tservices: { edges: Array<{ node: { id: string; name: string } }> };\n\t\t\t};\n\t\t}>(SERVICES_QUERY, { projectId: inputs.projectId });\n\n\t\tlet serviceId = result.project.services.edges.find(\n\t\t\t(edge) => edge.node.name === inputs.name,\n\t\t)?.node.id;\n\n\t\tif (serviceId) {\n\t\t\tpulumi.log.info(\n\t\t\t\t`Adopted existing Railway service \"${inputs.name}\" (${serviceId})`,\n\t\t\t);\n\t\t} else {\n\t\t\tconst createInput: Record<string, unknown> = {\n\t\t\t\tprojectId: inputs.projectId,\n\t\t\t\tenvironmentId: inputs.environmentId,\n\t\t\t\tname: inputs.name,\n\t\t\t};\n\n\t\t\tif (inputs.source) {\n\t\t\t\tcreateInput.source = { image: inputs.source.image };\n\t\t\t}\n\n\t\t\tconst created = await client.query<{\n\t\t\t\tserviceCreate: { id: string; name: string };\n\t\t\t}>(SERVICE_CREATE, { input: createInput });\n\n\t\t\tserviceId = created.serviceCreate.id;\n\n\t\t\tpulumi.log.info(\n\t\t\t\t`Created Railway service \"${inputs.name}\" (${serviceId})`,\n\t\t\t);\n\n\t\t\tif (inputs.source) {\n\t\t\t\tawait client.query(SERVICE_CONNECT, {\n\t\t\t\t\tid: serviceId,\n\t\t\t\t\tinput: { image: inputs.source.image },\n\t\t\t\t});\n\t\t\t}\n\n\t\t\tif (inputs.icon) {\n\t\t\t\tawait client.query(SERVICE_UPDATE, {\n\t\t\t\t\tid: serviceId,\n\t\t\t\t\tinput: { icon: inputs.icon },\n\t\t\t\t});\n\t\t\t}\n\t\t}\n\n\t\tawait ensureServiceInstance(client, serviceId, inputs.environmentId);\n\n\t\tconst { droppedHealthcheck } = await applyInstanceConfig(\n\t\t\tclient,\n\t\t\tserviceId,\n\t\t\tinputs.environmentId,\n\t\t\tinputs,\n\t\t);\n\n\t\t// Image services have no `railway up` step (see RailwayDeploy for code\n\t\t// services) — the provider owns their deploy, and with it the post-deploy\n\t\t// re-apply of any dropped healthcheck fields. Code services get theirs\n\t\t// from the RailwayDeploy monitor instead.\n\t\tif (inputs.source) {\n\t\t\tawait deployServiceInstance(client, serviceId, inputs.environmentId);\n\n\t\t\tif (droppedHealthcheck) {\n\t\t\t\tawait applyHealthcheckConfig(\n\t\t\t\t\tclient,\n\t\t\t\t\tserviceId,\n\t\t\t\t\tinputs.environmentId,\n\t\t\t\t\tinputs,\n\t\t\t\t);\n\t\t\t}\n\t\t}\n\n\t\tconst outs: RailwayServiceOutputs = { ...inputs, serviceId };\n\n\t\treturn { id: serviceId, outs };\n\t}\n\n\t/**\n\t * Updates service name/icon and re-applies instance configuration.\n\t */\n\tasync update(\n\t\tid: string,\n\t\tolds: RailwayServiceOutputs,\n\t\tnews: RailwayServiceInputs,\n\t): Promise<pulumi.dynamic.UpdateResult> {\n\t\tconst client = new RailwayClient(\n\t\t\tresolveCredential(news.token, news.tokenEnvVar),\n\t\t);\n\n\t\tconst updateInput: Record<string, unknown> = {};\n\n\t\tif (olds.name !== news.name) {\n\t\t\tupdateInput.name = news.name;\n\t\t}\n\n\t\tif (news.icon && olds.icon !== news.icon) {\n\t\t\tupdateInput.icon = news.icon;\n\t\t}\n\n\t\tif (Object.keys(updateInput).length > 0) {\n\t\t\tawait client.query(SERVICE_UPDATE, { id, input: updateInput });\n\t\t}\n\n\t\tawait ensureServiceInstance(client, id, news.environmentId);\n\n\t\tconst { droppedHealthcheck } = await applyInstanceConfig(\n\t\t\tclient,\n\t\t\tid,\n\t\t\tnews.environmentId,\n\t\t\tnews,\n\t\t);\n\n\t\t// Instance config changes (source, startCommand, …) only take effect on\n\t\t// the next deployment; image services get none unless the provider\n\t\t// triggers it. Dropped healthcheck fields are re-applied once that\n\t\t// deployment exists (see applyHealthcheckConfig).\n\t\tif (news.source) {\n\t\t\tawait deployServiceInstance(client, id, news.environmentId);\n\n\t\t\tif (droppedHealthcheck) {\n\t\t\t\tawait applyHealthcheckConfig(client, id, news.environmentId, news);\n\t\t\t}\n\t\t}\n\n\t\tconst outs: RailwayServiceOutputs = { ...news, serviceId: id };\n\n\t\treturn { outs };\n\t}\n\n\t/**\n\t * Reads current state for `pulumi refresh` by querying the service by ID.\n\t */\n\tasync read(\n\t\tid: string,\n\t\tprops: RailwayServiceOutputs,\n\t): Promise<pulumi.dynamic.ReadResult> {\n\t\tconst client = new RailwayClient(\n\t\t\tresolveCredential(props.token, props.tokenEnvVar),\n\t\t);\n\n\t\ttry {\n\t\t\tawait client.query<{ service: { id: string; name: string } }>(\n\t\t\t\tSERVICE_QUERY,\n\t\t\t\t{ serviceId: id },\n\t\t\t);\n\t\t} catch {\n\t\t\t// Resource gone → blank id lets refresh reconcile the deletion.\n\t\t\treturn {};\n\t\t}\n\n\t\treturn { id, props: { ...props, serviceId: id } };\n\t}\n\n\t/**\n\t * Deletion is a no-op. A Railway service is a project-level resource shared\n\t * across environments (forked environments adopt it by name), so a single\n\t * environment's destroy must never delete it. Deleting the *environment*\n\t * removes that environment's service instances instead.\n\t */\n\tasync delete(): Promise<void> {\n\t\tpulumi.log.warn(\n\t\t\t\"Railway service deletion skipped — services are project-level; delete the environment to remove its instances\",\n\t\t);\n\t}\n\n\t/**\n\t * Compares old and new inputs to determine what changed.\n\t *\n\t * ProjectId and environmentId changes trigger replacement.\n\t */\n\tasync diff(\n\t\t_id: string,\n\t\tolds: RailwayServiceOutputs,\n\t\tnews: RailwayServiceInputs,\n\t): Promise<pulumi.dynamic.DiffResult> {\n\t\tconst replaces: string[] = [];\n\t\tconst changes: string[] = [];\n\n\t\tif (olds.name !== news.name) {\n\t\t\tchanges.push(\"name\");\n\t\t}\n\n\t\tif (olds.projectId !== news.projectId) {\n\t\t\treplaces.push(\"projectId\");\n\t\t}\n\n\t\tif (olds.environmentId !== news.environmentId) {\n\t\t\treplaces.push(\"environmentId\");\n\t\t}\n\n\t\tif (olds.builder !== news.builder) {\n\t\t\tchanges.push(\"builder\");\n\t\t}\n\n\t\tif (olds.buildCommand !== news.buildCommand) {\n\t\t\tchanges.push(\"buildCommand\");\n\t\t}\n\n\t\tif (olds.startCommand !== news.startCommand) {\n\t\t\tchanges.push(\"startCommand\");\n\t\t}\n\n\t\tif (olds.restartPolicyType !== news.restartPolicyType) {\n\t\t\tchanges.push(\"restartPolicyType\");\n\t\t}\n\n\t\tif (olds.healthcheckPath !== news.healthcheckPath) {\n\t\t\tchanges.push(\"healthcheckPath\");\n\t\t}\n\n\t\tif (olds.healthcheckTimeout !== news.healthcheckTimeout) {\n\t\t\tchanges.push(\"healthcheckTimeout\");\n\t\t}\n\n\t\tif (olds.preDeployCommand !== news.preDeployCommand) {\n\t\t\tchanges.push(\"preDeployCommand\");\n\t\t}\n\n\t\tif (olds.icon !== news.icon) {\n\t\t\tchanges.push(\"icon\");\n\t\t}\n\n\t\treturn {\n\t\t\tchanges: replaces.length > 0 || changes.length > 0,\n\t\t\treplaces,\n\t\t\t// serviceId survives every in-place update (only projectId/environmentId\n\t\t\t// changes replace), so declaring it stable keeps it known during preview —\n\t\t\t// dependents (e.g. RailwayVolume) no longer see an unknown serviceId and\n\t\t\t// stop showing phantom replaces.\n\t\t\tstables: replaces.length === 0 ? [\"serviceId\"] : [],\n\t\t\tdeleteBeforeReplace: true,\n\t\t};\n\t}\n}\n\n/** Internal dynamic resource — not part of the public API. */\nclass RailwayServiceResource extends pulumi.dynamic.Resource {\n\tpublic declare readonly serviceId: pulumi.Output<string>;\n\n\tconstructor(\n\t\tname: string,\n\t\targs: {\n\t\t\ttoken?: pulumi.Input<string>;\n\t\t\ttokenEnvVar?: pulumi.Input<string>;\n\t\t\tprojectId: pulumi.Input<string>;\n\t\t\tenvironmentId: pulumi.Input<string>;\n\t\t\tname: pulumi.Input<string>;\n\t\t\ticon?: pulumi.Input<string>;\n\t\t\tsource?: pulumi.Input<{ image: pulumi.Input<string> }>;\n\t\t\tbuilder?: pulumi.Input<RailwayBuilder>;\n\t\t\tbuildCommand?: pulumi.Input<string>;\n\t\t\tstartCommand?: pulumi.Input<string>;\n\t\t\trestartPolicyType?: pulumi.Input<RailwayRestartPolicy>;\n\t\t\thealthcheckPath?: pulumi.Input<string>;\n\t\t\thealthcheckTimeout?: pulumi.Input<number>;\n\t\t\tpreDeployCommand?: pulumi.Input<string>;\n\t\t},\n\t\topts?: pulumi.CustomResourceOptions,\n\t) {\n\t\tsuper(\n\t\t\tnew RailwayServiceResourceProvider(),\n\t\t\tname,\n\t\t\t{ ...args, serviceId: undefined },\n\t\t\t// The API token flows into dynamic-provider state with the outputs — mark it secret there.\n\t\t\t{ ...opts, additionalSecretOutputs: [\"token\"] },\n\t\t);\n\t}\n}\n\n/** Options type for RailwayService — replaces Pulumi's native `provider` field. */\ntype RailwayServiceOptions = 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\n/** Args for RailwayService. */\nexport interface RailwayServiceArgs {\n\t/** Human-readable service name used for adopt-or-create matching. */\n\tname: pulumi.Input<string>;\n\n\t/** SVG icon URL displayed in the Railway dashboard. */\n\ticon?: pulumi.Input<string>;\n\n\t/** Docker image source for image-based services. */\n\tsource?: pulumi.Input<{ image: pulumi.Input<string> }>;\n\n\t/** Build system to use when building the service. */\n\tbuilder?: pulumi.Input<RailwayBuilder>;\n\n\t/** Shell command executed during the build phase. */\n\tbuildCommand?: pulumi.Input<string>;\n\n\t/** Shell command executed to start the service at runtime. */\n\tstartCommand?: pulumi.Input<string>;\n\n\t/** Restart behavior for the service container. */\n\trestartPolicyType?: pulumi.Input<RailwayRestartPolicy>;\n\n\t/** HTTP path polled for health checks (e.g. `\"/health-check\"`). */\n\thealthcheckPath?: pulumi.Input<string>;\n\n\t/** Seconds to wait for a healthy response before marking unhealthy. */\n\thealthcheckTimeout?: pulumi.Input<number>;\n\n\t/** Shell command executed before the main deploy (e.g. migrations). */\n\tpreDeployCommand?: pulumi.Input<string>;\n}\n\n/**\n * Manages a Railway service with adopt-or-create semantics.\n *\n * @example\n * ```typescript\n * const service = new RailwayService(\"api\", {\n * name: \"api\",\n * builder: RailwayBuilder.RAILPACK,\n * startCommand: \"node dist/index.js\",\n * healthcheckPath: \"/health\",\n * }, { provider, project, environment });\n *\n * // Use serviceId downstream\n * new RailwayVariable(\"api-vars\", {\n * variables: { DATABASE_URL: dbUrl },\n * }, { provider, project, environment, service });\n * ```\n */\nexport class RailwayService extends pulumi.ComponentResource {\n\t/** Railway service UUID. */\n\tpublic readonly id: pulumi.Output<string>;\n\n\tconstructor(\n\t\tname: string,\n\t\targs: RailwayServiceArgs,\n\t\topts: RailwayServiceOptions,\n\t) {\n\t\tconst { provider, project, environment, ...pulumiOpts } = opts;\n\n\t\tsuper(\"infracraft:railway:Service\", name, {}, pulumiOpts);\n\n\t\tconst resource = new RailwayServiceResource(\n\t\t\t`${name}-resource`,\n\t\t\t{\n\t\t\t\ttoken: provider.token,\n\t\t\t\ttokenEnvVar: provider.tokenEnvVar,\n\t\t\t\tprojectId: project.id,\n\t\t\t\tenvironmentId: environment.id,\n\t\t\t\t...args,\n\t\t\t},\n\t\t\t{ parent: this },\n\t\t);\n\n\t\tthis.id = resource.serviceId;\n\n\t\tthis.registerOutputs({ id: this.id });\n\t}\n}\n"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"service.d.cts","names":[],"sources":["../../src/railway/service.ts"],"mappings":";;;;;;;;;;AAcA;;;aAAY,cAAA;EACX,QAAA;EACA,QAAA;EACA,UAAA;EACA,MAAA;EACA,MAAA;AAAA;AAAM;AAOP;;;AAPO,aAOK,oBAAA;EACX,UAAA;EACA,MAAA;EACA,KAAA;AAAA;AAAK;AAAA,UAII,oBAAA;EAAoB;EAE7B,KAAK;AAAA;AAAA;AAAA,UAII,oBAAA;EAAoB;EAE7B,KAAA;EAkBS;EAfT,WAAA;EA2BoB;EAxBpB,SAAA;EAwBwC;EArBxC,aAAA;EANA;EASA,IAAA;EAHA;EAMA,IAAA;EAAA;EAGA,MAAA,GAAS,oBAAA;EAAA;EAGT,OAAA,GAAU,cAAA;EAAA;EAGV,YAAA;EAGA;EAAA,YAAA;EAGoB;EAApB,iBAAA,GAAoB,oBAAA;EAMpB;EAHA,eAAA;EAMgB;EAHhB,kBAAA;EAOS;EAJT,gBAAA;AAAA;;UAIS,qBAAA,SAA8B,oBAAoB;EAiU/C;EA/TZ,SAAS;AAAA;;;;;;;;;;cA+TG,8BAAA,YACD,MAAA,CAAO,OAAA,CAAQ,gBAAA;
|
|
1
|
+
{"version":3,"file":"service.d.cts","names":[],"sources":["../../src/railway/service.ts"],"mappings":";;;;;;;;;;AAcA;;;aAAY,cAAA;EACX,QAAA;EACA,QAAA;EACA,UAAA;EACA,MAAA;EACA,MAAA;AAAA;AAAM;AAOP;;;AAPO,aAOK,oBAAA;EACX,UAAA;EACA,MAAA;EACA,KAAA;AAAA;AAAK;AAAA,UAII,oBAAA;EAAoB;EAE7B,KAAK;AAAA;AAAA;AAAA,UAII,oBAAA;EAAoB;EAE7B,KAAA;EAkBS;EAfT,WAAA;EA2BoB;EAxBpB,SAAA;EAwBwC;EArBxC,aAAA;EANA;EASA,IAAA;EAHA;EAMA,IAAA;EAAA;EAGA,MAAA,GAAS,oBAAA;EAAA;EAGT,OAAA,GAAU,cAAA;EAAA;EAGV,YAAA;EAGA;EAAA,YAAA;EAGoB;EAApB,iBAAA,GAAoB,oBAAA;EAMpB;EAHA,eAAA;EAMgB;EAHhB,kBAAA;EAOS;EAJT,gBAAA;AAAA;;UAIS,qBAAA,SAA8B,oBAAoB;EAiU/C;EA/TZ,SAAS;AAAA;;;;;;;;;;cA+TG,8BAAA,YACD,MAAA,CAAO,OAAA,CAAQ,gBAAA;EAwIf;;;;EAlIL,KAAA,CACL,KAAA,EAAO,oBAAA,EACP,IAAA,EAAM,oBAAA,GACJ,OAAA,CAAQ,MAAA,CAAO,OAAA,CAAQ,WAAA,CAAY,oBAAA;EA0MtB;;;EA1KV,MAAA,CACL,MAAA,EAAQ,oBAAA,GACN,OAAA,CAAQ,MAAA,CAAO,OAAA,CAAQ,YAAA;EAuLvB;;;EA9FG,MAAA,CACL,EAAA,UACA,IAAA,EAAM,qBAAA,EACN,IAAA,EAAM,oBAAA,GACJ,OAAA,CAAQ,MAAA,CAAO,OAAA,CAAQ,YAAA;EAxIf;;;EAwLL,IAAA,CACL,EAAA,UACA,KAAA,EAAO,qBAAA,GACL,OAAA,CAAQ,MAAA,CAAO,OAAA,CAAQ,UAAA;EApLlB;;;;;;EA4MF,MAAA,CAAA,GAAU,OAAA;EA1MU;;;;;EAqNpB,IAAA,CACL,GAAA,UACA,IAAA,EAAM,qBAAA,EACN,IAAA,EAAM,oBAAA,GACJ,OAAA,CAAQ,MAAA,CAAO,OAAA,CAAQ,UAAA;AAAA;;KAgGtB,qBAAA,GAAwB,IAAA,CAC5B,MAAA,CAAO,wBAAA;EA/LD,sCAmMN,QAAA,EAAU,eAAA,EAjMH;EAoMP,OAAA,EAAS,cAAA,EAnMF;EAsMP,WAAA,EAAa,kBAAA;AAAA;;UAIG,kBAAA;EAzMU;EA2M1B,IAAA,EAAM,MAAA,CAAO,KAAA;EA1JZ;EA6JD,IAAA,GAAO,MAAA,CAAO,KAAA;EA5Jb;EA+JD,MAAA,GAAS,MAAA,CAAO,KAAA;IAAQ,KAAA,EAAO,MAAA,CAAO,KAAA;EAAA;EA9JZ;EAiK1B,OAAA,GAAU,MAAA,CAAO,KAAA,CAAM,cAAA;EAzIP;EA4IhB,YAAA,GAAe,MAAA,CAAO,KAAA;EAhIrB;EAmID,YAAA,GAAe,MAAA,CAAO,KAAA;EAlIrB;EAqID,iBAAA,GAAoB,MAAA,CAAO,KAAA,CAAM,oBAAA;EApIhC;EAuID,eAAA,GAAkB,MAAA,CAAO,KAAA;EAtId;EAyIX,kBAAA,GAAqB,MAAA,CAAO,KAAA;EAzIF;EA4I1B,gBAAA,GAAmB,MAAA,CAAO,KAAA;AAAA;AAjF1B;;;;;;;;;;;;;;;;;;AAAA,cAsGY,cAAA,SAAuB,MAAA,CAAO,iBAAA;EAtDX;EAAA,SAwDf,EAAA,EAAI,MAAA,CAAO,MAAA;cAG1B,IAAA,UACA,IAAA,EAAM,kBAAA,EACN,IAAA,EAAM,qBAAA;AAAA"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"service.d.mts","names":[],"sources":["../../src/railway/service.ts"],"mappings":";;;;;;;;;;AAcA;;;aAAY,cAAA;EACX,QAAA;EACA,QAAA;EACA,UAAA;EACA,MAAA;EACA,MAAA;AAAA;AAAM;AAOP;;;AAPO,aAOK,oBAAA;EACX,UAAA;EACA,MAAA;EACA,KAAA;AAAA;AAAK;AAAA,UAII,oBAAA;EAAoB;EAE7B,KAAK;AAAA;AAAA;AAAA,UAII,oBAAA;EAAoB;EAE7B,KAAA;EAkBS;EAfT,WAAA;EA2BoB;EAxBpB,SAAA;EAwBwC;EArBxC,aAAA;EANA;EASA,IAAA;EAHA;EAMA,IAAA;EAAA;EAGA,MAAA,GAAS,oBAAA;EAAA;EAGT,OAAA,GAAU,cAAA;EAAA;EAGV,YAAA;EAGA;EAAA,YAAA;EAGoB;EAApB,iBAAA,GAAoB,oBAAA;EAMpB;EAHA,eAAA;EAMgB;EAHhB,kBAAA;EAOS;EAJT,gBAAA;AAAA;;UAIS,qBAAA,SAA8B,oBAAoB;EAiU/C;EA/TZ,SAAS;AAAA;;;;;;;;;;cA+TG,8BAAA,YACD,MAAA,CAAO,OAAA,CAAQ,gBAAA;
|
|
1
|
+
{"version":3,"file":"service.d.mts","names":[],"sources":["../../src/railway/service.ts"],"mappings":";;;;;;;;;;AAcA;;;aAAY,cAAA;EACX,QAAA;EACA,QAAA;EACA,UAAA;EACA,MAAA;EACA,MAAA;AAAA;AAAM;AAOP;;;AAPO,aAOK,oBAAA;EACX,UAAA;EACA,MAAA;EACA,KAAA;AAAA;AAAK;AAAA,UAII,oBAAA;EAAoB;EAE7B,KAAK;AAAA;AAAA;AAAA,UAII,oBAAA;EAAoB;EAE7B,KAAA;EAkBS;EAfT,WAAA;EA2BoB;EAxBpB,SAAA;EAwBwC;EArBxC,aAAA;EANA;EASA,IAAA;EAHA;EAMA,IAAA;EAAA;EAGA,MAAA,GAAS,oBAAA;EAAA;EAGT,OAAA,GAAU,cAAA;EAAA;EAGV,YAAA;EAGA;EAAA,YAAA;EAGoB;EAApB,iBAAA,GAAoB,oBAAA;EAMpB;EAHA,eAAA;EAMgB;EAHhB,kBAAA;EAOS;EAJT,gBAAA;AAAA;;UAIS,qBAAA,SAA8B,oBAAoB;EAiU/C;EA/TZ,SAAS;AAAA;;;;;;;;;;cA+TG,8BAAA,YACD,MAAA,CAAO,OAAA,CAAQ,gBAAA;EAwIf;;;;EAlIL,KAAA,CACL,KAAA,EAAO,oBAAA,EACP,IAAA,EAAM,oBAAA,GACJ,OAAA,CAAQ,MAAA,CAAO,OAAA,CAAQ,WAAA,CAAY,oBAAA;EA0MtB;;;EA1KV,MAAA,CACL,MAAA,EAAQ,oBAAA,GACN,OAAA,CAAQ,MAAA,CAAO,OAAA,CAAQ,YAAA;EAuLvB;;;EA9FG,MAAA,CACL,EAAA,UACA,IAAA,EAAM,qBAAA,EACN,IAAA,EAAM,oBAAA,GACJ,OAAA,CAAQ,MAAA,CAAO,OAAA,CAAQ,YAAA;EAxIf;;;EAwLL,IAAA,CACL,EAAA,UACA,KAAA,EAAO,qBAAA,GACL,OAAA,CAAQ,MAAA,CAAO,OAAA,CAAQ,UAAA;EApLlB;;;;;;EA4MF,MAAA,CAAA,GAAU,OAAA;EA1MU;;;;;EAqNpB,IAAA,CACL,GAAA,UACA,IAAA,EAAM,qBAAA,EACN,IAAA,EAAM,oBAAA,GACJ,OAAA,CAAQ,MAAA,CAAO,OAAA,CAAQ,UAAA;AAAA;;KAgGtB,qBAAA,GAAwB,IAAA,CAC5B,MAAA,CAAO,wBAAA;EA/LD,sCAmMN,QAAA,EAAU,eAAA,EAjMH;EAoMP,OAAA,EAAS,cAAA,EAnMF;EAsMP,WAAA,EAAa,kBAAA;AAAA;;UAIG,kBAAA;EAzMU;EA2M1B,IAAA,EAAM,MAAA,CAAO,KAAA;EA1JZ;EA6JD,IAAA,GAAO,MAAA,CAAO,KAAA;EA5Jb;EA+JD,MAAA,GAAS,MAAA,CAAO,KAAA;IAAQ,KAAA,EAAO,MAAA,CAAO,KAAA;EAAA;EA9JZ;EAiK1B,OAAA,GAAU,MAAA,CAAO,KAAA,CAAM,cAAA;EAzIP;EA4IhB,YAAA,GAAe,MAAA,CAAO,KAAA;EAhIrB;EAmID,YAAA,GAAe,MAAA,CAAO,KAAA;EAlIrB;EAqID,iBAAA,GAAoB,MAAA,CAAO,KAAA,CAAM,oBAAA;EApIhC;EAuID,eAAA,GAAkB,MAAA,CAAO,KAAA;EAtId;EAyIX,kBAAA,GAAqB,MAAA,CAAO,KAAA;EAzIF;EA4I1B,gBAAA,GAAmB,MAAA,CAAO,KAAA;AAAA;AAjF1B;;;;;;;;;;;;;;;;;;AAAA,cAsGY,cAAA,SAAuB,MAAA,CAAO,iBAAA;EAtDX;EAAA,SAwDf,EAAA,EAAI,MAAA,CAAO,MAAA;cAG1B,IAAA,UACA,IAAA,EAAM,kBAAA,EACN,IAAA,EAAM,qBAAA;AAAA"}
|
package/dist/railway/service.mjs
CHANGED
|
@@ -243,6 +243,10 @@ var RailwayServiceResourceProvider = class {
|
|
|
243
243
|
property: "source.image",
|
|
244
244
|
reason: "source.image must be a non-empty Docker image reference (e.g. \"redis:8-alpine\")"
|
|
245
245
|
});
|
|
246
|
+
if (isResolvedString(news.healthcheckPath) && news.healthcheckPath.includes("-")) failures.push({
|
|
247
|
+
property: "healthcheckPath",
|
|
248
|
+
reason: "Railway rejects any healthcheckPath containing a hyphen with \"Invalid input\" (undocumented; proven by live probe matrix 2026-07-06) — use a hyphen-free path like \"/healthcheck\""
|
|
249
|
+
});
|
|
246
250
|
return {
|
|
247
251
|
inputs: news,
|
|
248
252
|
failures
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"service.mjs","names":[],"sources":["../../src/railway/service.ts"],"sourcesContent":["import * as pulumi from \"@pulumi/pulumi\";\nimport { isResolvedString } from \"../dynamic/is-resolved-string\";\nimport { resolveCredential } from \"../dynamic/resolve-credential\";\nimport { RailwayClient } from \"./client\";\nimport type { RailwayEnvironment } from \"./environment\";\nimport type { RailwayProject } from \"./project\";\nimport type { RailwayProvider } from \"./provider\";\n\n/**\n * Railway build system. Enum keys are UPPERCASE per convention; values are\n * Railway's required UPPERCASE wire literals.\n * Note: HEROKU and PAKETO were deprecated Feb 21 2025 and auto-migrated to\n * NIXPACKS by Railway, but remain in the schema and are accepted by the API.\n */\nexport enum RailwayBuilder {\n\tRAILPACK = \"RAILPACK\",\n\tNIXPACKS = \"NIXPACKS\",\n\tDOCKERFILE = \"DOCKERFILE\",\n\tHEROKU = \"HEROKU\",\n\tPAKETO = \"PAKETO\",\n}\n\n/**\n * Railway service restart policy. Controls when Railway restarts the service\n * container after it exits. Default is ON_FAILURE.\n */\nexport enum RailwayRestartPolicy {\n\tON_FAILURE = \"ON_FAILURE\",\n\tALWAYS = \"ALWAYS\",\n\tNEVER = \"NEVER\",\n}\n\n/** Docker image source for a Railway service (e.g. `redis:8-alpine`). */\ninterface RailwayServiceSource {\n\t/** Full Docker image reference including tag. */\n\timage: string;\n}\n\n/** Resolved inputs for the Railway service dynamic provider. */\ninterface RailwayServiceInputs {\n\t/** Railway API bearer token. Absent when `tokenEnvVar` is used instead. */\n\ttoken?: string;\n\n\t/** Env var name resolved to the token when `token` is absent (see `RailwayProviderArgs.tokenEnvVar`). */\n\ttokenEnvVar?: string;\n\n\t/** Railway project UUID. */\n\tprojectId: string;\n\n\t/** Railway environment UUID (e.g. production). */\n\tenvironmentId: string;\n\n\t/** Human-readable service name used for adopt-or-create matching. */\n\tname: string;\n\n\t/** SVG icon URL displayed in the Railway dashboard. */\n\ticon?: string;\n\n\t/** Docker image source for image-based services. */\n\tsource?: RailwayServiceSource;\n\n\t/** Build system to use when building the service. */\n\tbuilder?: RailwayBuilder;\n\n\t/** Shell command executed during the build phase. */\n\tbuildCommand?: string;\n\n\t/** Shell command executed to start the service at runtime. */\n\tstartCommand?: string;\n\n\t/** Restart behavior for the service container. */\n\trestartPolicyType?: RailwayRestartPolicy;\n\n\t/** HTTP path polled for health checks (e.g. `\"/health-check\"`). */\n\thealthcheckPath?: string;\n\n\t/** Seconds to wait for a healthy response before marking unhealthy. */\n\thealthcheckTimeout?: number;\n\n\t/** Shell command executed before the main deploy (e.g. migrations). */\n\tpreDeployCommand?: string;\n}\n\n/** Persisted state for the Railway service, extending inputs with the Railway-assigned ID. */\ninterface RailwayServiceOutputs extends RailwayServiceInputs {\n\t/** Railway-assigned service UUID. */\n\tserviceId: string;\n}\n\nconst SERVICES_QUERY = `\n query($projectId: String!) {\n project(id: $projectId) {\n services {\n edges {\n node {\n id\n name\n }\n }\n }\n }\n }\n`;\n\nconst SERVICE_QUERY = `\n query($serviceId: String!) {\n service(id: $serviceId) {\n id\n name\n }\n }\n`;\n\nconst SERVICE_CREATE = `\n mutation($input: ServiceCreateInput!) {\n serviceCreate(input: $input) {\n id\n name\n }\n }\n`;\n\nconst SERVICE_UPDATE = `\n mutation($id: String!, $input: ServiceUpdateInput!) {\n serviceUpdate(id: $id, input: $input) {\n id\n name\n }\n }\n`;\n\nconst SERVICE_INSTANCE_UPDATE = `\n mutation(\n $serviceId: String!\n $environmentId: String!\n $input: ServiceInstanceUpdateInput!\n ) {\n serviceInstanceUpdate(\n serviceId: $serviceId\n environmentId: $environmentId\n input: $input\n )\n }\n`;\n\nconst SERVICE_CONNECT = `\n mutation($id: String!, $input: ServiceConnectInput!) {\n serviceConnect(id: $id, input: $input) {\n id\n }\n }\n`;\n\nconst SERVICE_INSTANCE_DEPLOY = `\n mutation($serviceId: String!, $environmentId: String!) {\n serviceInstanceDeployV2(serviceId: $serviceId, environmentId: $environmentId)\n }\n`;\n\n/**\n * Triggers a deployment of the service instance in the target environment.\n * Image-sourced services never deploy there on their own: `serviceCreate`'s\n * auto-deploy only reaches the project's DEFAULT environment, and\n * `serviceInstanceUpdate` applies config without redeploying — so a service\n * in any other environment stays undeployed forever and its private DNS name\n * never registers. `environmentTriggersDeploy` is no alternative: it returns\n * success without creating anything for a service that has never deployed in\n * that environment (proven live, 2026-07-06).\n */\nasync function deployServiceInstance(\n\tclient: RailwayClient,\n\tserviceId: string,\n\tenvironmentId: string,\n): Promise<void> {\n\tconst result = await client.query<{ serviceInstanceDeployV2: string }>(\n\t\tSERVICE_INSTANCE_DEPLOY,\n\t\t{ serviceId, environmentId },\n\t);\n\n\tpulumi.log.info(\n\t\t`[infracraft] serviceInstanceDeployV2 created deployment ${result.serviceInstanceDeployV2}`,\n\t);\n}\n\nconst SERVICE_INSTANCE_QUERY = `\n query($serviceId: String!, $environmentId: String!) {\n serviceInstance(serviceId: $serviceId, environmentId: $environmentId) {\n id\n }\n }\n`;\n\nconst ENVIRONMENT_PATCH_COMMIT = `\n mutation($environmentId: String!, $patch: EnvironmentConfig!, $message: String!) {\n environmentPatchCommit(environmentId: $environmentId, patch: $patch, commitMessage: $message)\n }\n`;\n\n/**\n * Guarantees the service has an instance in the target environment.\n *\n * `serviceCreate` materializes an instance ONLY in the environment passed at\n * create time; in every other environment the service is \"skipped\" — no\n * instance exists there, `serviceInstanceUpdate` returns true while silently\n * doing nothing, and `railway up` fails with UPLOAD_FAILED 404 (live incident:\n * first-ever mesh deploy to production). Materialization goes through the\n * staged-changes flow — committing a config patch that keys the service in\n * `services` — which is the documented path for NAMED environments;\n * `environmentUnskipService` is rejected there (\"Can only unskip services in\n * PR environments\", proven live). The commit's return is not trusted: the\n * instance is re-queried afterward and a still-missing instance is a loud\n * error rather than another silent no-op.\n */\nasync function ensureServiceInstance(\n\tclient: RailwayClient,\n\tserviceId: string,\n\tenvironmentId: string,\n): Promise<void> {\n\tconst exists = async (): Promise<boolean> => {\n\t\ttry {\n\t\t\tconst result = await client.query<{\n\t\t\t\tserviceInstance: { id: string } | null;\n\t\t\t}>(SERVICE_INSTANCE_QUERY, { serviceId, environmentId });\n\n\t\t\treturn Boolean(result.serviceInstance);\n\t\t} catch (error) {\n\t\t\tif (error instanceof Error && /not found/i.test(error.message)) {\n\t\t\t\treturn false;\n\t\t\t}\n\n\t\t\tthrow error;\n\t\t}\n\t};\n\n\tif (await exists()) {\n\t\treturn;\n\t}\n\n\tpulumi.log.info(\n\t\t`[infracraft] service ${serviceId} has no instance in environment ${environmentId} — committing a config patch to materialize it`,\n\t);\n\n\tawait client.query(ENVIRONMENT_PATCH_COMMIT, {\n\t\tenvironmentId,\n\t\tpatch: { services: { [serviceId]: {} } },\n\t\tmessage: `[infracraft] add service ${serviceId} to environment`,\n\t});\n\n\tif (!(await exists())) {\n\t\tthrow new Error(\n\t\t\t`Railway service ${serviceId} still has no instance in environment ${environmentId} after the config-patch commit — cannot configure or deploy it`,\n\t\t);\n\t}\n}\n\n/** What `applyInstanceConfig` had to leave out to get the update accepted. */\ninterface InstanceConfigResult {\n\t/** True when the healthcheck fields were dropped from a failed first attempt. */\n\tdroppedHealthcheck: boolean;\n}\n\n/**\n * Applies service instance configuration (builder, commands, healthcheck).\n *\n * The healthcheck fields ride on the FIRST attempt — an instance with existing\n * deployments accepts them, so steady-state applies stay one call. A FRESH\n * instance with no deployment rejects `healthcheckPath` with \"Invalid input\"\n * (live drill finding), so on failure the call is retried without the\n * healthcheck fields and the drop is reported to the caller — which MUST\n * re-apply them after the first deployment exists (the provider itself for\n * image services, the RailwayDeploy monitor for code services). Silent loss is\n * not an option.\n */\nasync function applyInstanceConfig(\n\tclient: RailwayClient,\n\tserviceId: string,\n\tenvironmentId: string,\n\tinputs: RailwayServiceInputs,\n): Promise<InstanceConfigResult> {\n\tconst instanceInput: Record<string, unknown> = {};\n\n\t// Source must be applied PER ENVIRONMENT: `ServiceCreateInput.source` only\n\t// configures the instance of the environment passed at create time, and every\n\t// other environment's instance is born with source=null — deploy triggers\n\t// then no-op silently because there is nothing to deploy.\n\tif (inputs.source) {\n\t\tinstanceInput.source = { image: inputs.source.image };\n\t}\n\n\tif (inputs.builder) {\n\t\tinstanceInput.builder = inputs.builder;\n\t}\n\n\tif (inputs.buildCommand) {\n\t\tinstanceInput.buildCommand = inputs.buildCommand;\n\t}\n\n\tif (inputs.startCommand) {\n\t\tinstanceInput.startCommand = inputs.startCommand;\n\t}\n\n\tif (inputs.restartPolicyType) {\n\t\tinstanceInput.restartPolicyType = inputs.restartPolicyType;\n\t}\n\n\tif (inputs.healthcheckPath) {\n\t\tinstanceInput.healthcheckPath = inputs.healthcheckPath;\n\t}\n\n\tif (inputs.healthcheckTimeout) {\n\t\tinstanceInput.healthcheckTimeout = inputs.healthcheckTimeout;\n\t}\n\n\tif (inputs.preDeployCommand) {\n\t\tinstanceInput.preDeployCommand = inputs.preDeployCommand;\n\t}\n\n\tif (Object.keys(instanceInput).length === 0) {\n\t\treturn { droppedHealthcheck: false };\n\t}\n\n\tconst hasHealthcheck =\n\t\tinstanceInput.healthcheckPath !== undefined ||\n\t\tinstanceInput.healthcheckTimeout !== undefined;\n\n\ttry {\n\t\tawait client.query(SERVICE_INSTANCE_UPDATE, {\n\t\t\tserviceId,\n\t\t\tenvironmentId,\n\t\t\tinput: instanceInput,\n\t\t});\n\n\t\treturn { droppedHealthcheck: false };\n\t} catch (error) {\n\t\tif (!hasHealthcheck) {\n\t\t\tthrow error;\n\t\t}\n\n\t\tpulumi.log.warn(\n\t\t\t`serviceInstanceUpdate failed, retrying without healthcheck fields: ${error}`,\n\t\t);\n\n\t\tdelete instanceInput.healthcheckPath;\n\t\tdelete instanceInput.healthcheckTimeout;\n\n\t\tif (Object.keys(instanceInput).length > 0) {\n\t\t\tawait client.query(SERVICE_INSTANCE_UPDATE, {\n\t\t\t\tserviceId,\n\t\t\t\tenvironmentId,\n\t\t\t\tinput: instanceInput,\n\t\t\t});\n\t\t}\n\n\t\treturn { droppedHealthcheck: true };\n\t}\n}\n\n/**\n * Re-applies ONLY the healthcheck fields after the instance has a deployment.\n * Called when `applyInstanceConfig` had to drop them (fresh instance, no\n * deployment); a failure here would silently lose the healthcheck config\n * forever, so it throws loudly instead.\n */\nasync function applyHealthcheckConfig(\n\tclient: RailwayClient,\n\tserviceId: string,\n\tenvironmentId: string,\n\tinputs: RailwayServiceInputs,\n): Promise<void> {\n\tconst healthcheckInput: Record<string, unknown> = {};\n\n\tif (inputs.healthcheckPath) {\n\t\thealthcheckInput.healthcheckPath = inputs.healthcheckPath;\n\t}\n\n\tif (inputs.healthcheckTimeout) {\n\t\thealthcheckInput.healthcheckTimeout = inputs.healthcheckTimeout;\n\t}\n\n\tif (Object.keys(healthcheckInput).length === 0) {\n\t\treturn;\n\t}\n\n\ttry {\n\t\tawait client.query(SERVICE_INSTANCE_UPDATE, {\n\t\t\tserviceId,\n\t\t\tenvironmentId,\n\t\t\tinput: healthcheckInput,\n\t\t});\n\t} catch (error) {\n\t\tthrow new Error(\n\t\t\t`Railway healthcheck config ${JSON.stringify(healthcheckInput)} could not be applied to service ${serviceId} in environment ${environmentId} even after its deployment was created — failing instead of silently dropping it: ${error}`,\n\t\t);\n\t}\n}\n\n/**\n * Dynamic provider implementing CRUD for Railway services.\n *\n * Uses adopt-or-create on `create()`: queries services by project ID and name\n * before creating a new one. Service instance configuration (builder, commands,\n * healthcheck) is applied via `serviceInstanceUpdate` after create or update.\n *\n * @internal Exported only for unit testing; not part of the public API surface.\n */\nexport class RailwayServiceResourceProvider\n\timplements pulumi.dynamic.ResourceProvider\n{\n\t/**\n\t * Validates inputs at plan time. An empty `source.image` would otherwise\n\t * fail deep inside `serviceInstanceUpdate` with an opaque GraphQL error.\n\t */\n\tasync check(\n\t\t_olds: RailwayServiceInputs,\n\t\tnews: RailwayServiceInputs,\n\t): Promise<pulumi.dynamic.CheckResult<RailwayServiceInputs>> {\n\t\tconst failures: pulumi.dynamic.CheckFailure[] = [];\n\n\t\tif (\n\t\t\tnews.source &&\n\t\t\tisResolvedString(news.source.image) &&\n\t\t\tnews.source.image.trim().length === 0\n\t\t) {\n\t\t\tfailures.push({\n\t\t\t\tproperty: \"source.image\",\n\t\t\t\treason:\n\t\t\t\t\t'source.image must be a non-empty Docker image reference (e.g. \"redis:8-alpine\")',\n\t\t\t});\n\t\t}\n\n\t\treturn { inputs: news, failures };\n\t}\n\n\t/**\n\t * Creates or adopts a Railway service by name, then applies instance config.\n\t */\n\tasync create(\n\t\tinputs: RailwayServiceInputs,\n\t): Promise<pulumi.dynamic.CreateResult> {\n\t\tconst client = new RailwayClient(\n\t\t\tresolveCredential(inputs.token, inputs.tokenEnvVar),\n\t\t);\n\n\t\tconst result = await client.query<{\n\t\t\tproject: {\n\t\t\t\tservices: { edges: Array<{ node: { id: string; name: string } }> };\n\t\t\t};\n\t\t}>(SERVICES_QUERY, { projectId: inputs.projectId });\n\n\t\tlet serviceId = result.project.services.edges.find(\n\t\t\t(edge) => edge.node.name === inputs.name,\n\t\t)?.node.id;\n\n\t\tif (serviceId) {\n\t\t\tpulumi.log.info(\n\t\t\t\t`Adopted existing Railway service \"${inputs.name}\" (${serviceId})`,\n\t\t\t);\n\t\t} else {\n\t\t\tconst createInput: Record<string, unknown> = {\n\t\t\t\tprojectId: inputs.projectId,\n\t\t\t\tenvironmentId: inputs.environmentId,\n\t\t\t\tname: inputs.name,\n\t\t\t};\n\n\t\t\tif (inputs.source) {\n\t\t\t\tcreateInput.source = { image: inputs.source.image };\n\t\t\t}\n\n\t\t\tconst created = await client.query<{\n\t\t\t\tserviceCreate: { id: string; name: string };\n\t\t\t}>(SERVICE_CREATE, { input: createInput });\n\n\t\t\tserviceId = created.serviceCreate.id;\n\n\t\t\tpulumi.log.info(\n\t\t\t\t`Created Railway service \"${inputs.name}\" (${serviceId})`,\n\t\t\t);\n\n\t\t\tif (inputs.source) {\n\t\t\t\tawait client.query(SERVICE_CONNECT, {\n\t\t\t\t\tid: serviceId,\n\t\t\t\t\tinput: { image: inputs.source.image },\n\t\t\t\t});\n\t\t\t}\n\n\t\t\tif (inputs.icon) {\n\t\t\t\tawait client.query(SERVICE_UPDATE, {\n\t\t\t\t\tid: serviceId,\n\t\t\t\t\tinput: { icon: inputs.icon },\n\t\t\t\t});\n\t\t\t}\n\t\t}\n\n\t\tawait ensureServiceInstance(client, serviceId, inputs.environmentId);\n\n\t\tconst { droppedHealthcheck } = await applyInstanceConfig(\n\t\t\tclient,\n\t\t\tserviceId,\n\t\t\tinputs.environmentId,\n\t\t\tinputs,\n\t\t);\n\n\t\t// Image services have no `railway up` step (see RailwayDeploy for code\n\t\t// services) — the provider owns their deploy, and with it the post-deploy\n\t\t// re-apply of any dropped healthcheck fields. Code services get theirs\n\t\t// from the RailwayDeploy monitor instead.\n\t\tif (inputs.source) {\n\t\t\tawait deployServiceInstance(client, serviceId, inputs.environmentId);\n\n\t\t\tif (droppedHealthcheck) {\n\t\t\t\tawait applyHealthcheckConfig(\n\t\t\t\t\tclient,\n\t\t\t\t\tserviceId,\n\t\t\t\t\tinputs.environmentId,\n\t\t\t\t\tinputs,\n\t\t\t\t);\n\t\t\t}\n\t\t}\n\n\t\tconst outs: RailwayServiceOutputs = { ...inputs, serviceId };\n\n\t\treturn { id: serviceId, outs };\n\t}\n\n\t/**\n\t * Updates service name/icon and re-applies instance configuration.\n\t */\n\tasync update(\n\t\tid: string,\n\t\tolds: RailwayServiceOutputs,\n\t\tnews: RailwayServiceInputs,\n\t): Promise<pulumi.dynamic.UpdateResult> {\n\t\tconst client = new RailwayClient(\n\t\t\tresolveCredential(news.token, news.tokenEnvVar),\n\t\t);\n\n\t\tconst updateInput: Record<string, unknown> = {};\n\n\t\tif (olds.name !== news.name) {\n\t\t\tupdateInput.name = news.name;\n\t\t}\n\n\t\tif (news.icon && olds.icon !== news.icon) {\n\t\t\tupdateInput.icon = news.icon;\n\t\t}\n\n\t\tif (Object.keys(updateInput).length > 0) {\n\t\t\tawait client.query(SERVICE_UPDATE, { id, input: updateInput });\n\t\t}\n\n\t\tawait ensureServiceInstance(client, id, news.environmentId);\n\n\t\tconst { droppedHealthcheck } = await applyInstanceConfig(\n\t\t\tclient,\n\t\t\tid,\n\t\t\tnews.environmentId,\n\t\t\tnews,\n\t\t);\n\n\t\t// Instance config changes (source, startCommand, …) only take effect on\n\t\t// the next deployment; image services get none unless the provider\n\t\t// triggers it. Dropped healthcheck fields are re-applied once that\n\t\t// deployment exists (see applyHealthcheckConfig).\n\t\tif (news.source) {\n\t\t\tawait deployServiceInstance(client, id, news.environmentId);\n\n\t\t\tif (droppedHealthcheck) {\n\t\t\t\tawait applyHealthcheckConfig(client, id, news.environmentId, news);\n\t\t\t}\n\t\t}\n\n\t\tconst outs: RailwayServiceOutputs = { ...news, serviceId: id };\n\n\t\treturn { outs };\n\t}\n\n\t/**\n\t * Reads current state for `pulumi refresh` by querying the service by ID.\n\t */\n\tasync read(\n\t\tid: string,\n\t\tprops: RailwayServiceOutputs,\n\t): Promise<pulumi.dynamic.ReadResult> {\n\t\tconst client = new RailwayClient(\n\t\t\tresolveCredential(props.token, props.tokenEnvVar),\n\t\t);\n\n\t\ttry {\n\t\t\tawait client.query<{ service: { id: string; name: string } }>(\n\t\t\t\tSERVICE_QUERY,\n\t\t\t\t{ serviceId: id },\n\t\t\t);\n\t\t} catch {\n\t\t\t// Resource gone → blank id lets refresh reconcile the deletion.\n\t\t\treturn {};\n\t\t}\n\n\t\treturn { id, props: { ...props, serviceId: id } };\n\t}\n\n\t/**\n\t * Deletion is a no-op. A Railway service is a project-level resource shared\n\t * across environments (forked environments adopt it by name), so a single\n\t * environment's destroy must never delete it. Deleting the *environment*\n\t * removes that environment's service instances instead.\n\t */\n\tasync delete(): Promise<void> {\n\t\tpulumi.log.warn(\n\t\t\t\"Railway service deletion skipped — services are project-level; delete the environment to remove its instances\",\n\t\t);\n\t}\n\n\t/**\n\t * Compares old and new inputs to determine what changed.\n\t *\n\t * ProjectId and environmentId changes trigger replacement.\n\t */\n\tasync diff(\n\t\t_id: string,\n\t\tolds: RailwayServiceOutputs,\n\t\tnews: RailwayServiceInputs,\n\t): Promise<pulumi.dynamic.DiffResult> {\n\t\tconst replaces: string[] = [];\n\t\tconst changes: string[] = [];\n\n\t\tif (olds.name !== news.name) {\n\t\t\tchanges.push(\"name\");\n\t\t}\n\n\t\tif (olds.projectId !== news.projectId) {\n\t\t\treplaces.push(\"projectId\");\n\t\t}\n\n\t\tif (olds.environmentId !== news.environmentId) {\n\t\t\treplaces.push(\"environmentId\");\n\t\t}\n\n\t\tif (olds.builder !== news.builder) {\n\t\t\tchanges.push(\"builder\");\n\t\t}\n\n\t\tif (olds.buildCommand !== news.buildCommand) {\n\t\t\tchanges.push(\"buildCommand\");\n\t\t}\n\n\t\tif (olds.startCommand !== news.startCommand) {\n\t\t\tchanges.push(\"startCommand\");\n\t\t}\n\n\t\tif (olds.restartPolicyType !== news.restartPolicyType) {\n\t\t\tchanges.push(\"restartPolicyType\");\n\t\t}\n\n\t\tif (olds.healthcheckPath !== news.healthcheckPath) {\n\t\t\tchanges.push(\"healthcheckPath\");\n\t\t}\n\n\t\tif (olds.healthcheckTimeout !== news.healthcheckTimeout) {\n\t\t\tchanges.push(\"healthcheckTimeout\");\n\t\t}\n\n\t\tif (olds.preDeployCommand !== news.preDeployCommand) {\n\t\t\tchanges.push(\"preDeployCommand\");\n\t\t}\n\n\t\tif (olds.icon !== news.icon) {\n\t\t\tchanges.push(\"icon\");\n\t\t}\n\n\t\treturn {\n\t\t\tchanges: replaces.length > 0 || changes.length > 0,\n\t\t\treplaces,\n\t\t\t// serviceId survives every in-place update (only projectId/environmentId\n\t\t\t// changes replace), so declaring it stable keeps it known during preview —\n\t\t\t// dependents (e.g. RailwayVolume) no longer see an unknown serviceId and\n\t\t\t// stop showing phantom replaces.\n\t\t\tstables: replaces.length === 0 ? [\"serviceId\"] : [],\n\t\t\tdeleteBeforeReplace: true,\n\t\t};\n\t}\n}\n\n/** Internal dynamic resource — not part of the public API. */\nclass RailwayServiceResource extends pulumi.dynamic.Resource {\n\tpublic declare readonly serviceId: pulumi.Output<string>;\n\n\tconstructor(\n\t\tname: string,\n\t\targs: {\n\t\t\ttoken?: pulumi.Input<string>;\n\t\t\ttokenEnvVar?: pulumi.Input<string>;\n\t\t\tprojectId: pulumi.Input<string>;\n\t\t\tenvironmentId: pulumi.Input<string>;\n\t\t\tname: pulumi.Input<string>;\n\t\t\ticon?: pulumi.Input<string>;\n\t\t\tsource?: pulumi.Input<{ image: pulumi.Input<string> }>;\n\t\t\tbuilder?: pulumi.Input<RailwayBuilder>;\n\t\t\tbuildCommand?: pulumi.Input<string>;\n\t\t\tstartCommand?: pulumi.Input<string>;\n\t\t\trestartPolicyType?: pulumi.Input<RailwayRestartPolicy>;\n\t\t\thealthcheckPath?: pulumi.Input<string>;\n\t\t\thealthcheckTimeout?: pulumi.Input<number>;\n\t\t\tpreDeployCommand?: pulumi.Input<string>;\n\t\t},\n\t\topts?: pulumi.CustomResourceOptions,\n\t) {\n\t\tsuper(\n\t\t\tnew RailwayServiceResourceProvider(),\n\t\t\tname,\n\t\t\t{ ...args, serviceId: undefined },\n\t\t\t// The API token flows into dynamic-provider state with the outputs — mark it secret there.\n\t\t\t{ ...opts, additionalSecretOutputs: [\"token\"] },\n\t\t);\n\t}\n}\n\n/** Options type for RailwayService — replaces Pulumi's native `provider` field. */\ntype RailwayServiceOptions = 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\n/** Args for RailwayService. */\nexport interface RailwayServiceArgs {\n\t/** Human-readable service name used for adopt-or-create matching. */\n\tname: pulumi.Input<string>;\n\n\t/** SVG icon URL displayed in the Railway dashboard. */\n\ticon?: pulumi.Input<string>;\n\n\t/** Docker image source for image-based services. */\n\tsource?: pulumi.Input<{ image: pulumi.Input<string> }>;\n\n\t/** Build system to use when building the service. */\n\tbuilder?: pulumi.Input<RailwayBuilder>;\n\n\t/** Shell command executed during the build phase. */\n\tbuildCommand?: pulumi.Input<string>;\n\n\t/** Shell command executed to start the service at runtime. */\n\tstartCommand?: pulumi.Input<string>;\n\n\t/** Restart behavior for the service container. */\n\trestartPolicyType?: pulumi.Input<RailwayRestartPolicy>;\n\n\t/** HTTP path polled for health checks (e.g. `\"/health-check\"`). */\n\thealthcheckPath?: pulumi.Input<string>;\n\n\t/** Seconds to wait for a healthy response before marking unhealthy. */\n\thealthcheckTimeout?: pulumi.Input<number>;\n\n\t/** Shell command executed before the main deploy (e.g. migrations). */\n\tpreDeployCommand?: pulumi.Input<string>;\n}\n\n/**\n * Manages a Railway service with adopt-or-create semantics.\n *\n * @example\n * ```typescript\n * const service = new RailwayService(\"api\", {\n * name: \"api\",\n * builder: RailwayBuilder.RAILPACK,\n * startCommand: \"node dist/index.js\",\n * healthcheckPath: \"/health\",\n * }, { provider, project, environment });\n *\n * // Use serviceId downstream\n * new RailwayVariable(\"api-vars\", {\n * variables: { DATABASE_URL: dbUrl },\n * }, { provider, project, environment, service });\n * ```\n */\nexport class RailwayService extends pulumi.ComponentResource {\n\t/** Railway service UUID. */\n\tpublic readonly id: pulumi.Output<string>;\n\n\tconstructor(\n\t\tname: string,\n\t\targs: RailwayServiceArgs,\n\t\topts: RailwayServiceOptions,\n\t) {\n\t\tconst { provider, project, environment, ...pulumiOpts } = opts;\n\n\t\tsuper(\"infracraft:railway:Service\", name, {}, pulumiOpts);\n\n\t\tconst resource = new RailwayServiceResource(\n\t\t\t`${name}-resource`,\n\t\t\t{\n\t\t\t\ttoken: provider.token,\n\t\t\t\ttokenEnvVar: provider.tokenEnvVar,\n\t\t\t\tprojectId: project.id,\n\t\t\t\tenvironmentId: environment.id,\n\t\t\t\t...args,\n\t\t\t},\n\t\t\t{ parent: this },\n\t\t);\n\n\t\tthis.id = resource.serviceId;\n\n\t\tthis.registerOutputs({ id: this.id });\n\t}\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"service.mjs","names":[],"sources":["../../src/railway/service.ts"],"sourcesContent":["import * as pulumi from \"@pulumi/pulumi\";\nimport { isResolvedString } from \"../dynamic/is-resolved-string\";\nimport { resolveCredential } from \"../dynamic/resolve-credential\";\nimport { RailwayClient } from \"./client\";\nimport type { RailwayEnvironment } from \"./environment\";\nimport type { RailwayProject } from \"./project\";\nimport type { RailwayProvider } from \"./provider\";\n\n/**\n * Railway build system. Enum keys are UPPERCASE per convention; values are\n * Railway's required UPPERCASE wire literals.\n * Note: HEROKU and PAKETO were deprecated Feb 21 2025 and auto-migrated to\n * NIXPACKS by Railway, but remain in the schema and are accepted by the API.\n */\nexport enum RailwayBuilder {\n\tRAILPACK = \"RAILPACK\",\n\tNIXPACKS = \"NIXPACKS\",\n\tDOCKERFILE = \"DOCKERFILE\",\n\tHEROKU = \"HEROKU\",\n\tPAKETO = \"PAKETO\",\n}\n\n/**\n * Railway service restart policy. Controls when Railway restarts the service\n * container after it exits. Default is ON_FAILURE.\n */\nexport enum RailwayRestartPolicy {\n\tON_FAILURE = \"ON_FAILURE\",\n\tALWAYS = \"ALWAYS\",\n\tNEVER = \"NEVER\",\n}\n\n/** Docker image source for a Railway service (e.g. `redis:8-alpine`). */\ninterface RailwayServiceSource {\n\t/** Full Docker image reference including tag. */\n\timage: string;\n}\n\n/** Resolved inputs for the Railway service dynamic provider. */\ninterface RailwayServiceInputs {\n\t/** Railway API bearer token. Absent when `tokenEnvVar` is used instead. */\n\ttoken?: string;\n\n\t/** Env var name resolved to the token when `token` is absent (see `RailwayProviderArgs.tokenEnvVar`). */\n\ttokenEnvVar?: string;\n\n\t/** Railway project UUID. */\n\tprojectId: string;\n\n\t/** Railway environment UUID (e.g. production). */\n\tenvironmentId: string;\n\n\t/** Human-readable service name used for adopt-or-create matching. */\n\tname: string;\n\n\t/** SVG icon URL displayed in the Railway dashboard. */\n\ticon?: string;\n\n\t/** Docker image source for image-based services. */\n\tsource?: RailwayServiceSource;\n\n\t/** Build system to use when building the service. */\n\tbuilder?: RailwayBuilder;\n\n\t/** Shell command executed during the build phase. */\n\tbuildCommand?: string;\n\n\t/** Shell command executed to start the service at runtime. */\n\tstartCommand?: string;\n\n\t/** Restart behavior for the service container. */\n\trestartPolicyType?: RailwayRestartPolicy;\n\n\t/** HTTP path polled for health checks (e.g. `\"/health-check\"`). */\n\thealthcheckPath?: string;\n\n\t/** Seconds to wait for a healthy response before marking unhealthy. */\n\thealthcheckTimeout?: number;\n\n\t/** Shell command executed before the main deploy (e.g. migrations). */\n\tpreDeployCommand?: string;\n}\n\n/** Persisted state for the Railway service, extending inputs with the Railway-assigned ID. */\ninterface RailwayServiceOutputs extends RailwayServiceInputs {\n\t/** Railway-assigned service UUID. */\n\tserviceId: string;\n}\n\nconst SERVICES_QUERY = `\n query($projectId: String!) {\n project(id: $projectId) {\n services {\n edges {\n node {\n id\n name\n }\n }\n }\n }\n }\n`;\n\nconst SERVICE_QUERY = `\n query($serviceId: String!) {\n service(id: $serviceId) {\n id\n name\n }\n }\n`;\n\nconst SERVICE_CREATE = `\n mutation($input: ServiceCreateInput!) {\n serviceCreate(input: $input) {\n id\n name\n }\n }\n`;\n\nconst SERVICE_UPDATE = `\n mutation($id: String!, $input: ServiceUpdateInput!) {\n serviceUpdate(id: $id, input: $input) {\n id\n name\n }\n }\n`;\n\nconst SERVICE_INSTANCE_UPDATE = `\n mutation(\n $serviceId: String!\n $environmentId: String!\n $input: ServiceInstanceUpdateInput!\n ) {\n serviceInstanceUpdate(\n serviceId: $serviceId\n environmentId: $environmentId\n input: $input\n )\n }\n`;\n\nconst SERVICE_CONNECT = `\n mutation($id: String!, $input: ServiceConnectInput!) {\n serviceConnect(id: $id, input: $input) {\n id\n }\n }\n`;\n\nconst SERVICE_INSTANCE_DEPLOY = `\n mutation($serviceId: String!, $environmentId: String!) {\n serviceInstanceDeployV2(serviceId: $serviceId, environmentId: $environmentId)\n }\n`;\n\n/**\n * Triggers a deployment of the service instance in the target environment.\n * Image-sourced services never deploy there on their own: `serviceCreate`'s\n * auto-deploy only reaches the project's DEFAULT environment, and\n * `serviceInstanceUpdate` applies config without redeploying — so a service\n * in any other environment stays undeployed forever and its private DNS name\n * never registers. `environmentTriggersDeploy` is no alternative: it returns\n * success without creating anything for a service that has never deployed in\n * that environment (proven live, 2026-07-06).\n */\nasync function deployServiceInstance(\n\tclient: RailwayClient,\n\tserviceId: string,\n\tenvironmentId: string,\n): Promise<void> {\n\tconst result = await client.query<{ serviceInstanceDeployV2: string }>(\n\t\tSERVICE_INSTANCE_DEPLOY,\n\t\t{ serviceId, environmentId },\n\t);\n\n\tpulumi.log.info(\n\t\t`[infracraft] serviceInstanceDeployV2 created deployment ${result.serviceInstanceDeployV2}`,\n\t);\n}\n\nconst SERVICE_INSTANCE_QUERY = `\n query($serviceId: String!, $environmentId: String!) {\n serviceInstance(serviceId: $serviceId, environmentId: $environmentId) {\n id\n }\n }\n`;\n\nconst ENVIRONMENT_PATCH_COMMIT = `\n mutation($environmentId: String!, $patch: EnvironmentConfig!, $message: String!) {\n environmentPatchCommit(environmentId: $environmentId, patch: $patch, commitMessage: $message)\n }\n`;\n\n/**\n * Guarantees the service has an instance in the target environment.\n *\n * `serviceCreate` materializes an instance ONLY in the environment passed at\n * create time; in every other environment the service is \"skipped\" — no\n * instance exists there, `serviceInstanceUpdate` returns true while silently\n * doing nothing, and `railway up` fails with UPLOAD_FAILED 404 (live incident:\n * first-ever mesh deploy to production). Materialization goes through the\n * staged-changes flow — committing a config patch that keys the service in\n * `services` — which is the documented path for NAMED environments;\n * `environmentUnskipService` is rejected there (\"Can only unskip services in\n * PR environments\", proven live). The commit's return is not trusted: the\n * instance is re-queried afterward and a still-missing instance is a loud\n * error rather than another silent no-op.\n */\nasync function ensureServiceInstance(\n\tclient: RailwayClient,\n\tserviceId: string,\n\tenvironmentId: string,\n): Promise<void> {\n\tconst exists = async (): Promise<boolean> => {\n\t\ttry {\n\t\t\tconst result = await client.query<{\n\t\t\t\tserviceInstance: { id: string } | null;\n\t\t\t}>(SERVICE_INSTANCE_QUERY, { serviceId, environmentId });\n\n\t\t\treturn Boolean(result.serviceInstance);\n\t\t} catch (error) {\n\t\t\tif (error instanceof Error && /not found/i.test(error.message)) {\n\t\t\t\treturn false;\n\t\t\t}\n\n\t\t\tthrow error;\n\t\t}\n\t};\n\n\tif (await exists()) {\n\t\treturn;\n\t}\n\n\tpulumi.log.info(\n\t\t`[infracraft] service ${serviceId} has no instance in environment ${environmentId} — committing a config patch to materialize it`,\n\t);\n\n\tawait client.query(ENVIRONMENT_PATCH_COMMIT, {\n\t\tenvironmentId,\n\t\tpatch: { services: { [serviceId]: {} } },\n\t\tmessage: `[infracraft] add service ${serviceId} to environment`,\n\t});\n\n\tif (!(await exists())) {\n\t\tthrow new Error(\n\t\t\t`Railway service ${serviceId} still has no instance in environment ${environmentId} after the config-patch commit — cannot configure or deploy it`,\n\t\t);\n\t}\n}\n\n/** What `applyInstanceConfig` had to leave out to get the update accepted. */\ninterface InstanceConfigResult {\n\t/** True when the healthcheck fields were dropped from a failed first attempt. */\n\tdroppedHealthcheck: boolean;\n}\n\n/**\n * Applies service instance configuration (builder, commands, healthcheck).\n *\n * The healthcheck fields ride on the FIRST attempt — an instance with existing\n * deployments accepts them, so steady-state applies stay one call. A FRESH\n * instance with no deployment rejects `healthcheckPath` with \"Invalid input\"\n * (live drill finding), so on failure the call is retried without the\n * healthcheck fields and the drop is reported to the caller — which MUST\n * re-apply them after the first deployment exists (the provider itself for\n * image services, the RailwayDeploy monitor for code services). Silent loss is\n * not an option.\n */\nasync function applyInstanceConfig(\n\tclient: RailwayClient,\n\tserviceId: string,\n\tenvironmentId: string,\n\tinputs: RailwayServiceInputs,\n): Promise<InstanceConfigResult> {\n\tconst instanceInput: Record<string, unknown> = {};\n\n\t// Source must be applied PER ENVIRONMENT: `ServiceCreateInput.source` only\n\t// configures the instance of the environment passed at create time, and every\n\t// other environment's instance is born with source=null — deploy triggers\n\t// then no-op silently because there is nothing to deploy.\n\tif (inputs.source) {\n\t\tinstanceInput.source = { image: inputs.source.image };\n\t}\n\n\tif (inputs.builder) {\n\t\tinstanceInput.builder = inputs.builder;\n\t}\n\n\tif (inputs.buildCommand) {\n\t\tinstanceInput.buildCommand = inputs.buildCommand;\n\t}\n\n\tif (inputs.startCommand) {\n\t\tinstanceInput.startCommand = inputs.startCommand;\n\t}\n\n\tif (inputs.restartPolicyType) {\n\t\tinstanceInput.restartPolicyType = inputs.restartPolicyType;\n\t}\n\n\tif (inputs.healthcheckPath) {\n\t\tinstanceInput.healthcheckPath = inputs.healthcheckPath;\n\t}\n\n\tif (inputs.healthcheckTimeout) {\n\t\tinstanceInput.healthcheckTimeout = inputs.healthcheckTimeout;\n\t}\n\n\tif (inputs.preDeployCommand) {\n\t\tinstanceInput.preDeployCommand = inputs.preDeployCommand;\n\t}\n\n\tif (Object.keys(instanceInput).length === 0) {\n\t\treturn { droppedHealthcheck: false };\n\t}\n\n\tconst hasHealthcheck =\n\t\tinstanceInput.healthcheckPath !== undefined ||\n\t\tinstanceInput.healthcheckTimeout !== undefined;\n\n\ttry {\n\t\tawait client.query(SERVICE_INSTANCE_UPDATE, {\n\t\t\tserviceId,\n\t\t\tenvironmentId,\n\t\t\tinput: instanceInput,\n\t\t});\n\n\t\treturn { droppedHealthcheck: false };\n\t} catch (error) {\n\t\tif (!hasHealthcheck) {\n\t\t\tthrow error;\n\t\t}\n\n\t\tpulumi.log.warn(\n\t\t\t`serviceInstanceUpdate failed, retrying without healthcheck fields: ${error}`,\n\t\t);\n\n\t\tdelete instanceInput.healthcheckPath;\n\t\tdelete instanceInput.healthcheckTimeout;\n\n\t\tif (Object.keys(instanceInput).length > 0) {\n\t\t\tawait client.query(SERVICE_INSTANCE_UPDATE, {\n\t\t\t\tserviceId,\n\t\t\t\tenvironmentId,\n\t\t\t\tinput: instanceInput,\n\t\t\t});\n\t\t}\n\n\t\treturn { droppedHealthcheck: true };\n\t}\n}\n\n/**\n * Re-applies ONLY the healthcheck fields after the instance has a deployment.\n * Called when `applyInstanceConfig` had to drop them (fresh instance, no\n * deployment); a failure here would silently lose the healthcheck config\n * forever, so it throws loudly instead.\n */\nasync function applyHealthcheckConfig(\n\tclient: RailwayClient,\n\tserviceId: string,\n\tenvironmentId: string,\n\tinputs: RailwayServiceInputs,\n): Promise<void> {\n\tconst healthcheckInput: Record<string, unknown> = {};\n\n\tif (inputs.healthcheckPath) {\n\t\thealthcheckInput.healthcheckPath = inputs.healthcheckPath;\n\t}\n\n\tif (inputs.healthcheckTimeout) {\n\t\thealthcheckInput.healthcheckTimeout = inputs.healthcheckTimeout;\n\t}\n\n\tif (Object.keys(healthcheckInput).length === 0) {\n\t\treturn;\n\t}\n\n\ttry {\n\t\tawait client.query(SERVICE_INSTANCE_UPDATE, {\n\t\t\tserviceId,\n\t\t\tenvironmentId,\n\t\t\tinput: healthcheckInput,\n\t\t});\n\t} catch (error) {\n\t\tthrow new Error(\n\t\t\t`Railway healthcheck config ${JSON.stringify(healthcheckInput)} could not be applied to service ${serviceId} in environment ${environmentId} even after its deployment was created — failing instead of silently dropping it: ${error}`,\n\t\t);\n\t}\n}\n\n/**\n * Dynamic provider implementing CRUD for Railway services.\n *\n * Uses adopt-or-create on `create()`: queries services by project ID and name\n * before creating a new one. Service instance configuration (builder, commands,\n * healthcheck) is applied via `serviceInstanceUpdate` after create or update.\n *\n * @internal Exported only for unit testing; not part of the public API surface.\n */\nexport class RailwayServiceResourceProvider\n\timplements pulumi.dynamic.ResourceProvider\n{\n\t/**\n\t * Validates inputs at plan time. An empty `source.image` would otherwise\n\t * fail deep inside `serviceInstanceUpdate` with an opaque GraphQL error.\n\t */\n\tasync check(\n\t\t_olds: RailwayServiceInputs,\n\t\tnews: RailwayServiceInputs,\n\t): Promise<pulumi.dynamic.CheckResult<RailwayServiceInputs>> {\n\t\tconst failures: pulumi.dynamic.CheckFailure[] = [];\n\n\t\tif (\n\t\t\tnews.source &&\n\t\t\tisResolvedString(news.source.image) &&\n\t\t\tnews.source.image.trim().length === 0\n\t\t) {\n\t\t\tfailures.push({\n\t\t\t\tproperty: \"source.image\",\n\t\t\t\treason:\n\t\t\t\t\t'source.image must be a non-empty Docker image reference (e.g. \"redis:8-alpine\")',\n\t\t\t});\n\t\t}\n\n\t\tif (\n\t\t\tisResolvedString(news.healthcheckPath) &&\n\t\t\tnews.healthcheckPath.includes(\"-\")\n\t\t) {\n\t\t\tfailures.push({\n\t\t\t\tproperty: \"healthcheckPath\",\n\t\t\t\treason:\n\t\t\t\t\t'Railway rejects any healthcheckPath containing a hyphen with \"Invalid input\" (undocumented; proven by live probe matrix 2026-07-06) — use a hyphen-free path like \"/healthcheck\"',\n\t\t\t});\n\t\t}\n\n\t\treturn { inputs: news, failures };\n\t}\n\n\t/**\n\t * Creates or adopts a Railway service by name, then applies instance config.\n\t */\n\tasync create(\n\t\tinputs: RailwayServiceInputs,\n\t): Promise<pulumi.dynamic.CreateResult> {\n\t\tconst client = new RailwayClient(\n\t\t\tresolveCredential(inputs.token, inputs.tokenEnvVar),\n\t\t);\n\n\t\tconst result = await client.query<{\n\t\t\tproject: {\n\t\t\t\tservices: { edges: Array<{ node: { id: string; name: string } }> };\n\t\t\t};\n\t\t}>(SERVICES_QUERY, { projectId: inputs.projectId });\n\n\t\tlet serviceId = result.project.services.edges.find(\n\t\t\t(edge) => edge.node.name === inputs.name,\n\t\t)?.node.id;\n\n\t\tif (serviceId) {\n\t\t\tpulumi.log.info(\n\t\t\t\t`Adopted existing Railway service \"${inputs.name}\" (${serviceId})`,\n\t\t\t);\n\t\t} else {\n\t\t\tconst createInput: Record<string, unknown> = {\n\t\t\t\tprojectId: inputs.projectId,\n\t\t\t\tenvironmentId: inputs.environmentId,\n\t\t\t\tname: inputs.name,\n\t\t\t};\n\n\t\t\tif (inputs.source) {\n\t\t\t\tcreateInput.source = { image: inputs.source.image };\n\t\t\t}\n\n\t\t\tconst created = await client.query<{\n\t\t\t\tserviceCreate: { id: string; name: string };\n\t\t\t}>(SERVICE_CREATE, { input: createInput });\n\n\t\t\tserviceId = created.serviceCreate.id;\n\n\t\t\tpulumi.log.info(\n\t\t\t\t`Created Railway service \"${inputs.name}\" (${serviceId})`,\n\t\t\t);\n\n\t\t\tif (inputs.source) {\n\t\t\t\tawait client.query(SERVICE_CONNECT, {\n\t\t\t\t\tid: serviceId,\n\t\t\t\t\tinput: { image: inputs.source.image },\n\t\t\t\t});\n\t\t\t}\n\n\t\t\tif (inputs.icon) {\n\t\t\t\tawait client.query(SERVICE_UPDATE, {\n\t\t\t\t\tid: serviceId,\n\t\t\t\t\tinput: { icon: inputs.icon },\n\t\t\t\t});\n\t\t\t}\n\t\t}\n\n\t\tawait ensureServiceInstance(client, serviceId, inputs.environmentId);\n\n\t\tconst { droppedHealthcheck } = await applyInstanceConfig(\n\t\t\tclient,\n\t\t\tserviceId,\n\t\t\tinputs.environmentId,\n\t\t\tinputs,\n\t\t);\n\n\t\t// Image services have no `railway up` step (see RailwayDeploy for code\n\t\t// services) — the provider owns their deploy, and with it the post-deploy\n\t\t// re-apply of any dropped healthcheck fields. Code services get theirs\n\t\t// from the RailwayDeploy monitor instead.\n\t\tif (inputs.source) {\n\t\t\tawait deployServiceInstance(client, serviceId, inputs.environmentId);\n\n\t\t\tif (droppedHealthcheck) {\n\t\t\t\tawait applyHealthcheckConfig(\n\t\t\t\t\tclient,\n\t\t\t\t\tserviceId,\n\t\t\t\t\tinputs.environmentId,\n\t\t\t\t\tinputs,\n\t\t\t\t);\n\t\t\t}\n\t\t}\n\n\t\tconst outs: RailwayServiceOutputs = { ...inputs, serviceId };\n\n\t\treturn { id: serviceId, outs };\n\t}\n\n\t/**\n\t * Updates service name/icon and re-applies instance configuration.\n\t */\n\tasync update(\n\t\tid: string,\n\t\tolds: RailwayServiceOutputs,\n\t\tnews: RailwayServiceInputs,\n\t): Promise<pulumi.dynamic.UpdateResult> {\n\t\tconst client = new RailwayClient(\n\t\t\tresolveCredential(news.token, news.tokenEnvVar),\n\t\t);\n\n\t\tconst updateInput: Record<string, unknown> = {};\n\n\t\tif (olds.name !== news.name) {\n\t\t\tupdateInput.name = news.name;\n\t\t}\n\n\t\tif (news.icon && olds.icon !== news.icon) {\n\t\t\tupdateInput.icon = news.icon;\n\t\t}\n\n\t\tif (Object.keys(updateInput).length > 0) {\n\t\t\tawait client.query(SERVICE_UPDATE, { id, input: updateInput });\n\t\t}\n\n\t\tawait ensureServiceInstance(client, id, news.environmentId);\n\n\t\tconst { droppedHealthcheck } = await applyInstanceConfig(\n\t\t\tclient,\n\t\t\tid,\n\t\t\tnews.environmentId,\n\t\t\tnews,\n\t\t);\n\n\t\t// Instance config changes (source, startCommand, …) only take effect on\n\t\t// the next deployment; image services get none unless the provider\n\t\t// triggers it. Dropped healthcheck fields are re-applied once that\n\t\t// deployment exists (see applyHealthcheckConfig).\n\t\tif (news.source) {\n\t\t\tawait deployServiceInstance(client, id, news.environmentId);\n\n\t\t\tif (droppedHealthcheck) {\n\t\t\t\tawait applyHealthcheckConfig(client, id, news.environmentId, news);\n\t\t\t}\n\t\t}\n\n\t\tconst outs: RailwayServiceOutputs = { ...news, serviceId: id };\n\n\t\treturn { outs };\n\t}\n\n\t/**\n\t * Reads current state for `pulumi refresh` by querying the service by ID.\n\t */\n\tasync read(\n\t\tid: string,\n\t\tprops: RailwayServiceOutputs,\n\t): Promise<pulumi.dynamic.ReadResult> {\n\t\tconst client = new RailwayClient(\n\t\t\tresolveCredential(props.token, props.tokenEnvVar),\n\t\t);\n\n\t\ttry {\n\t\t\tawait client.query<{ service: { id: string; name: string } }>(\n\t\t\t\tSERVICE_QUERY,\n\t\t\t\t{ serviceId: id },\n\t\t\t);\n\t\t} catch {\n\t\t\t// Resource gone → blank id lets refresh reconcile the deletion.\n\t\t\treturn {};\n\t\t}\n\n\t\treturn { id, props: { ...props, serviceId: id } };\n\t}\n\n\t/**\n\t * Deletion is a no-op. A Railway service is a project-level resource shared\n\t * across environments (forked environments adopt it by name), so a single\n\t * environment's destroy must never delete it. Deleting the *environment*\n\t * removes that environment's service instances instead.\n\t */\n\tasync delete(): Promise<void> {\n\t\tpulumi.log.warn(\n\t\t\t\"Railway service deletion skipped — services are project-level; delete the environment to remove its instances\",\n\t\t);\n\t}\n\n\t/**\n\t * Compares old and new inputs to determine what changed.\n\t *\n\t * ProjectId and environmentId changes trigger replacement.\n\t */\n\tasync diff(\n\t\t_id: string,\n\t\tolds: RailwayServiceOutputs,\n\t\tnews: RailwayServiceInputs,\n\t): Promise<pulumi.dynamic.DiffResult> {\n\t\tconst replaces: string[] = [];\n\t\tconst changes: string[] = [];\n\n\t\tif (olds.name !== news.name) {\n\t\t\tchanges.push(\"name\");\n\t\t}\n\n\t\tif (olds.projectId !== news.projectId) {\n\t\t\treplaces.push(\"projectId\");\n\t\t}\n\n\t\tif (olds.environmentId !== news.environmentId) {\n\t\t\treplaces.push(\"environmentId\");\n\t\t}\n\n\t\tif (olds.builder !== news.builder) {\n\t\t\tchanges.push(\"builder\");\n\t\t}\n\n\t\tif (olds.buildCommand !== news.buildCommand) {\n\t\t\tchanges.push(\"buildCommand\");\n\t\t}\n\n\t\tif (olds.startCommand !== news.startCommand) {\n\t\t\tchanges.push(\"startCommand\");\n\t\t}\n\n\t\tif (olds.restartPolicyType !== news.restartPolicyType) {\n\t\t\tchanges.push(\"restartPolicyType\");\n\t\t}\n\n\t\tif (olds.healthcheckPath !== news.healthcheckPath) {\n\t\t\tchanges.push(\"healthcheckPath\");\n\t\t}\n\n\t\tif (olds.healthcheckTimeout !== news.healthcheckTimeout) {\n\t\t\tchanges.push(\"healthcheckTimeout\");\n\t\t}\n\n\t\tif (olds.preDeployCommand !== news.preDeployCommand) {\n\t\t\tchanges.push(\"preDeployCommand\");\n\t\t}\n\n\t\tif (olds.icon !== news.icon) {\n\t\t\tchanges.push(\"icon\");\n\t\t}\n\n\t\treturn {\n\t\t\tchanges: replaces.length > 0 || changes.length > 0,\n\t\t\treplaces,\n\t\t\t// serviceId survives every in-place update (only projectId/environmentId\n\t\t\t// changes replace), so declaring it stable keeps it known during preview —\n\t\t\t// dependents (e.g. RailwayVolume) no longer see an unknown serviceId and\n\t\t\t// stop showing phantom replaces.\n\t\t\tstables: replaces.length === 0 ? [\"serviceId\"] : [],\n\t\t\tdeleteBeforeReplace: true,\n\t\t};\n\t}\n}\n\n/** Internal dynamic resource — not part of the public API. */\nclass RailwayServiceResource extends pulumi.dynamic.Resource {\n\tpublic declare readonly serviceId: pulumi.Output<string>;\n\n\tconstructor(\n\t\tname: string,\n\t\targs: {\n\t\t\ttoken?: pulumi.Input<string>;\n\t\t\ttokenEnvVar?: pulumi.Input<string>;\n\t\t\tprojectId: pulumi.Input<string>;\n\t\t\tenvironmentId: pulumi.Input<string>;\n\t\t\tname: pulumi.Input<string>;\n\t\t\ticon?: pulumi.Input<string>;\n\t\t\tsource?: pulumi.Input<{ image: pulumi.Input<string> }>;\n\t\t\tbuilder?: pulumi.Input<RailwayBuilder>;\n\t\t\tbuildCommand?: pulumi.Input<string>;\n\t\t\tstartCommand?: pulumi.Input<string>;\n\t\t\trestartPolicyType?: pulumi.Input<RailwayRestartPolicy>;\n\t\t\thealthcheckPath?: pulumi.Input<string>;\n\t\t\thealthcheckTimeout?: pulumi.Input<number>;\n\t\t\tpreDeployCommand?: pulumi.Input<string>;\n\t\t},\n\t\topts?: pulumi.CustomResourceOptions,\n\t) {\n\t\tsuper(\n\t\t\tnew RailwayServiceResourceProvider(),\n\t\t\tname,\n\t\t\t{ ...args, serviceId: undefined },\n\t\t\t// The API token flows into dynamic-provider state with the outputs — mark it secret there.\n\t\t\t{ ...opts, additionalSecretOutputs: [\"token\"] },\n\t\t);\n\t}\n}\n\n/** Options type for RailwayService — replaces Pulumi's native `provider` field. */\ntype RailwayServiceOptions = 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\n/** Args for RailwayService. */\nexport interface RailwayServiceArgs {\n\t/** Human-readable service name used for adopt-or-create matching. */\n\tname: pulumi.Input<string>;\n\n\t/** SVG icon URL displayed in the Railway dashboard. */\n\ticon?: pulumi.Input<string>;\n\n\t/** Docker image source for image-based services. */\n\tsource?: pulumi.Input<{ image: pulumi.Input<string> }>;\n\n\t/** Build system to use when building the service. */\n\tbuilder?: pulumi.Input<RailwayBuilder>;\n\n\t/** Shell command executed during the build phase. */\n\tbuildCommand?: pulumi.Input<string>;\n\n\t/** Shell command executed to start the service at runtime. */\n\tstartCommand?: pulumi.Input<string>;\n\n\t/** Restart behavior for the service container. */\n\trestartPolicyType?: pulumi.Input<RailwayRestartPolicy>;\n\n\t/** HTTP path polled for health checks (e.g. `\"/health-check\"`). */\n\thealthcheckPath?: pulumi.Input<string>;\n\n\t/** Seconds to wait for a healthy response before marking unhealthy. */\n\thealthcheckTimeout?: pulumi.Input<number>;\n\n\t/** Shell command executed before the main deploy (e.g. migrations). */\n\tpreDeployCommand?: pulumi.Input<string>;\n}\n\n/**\n * Manages a Railway service with adopt-or-create semantics.\n *\n * @example\n * ```typescript\n * const service = new RailwayService(\"api\", {\n * name: \"api\",\n * builder: RailwayBuilder.RAILPACK,\n * startCommand: \"node dist/index.js\",\n * healthcheckPath: \"/health\",\n * }, { provider, project, environment });\n *\n * // Use serviceId downstream\n * new RailwayVariable(\"api-vars\", {\n * variables: { DATABASE_URL: dbUrl },\n * }, { provider, project, environment, service });\n * ```\n */\nexport class RailwayService extends pulumi.ComponentResource {\n\t/** Railway service UUID. */\n\tpublic readonly id: pulumi.Output<string>;\n\n\tconstructor(\n\t\tname: string,\n\t\targs: RailwayServiceArgs,\n\t\topts: RailwayServiceOptions,\n\t) {\n\t\tconst { provider, project, environment, ...pulumiOpts } = opts;\n\n\t\tsuper(\"infracraft:railway:Service\", name, {}, pulumiOpts);\n\n\t\tconst resource = new RailwayServiceResource(\n\t\t\t`${name}-resource`,\n\t\t\t{\n\t\t\t\ttoken: provider.token,\n\t\t\t\ttokenEnvVar: provider.tokenEnvVar,\n\t\t\t\tprojectId: project.id,\n\t\t\t\tenvironmentId: environment.id,\n\t\t\t\t...args,\n\t\t\t},\n\t\t\t{ parent: this },\n\t\t);\n\n\t\tthis.id = resource.serviceId;\n\n\t\tthis.registerOutputs({ id: this.id });\n\t}\n}\n"],"mappings":"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"}
|
package/dist/vercel/domain.cjs
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
|
|
2
2
|
const require_chunk = require('../chunk-BVYJZCqc.cjs');
|
|
3
|
-
const require_errors_api_not_found_error = require('../errors/api-not-found-error.cjs');
|
|
4
3
|
const require_dynamic_resolve_credential = require('../dynamic/resolve-credential.cjs');
|
|
4
|
+
const require_errors_api_not_found_error = require('../errors/api-not-found-error.cjs');
|
|
5
5
|
const require_vercel_client = require('./client.cjs');
|
|
6
6
|
let _pulumi_pulumi = require("@pulumi/pulumi");
|
|
7
7
|
_pulumi_pulumi = require_chunk.__toESM(_pulumi_pulumi, 1);
|
package/dist/vercel/domain.mjs
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { t as __name } from "../chunk-OPjESj5l.mjs";
|
|
2
|
-
import { ApiNotFoundError } from "../errors/api-not-found-error.mjs";
|
|
3
2
|
import { resolveCredential } from "../dynamic/resolve-credential.mjs";
|
|
3
|
+
import { ApiNotFoundError } from "../errors/api-not-found-error.mjs";
|
|
4
4
|
import { VercelClient } from "./client.mjs";
|
|
5
5
|
import * as pulumi from "@pulumi/pulumi";
|
|
6
6
|
|
package/dist/vercel/project.cjs
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
|
|
2
2
|
const require_chunk = require('../chunk-BVYJZCqc.cjs');
|
|
3
|
-
const require_errors_api_not_found_error = require('../errors/api-not-found-error.cjs');
|
|
4
3
|
const require_dynamic_is_resolved_string = require('../dynamic/is-resolved-string.cjs');
|
|
5
4
|
const require_dynamic_resolve_credential = require('../dynamic/resolve-credential.cjs');
|
|
5
|
+
const require_errors_api_not_found_error = require('../errors/api-not-found-error.cjs');
|
|
6
6
|
const require_vercel_client = require('./client.cjs');
|
|
7
7
|
let _pulumi_pulumi = require("@pulumi/pulumi");
|
|
8
8
|
_pulumi_pulumi = require_chunk.__toESM(_pulumi_pulumi, 1);
|
package/dist/vercel/project.mjs
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { t as __name } from "../chunk-OPjESj5l.mjs";
|
|
2
|
-
import { ApiNotFoundError } from "../errors/api-not-found-error.mjs";
|
|
3
2
|
import { isResolvedString } from "../dynamic/is-resolved-string.mjs";
|
|
4
3
|
import { resolveCredential, resolveCredentialOutput } from "../dynamic/resolve-credential.mjs";
|
|
4
|
+
import { ApiNotFoundError } from "../errors/api-not-found-error.mjs";
|
|
5
5
|
import { VercelClient } from "./client.mjs";
|
|
6
6
|
import * as pulumi from "@pulumi/pulumi";
|
|
7
7
|
|
package/dist/vercel/variable.cjs
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
|
|
2
2
|
const require_chunk = require('../chunk-BVYJZCqc.cjs');
|
|
3
|
-
const require_errors_api_not_found_error = require('../errors/api-not-found-error.cjs');
|
|
4
3
|
const require_dynamic_resolve_credential = require('../dynamic/resolve-credential.cjs');
|
|
4
|
+
const require_errors_api_not_found_error = require('../errors/api-not-found-error.cjs');
|
|
5
5
|
const require_vercel_client = require('./client.cjs');
|
|
6
6
|
let _pulumi_pulumi = require("@pulumi/pulumi");
|
|
7
7
|
_pulumi_pulumi = require_chunk.__toESM(_pulumi_pulumi, 1);
|
package/dist/vercel/variable.mjs
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { t as __name } from "../chunk-OPjESj5l.mjs";
|
|
2
|
-
import { ApiNotFoundError } from "../errors/api-not-found-error.mjs";
|
|
3
2
|
import { resolveCredential } from "../dynamic/resolve-credential.mjs";
|
|
3
|
+
import { ApiNotFoundError } from "../errors/api-not-found-error.mjs";
|
|
4
4
|
import { VercelClient } from "./client.mjs";
|
|
5
5
|
import * as pulumi from "@pulumi/pulumi";
|
|
6
6
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@infracraft/pulumi",
|
|
3
|
-
"version": "1.30.
|
|
3
|
+
"version": "1.30.3",
|
|
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.",
|