@infracraft/pulumi 1.30.2 → 1.31.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +19 -9
- 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/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/bin/apply-env.cjs +37 -0
- package/dist/vercel/bin/apply-env.cjs.map +1 -0
- package/dist/vercel/bin/apply-env.d.cts +1 -0
- package/dist/vercel/bin/apply-env.d.mts +1 -0
- package/dist/vercel/bin/apply-env.mjs +38 -0
- package/dist/vercel/bin/apply-env.mjs.map +1 -0
- package/dist/vercel/deploy.cjs +37 -9
- package/dist/vercel/deploy.cjs.map +1 -1
- package/dist/vercel/deploy.d.cts +24 -2
- package/dist/vercel/deploy.d.cts.map +1 -1
- package/dist/vercel/deploy.d.mts +24 -2
- package/dist/vercel/deploy.d.mts.map +1 -1
- package/dist/vercel/deploy.mjs +36 -9
- package/dist/vercel/deploy.mjs.map +1 -1
- package/dist/vercel/domain.cjs +1 -1
- package/dist/vercel/domain.mjs +1 -1
- package/dist/vercel/env-applier.cjs +48 -0
- package/dist/vercel/env-applier.cjs.map +1 -0
- package/dist/vercel/env-applier.d.cts +31 -0
- package/dist/vercel/env-applier.d.cts.map +1 -0
- package/dist/vercel/env-applier.d.mts +31 -0
- package/dist/vercel/env-applier.d.mts.map +1 -0
- package/dist/vercel/env-applier.mjs +47 -0
- package/dist/vercel/env-applier.mjs.map +1 -0
- package/dist/vercel/env-var-api.cjs +83 -0
- package/dist/vercel/env-var-api.cjs.map +1 -0
- package/dist/vercel/env-var-api.d.cts +34 -0
- package/dist/vercel/env-var-api.d.cts.map +1 -0
- package/dist/vercel/env-var-api.d.mts +34 -0
- package/dist/vercel/env-var-api.d.mts.map +1 -0
- package/dist/vercel/env-var-api.mjs +79 -0
- package/dist/vercel/env-var-api.mjs.map +1 -0
- package/dist/vercel/project.cjs +1 -1
- package/dist/vercel/project.mjs +1 -1
- package/dist/vercel/variable.cjs +18 -68
- package/dist/vercel/variable.cjs.map +1 -1
- package/dist/vercel/variable.d.cts +11 -0
- package/dist/vercel/variable.d.cts.map +1 -1
- package/dist/vercel/variable.d.mts +11 -0
- package/dist/vercel/variable.d.mts.map +1 -1
- package/dist/vercel/variable.mjs +14 -64
- package/dist/vercel/variable.mjs.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -33,7 +33,7 @@ Native Pulumi providers with adopt-or-create semantics and deploy orchestration.
|
|
|
33
33
|
|---|---|---|---|
|
|
34
34
|
| 🚂 | **Railway** | `@infracraft/pulumi/railway` | The only Pulumi provider for Railway. Projects, environments, services, variables, volumes, domains, deploy tokens, deploys. |
|
|
35
35
|
| 🐘 | **Neon** | `@infracraft/pulumi/neon` | Adopt-or-create layer for Neon Postgres. Projects, branches, endpoints, roles, databases. |
|
|
36
|
-
| ▲ | **Vercel** | `@infracraft/pulumi/vercel` | Projects with adopt-or-create, deploy orchestration, custom domains, marketplace resources
|
|
36
|
+
| ▲ | **Vercel** | `@infracraft/pulumi/vercel` | Projects with adopt-or-create, deploy orchestration with deploy-integrated env vars, custom domains, and marketplace resources. |
|
|
37
37
|
| 🎯 | **Fly.io** | `@infracraft/pulumi/fly` | App, Secret, Volume, Certificate, IP, and Deploy resources via the Machines REST API and Fly GraphQL API. |
|
|
38
38
|
| 🤖 | **Agents** | `@infracraft/pulumi/agents` | Emit operating hints for AI coding agents working on the stack. |
|
|
39
39
|
| #️⃣ | **Hash** | `@infracraft/pulumi/hash` | Deterministic directory/env-var/app hashing for deploy triggers. |
|
|
@@ -232,7 +232,6 @@ Leave the version unset until the first rotation is needed. Identity changes (na
|
|
|
232
232
|
import {
|
|
233
233
|
VercelProvider,
|
|
234
234
|
VercelProject,
|
|
235
|
-
VercelVariable,
|
|
236
235
|
VercelDeploy,
|
|
237
236
|
VercelDomain,
|
|
238
237
|
VercelIntegration,
|
|
@@ -255,12 +254,12 @@ const project = new VercelProject("web", {
|
|
|
255
254
|
// project.url is a full https:// URL — custom domain or <name>.vercel.app
|
|
256
255
|
export const url = project.url
|
|
257
256
|
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
257
|
+
// Env vars are applied by the deploy command itself, right before
|
|
258
|
+
// `vercel deploy` — see "Deploy-integrated env vars" below. A change to any
|
|
259
|
+
// variable redeploys automatically (a non-secret digest joins the triggers).
|
|
262
260
|
new VercelDeploy("web-deploy", {
|
|
263
|
-
|
|
261
|
+
variables: { NEXT_PUBLIC_API_URL: apiUrl },
|
|
262
|
+
triggers: [hash("apps/web")],
|
|
264
263
|
}, { provider, project })
|
|
265
264
|
|
|
266
265
|
// Custom domain: point the domain's DNS CNAME at `cnameTarget`
|
|
@@ -295,8 +294,8 @@ new VercelResourceConnection("kv-conn", {
|
|
|
295
294
|
|---|---|---|
|
|
296
295
|
| `VercelProvider` | — | `token` or `tokenEnvVar` (see [Provider credentials](#provider-credentials)) + `teamId` |
|
|
297
296
|
| `VercelProject` | `.id`, `.url` | `.url` is a full `https://` URL; prefers the custom production domain over `<name>.vercel.app`. Deletes the project on destroy — `protect: true` production projects |
|
|
298
|
-
| `VercelVariable` | `.contentHash` |
|
|
299
|
-
| `VercelDeploy` | `.deploymentUrl` | Runs `vercel deploy --prod --yes` |
|
|
297
|
+
| `VercelVariable` | `.contentHash` | ⚠️ Deprecated — prefer `VercelDeploy.variables`. As a dynamic resource it hits a Pulumi engine-level marshal bug on clean-slate creates ("Unexpected struct type", alternating pass/fail across identical from-zero runs, reproduced with plain-literal inputs) — see [Deploy-integrated env vars](#deploy-integrated-env-vars). Stays exported for existing stacks |
|
|
298
|
+
| `VercelDeploy` | `.deploymentUrl` | Runs `vercel deploy --prod --yes`; optional `variables` map is upserted (production + preview + development) by the deploy command itself right before the deploy, and hashed into the triggers so variable changes redeploy |
|
|
300
299
|
| `VercelDomain` | `.name`, `.verified`, `.cnameTarget` | Attaches a custom domain to a project (adopt-or-create); `.cnameTarget` is Vercel's own DNS recommendation for that specific domain |
|
|
301
300
|
| `VercelIntegration` | `.configurationId` (`icfg_…`) | Resolves an installed marketplace integration by slug (install it once via the dashboard first) |
|
|
302
301
|
| `VercelMarketplaceResource` | `.id`, `.externalResourceId`, `.status` | Provisions a marketplace store; `type` is the integration product ID or slug |
|
|
@@ -305,6 +304,17 @@ new VercelResourceConnection("kv-conn", {
|
|
|
305
304
|
|
|
306
305
|
**Helpers:** `VERCEL_FRAMEWORKS` (const array), `VercelFramework` (derived union type)
|
|
307
306
|
|
|
307
|
+
### Deploy-integrated env vars
|
|
308
|
+
|
|
309
|
+
`VercelDeploy.variables` applies env vars inside the deploy command flow instead of as a Pulumi resource, because the dynamic-resource path (`VercelVariable`) hits a Pulumi engine-internal stateful bug on clean-slate first creates: "Unexpected struct type", strictly alternating pass/fail across identical from-zero runs, reproduced with plain-literal inputs — zero Outputs or secrets — on matched CLI/SDK versions, with four structural theories falsified by bisection. No input shape avoids an engine-internal bug, so the fix is architectural: keep env vars off the dynamic-provider marshal path entirely, the same way `RailwayDeploy`'s monitor bin owns Railway's imperative deploy steps.
|
|
310
|
+
|
|
311
|
+
When `variables` is set, the deploy command runs a standalone applier bin (`node dist/vercel/bin/apply-env.mjs`) before `vercel deploy --prod --yes`:
|
|
312
|
+
|
|
313
|
+
- Each entry is upserted for production + preview + development — created, or updated in place when the key already exists (ENV_CONFLICT) — via the same REST logic `VercelVariable` uses.
|
|
314
|
+
- The key→value payload travels as a secret JSON value in the command environment (`IC_VC_ENV_JSON`), masked in state and never present in the script text pulumi-command echoes on failure. Values must be known at preview (config-derived) — the same class as the token the command env already carries.
|
|
315
|
+
- A non-secret digest of the variables joins the command triggers automatically, so any variable change redeploys.
|
|
316
|
+
- The applier logs one line per applied key (names only, never values) and exits non-zero on the first failed key — the deploy never runs against a half-applied environment.
|
|
317
|
+
|
|
308
318
|
## Fly.io
|
|
309
319
|
|
|
310
320
|
```typescript
|
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/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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"}
|