@infracraft/pulumi 1.30.2 → 1.30.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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);
@@ -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
 
@@ -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);
@@ -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
 
@@ -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);
@@ -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
 
@@ -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);
@@ -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
 
@@ -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);
@@ -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
 
@@ -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);
@@ -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
 
@@ -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);
@@ -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
 
@@ -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);
@@ -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
 
@@ -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;EA6Hf;;;;EAvHL,KAAA,CACL,KAAA,EAAO,oBAAA,EACP,IAAA,EAAM,oBAAA,GACJ,OAAA,CAAQ,MAAA,CAAO,OAAA,CAAQ,WAAA,CAAY,oBAAA;EA+LtB;;;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;EA7Hf;;;EA6KL,IAAA,CACL,EAAA,UACA,KAAA,EAAO,qBAAA,GACL,OAAA,CAAQ,MAAA,CAAO,OAAA,CAAQ,UAAA;EAzKlB;;;;;;EAiMF,MAAA,CAAA,GAAU,OAAA;EA/LU;;;;;EA0MpB,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
+ {"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;EA6Hf;;;;EAvHL,KAAA,CACL,KAAA,EAAO,oBAAA,EACP,IAAA,EAAM,oBAAA,GACJ,OAAA,CAAQ,MAAA,CAAO,OAAA,CAAQ,WAAA,CAAY,oBAAA;EA+LtB;;;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;EA7Hf;;;EA6KL,IAAA,CACL,EAAA,UACA,KAAA,EAAO,qBAAA,GACL,OAAA,CAAQ,MAAA,CAAO,OAAA,CAAQ,UAAA;EAzKlB;;;;;;EAiMF,MAAA,CAAA,GAAU,OAAA;EA/LU;;;;;EA0MpB,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
+ {"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"}
@@ -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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"}
@@ -1,7 +1,7 @@
1
1
  Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
2
2
  const require_chunk = require('../chunk-BVYJZCqc.cjs');
3
- const require_errors_api_not_found_error = require('../errors/api-not-found-error.cjs');
4
3
  const require_dynamic_resolve_credential = require('../dynamic/resolve-credential.cjs');
4
+ const require_errors_api_not_found_error = require('../errors/api-not-found-error.cjs');
5
5
  const require_vercel_client = require('./client.cjs');
6
6
  let _pulumi_pulumi = require("@pulumi/pulumi");
7
7
  _pulumi_pulumi = require_chunk.__toESM(_pulumi_pulumi, 1);
@@ -1,6 +1,6 @@
1
1
  import { t as __name } from "../chunk-OPjESj5l.mjs";
2
- import { ApiNotFoundError } from "../errors/api-not-found-error.mjs";
3
2
  import { resolveCredential } from "../dynamic/resolve-credential.mjs";
3
+ import { ApiNotFoundError } from "../errors/api-not-found-error.mjs";
4
4
  import { VercelClient } from "./client.mjs";
5
5
  import * as pulumi from "@pulumi/pulumi";
6
6
 
@@ -1,8 +1,8 @@
1
1
  Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
2
2
  const require_chunk = require('../chunk-BVYJZCqc.cjs');
3
- const require_errors_api_not_found_error = require('../errors/api-not-found-error.cjs');
4
3
  const require_dynamic_is_resolved_string = require('../dynamic/is-resolved-string.cjs');
5
4
  const require_dynamic_resolve_credential = require('../dynamic/resolve-credential.cjs');
5
+ const require_errors_api_not_found_error = require('../errors/api-not-found-error.cjs');
6
6
  const require_vercel_client = require('./client.cjs');
7
7
  let _pulumi_pulumi = require("@pulumi/pulumi");
8
8
  _pulumi_pulumi = require_chunk.__toESM(_pulumi_pulumi, 1);
@@ -1,7 +1,7 @@
1
1
  import { t as __name } from "../chunk-OPjESj5l.mjs";
2
- import { ApiNotFoundError } from "../errors/api-not-found-error.mjs";
3
2
  import { isResolvedString } from "../dynamic/is-resolved-string.mjs";
4
3
  import { resolveCredential, resolveCredentialOutput } from "../dynamic/resolve-credential.mjs";
4
+ import { ApiNotFoundError } from "../errors/api-not-found-error.mjs";
5
5
  import { VercelClient } from "./client.mjs";
6
6
  import * as pulumi from "@pulumi/pulumi";
7
7
 
@@ -1,7 +1,7 @@
1
1
  Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
2
2
  const require_chunk = require('../chunk-BVYJZCqc.cjs');
3
- const require_errors_api_not_found_error = require('../errors/api-not-found-error.cjs');
4
3
  const require_dynamic_resolve_credential = require('../dynamic/resolve-credential.cjs');
4
+ const require_errors_api_not_found_error = require('../errors/api-not-found-error.cjs');
5
5
  const require_vercel_client = require('./client.cjs');
6
6
  let _pulumi_pulumi = require("@pulumi/pulumi");
7
7
  _pulumi_pulumi = require_chunk.__toESM(_pulumi_pulumi, 1);
@@ -1,6 +1,6 @@
1
1
  import { t as __name } from "../chunk-OPjESj5l.mjs";
2
- import { ApiNotFoundError } from "../errors/api-not-found-error.mjs";
3
2
  import { resolveCredential } from "../dynamic/resolve-credential.mjs";
3
+ import { ApiNotFoundError } from "../errors/api-not-found-error.mjs";
4
4
  import { VercelClient } from "./client.mjs";
5
5
  import * as pulumi from "@pulumi/pulumi";
6
6
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@infracraft/pulumi",
3
- "version": "1.30.2",
3
+ "version": "1.30.3",
4
4
  "type": "module",
5
5
  "license": "MIT",
6
6
  "description": "Native Pulumi providers for Railway, Neon, Vercel, and Fly.io with adopt-or-create semantics and deploy orchestration. No Terraform bridge.",