@infracraft/pulumi 0.1.2 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of @infracraft/pulumi might be problematic. Click here for more details.
- package/dist/neon/role.cjs +1 -1
- package/dist/neon/role.cjs.map +1 -1
- package/dist/neon/role.mjs +1 -1
- package/dist/neon/role.mjs.map +1 -1
- package/dist/railway/deploy.cjs.map +1 -1
- package/dist/railway/deploy.d.cts +2 -2
- package/dist/railway/deploy.d.cts.map +1 -1
- package/dist/railway/deploy.d.mts +2 -2
- package/dist/railway/deploy.d.mts.map +1 -1
- package/dist/railway/deploy.mjs.map +1 -1
- package/dist/railway/service.cjs +9 -6
- package/dist/railway/service.cjs.map +1 -1
- package/dist/railway/service.d.cts.map +1 -1
- package/dist/railway/service.d.mts.map +1 -1
- package/dist/railway/service.mjs +9 -6
- package/dist/railway/service.mjs.map +1 -1
- package/package.json +1 -1
package/dist/neon/role.cjs
CHANGED
package/dist/neon/role.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"role.cjs","names":["NeonClient","pulumi"],"sources":["../../src/neon/role.ts"],"sourcesContent":["import * as pulumi from \"@pulumi/pulumi\";\nimport { NeonClient } from \"./client.js\";\n\n/** Resolved inputs for the Neon role dynamic provider. */\nexport interface NeonRoleInputs {\n\t/** Neon API key. */\n\tapiKey: string;\n\n\t/** Neon project ID. */\n\tprojectId: string;\n\n\t/** Branch ID the role belongs to. */\n\tbranchId: string;\n\n\t/** Role name (e.g. `\"neondb_owner\"`). */\n\tname: string;\n}\n\n/** Persisted state for the Neon role. */\ninterface NeonRoleOutputs extends NeonRoleInputs {\n\t/** Role password (auto-generated by Neon). */\n\tpassword: string;\n}\n\n/** Neon API response for the reveal_password endpoint. */\ninterface PasswordResponse {\n\tpassword: string;\n}\n\n/** Neon API response for listing roles. */\ninterface RoleListResponse {\n\troles: Array<{\n\t\tname: string;\n\t}>;\n}\n\n/**\n * Checks if a role exists by name on a branch.\n *\n * @param client Authenticated Neon API client\n * @param projectId Neon project ID\n * @param branchId Branch ID to search within\n * @param name Exact role name to match\n * @returns `true` if found, `false` otherwise\n */\nasync function roleExists(\n\tclient: NeonClient,\n\tprojectId: string,\n\tbranchId: string,\n\tname: string,\n): Promise<boolean> {\n\tconst result = await client.get<RoleListResponse>(\n\t\t`/projects/${projectId}/branches/${branchId}/roles`,\n\t);\n\n\treturn result.roles.some((r) => r.name === name);\n}\n\n/**\n * Reveals the password for an existing role via the dedicated API endpoint.\n *\n * @param client Authenticated Neon API client\n * @param projectId Neon project ID\n * @param branchId Branch ID\n * @param name Role name\n * @returns The role password\n */\nasync function revealPassword(\n\tclient: NeonClient,\n\tprojectId: string,\n\tbranchId: string,\n\tname: string,\n): Promise<string> {\n\tconst result = await client.get<PasswordResponse>(\n\t\t`/projects/${projectId}/branches/${branchId}/roles/${name}/reveal_password`,\n\t);\n\n\treturn result.password;\n}\n\n/**\n * Dynamic provider implementing CRUD for Neon database roles.\n *\n * Uses adopt-or-create on `create()`: finds an existing role by name\n * before creating a new one.\n */\nclass NeonRoleProvider implements pulumi.dynamic.ResourceProvider {\n\t/**\n\t * Creates or adopts a Neon role by name.\n\t *\n\t * @param inputs Resolved role configuration\n\t * @returns Composite ID `{branchId}/{roleName}` as the resource ID\n\t */\n\tasync create(inputs: NeonRoleInputs): Promise<pulumi.dynamic.CreateResult> {\n\t\tconst client = new NeonClient(inputs.apiKey);\n\n\t\tconst exists = await roleExists(\n\t\t\tclient,\n\t\t\tinputs.projectId,\n\t\t\tinputs.branchId,\n\t\t\tinputs.name,\n\t\t);\n\n\t\tif (!exists) {\n\t\t\tawait client.post(\n\t\t\t\t`/projects/${inputs.projectId}/branches/${inputs.branchId}/roles`,\n\t\t\t\t{ role: { name: inputs.name } },\n\t\t\t);\n\t\t} else {\n\t\t\tpulumi.log.info(`Adopting existing Neon role \"${inputs.name}\"`);\n\t\t}\n\n\t\tconst password = await revealPassword(\n\t\t\tclient,\n\t\t\tinputs.projectId,\n\t\t\tinputs.branchId,\n\t\t\tinputs.name,\n\t\t);\n\n\t\treturn {\n\t\t\tid: `${inputs.branchId}/${inputs.name}`,\n\t\t\touts: { ...inputs, password },\n\t\t};\n\t}\n\n\t/**\n\t * Reads current state for `pulumi refresh`.\n\t *\n\t * @param id Current composite role ID\n\t * @param props Last known persisted state\n\t * @returns Refreshed resource ID and properties with current password\n\t * @throws {Error} If the role no longer exists\n\t */\n\tasync read(\n\t\tid: string,\n\t\tprops: NeonRoleOutputs,\n\t): Promise<pulumi.dynamic.ReadResult> {\n\t\tconst client = new NeonClient(props.apiKey);\n\n\t\tconst password = await revealPassword(\n\t\t\tclient,\n\t\t\tprops.projectId,\n\t\t\tprops.branchId,\n\t\t\tprops.name,\n\t\t);\n\n\t\treturn {\n\t\t\tid,\n\t\t\tprops: { ...props, password },\n\t\t};\n\t}\n\n\t/**\n\t * Deletes the Neon role. Silently succeeds if already deleted.\n\t */\n\tasync delete(_id: string, props: NeonRoleOutputs): Promise<void> {\n\t\tconst client = new NeonClient(props.apiKey);\n\n\t\ttry {\n\t\t\tawait client.delete(\n\t\t\t\t`/projects/${props.projectId}/branches/${props.branchId}/roles/${props.name}`,\n\t\t\t);\n\t\t} catch {\n\t\t\tpulumi.log.warn(\n\t\t\t\t`Failed to delete Neon role \"${props.name}\" (may already be deleted)`,\n\t\t\t);\n\t\t}\n\t}\n\n\t/**\n\t * Compares old and new inputs. Changing any field triggers replacement\n\t * since roles cannot be renamed or moved between branches.\n\t */\n\tasync diff(\n\t\t_id: string,\n\t\tolds: NeonRoleOutputs,\n\t\tnews: NeonRoleInputs,\n\t): Promise<pulumi.dynamic.DiffResult> {\n\t\tconst replaces: string[] = [];\n\n\t\tif (olds.projectId !== news.projectId) {\n\t\t\treplaces.push(\"projectId\");\n\t\t}\n\n\t\tif (olds.branchId !== news.branchId) {\n\t\t\treplaces.push(\"branchId\");\n\t\t}\n\n\t\tif (olds.name !== news.name) {\n\t\t\treplaces.push(\"name\");\n\t\t}\n\n\t\treturn {\n\t\t\tchanges: replaces.length > 0,\n\t\t\treplaces,\n\t\t\tdeleteBeforeReplace: true,\n\t\t};\n\t}\n}\n\n/**\n * Manages a Neon database role with adopt-or-create semantics.\n * Exposes `password` as a secret output for connection string composition.\n *\n * @example\n * ```typescript\n * const role = new NeonRole(\"neon-role-owner\", {\n * apiKey: config.requireSecret(\"neonApiKey\"),\n * projectId: \"quiet-forest-69719462\",\n * branchId: branch.id,\n * name: \"neondb_owner\",\n * });\n *\n * const password = role.password;\n * ```\n */\nexport class NeonRole extends pulumi.dynamic.Resource {\n\t/** Role password (auto-generated by Neon). */\n\tpublic declare readonly password: pulumi.Output<string>;\n\n\t/**\n\t * @param name Pulumi resource name\n\t * @param args Role configuration inputs\n\t * @param opts Standard Pulumi resource options\n\t */\n\tconstructor(\n\t\tname: string,\n\t\targs: {\n\t\t\t/** Neon API key. */\n\t\t\tapiKey: pulumi.Input<string>;\n\n\t\t\t/** Neon project ID. */\n\t\t\tprojectId: pulumi.Input<string>;\n\n\t\t\t/** Branch ID the role belongs to. */\n\t\t\tbranchId: pulumi.Input<string>;\n\n\t\t\t/** Role name (e.g. `\"neondb_owner\"`). */\n\t\t\tname: pulumi.Input<string>;\n\t\t},\n\t\topts?: pulumi.CustomResourceOptions,\n\t) {\n\t\tsuper(new NeonRoleProvider(), name, { ...args, password: undefined }, opts);\n\t}\n}\n"],"mappings":";;;;;;;;;;;;;;;;AA6CA,eAAe,WACd,QACA,WACA,UACA,MACmB;CAKnB,QAAO,MAJc,OAAO,IAC3B,aAAa,UAAU,YAAY,SAAS,OAC7C,GAEc,MAAM,MAAM,MAAM,EAAE,SAAS,IAAI;AAChD;;;;;;;;;;AAWA,eAAe,eACd,QACA,WACA,UACA,MACkB;CAKlB,QAAO,MAJc,OAAO,IAC3B,aAAa,UAAU,YAAY,SAAS,SAAS,KAAK,iBAC3D,GAEc;AACf;;;;;;;AAQA,IAAM,mBAAN,MAAkE;;;;;;;CAOjE,MAAM,OAAO,QAA8D;EAC1E,MAAM,SAAS,IAAIA,+BAAW,OAAO,MAAM;EAS3C,IAAI,CAAC,MAPgB,WACpB,QACA,OAAO,WACP,OAAO,UACP,OAAO,IACR,GAGC,MAAM,OAAO,KACZ,aAAa,OAAO,UAAU,YAAY,OAAO,SAAS,SAC1D,EAAE,MAAM,EAAE,MAAM,OAAO,KAAK,EAAE,CAC/B;OAEA,eAAO,IAAI,KAAK,gCAAgC,OAAO,KAAK,EAAE;EAG/D,MAAM,WAAW,MAAM,eACtB,QACA,OAAO,WACP,OAAO,UACP,OAAO,IACR;EAEA,OAAO;GACN,IAAI,GAAG,OAAO,SAAS,GAAG,OAAO;GACjC,MAAM;IAAE,GAAG;IAAQ;GAAS;EAC7B;CACD;;;;;;;;;CAUA,MAAM,KACL,IACA,OACqC;EAGrC,MAAM,WAAW,MAAM,eACtB,IAHkBA,+BAAW,MAAM,MAG9B,GACL,MAAM,WACN,MAAM,UACN,MAAM,IACP;EAEA,OAAO;GACN;GACA,OAAO;IAAE,GAAG;IAAO;GAAS;EAC7B;CACD;;;;CAKA,MAAM,OAAO,KAAa,OAAuC;EAChE,MAAM,SAAS,IAAIA,+BAAW,MAAM,MAAM;EAE1C,IAAI;GACH,MAAM,OAAO,OACZ,aAAa,MAAM,UAAU,YAAY,MAAM,SAAS,SAAS,MAAM,MACxE;EACD,QAAQ;GACP,eAAO,IAAI,KACV,+BAA+B,MAAM,KAAK,2BAC3C;EACD;CACD;;;;;CAMA,MAAM,KACL,KACA,MACA,MACqC;EACrC,MAAM,WAAqB,CAAC;EAE5B,IAAI,KAAK,cAAc,KAAK,WAC3B,SAAS,KAAK,WAAW;EAG1B,IAAI,KAAK,aAAa,KAAK,UAC1B,SAAS,KAAK,UAAU;EAGzB,IAAI,KAAK,SAAS,KAAK,MACtB,SAAS,KAAK,MAAM;EAGrB,OAAO;GACN,SAAS,SAAS,SAAS;GAC3B;GACA,qBAAqB;EACtB;CACD;AACD;;;;;;;;;;;;;;;;;AAkBA,IAAa,WAAb,cAA8BC,eAAO,QAAQ,SAAS;;;;;;CASrD,YACC,MACA,MAaA,MACC;EACD,MAAM,IAAI,iBAAiB,GAAG,MAAM;GAAE,GAAG;GAAM,
|
|
1
|
+
{"version":3,"file":"role.cjs","names":["NeonClient","pulumi"],"sources":["../../src/neon/role.ts"],"sourcesContent":["import * as pulumi from \"@pulumi/pulumi\";\nimport { NeonClient } from \"./client.js\";\n\n/** Resolved inputs for the Neon role dynamic provider. */\nexport interface NeonRoleInputs {\n\t/** Neon API key. */\n\tapiKey: string;\n\n\t/** Neon project ID. */\n\tprojectId: string;\n\n\t/** Branch ID the role belongs to. */\n\tbranchId: string;\n\n\t/** Role name (e.g. `\"neondb_owner\"`). */\n\tname: string;\n}\n\n/** Persisted state for the Neon role. */\ninterface NeonRoleOutputs extends NeonRoleInputs {\n\t/** Role password (auto-generated by Neon). */\n\tpassword: string;\n}\n\n/** Neon API response for the reveal_password endpoint. */\ninterface PasswordResponse {\n\tpassword: string;\n}\n\n/** Neon API response for listing roles. */\ninterface RoleListResponse {\n\troles: Array<{\n\t\tname: string;\n\t}>;\n}\n\n/**\n * Checks if a role exists by name on a branch.\n *\n * @param client Authenticated Neon API client\n * @param projectId Neon project ID\n * @param branchId Branch ID to search within\n * @param name Exact role name to match\n * @returns `true` if found, `false` otherwise\n */\nasync function roleExists(\n\tclient: NeonClient,\n\tprojectId: string,\n\tbranchId: string,\n\tname: string,\n): Promise<boolean> {\n\tconst result = await client.get<RoleListResponse>(\n\t\t`/projects/${projectId}/branches/${branchId}/roles`,\n\t);\n\n\treturn result.roles.some((r) => r.name === name);\n}\n\n/**\n * Reveals the password for an existing role via the dedicated API endpoint.\n *\n * @param client Authenticated Neon API client\n * @param projectId Neon project ID\n * @param branchId Branch ID\n * @param name Role name\n * @returns The role password\n */\nasync function revealPassword(\n\tclient: NeonClient,\n\tprojectId: string,\n\tbranchId: string,\n\tname: string,\n): Promise<string> {\n\tconst result = await client.get<PasswordResponse>(\n\t\t`/projects/${projectId}/branches/${branchId}/roles/${name}/reveal_password`,\n\t);\n\n\treturn result.password;\n}\n\n/**\n * Dynamic provider implementing CRUD for Neon database roles.\n *\n * Uses adopt-or-create on `create()`: finds an existing role by name\n * before creating a new one.\n */\nclass NeonRoleProvider implements pulumi.dynamic.ResourceProvider {\n\t/**\n\t * Creates or adopts a Neon role by name.\n\t *\n\t * @param inputs Resolved role configuration\n\t * @returns Composite ID `{branchId}/{roleName}` as the resource ID\n\t */\n\tasync create(inputs: NeonRoleInputs): Promise<pulumi.dynamic.CreateResult> {\n\t\tconst client = new NeonClient(inputs.apiKey);\n\n\t\tconst exists = await roleExists(\n\t\t\tclient,\n\t\t\tinputs.projectId,\n\t\t\tinputs.branchId,\n\t\t\tinputs.name,\n\t\t);\n\n\t\tif (!exists) {\n\t\t\tawait client.post(\n\t\t\t\t`/projects/${inputs.projectId}/branches/${inputs.branchId}/roles`,\n\t\t\t\t{ role: { name: inputs.name } },\n\t\t\t);\n\t\t} else {\n\t\t\tpulumi.log.info(`Adopting existing Neon role \"${inputs.name}\"`);\n\t\t}\n\n\t\tconst password = await revealPassword(\n\t\t\tclient,\n\t\t\tinputs.projectId,\n\t\t\tinputs.branchId,\n\t\t\tinputs.name,\n\t\t);\n\n\t\treturn {\n\t\t\tid: `${inputs.branchId}/${inputs.name}`,\n\t\t\touts: { ...inputs, password },\n\t\t};\n\t}\n\n\t/**\n\t * Reads current state for `pulumi refresh`.\n\t *\n\t * @param id Current composite role ID\n\t * @param props Last known persisted state\n\t * @returns Refreshed resource ID and properties with current password\n\t * @throws {Error} If the role no longer exists\n\t */\n\tasync read(\n\t\tid: string,\n\t\tprops: NeonRoleOutputs,\n\t): Promise<pulumi.dynamic.ReadResult> {\n\t\tconst client = new NeonClient(props.apiKey);\n\n\t\tconst password = await revealPassword(\n\t\t\tclient,\n\t\t\tprops.projectId,\n\t\t\tprops.branchId,\n\t\t\tprops.name,\n\t\t);\n\n\t\treturn {\n\t\t\tid,\n\t\t\tprops: { ...props, password },\n\t\t};\n\t}\n\n\t/**\n\t * Deletes the Neon role. Silently succeeds if already deleted.\n\t */\n\tasync delete(_id: string, props: NeonRoleOutputs): Promise<void> {\n\t\tconst client = new NeonClient(props.apiKey);\n\n\t\ttry {\n\t\t\tawait client.delete(\n\t\t\t\t`/projects/${props.projectId}/branches/${props.branchId}/roles/${props.name}`,\n\t\t\t);\n\t\t} catch {\n\t\t\tpulumi.log.warn(\n\t\t\t\t`Failed to delete Neon role \"${props.name}\" (may already be deleted)`,\n\t\t\t);\n\t\t}\n\t}\n\n\t/**\n\t * Compares old and new inputs. Changing any field triggers replacement\n\t * since roles cannot be renamed or moved between branches.\n\t */\n\tasync diff(\n\t\t_id: string,\n\t\tolds: NeonRoleOutputs,\n\t\tnews: NeonRoleInputs,\n\t): Promise<pulumi.dynamic.DiffResult> {\n\t\tconst replaces: string[] = [];\n\n\t\tif (olds.projectId !== news.projectId) {\n\t\t\treplaces.push(\"projectId\");\n\t\t}\n\n\t\tif (olds.branchId !== news.branchId) {\n\t\t\treplaces.push(\"branchId\");\n\t\t}\n\n\t\tif (olds.name !== news.name) {\n\t\t\treplaces.push(\"name\");\n\t\t}\n\n\t\treturn {\n\t\t\tchanges: replaces.length > 0,\n\t\t\treplaces,\n\t\t\tdeleteBeforeReplace: true,\n\t\t};\n\t}\n}\n\n/**\n * Manages a Neon database role with adopt-or-create semantics.\n * Exposes `password` as a secret output for connection string composition.\n *\n * @example\n * ```typescript\n * const role = new NeonRole(\"neon-role-owner\", {\n * apiKey: config.requireSecret(\"neonApiKey\"),\n * projectId: \"quiet-forest-69719462\",\n * branchId: branch.id,\n * name: \"neondb_owner\",\n * });\n *\n * const password = role.password;\n * ```\n */\nexport class NeonRole extends pulumi.dynamic.Resource {\n\t/** Role password (auto-generated by Neon). */\n\tpublic declare readonly password: pulumi.Output<string>;\n\n\t/**\n\t * @param name Pulumi resource name\n\t * @param args Role configuration inputs\n\t * @param opts Standard Pulumi resource options\n\t */\n\tconstructor(\n\t\tname: string,\n\t\targs: {\n\t\t\t/** Neon API key. */\n\t\t\tapiKey: pulumi.Input<string>;\n\n\t\t\t/** Neon project ID. */\n\t\t\tprojectId: pulumi.Input<string>;\n\n\t\t\t/** Branch ID the role belongs to. */\n\t\t\tbranchId: pulumi.Input<string>;\n\n\t\t\t/** Role name (e.g. `\"neondb_owner\"`). */\n\t\t\tname: pulumi.Input<string>;\n\t\t},\n\t\topts?: pulumi.CustomResourceOptions,\n\t) {\n\t\tsuper(new NeonRoleProvider(), name, { ...args, password: pulumi.secret(undefined as unknown as string) }, opts);\n\t}\n}\n"],"mappings":";;;;;;;;;;;;;;;;AA6CA,eAAe,WACd,QACA,WACA,UACA,MACmB;CAKnB,QAAO,MAJc,OAAO,IAC3B,aAAa,UAAU,YAAY,SAAS,OAC7C,GAEc,MAAM,MAAM,MAAM,EAAE,SAAS,IAAI;AAChD;;;;;;;;;;AAWA,eAAe,eACd,QACA,WACA,UACA,MACkB;CAKlB,QAAO,MAJc,OAAO,IAC3B,aAAa,UAAU,YAAY,SAAS,SAAS,KAAK,iBAC3D,GAEc;AACf;;;;;;;AAQA,IAAM,mBAAN,MAAkE;;;;;;;CAOjE,MAAM,OAAO,QAA8D;EAC1E,MAAM,SAAS,IAAIA,+BAAW,OAAO,MAAM;EAS3C,IAAI,CAAC,MAPgB,WACpB,QACA,OAAO,WACP,OAAO,UACP,OAAO,IACR,GAGC,MAAM,OAAO,KACZ,aAAa,OAAO,UAAU,YAAY,OAAO,SAAS,SAC1D,EAAE,MAAM,EAAE,MAAM,OAAO,KAAK,EAAE,CAC/B;OAEA,eAAO,IAAI,KAAK,gCAAgC,OAAO,KAAK,EAAE;EAG/D,MAAM,WAAW,MAAM,eACtB,QACA,OAAO,WACP,OAAO,UACP,OAAO,IACR;EAEA,OAAO;GACN,IAAI,GAAG,OAAO,SAAS,GAAG,OAAO;GACjC,MAAM;IAAE,GAAG;IAAQ;GAAS;EAC7B;CACD;;;;;;;;;CAUA,MAAM,KACL,IACA,OACqC;EAGrC,MAAM,WAAW,MAAM,eACtB,IAHkBA,+BAAW,MAAM,MAG9B,GACL,MAAM,WACN,MAAM,UACN,MAAM,IACP;EAEA,OAAO;GACN;GACA,OAAO;IAAE,GAAG;IAAO;GAAS;EAC7B;CACD;;;;CAKA,MAAM,OAAO,KAAa,OAAuC;EAChE,MAAM,SAAS,IAAIA,+BAAW,MAAM,MAAM;EAE1C,IAAI;GACH,MAAM,OAAO,OACZ,aAAa,MAAM,UAAU,YAAY,MAAM,SAAS,SAAS,MAAM,MACxE;EACD,QAAQ;GACP,eAAO,IAAI,KACV,+BAA+B,MAAM,KAAK,2BAC3C;EACD;CACD;;;;;CAMA,MAAM,KACL,KACA,MACA,MACqC;EACrC,MAAM,WAAqB,CAAC;EAE5B,IAAI,KAAK,cAAc,KAAK,WAC3B,SAAS,KAAK,WAAW;EAG1B,IAAI,KAAK,aAAa,KAAK,UAC1B,SAAS,KAAK,UAAU;EAGzB,IAAI,KAAK,SAAS,KAAK,MACtB,SAAS,KAAK,MAAM;EAGrB,OAAO;GACN,SAAS,SAAS,SAAS;GAC3B;GACA,qBAAqB;EACtB;CACD;AACD;;;;;;;;;;;;;;;;;AAkBA,IAAa,WAAb,cAA8BC,eAAO,QAAQ,SAAS;;;;;;CASrD,YACC,MACA,MAaA,MACC;EACD,MAAM,IAAI,iBAAiB,GAAG,MAAM;GAAE,GAAG;GAAM,UAAUA,eAAO,OAAO,MAA8B;EAAE,GAAG,IAAI;CAC/G;AACD"}
|
package/dist/neon/role.mjs
CHANGED
package/dist/neon/role.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"role.mjs","names":[],"sources":["../../src/neon/role.ts"],"sourcesContent":["import * as pulumi from \"@pulumi/pulumi\";\nimport { NeonClient } from \"./client.js\";\n\n/** Resolved inputs for the Neon role dynamic provider. */\nexport interface NeonRoleInputs {\n\t/** Neon API key. */\n\tapiKey: string;\n\n\t/** Neon project ID. */\n\tprojectId: string;\n\n\t/** Branch ID the role belongs to. */\n\tbranchId: string;\n\n\t/** Role name (e.g. `\"neondb_owner\"`). */\n\tname: string;\n}\n\n/** Persisted state for the Neon role. */\ninterface NeonRoleOutputs extends NeonRoleInputs {\n\t/** Role password (auto-generated by Neon). */\n\tpassword: string;\n}\n\n/** Neon API response for the reveal_password endpoint. */\ninterface PasswordResponse {\n\tpassword: string;\n}\n\n/** Neon API response for listing roles. */\ninterface RoleListResponse {\n\troles: Array<{\n\t\tname: string;\n\t}>;\n}\n\n/**\n * Checks if a role exists by name on a branch.\n *\n * @param client Authenticated Neon API client\n * @param projectId Neon project ID\n * @param branchId Branch ID to search within\n * @param name Exact role name to match\n * @returns `true` if found, `false` otherwise\n */\nasync function roleExists(\n\tclient: NeonClient,\n\tprojectId: string,\n\tbranchId: string,\n\tname: string,\n): Promise<boolean> {\n\tconst result = await client.get<RoleListResponse>(\n\t\t`/projects/${projectId}/branches/${branchId}/roles`,\n\t);\n\n\treturn result.roles.some((r) => r.name === name);\n}\n\n/**\n * Reveals the password for an existing role via the dedicated API endpoint.\n *\n * @param client Authenticated Neon API client\n * @param projectId Neon project ID\n * @param branchId Branch ID\n * @param name Role name\n * @returns The role password\n */\nasync function revealPassword(\n\tclient: NeonClient,\n\tprojectId: string,\n\tbranchId: string,\n\tname: string,\n): Promise<string> {\n\tconst result = await client.get<PasswordResponse>(\n\t\t`/projects/${projectId}/branches/${branchId}/roles/${name}/reveal_password`,\n\t);\n\n\treturn result.password;\n}\n\n/**\n * Dynamic provider implementing CRUD for Neon database roles.\n *\n * Uses adopt-or-create on `create()`: finds an existing role by name\n * before creating a new one.\n */\nclass NeonRoleProvider implements pulumi.dynamic.ResourceProvider {\n\t/**\n\t * Creates or adopts a Neon role by name.\n\t *\n\t * @param inputs Resolved role configuration\n\t * @returns Composite ID `{branchId}/{roleName}` as the resource ID\n\t */\n\tasync create(inputs: NeonRoleInputs): Promise<pulumi.dynamic.CreateResult> {\n\t\tconst client = new NeonClient(inputs.apiKey);\n\n\t\tconst exists = await roleExists(\n\t\t\tclient,\n\t\t\tinputs.projectId,\n\t\t\tinputs.branchId,\n\t\t\tinputs.name,\n\t\t);\n\n\t\tif (!exists) {\n\t\t\tawait client.post(\n\t\t\t\t`/projects/${inputs.projectId}/branches/${inputs.branchId}/roles`,\n\t\t\t\t{ role: { name: inputs.name } },\n\t\t\t);\n\t\t} else {\n\t\t\tpulumi.log.info(`Adopting existing Neon role \"${inputs.name}\"`);\n\t\t}\n\n\t\tconst password = await revealPassword(\n\t\t\tclient,\n\t\t\tinputs.projectId,\n\t\t\tinputs.branchId,\n\t\t\tinputs.name,\n\t\t);\n\n\t\treturn {\n\t\t\tid: `${inputs.branchId}/${inputs.name}`,\n\t\t\touts: { ...inputs, password },\n\t\t};\n\t}\n\n\t/**\n\t * Reads current state for `pulumi refresh`.\n\t *\n\t * @param id Current composite role ID\n\t * @param props Last known persisted state\n\t * @returns Refreshed resource ID and properties with current password\n\t * @throws {Error} If the role no longer exists\n\t */\n\tasync read(\n\t\tid: string,\n\t\tprops: NeonRoleOutputs,\n\t): Promise<pulumi.dynamic.ReadResult> {\n\t\tconst client = new NeonClient(props.apiKey);\n\n\t\tconst password = await revealPassword(\n\t\t\tclient,\n\t\t\tprops.projectId,\n\t\t\tprops.branchId,\n\t\t\tprops.name,\n\t\t);\n\n\t\treturn {\n\t\t\tid,\n\t\t\tprops: { ...props, password },\n\t\t};\n\t}\n\n\t/**\n\t * Deletes the Neon role. Silently succeeds if already deleted.\n\t */\n\tasync delete(_id: string, props: NeonRoleOutputs): Promise<void> {\n\t\tconst client = new NeonClient(props.apiKey);\n\n\t\ttry {\n\t\t\tawait client.delete(\n\t\t\t\t`/projects/${props.projectId}/branches/${props.branchId}/roles/${props.name}`,\n\t\t\t);\n\t\t} catch {\n\t\t\tpulumi.log.warn(\n\t\t\t\t`Failed to delete Neon role \"${props.name}\" (may already be deleted)`,\n\t\t\t);\n\t\t}\n\t}\n\n\t/**\n\t * Compares old and new inputs. Changing any field triggers replacement\n\t * since roles cannot be renamed or moved between branches.\n\t */\n\tasync diff(\n\t\t_id: string,\n\t\tolds: NeonRoleOutputs,\n\t\tnews: NeonRoleInputs,\n\t): Promise<pulumi.dynamic.DiffResult> {\n\t\tconst replaces: string[] = [];\n\n\t\tif (olds.projectId !== news.projectId) {\n\t\t\treplaces.push(\"projectId\");\n\t\t}\n\n\t\tif (olds.branchId !== news.branchId) {\n\t\t\treplaces.push(\"branchId\");\n\t\t}\n\n\t\tif (olds.name !== news.name) {\n\t\t\treplaces.push(\"name\");\n\t\t}\n\n\t\treturn {\n\t\t\tchanges: replaces.length > 0,\n\t\t\treplaces,\n\t\t\tdeleteBeforeReplace: true,\n\t\t};\n\t}\n}\n\n/**\n * Manages a Neon database role with adopt-or-create semantics.\n * Exposes `password` as a secret output for connection string composition.\n *\n * @example\n * ```typescript\n * const role = new NeonRole(\"neon-role-owner\", {\n * apiKey: config.requireSecret(\"neonApiKey\"),\n * projectId: \"quiet-forest-69719462\",\n * branchId: branch.id,\n * name: \"neondb_owner\",\n * });\n *\n * const password = role.password;\n * ```\n */\nexport class NeonRole extends pulumi.dynamic.Resource {\n\t/** Role password (auto-generated by Neon). */\n\tpublic declare readonly password: pulumi.Output<string>;\n\n\t/**\n\t * @param name Pulumi resource name\n\t * @param args Role configuration inputs\n\t * @param opts Standard Pulumi resource options\n\t */\n\tconstructor(\n\t\tname: string,\n\t\targs: {\n\t\t\t/** Neon API key. */\n\t\t\tapiKey: pulumi.Input<string>;\n\n\t\t\t/** Neon project ID. */\n\t\t\tprojectId: pulumi.Input<string>;\n\n\t\t\t/** Branch ID the role belongs to. */\n\t\t\tbranchId: pulumi.Input<string>;\n\n\t\t\t/** Role name (e.g. `\"neondb_owner\"`). */\n\t\t\tname: pulumi.Input<string>;\n\t\t},\n\t\topts?: pulumi.CustomResourceOptions,\n\t) {\n\t\tsuper(new NeonRoleProvider(), name, { ...args, password: undefined }, opts);\n\t}\n}\n"],"mappings":";;;;;;;;;;;;;;AA6CA,eAAe,WACd,QACA,WACA,UACA,MACmB;CAKnB,QAAO,MAJc,OAAO,IAC3B,aAAa,UAAU,YAAY,SAAS,OAC7C,GAEc,MAAM,MAAM,MAAM,EAAE,SAAS,IAAI;AAChD;;;;;;;;;;AAWA,eAAe,eACd,QACA,WACA,UACA,MACkB;CAKlB,QAAO,MAJc,OAAO,IAC3B,aAAa,UAAU,YAAY,SAAS,SAAS,KAAK,iBAC3D,GAEc;AACf;;;;;;;AAQA,IAAM,mBAAN,MAAkE;;;;;;;CAOjE,MAAM,OAAO,QAA8D;EAC1E,MAAM,SAAS,IAAI,WAAW,OAAO,MAAM;EAS3C,IAAI,CAAC,MAPgB,WACpB,QACA,OAAO,WACP,OAAO,UACP,OAAO,IACR,GAGC,MAAM,OAAO,KACZ,aAAa,OAAO,UAAU,YAAY,OAAO,SAAS,SAC1D,EAAE,MAAM,EAAE,MAAM,OAAO,KAAK,EAAE,CAC/B;OAEA,OAAO,IAAI,KAAK,gCAAgC,OAAO,KAAK,EAAE;EAG/D,MAAM,WAAW,MAAM,eACtB,QACA,OAAO,WACP,OAAO,UACP,OAAO,IACR;EAEA,OAAO;GACN,IAAI,GAAG,OAAO,SAAS,GAAG,OAAO;GACjC,MAAM;IAAE,GAAG;IAAQ;GAAS;EAC7B;CACD;;;;;;;;;CAUA,MAAM,KACL,IACA,OACqC;EAGrC,MAAM,WAAW,MAAM,eACtB,IAHkB,WAAW,MAAM,MAG9B,GACL,MAAM,WACN,MAAM,UACN,MAAM,IACP;EAEA,OAAO;GACN;GACA,OAAO;IAAE,GAAG;IAAO;GAAS;EAC7B;CACD;;;;CAKA,MAAM,OAAO,KAAa,OAAuC;EAChE,MAAM,SAAS,IAAI,WAAW,MAAM,MAAM;EAE1C,IAAI;GACH,MAAM,OAAO,OACZ,aAAa,MAAM,UAAU,YAAY,MAAM,SAAS,SAAS,MAAM,MACxE;EACD,QAAQ;GACP,OAAO,IAAI,KACV,+BAA+B,MAAM,KAAK,2BAC3C;EACD;CACD;;;;;CAMA,MAAM,KACL,KACA,MACA,MACqC;EACrC,MAAM,WAAqB,CAAC;EAE5B,IAAI,KAAK,cAAc,KAAK,WAC3B,SAAS,KAAK,WAAW;EAG1B,IAAI,KAAK,aAAa,KAAK,UAC1B,SAAS,KAAK,UAAU;EAGzB,IAAI,KAAK,SAAS,KAAK,MACtB,SAAS,KAAK,MAAM;EAGrB,OAAO;GACN,SAAS,SAAS,SAAS;GAC3B;GACA,qBAAqB;EACtB;CACD;AACD;;;;;;;;;;;;;;;;;AAkBA,IAAa,WAAb,cAA8B,OAAO,QAAQ,SAAS;;;;;;CASrD,YACC,MACA,MAaA,MACC;EACD,MAAM,IAAI,iBAAiB,GAAG,MAAM;GAAE,GAAG;GAAM,UAAU;
|
|
1
|
+
{"version":3,"file":"role.mjs","names":[],"sources":["../../src/neon/role.ts"],"sourcesContent":["import * as pulumi from \"@pulumi/pulumi\";\nimport { NeonClient } from \"./client.js\";\n\n/** Resolved inputs for the Neon role dynamic provider. */\nexport interface NeonRoleInputs {\n\t/** Neon API key. */\n\tapiKey: string;\n\n\t/** Neon project ID. */\n\tprojectId: string;\n\n\t/** Branch ID the role belongs to. */\n\tbranchId: string;\n\n\t/** Role name (e.g. `\"neondb_owner\"`). */\n\tname: string;\n}\n\n/** Persisted state for the Neon role. */\ninterface NeonRoleOutputs extends NeonRoleInputs {\n\t/** Role password (auto-generated by Neon). */\n\tpassword: string;\n}\n\n/** Neon API response for the reveal_password endpoint. */\ninterface PasswordResponse {\n\tpassword: string;\n}\n\n/** Neon API response for listing roles. */\ninterface RoleListResponse {\n\troles: Array<{\n\t\tname: string;\n\t}>;\n}\n\n/**\n * Checks if a role exists by name on a branch.\n *\n * @param client Authenticated Neon API client\n * @param projectId Neon project ID\n * @param branchId Branch ID to search within\n * @param name Exact role name to match\n * @returns `true` if found, `false` otherwise\n */\nasync function roleExists(\n\tclient: NeonClient,\n\tprojectId: string,\n\tbranchId: string,\n\tname: string,\n): Promise<boolean> {\n\tconst result = await client.get<RoleListResponse>(\n\t\t`/projects/${projectId}/branches/${branchId}/roles`,\n\t);\n\n\treturn result.roles.some((r) => r.name === name);\n}\n\n/**\n * Reveals the password for an existing role via the dedicated API endpoint.\n *\n * @param client Authenticated Neon API client\n * @param projectId Neon project ID\n * @param branchId Branch ID\n * @param name Role name\n * @returns The role password\n */\nasync function revealPassword(\n\tclient: NeonClient,\n\tprojectId: string,\n\tbranchId: string,\n\tname: string,\n): Promise<string> {\n\tconst result = await client.get<PasswordResponse>(\n\t\t`/projects/${projectId}/branches/${branchId}/roles/${name}/reveal_password`,\n\t);\n\n\treturn result.password;\n}\n\n/**\n * Dynamic provider implementing CRUD for Neon database roles.\n *\n * Uses adopt-or-create on `create()`: finds an existing role by name\n * before creating a new one.\n */\nclass NeonRoleProvider implements pulumi.dynamic.ResourceProvider {\n\t/**\n\t * Creates or adopts a Neon role by name.\n\t *\n\t * @param inputs Resolved role configuration\n\t * @returns Composite ID `{branchId}/{roleName}` as the resource ID\n\t */\n\tasync create(inputs: NeonRoleInputs): Promise<pulumi.dynamic.CreateResult> {\n\t\tconst client = new NeonClient(inputs.apiKey);\n\n\t\tconst exists = await roleExists(\n\t\t\tclient,\n\t\t\tinputs.projectId,\n\t\t\tinputs.branchId,\n\t\t\tinputs.name,\n\t\t);\n\n\t\tif (!exists) {\n\t\t\tawait client.post(\n\t\t\t\t`/projects/${inputs.projectId}/branches/${inputs.branchId}/roles`,\n\t\t\t\t{ role: { name: inputs.name } },\n\t\t\t);\n\t\t} else {\n\t\t\tpulumi.log.info(`Adopting existing Neon role \"${inputs.name}\"`);\n\t\t}\n\n\t\tconst password = await revealPassword(\n\t\t\tclient,\n\t\t\tinputs.projectId,\n\t\t\tinputs.branchId,\n\t\t\tinputs.name,\n\t\t);\n\n\t\treturn {\n\t\t\tid: `${inputs.branchId}/${inputs.name}`,\n\t\t\touts: { ...inputs, password },\n\t\t};\n\t}\n\n\t/**\n\t * Reads current state for `pulumi refresh`.\n\t *\n\t * @param id Current composite role ID\n\t * @param props Last known persisted state\n\t * @returns Refreshed resource ID and properties with current password\n\t * @throws {Error} If the role no longer exists\n\t */\n\tasync read(\n\t\tid: string,\n\t\tprops: NeonRoleOutputs,\n\t): Promise<pulumi.dynamic.ReadResult> {\n\t\tconst client = new NeonClient(props.apiKey);\n\n\t\tconst password = await revealPassword(\n\t\t\tclient,\n\t\t\tprops.projectId,\n\t\t\tprops.branchId,\n\t\t\tprops.name,\n\t\t);\n\n\t\treturn {\n\t\t\tid,\n\t\t\tprops: { ...props, password },\n\t\t};\n\t}\n\n\t/**\n\t * Deletes the Neon role. Silently succeeds if already deleted.\n\t */\n\tasync delete(_id: string, props: NeonRoleOutputs): Promise<void> {\n\t\tconst client = new NeonClient(props.apiKey);\n\n\t\ttry {\n\t\t\tawait client.delete(\n\t\t\t\t`/projects/${props.projectId}/branches/${props.branchId}/roles/${props.name}`,\n\t\t\t);\n\t\t} catch {\n\t\t\tpulumi.log.warn(\n\t\t\t\t`Failed to delete Neon role \"${props.name}\" (may already be deleted)`,\n\t\t\t);\n\t\t}\n\t}\n\n\t/**\n\t * Compares old and new inputs. Changing any field triggers replacement\n\t * since roles cannot be renamed or moved between branches.\n\t */\n\tasync diff(\n\t\t_id: string,\n\t\tolds: NeonRoleOutputs,\n\t\tnews: NeonRoleInputs,\n\t): Promise<pulumi.dynamic.DiffResult> {\n\t\tconst replaces: string[] = [];\n\n\t\tif (olds.projectId !== news.projectId) {\n\t\t\treplaces.push(\"projectId\");\n\t\t}\n\n\t\tif (olds.branchId !== news.branchId) {\n\t\t\treplaces.push(\"branchId\");\n\t\t}\n\n\t\tif (olds.name !== news.name) {\n\t\t\treplaces.push(\"name\");\n\t\t}\n\n\t\treturn {\n\t\t\tchanges: replaces.length > 0,\n\t\t\treplaces,\n\t\t\tdeleteBeforeReplace: true,\n\t\t};\n\t}\n}\n\n/**\n * Manages a Neon database role with adopt-or-create semantics.\n * Exposes `password` as a secret output for connection string composition.\n *\n * @example\n * ```typescript\n * const role = new NeonRole(\"neon-role-owner\", {\n * apiKey: config.requireSecret(\"neonApiKey\"),\n * projectId: \"quiet-forest-69719462\",\n * branchId: branch.id,\n * name: \"neondb_owner\",\n * });\n *\n * const password = role.password;\n * ```\n */\nexport class NeonRole extends pulumi.dynamic.Resource {\n\t/** Role password (auto-generated by Neon). */\n\tpublic declare readonly password: pulumi.Output<string>;\n\n\t/**\n\t * @param name Pulumi resource name\n\t * @param args Role configuration inputs\n\t * @param opts Standard Pulumi resource options\n\t */\n\tconstructor(\n\t\tname: string,\n\t\targs: {\n\t\t\t/** Neon API key. */\n\t\t\tapiKey: pulumi.Input<string>;\n\n\t\t\t/** Neon project ID. */\n\t\t\tprojectId: pulumi.Input<string>;\n\n\t\t\t/** Branch ID the role belongs to. */\n\t\t\tbranchId: pulumi.Input<string>;\n\n\t\t\t/** Role name (e.g. `\"neondb_owner\"`). */\n\t\t\tname: pulumi.Input<string>;\n\t\t},\n\t\topts?: pulumi.CustomResourceOptions,\n\t) {\n\t\tsuper(new NeonRoleProvider(), name, { ...args, password: pulumi.secret(undefined as unknown as string) }, opts);\n\t}\n}\n"],"mappings":";;;;;;;;;;;;;;AA6CA,eAAe,WACd,QACA,WACA,UACA,MACmB;CAKnB,QAAO,MAJc,OAAO,IAC3B,aAAa,UAAU,YAAY,SAAS,OAC7C,GAEc,MAAM,MAAM,MAAM,EAAE,SAAS,IAAI;AAChD;;;;;;;;;;AAWA,eAAe,eACd,QACA,WACA,UACA,MACkB;CAKlB,QAAO,MAJc,OAAO,IAC3B,aAAa,UAAU,YAAY,SAAS,SAAS,KAAK,iBAC3D,GAEc;AACf;;;;;;;AAQA,IAAM,mBAAN,MAAkE;;;;;;;CAOjE,MAAM,OAAO,QAA8D;EAC1E,MAAM,SAAS,IAAI,WAAW,OAAO,MAAM;EAS3C,IAAI,CAAC,MAPgB,WACpB,QACA,OAAO,WACP,OAAO,UACP,OAAO,IACR,GAGC,MAAM,OAAO,KACZ,aAAa,OAAO,UAAU,YAAY,OAAO,SAAS,SAC1D,EAAE,MAAM,EAAE,MAAM,OAAO,KAAK,EAAE,CAC/B;OAEA,OAAO,IAAI,KAAK,gCAAgC,OAAO,KAAK,EAAE;EAG/D,MAAM,WAAW,MAAM,eACtB,QACA,OAAO,WACP,OAAO,UACP,OAAO,IACR;EAEA,OAAO;GACN,IAAI,GAAG,OAAO,SAAS,GAAG,OAAO;GACjC,MAAM;IAAE,GAAG;IAAQ;GAAS;EAC7B;CACD;;;;;;;;;CAUA,MAAM,KACL,IACA,OACqC;EAGrC,MAAM,WAAW,MAAM,eACtB,IAHkB,WAAW,MAAM,MAG9B,GACL,MAAM,WACN,MAAM,UACN,MAAM,IACP;EAEA,OAAO;GACN;GACA,OAAO;IAAE,GAAG;IAAO;GAAS;EAC7B;CACD;;;;CAKA,MAAM,OAAO,KAAa,OAAuC;EAChE,MAAM,SAAS,IAAI,WAAW,MAAM,MAAM;EAE1C,IAAI;GACH,MAAM,OAAO,OACZ,aAAa,MAAM,UAAU,YAAY,MAAM,SAAS,SAAS,MAAM,MACxE;EACD,QAAQ;GACP,OAAO,IAAI,KACV,+BAA+B,MAAM,KAAK,2BAC3C;EACD;CACD;;;;;CAMA,MAAM,KACL,KACA,MACA,MACqC;EACrC,MAAM,WAAqB,CAAC;EAE5B,IAAI,KAAK,cAAc,KAAK,WAC3B,SAAS,KAAK,WAAW;EAG1B,IAAI,KAAK,aAAa,KAAK,UAC1B,SAAS,KAAK,UAAU;EAGzB,IAAI,KAAK,SAAS,KAAK,MACtB,SAAS,KAAK,MAAM;EAGrB,OAAO;GACN,SAAS,SAAS,SAAS;GAC3B;GACA,qBAAqB;EACtB;CACD;AACD;;;;;;;;;;;;;;;;;AAkBA,IAAa,WAAb,cAA8B,OAAO,QAAQ,SAAS;;;;;;CASrD,YACC,MACA,MAaA,MACC;EACD,MAAM,IAAI,iBAAiB,GAAG,MAAM;GAAE,GAAG;GAAM,UAAU,OAAO,OAAO,MAA8B;EAAE,GAAG,IAAI;CAC/G;AACD"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"deploy.cjs","names":["pulumi","command"],"sources":["../../src/railway/deploy.ts"],"sourcesContent":["import * as command from \"@pulumi/command\";\nimport * as pulumi from \"@pulumi/pulumi\";\n\n/** Build and deploy configuration for a Railway service. */\nexport interface RailwayDeployConfig {\n\t/** Build system: `\"RAILPACK\"`, `\"NIXPACKS\"`, or `\"DOCKERFILE\"`. */\n\tbuilder?: string;\n\n\t/** Shell command executed to start the service at runtime. */\n\tstartCommand?: string;\n\n\t/** Shell command executed before the main deploy (e.g. migrations). */\n\tpreDeployCommand?: string;\n}\n\ninterface RailwayDeployArgs {\n\t/** Project-scoped Railway token for `railway up` CLI (stable across runs). */\n\tprojectToken: pulumi.Input<string>;\n\n\t/** Railway project UUID. */\n\tprojectId: string
|
|
1
|
+
{"version":3,"file":"deploy.cjs","names":["pulumi","command"],"sources":["../../src/railway/deploy.ts"],"sourcesContent":["import * as command from \"@pulumi/command\";\nimport * as pulumi from \"@pulumi/pulumi\";\n\n/** Build and deploy configuration for a Railway service. */\nexport interface RailwayDeployConfig {\n\t/** Build system: `\"RAILPACK\"`, `\"NIXPACKS\"`, or `\"DOCKERFILE\"`. */\n\tbuilder?: string;\n\n\t/** Shell command executed to start the service at runtime. */\n\tstartCommand?: string;\n\n\t/** Shell command executed before the main deploy (e.g. migrations). */\n\tpreDeployCommand?: string;\n}\n\ninterface RailwayDeployArgs {\n\t/** Project-scoped Railway token for `railway up` CLI (stable across runs). */\n\tprojectToken: pulumi.Input<string>;\n\n\t/** Railway project UUID. */\n\tprojectId: pulumi.Input<string>;\n\n\t/** Railway service UUID to deploy to. */\n\tserviceId: pulumi.Input<string>;\n\n\t/** Railway environment UUID (e.g. production). */\n\tenvironmentId: pulumi.Input<string>;\n\n\t/** Absolute path to the monorepo root (working directory for `railway up`). */\n\tdirectory: string;\n\n\t/** SHA-256 hash of the app source directory, used as a deploy trigger. */\n\tsourceHash: string;\n\n\t/** Env var map used as deploy trigger. */\n\tenv: Record<string, pulumi.Input<string>>;\n\n\t/** Directories to exclude via `.railwayignore`. */\n\texcludePaths?: string[];\n\n\t/** Railpack configuration written to `railpack.json` before deploy. */\n\trailpackConfig?: Record<string, unknown>;\n}\n\nconst LOCK_DIR = \"/tmp/.railway-upload-lock\";\n\n/**\n * Deploys a Railway service and waits for the build to complete.\n *\n * Uses `railway up --ci` which blocks until the build finishes.\n * Multiple deploys run in parallel — a mkdir lock serializes only the\n * brief upload phase (~5s) when `.railwayignore` must be consistent,\n * then releases so builds stream concurrently.\n */\nexport class RailwayDeploy extends pulumi.ComponentResource {\n\tconstructor(\n\t\tname: string,\n\t\targs: RailwayDeployArgs,\n\t\topts?: pulumi.ComponentResourceOptions,\n\t) {\n\t\tsuper(\"infrakit:railway:Deploy\", name, {}, opts);\n\n\t\tconst ignorePatterns = (args.excludePaths ?? [])\n\t\t\t.map((dir) => {\n\t\t\t\tif (dir.startsWith(\"apps/\")) {\n\t\t\t\t\treturn `${dir}/**\\\\n!${dir}/package.json`;\n\t\t\t\t}\n\n\t\t\t\treturn dir;\n\t\t\t})\n\t\t\t.join(\"\\\\n\");\n\n\t\tconst writeIgnore = ignorePatterns\n\t\t\t? `printf '${ignorePatterns}\\\\n' > .railwayignore`\n\t\t\t: \"\";\n\n\t\tconst writeRailpack = args.railpackConfig\n\t\t\t? `printf '${JSON.stringify(args.railpackConfig).replace(/'/g, \"\\\\'\")}' > railpack.json`\n\t\t\t: \"\";\n\n\t\tconst setupLines = [writeIgnore, writeRailpack].filter(Boolean).join(\"; \");\n\n\t\tconst envHash = pulumi\n\t\t\t.all(\n\t\t\t\tObject.entries(args.env)\n\t\t\t\t\t.sort(([a], [b]) => a.localeCompare(b))\n\t\t\t\t\t.map(([k, v]) => pulumi.output(v).apply((val) => `${k}=${val}`)),\n\t\t\t)\n\t\t\t.apply((parts) => parts.join(\",\"));\n\n\t\t// Parallel-safe upload: multiple stacks deploy concurrently, but each writes\n\t\t// .railwayignore and railpack.json to the same monorepo root before calling\n\t\t// `railway up`. The mkdir lock serializes that brief window (~5s upload phase).\n\t\t// After upload, the background job releases the lock so builds stream in parallel.\n\t\t//\n\t\t// Flow: acquire lock → write config files → release lock after 5s (background) →\n\t\t// railway up --ci (blocks through upload, then streams build logs) →\n\t\t// cleanup on exit\n\t\tconst deployCmd = pulumi.interpolate`while ! mkdir ${LOCK_DIR} 2>/dev/null; do sleep 1; done; ${setupLines}; { sleep 5; rm -f .railwayignore railpack.json; rmdir ${LOCK_DIR} 2>/dev/null; } & railway up --ci --project ${args.projectId} --service ${args.serviceId} --environment ${args.environmentId}; EXIT=$?; rm -f .railwayignore railpack.json; rmdir ${LOCK_DIR} 2>/dev/null; wait; exit $EXIT`;\n\n\t\tnew command.local.Command(\n\t\t\t`${name}-deploy`,\n\t\t\t{\n\t\t\t\tcreate: deployCmd,\n\t\t\t\ttriggers: [args.sourceHash, envHash],\n\t\t\t\tdir: args.directory,\n\t\t\t\tenvironment: {\n\t\t\t\t\tRAILWAY_TOKEN: args.projectToken,\n\t\t\t\t},\n\t\t\t},\n\t\t\t{ parent: this },\n\t\t);\n\n\t\tthis.registerOutputs({});\n\t}\n}\n"],"mappings":";;;;;;;;AA4CA,MAAM,WAAW;;;;;;;;;AAUjB,IAAa,gBAAb,cAAmCA,eAAO,kBAAkB;CAC3D,YACC,MACA,MACA,MACC;EACD,MAAM,2BAA2B,MAAM,CAAC,GAAG,IAAI;EAE/C,MAAM,kBAAkB,KAAK,gBAAgB,CAAC,GAC5C,KAAK,QAAQ;GACb,IAAI,IAAI,WAAW,OAAO,GACzB,OAAO,GAAG,IAAI,SAAS,IAAI;GAG5B,OAAO;EACR,CAAC,EACA,KAAK,KAAK;EAUZ,MAAM,aAAa,CARC,iBACjB,WAAW,eAAe,yBAC1B,IAEmB,KAAK,iBACxB,WAAW,KAAK,UAAU,KAAK,cAAc,EAAE,QAAQ,MAAM,KAAK,EAAE,qBACpE,EAE2C,EAAE,OAAO,OAAO,EAAE,KAAK,IAAI;EAEzE,MAAM,UAAUA,eACd,IACA,OAAO,QAAQ,KAAK,GAAG,EACrB,MAAM,CAAC,IAAI,CAAC,OAAO,EAAE,cAAc,CAAC,CAAC,EACrC,KAAK,CAAC,GAAG,OAAOA,eAAO,OAAO,CAAC,EAAE,OAAO,QAAQ,GAAG,EAAE,GAAG,KAAK,CAAC,CACjE,EACC,OAAO,UAAU,MAAM,KAAK,GAAG,CAAC;EAUlC,MAAM,YAAY,eAAO,WAAW,iBAAiB,SAAS,kCAAkC,WAAW,yDAAyD,SAAS,8CAA8C,KAAK,UAAU,aAAa,KAAK,UAAU,iBAAiB,KAAK,cAAc,uDAAuD,SAAS;EAE1W,IAAIC,gBAAQ,MAAM,QACjB,GAAG,KAAK,UACR;GACC,QAAQ;GACR,UAAU,CAAC,KAAK,YAAY,OAAO;GACnC,KAAK,KAAK;GACV,aAAa,EACZ,eAAe,KAAK,aACrB;EACD,GACA,EAAE,QAAQ,KAAK,CAChB;EAEA,KAAK,gBAAgB,CAAC,CAAC;CACxB;AACD"}
|
|
@@ -15,11 +15,11 @@ interface RailwayDeployArgs {
|
|
|
15
15
|
/** Project-scoped Railway token for `railway up` CLI (stable across runs). */
|
|
16
16
|
projectToken: pulumi.Input<string>;
|
|
17
17
|
/** Railway project UUID. */
|
|
18
|
-
projectId: string
|
|
18
|
+
projectId: pulumi.Input<string>;
|
|
19
19
|
/** Railway service UUID to deploy to. */
|
|
20
20
|
serviceId: pulumi.Input<string>;
|
|
21
21
|
/** Railway environment UUID (e.g. production). */
|
|
22
|
-
environmentId: string
|
|
22
|
+
environmentId: pulumi.Input<string>;
|
|
23
23
|
/** Absolute path to the monorepo root (working directory for `railway up`). */
|
|
24
24
|
directory: string;
|
|
25
25
|
/** SHA-256 hash of the app source directory, used as a deploy trigger. */
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"deploy.d.cts","names":[],"sources":["../../src/railway/deploy.ts"],"mappings":";;;;;UAIiB,mBAAA;;EAEhB,OAAA;EAFmC;EAKnC,YAAA;EALmC;EAQnC,gBAAA;AAAA;AAAA,UAGS,iBAAA;EAHO;EAKhB,YAAA,EAAc,MAAA,CAAO,KAAA;EAFZ;EAKT,SAAA;;
|
|
1
|
+
{"version":3,"file":"deploy.d.cts","names":[],"sources":["../../src/railway/deploy.ts"],"mappings":";;;;;UAIiB,mBAAA;;EAEhB,OAAA;EAFmC;EAKnC,YAAA;EALmC;EAQnC,gBAAA;AAAA;AAAA,UAGS,iBAAA;EAHO;EAKhB,YAAA,EAAc,MAAA,CAAO,KAAA;EAFZ;EAKT,SAAA,EAAW,MAAA,CAAO,KAAA;;EAGlB,SAAA,EAAW,MAAA,CAAO,KAAA;EAHP;EAMX,aAAA,EAAe,MAAA,CAAO,KAAA;EAAP;EAGf,SAAA;EAMK;EAHL,UAAA;EASuB;EANvB,GAAA,EAAK,MAAA,SAAe,MAAA,CAAO,KAAA;EAlB3B;EAqBA,YAAA;EArBqB;EAwBrB,cAAA,GAAiB,MAAA;AAAA;;;;;;;;;cAaL,aAAA,SAAsB,MAAA,CAAO,iBAAA;cAExC,IAAA,UACA,IAAA,EAAM,iBAAA,EACN,IAAA,GAAO,MAAA,CAAO,wBAAA;AAAA"}
|
|
@@ -15,11 +15,11 @@ interface RailwayDeployArgs {
|
|
|
15
15
|
/** Project-scoped Railway token for `railway up` CLI (stable across runs). */
|
|
16
16
|
projectToken: pulumi.Input<string>;
|
|
17
17
|
/** Railway project UUID. */
|
|
18
|
-
projectId: string
|
|
18
|
+
projectId: pulumi.Input<string>;
|
|
19
19
|
/** Railway service UUID to deploy to. */
|
|
20
20
|
serviceId: pulumi.Input<string>;
|
|
21
21
|
/** Railway environment UUID (e.g. production). */
|
|
22
|
-
environmentId: string
|
|
22
|
+
environmentId: pulumi.Input<string>;
|
|
23
23
|
/** Absolute path to the monorepo root (working directory for `railway up`). */
|
|
24
24
|
directory: string;
|
|
25
25
|
/** SHA-256 hash of the app source directory, used as a deploy trigger. */
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"deploy.d.mts","names":[],"sources":["../../src/railway/deploy.ts"],"mappings":";;;;;UAIiB,mBAAA;;EAEhB,OAAA;EAFmC;EAKnC,YAAA;EALmC;EAQnC,gBAAA;AAAA;AAAA,UAGS,iBAAA;EAHO;EAKhB,YAAA,EAAc,MAAA,CAAO,KAAA;EAFZ;EAKT,SAAA;;
|
|
1
|
+
{"version":3,"file":"deploy.d.mts","names":[],"sources":["../../src/railway/deploy.ts"],"mappings":";;;;;UAIiB,mBAAA;;EAEhB,OAAA;EAFmC;EAKnC,YAAA;EALmC;EAQnC,gBAAA;AAAA;AAAA,UAGS,iBAAA;EAHO;EAKhB,YAAA,EAAc,MAAA,CAAO,KAAA;EAFZ;EAKT,SAAA,EAAW,MAAA,CAAO,KAAA;;EAGlB,SAAA,EAAW,MAAA,CAAO,KAAA;EAHP;EAMX,aAAA,EAAe,MAAA,CAAO,KAAA;EAAP;EAGf,SAAA;EAMK;EAHL,UAAA;EASuB;EANvB,GAAA,EAAK,MAAA,SAAe,MAAA,CAAO,KAAA;EAlB3B;EAqBA,YAAA;EArBqB;EAwBrB,cAAA,GAAiB,MAAA;AAAA;;;;;;;;;cAaL,aAAA,SAAsB,MAAA,CAAO,iBAAA;cAExC,IAAA,UACA,IAAA,EAAM,iBAAA,EACN,IAAA,GAAO,MAAA,CAAO,wBAAA;AAAA"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"deploy.mjs","names":[],"sources":["../../src/railway/deploy.ts"],"sourcesContent":["import * as command from \"@pulumi/command\";\nimport * as pulumi from \"@pulumi/pulumi\";\n\n/** Build and deploy configuration for a Railway service. */\nexport interface RailwayDeployConfig {\n\t/** Build system: `\"RAILPACK\"`, `\"NIXPACKS\"`, or `\"DOCKERFILE\"`. */\n\tbuilder?: string;\n\n\t/** Shell command executed to start the service at runtime. */\n\tstartCommand?: string;\n\n\t/** Shell command executed before the main deploy (e.g. migrations). */\n\tpreDeployCommand?: string;\n}\n\ninterface RailwayDeployArgs {\n\t/** Project-scoped Railway token for `railway up` CLI (stable across runs). */\n\tprojectToken: pulumi.Input<string>;\n\n\t/** Railway project UUID. */\n\tprojectId: string
|
|
1
|
+
{"version":3,"file":"deploy.mjs","names":[],"sources":["../../src/railway/deploy.ts"],"sourcesContent":["import * as command from \"@pulumi/command\";\nimport * as pulumi from \"@pulumi/pulumi\";\n\n/** Build and deploy configuration for a Railway service. */\nexport interface RailwayDeployConfig {\n\t/** Build system: `\"RAILPACK\"`, `\"NIXPACKS\"`, or `\"DOCKERFILE\"`. */\n\tbuilder?: string;\n\n\t/** Shell command executed to start the service at runtime. */\n\tstartCommand?: string;\n\n\t/** Shell command executed before the main deploy (e.g. migrations). */\n\tpreDeployCommand?: string;\n}\n\ninterface RailwayDeployArgs {\n\t/** Project-scoped Railway token for `railway up` CLI (stable across runs). */\n\tprojectToken: pulumi.Input<string>;\n\n\t/** Railway project UUID. */\n\tprojectId: pulumi.Input<string>;\n\n\t/** Railway service UUID to deploy to. */\n\tserviceId: pulumi.Input<string>;\n\n\t/** Railway environment UUID (e.g. production). */\n\tenvironmentId: pulumi.Input<string>;\n\n\t/** Absolute path to the monorepo root (working directory for `railway up`). */\n\tdirectory: string;\n\n\t/** SHA-256 hash of the app source directory, used as a deploy trigger. */\n\tsourceHash: string;\n\n\t/** Env var map used as deploy trigger. */\n\tenv: Record<string, pulumi.Input<string>>;\n\n\t/** Directories to exclude via `.railwayignore`. */\n\texcludePaths?: string[];\n\n\t/** Railpack configuration written to `railpack.json` before deploy. */\n\trailpackConfig?: Record<string, unknown>;\n}\n\nconst LOCK_DIR = \"/tmp/.railway-upload-lock\";\n\n/**\n * Deploys a Railway service and waits for the build to complete.\n *\n * Uses `railway up --ci` which blocks until the build finishes.\n * Multiple deploys run in parallel — a mkdir lock serializes only the\n * brief upload phase (~5s) when `.railwayignore` must be consistent,\n * then releases so builds stream concurrently.\n */\nexport class RailwayDeploy extends pulumi.ComponentResource {\n\tconstructor(\n\t\tname: string,\n\t\targs: RailwayDeployArgs,\n\t\topts?: pulumi.ComponentResourceOptions,\n\t) {\n\t\tsuper(\"infrakit:railway:Deploy\", name, {}, opts);\n\n\t\tconst ignorePatterns = (args.excludePaths ?? [])\n\t\t\t.map((dir) => {\n\t\t\t\tif (dir.startsWith(\"apps/\")) {\n\t\t\t\t\treturn `${dir}/**\\\\n!${dir}/package.json`;\n\t\t\t\t}\n\n\t\t\t\treturn dir;\n\t\t\t})\n\t\t\t.join(\"\\\\n\");\n\n\t\tconst writeIgnore = ignorePatterns\n\t\t\t? `printf '${ignorePatterns}\\\\n' > .railwayignore`\n\t\t\t: \"\";\n\n\t\tconst writeRailpack = args.railpackConfig\n\t\t\t? `printf '${JSON.stringify(args.railpackConfig).replace(/'/g, \"\\\\'\")}' > railpack.json`\n\t\t\t: \"\";\n\n\t\tconst setupLines = [writeIgnore, writeRailpack].filter(Boolean).join(\"; \");\n\n\t\tconst envHash = pulumi\n\t\t\t.all(\n\t\t\t\tObject.entries(args.env)\n\t\t\t\t\t.sort(([a], [b]) => a.localeCompare(b))\n\t\t\t\t\t.map(([k, v]) => pulumi.output(v).apply((val) => `${k}=${val}`)),\n\t\t\t)\n\t\t\t.apply((parts) => parts.join(\",\"));\n\n\t\t// Parallel-safe upload: multiple stacks deploy concurrently, but each writes\n\t\t// .railwayignore and railpack.json to the same monorepo root before calling\n\t\t// `railway up`. The mkdir lock serializes that brief window (~5s upload phase).\n\t\t// After upload, the background job releases the lock so builds stream in parallel.\n\t\t//\n\t\t// Flow: acquire lock → write config files → release lock after 5s (background) →\n\t\t// railway up --ci (blocks through upload, then streams build logs) →\n\t\t// cleanup on exit\n\t\tconst deployCmd = pulumi.interpolate`while ! mkdir ${LOCK_DIR} 2>/dev/null; do sleep 1; done; ${setupLines}; { sleep 5; rm -f .railwayignore railpack.json; rmdir ${LOCK_DIR} 2>/dev/null; } & railway up --ci --project ${args.projectId} --service ${args.serviceId} --environment ${args.environmentId}; EXIT=$?; rm -f .railwayignore railpack.json; rmdir ${LOCK_DIR} 2>/dev/null; wait; exit $EXIT`;\n\n\t\tnew command.local.Command(\n\t\t\t`${name}-deploy`,\n\t\t\t{\n\t\t\t\tcreate: deployCmd,\n\t\t\t\ttriggers: [args.sourceHash, envHash],\n\t\t\t\tdir: args.directory,\n\t\t\t\tenvironment: {\n\t\t\t\t\tRAILWAY_TOKEN: args.projectToken,\n\t\t\t\t},\n\t\t\t},\n\t\t\t{ parent: this },\n\t\t);\n\n\t\tthis.registerOutputs({});\n\t}\n}\n"],"mappings":";;;;;AA4CA,MAAM,WAAW;;;;;;;;;AAUjB,IAAa,gBAAb,cAAmC,OAAO,kBAAkB;CAC3D,YACC,MACA,MACA,MACC;EACD,MAAM,2BAA2B,MAAM,CAAC,GAAG,IAAI;EAE/C,MAAM,kBAAkB,KAAK,gBAAgB,CAAC,GAC5C,KAAK,QAAQ;GACb,IAAI,IAAI,WAAW,OAAO,GACzB,OAAO,GAAG,IAAI,SAAS,IAAI;GAG5B,OAAO;EACR,CAAC,EACA,KAAK,KAAK;EAUZ,MAAM,aAAa,CARC,iBACjB,WAAW,eAAe,yBAC1B,IAEmB,KAAK,iBACxB,WAAW,KAAK,UAAU,KAAK,cAAc,EAAE,QAAQ,MAAM,KAAK,EAAE,qBACpE,EAE2C,EAAE,OAAO,OAAO,EAAE,KAAK,IAAI;EAEzE,MAAM,UAAU,OACd,IACA,OAAO,QAAQ,KAAK,GAAG,EACrB,MAAM,CAAC,IAAI,CAAC,OAAO,EAAE,cAAc,CAAC,CAAC,EACrC,KAAK,CAAC,GAAG,OAAO,OAAO,OAAO,CAAC,EAAE,OAAO,QAAQ,GAAG,EAAE,GAAG,KAAK,CAAC,CACjE,EACC,OAAO,UAAU,MAAM,KAAK,GAAG,CAAC;EAUlC,MAAM,YAAY,OAAO,WAAW,iBAAiB,SAAS,kCAAkC,WAAW,yDAAyD,SAAS,8CAA8C,KAAK,UAAU,aAAa,KAAK,UAAU,iBAAiB,KAAK,cAAc,uDAAuD,SAAS;EAE1W,IAAI,QAAQ,MAAM,QACjB,GAAG,KAAK,UACR;GACC,QAAQ;GACR,UAAU,CAAC,KAAK,YAAY,OAAO;GACnC,KAAK,KAAK;GACV,aAAa,EACZ,eAAe,KAAK,aACrB;EACD,GACA,EAAE,QAAQ,KAAK,CAChB;EAEA,KAAK,gBAAgB,CAAC,CAAC;CACxB;AACD"}
|
package/dist/railway/service.cjs
CHANGED
|
@@ -149,11 +149,14 @@ var RailwayServiceProvider = class {
|
|
|
149
149
|
* @param _olds Previous persisted state
|
|
150
150
|
* @param news New desired configuration
|
|
151
151
|
*/
|
|
152
|
-
async update(id,
|
|
152
|
+
async update(id, olds, news) {
|
|
153
153
|
const client = new require_railway_client.RailwayClient(news.token);
|
|
154
|
-
|
|
154
|
+
const updateInput = {};
|
|
155
|
+
if (olds.name !== news.name) updateInput.name = news.name;
|
|
156
|
+
if (news.icon && olds.icon !== news.icon) updateInput.icon = news.icon;
|
|
157
|
+
if (Object.keys(updateInput).length > 0) await client.query(SERVICE_UPDATE, {
|
|
155
158
|
id,
|
|
156
|
-
input:
|
|
159
|
+
input: updateInput
|
|
157
160
|
});
|
|
158
161
|
await applyInstanceConfig(client, id, news.environmentId, news);
|
|
159
162
|
return { outs: {
|
|
@@ -200,8 +203,8 @@ var RailwayServiceProvider = class {
|
|
|
200
203
|
/**
|
|
201
204
|
* Compares old and new inputs to determine what changed.
|
|
202
205
|
*
|
|
203
|
-
*
|
|
204
|
-
*
|
|
206
|
+
* ProjectId and environmentId changes trigger replacement.
|
|
207
|
+
* Name, builder, commands, and healthcheck changes trigger in-place update.
|
|
205
208
|
*
|
|
206
209
|
* @param _id Current resource ID (unused)
|
|
207
210
|
* @param olds Previous persisted state
|
|
@@ -210,7 +213,7 @@ var RailwayServiceProvider = class {
|
|
|
210
213
|
async diff(_id, olds, news) {
|
|
211
214
|
const replaces = [];
|
|
212
215
|
const changes = [];
|
|
213
|
-
if (olds.name !== news.name)
|
|
216
|
+
if (olds.name !== news.name) changes.push("name");
|
|
214
217
|
if (olds.projectId !== news.projectId) replaces.push("projectId");
|
|
215
218
|
if (olds.environmentId !== news.environmentId) replaces.push("environmentId");
|
|
216
219
|
if (olds.builder !== news.builder) changes.push("builder");
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"service.cjs","names":["RailwayClient","pulumi"],"sources":["../../src/railway/service.ts"],"sourcesContent":["import * as pulumi from \"@pulumi/pulumi\";\nimport { RailwayClient } from \"./client.js\";\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. */\nexport interface RailwayServiceInputs {\n\t/** Railway API bearer token. */\n\ttoken: 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: `\"RAILPACK\"`, `\"NIXPACKS\"`, or `\"DOCKERFILE\"`. */\n\tbuilder?: string;\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: `\"ON_FAILURE\"`, `\"ALWAYS\"`, or `\"NEVER\"`. */\n\trestartPolicyType?: string;\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_DELETE = `\n mutation($id: String!) { serviceDelete(id: $id) }\n`;\n\n/**\n * Applies service instance configuration (builder, commands, healthcheck).\n * Retries without healthcheck fields if the first call fails.\n */\nasync function applyInstanceConfig(\n\tclient: RailwayClient,\n\tserviceId: string,\n\tenvironmentId: string,\n\tinputs: RailwayServiceInputs,\n): Promise<void> {\n\tconst instanceInput: Record<string, unknown> = {};\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;\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: instanceInput,\n\t\t});\n\t} catch (error) {\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\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 */\nclass RailwayServiceProvider implements pulumi.dynamic.ResourceProvider {\n\t/**\n\t * Creates or adopts a Railway service by name, then applies instance config.\n\t *\n\t * @param inputs Resolved service configuration\n\t * @returns The Railway service UUID as the resource ID\n\t */\n\tasync create(\n\t\tinputs: RailwayServiceInputs,\n\t): Promise<pulumi.dynamic.CreateResult> {\n\t\tconst client = new RailwayClient(inputs.token);\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\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 applyInstanceConfig(client, serviceId, inputs.environmentId, inputs);\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\t * @param id Railway service UUID\n\t * @param _olds Previous persisted state\n\t * @param news New desired configuration\n\t */\n\tasync update(\n\t\tid: string,\n\t\t_olds: RailwayServiceOutputs,\n\t\tnews: RailwayServiceInputs,\n\t): Promise<pulumi.dynamic.UpdateResult> {\n\t\tconst client = new RailwayClient(news.token);\n\n\t\tif (news.icon) {\n\t\t\tawait client.query(SERVICE_UPDATE, {\n\t\t\t\tid,\n\t\t\t\tinput: { icon: news.icon },\n\t\t\t});\n\t\t}\n\n\t\tawait applyInstanceConfig(client, id, news.environmentId, news);\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\t * @param id Railway service UUID\n\t * @param props Last known persisted state\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(props.token);\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\tthrow new Error(`Railway service \"${props.name}\" (${id}) not found`);\n\t\t}\n\n\t\treturn { id, props: { ...props, serviceId: id } };\n\t}\n\n\t/**\n\t * Deletes the Railway service. Silently succeeds if already deleted.\n\t *\n\t * @param id Railway service UUID to delete\n\t * @param props Last known persisted state (used for token and logging)\n\t */\n\tasync delete(id: string, props: RailwayServiceOutputs): Promise<void> {\n\t\tconst client = new RailwayClient(props.token);\n\n\t\ttry {\n\t\t\tawait client.query(SERVICE_DELETE, { id });\n\n\t\t\tpulumi.log.info(`Deleted Railway service \"${props.name}\" (${id})`);\n\t\t} catch {\n\t\t\tpulumi.log.warn(\n\t\t\t\t`Failed to delete Railway service \"${props.name}\" (${id}) — may already be deleted`,\n\t\t\t);\n\t\t}\n\t}\n\n\t/**\n\t * Compares old and new inputs to determine what changed.\n\t *\n\t * Name, projectId, and environmentId changes trigger replacement.\n\t * Builder, commands, and healthcheck changes trigger in-place update.\n\t *\n\t * @param _id Current resource ID (unused)\n\t * @param olds Previous persisted state\n\t * @param news New desired configuration\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\treplaces.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\tdeleteBeforeReplace: true,\n\t\t};\n\t}\n}\n\n/**\n * Manages a Railway service with adopt-or-create semantics.\n *\n * Queries existing services by project ID and name before creating new ones.\n * Service instance configuration (builder, start command, healthcheck) is\n * applied after creation via `serviceInstanceUpdate`.\n *\n * @example\n * ```typescript\n * const service = new RailwayService(\"railway-service-api\", {\n * token: project.projectToken,\n * projectId: project.projectId,\n * environmentId: project.productionEnvironmentId,\n * name: \"@my-app/api\",\n * builder: \"RAILPACK\",\n * startCommand: \"node dist/index.js\",\n * healthcheckPath: \"/health\",\n * });\n *\n * // Use serviceId downstream\n * new RailwayVariable(\"railway-variable-api\", {\n * serviceId: service.serviceId,\n * ...\n * });\n * ```\n */\nexport class RailwayService extends pulumi.dynamic.Resource {\n\t/** Railway service UUID. */\n\tpublic declare readonly serviceId: pulumi.Output<string>;\n\n\tconstructor(\n\t\tname: string,\n\t\targs: {\n\t\t\t/** Railway API bearer token. */\n\t\t\ttoken: pulumi.Input<string>;\n\n\t\t\t/** Railway project UUID. */\n\t\t\tprojectId: pulumi.Input<string>;\n\n\t\t\t/** Railway environment UUID (e.g. production). */\n\t\t\tenvironmentId: pulumi.Input<string>;\n\n\t\t\t/** Human-readable service name used for adopt-or-create matching. */\n\t\t\tname: pulumi.Input<string>;\n\n\t\t\t/** SVG icon URL displayed in the Railway dashboard. */\n\t\t\ticon?: pulumi.Input<string>;\n\n\t\t\t/** Docker image source for image-based services. */\n\t\t\tsource?: pulumi.Input<{ image: pulumi.Input<string> }>;\n\n\t\t\t/** Build system: `\"RAILPACK\"`, `\"NIXPACKS\"`, or `\"DOCKERFILE\"`. */\n\t\t\tbuilder?: pulumi.Input<string>;\n\n\t\t\t/** Shell command executed during the build phase. */\n\t\t\tbuildCommand?: pulumi.Input<string>;\n\n\t\t\t/** Shell command executed to start the service at runtime. */\n\t\t\tstartCommand?: pulumi.Input<string>;\n\n\t\t\t/** Restart behavior: `\"ON_FAILURE\"`, `\"ALWAYS\"`, or `\"NEVER\"`. */\n\t\t\trestartPolicyType?: pulumi.Input<string>;\n\n\t\t\t/** HTTP path polled for health checks (e.g. `\"/health-check\"`). */\n\t\t\thealthcheckPath?: pulumi.Input<string>;\n\n\t\t\t/** Seconds to wait for a healthy response before marking unhealthy. */\n\t\t\thealthcheckTimeout?: pulumi.Input<number>;\n\n\t\t\t/** Shell command executed before the main deploy (e.g. migrations). */\n\t\t\tpreDeployCommand?: pulumi.Input<string>;\n\t\t},\n\t\topts?: pulumi.CustomResourceOptions,\n\t) {\n\t\tsuper(\n\t\t\tnew RailwayServiceProvider(),\n\t\t\tname,\n\t\t\t{ ...args, serviceId: undefined },\n\t\t\topts,\n\t\t);\n\t}\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"service.cjs","names":["RailwayClient","pulumi"],"sources":["../../src/railway/service.ts"],"sourcesContent":["import * as pulumi from \"@pulumi/pulumi\";\nimport { RailwayClient } from \"./client.js\";\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. */\nexport interface RailwayServiceInputs {\n\t/** Railway API bearer token. */\n\ttoken: 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: `\"RAILPACK\"`, `\"NIXPACKS\"`, or `\"DOCKERFILE\"`. */\n\tbuilder?: string;\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: `\"ON_FAILURE\"`, `\"ALWAYS\"`, or `\"NEVER\"`. */\n\trestartPolicyType?: string;\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_DELETE = `\n mutation($id: String!) { serviceDelete(id: $id) }\n`;\n\n/**\n * Applies service instance configuration (builder, commands, healthcheck).\n * Retries without healthcheck fields if the first call fails.\n */\nasync function applyInstanceConfig(\n\tclient: RailwayClient,\n\tserviceId: string,\n\tenvironmentId: string,\n\tinputs: RailwayServiceInputs,\n): Promise<void> {\n\tconst instanceInput: Record<string, unknown> = {};\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;\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: instanceInput,\n\t\t});\n\t} catch (error) {\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\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 */\nclass RailwayServiceProvider implements pulumi.dynamic.ResourceProvider {\n\t/**\n\t * Creates or adopts a Railway service by name, then applies instance config.\n\t *\n\t * @param inputs Resolved service configuration\n\t * @returns The Railway service UUID as the resource ID\n\t */\n\tasync create(\n\t\tinputs: RailwayServiceInputs,\n\t): Promise<pulumi.dynamic.CreateResult> {\n\t\tconst client = new RailwayClient(inputs.token);\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\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 applyInstanceConfig(client, serviceId, inputs.environmentId, inputs);\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\t * @param id Railway service UUID\n\t * @param _olds Previous persisted state\n\t * @param news New desired 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(news.token);\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 applyInstanceConfig(client, id, news.environmentId, news);\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\t * @param id Railway service UUID\n\t * @param props Last known persisted state\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(props.token);\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\tthrow new Error(`Railway service \"${props.name}\" (${id}) not found`);\n\t\t}\n\n\t\treturn { id, props: { ...props, serviceId: id } };\n\t}\n\n\t/**\n\t * Deletes the Railway service. Silently succeeds if already deleted.\n\t *\n\t * @param id Railway service UUID to delete\n\t * @param props Last known persisted state (used for token and logging)\n\t */\n\tasync delete(id: string, props: RailwayServiceOutputs): Promise<void> {\n\t\tconst client = new RailwayClient(props.token);\n\n\t\ttry {\n\t\t\tawait client.query(SERVICE_DELETE, { id });\n\n\t\t\tpulumi.log.info(`Deleted Railway service \"${props.name}\" (${id})`);\n\t\t} catch {\n\t\t\tpulumi.log.warn(\n\t\t\t\t`Failed to delete Railway service \"${props.name}\" (${id}) — may already be deleted`,\n\t\t\t);\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 * Name, builder, commands, and healthcheck changes trigger in-place update.\n\t *\n\t * @param _id Current resource ID (unused)\n\t * @param olds Previous persisted state\n\t * @param news New desired configuration\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\tdeleteBeforeReplace: true,\n\t\t};\n\t}\n}\n\n/**\n * Manages a Railway service with adopt-or-create semantics.\n *\n * Queries existing services by project ID and name before creating new ones.\n * Service instance configuration (builder, start command, healthcheck) is\n * applied after creation via `serviceInstanceUpdate`.\n *\n * @example\n * ```typescript\n * const service = new RailwayService(\"railway-service-api\", {\n * token: project.projectToken,\n * projectId: project.projectId,\n * environmentId: project.productionEnvironmentId,\n * name: \"@my-app/api\",\n * builder: \"RAILPACK\",\n * startCommand: \"node dist/index.js\",\n * healthcheckPath: \"/health\",\n * });\n *\n * // Use serviceId downstream\n * new RailwayVariable(\"railway-variable-api\", {\n * serviceId: service.serviceId,\n * ...\n * });\n * ```\n */\nexport class RailwayService extends pulumi.dynamic.Resource {\n\t/** Railway service UUID. */\n\tpublic declare readonly serviceId: pulumi.Output<string>;\n\n\tconstructor(\n\t\tname: string,\n\t\targs: {\n\t\t\t/** Railway API bearer token. */\n\t\t\ttoken: pulumi.Input<string>;\n\n\t\t\t/** Railway project UUID. */\n\t\t\tprojectId: pulumi.Input<string>;\n\n\t\t\t/** Railway environment UUID (e.g. production). */\n\t\t\tenvironmentId: pulumi.Input<string>;\n\n\t\t\t/** Human-readable service name used for adopt-or-create matching. */\n\t\t\tname: pulumi.Input<string>;\n\n\t\t\t/** SVG icon URL displayed in the Railway dashboard. */\n\t\t\ticon?: pulumi.Input<string>;\n\n\t\t\t/** Docker image source for image-based services. */\n\t\t\tsource?: pulumi.Input<{ image: pulumi.Input<string> }>;\n\n\t\t\t/** Build system: `\"RAILPACK\"`, `\"NIXPACKS\"`, or `\"DOCKERFILE\"`. */\n\t\t\tbuilder?: pulumi.Input<string>;\n\n\t\t\t/** Shell command executed during the build phase. */\n\t\t\tbuildCommand?: pulumi.Input<string>;\n\n\t\t\t/** Shell command executed to start the service at runtime. */\n\t\t\tstartCommand?: pulumi.Input<string>;\n\n\t\t\t/** Restart behavior: `\"ON_FAILURE\"`, `\"ALWAYS\"`, or `\"NEVER\"`. */\n\t\t\trestartPolicyType?: pulumi.Input<string>;\n\n\t\t\t/** HTTP path polled for health checks (e.g. `\"/health-check\"`). */\n\t\t\thealthcheckPath?: pulumi.Input<string>;\n\n\t\t\t/** Seconds to wait for a healthy response before marking unhealthy. */\n\t\t\thealthcheckTimeout?: pulumi.Input<number>;\n\n\t\t\t/** Shell command executed before the main deploy (e.g. migrations). */\n\t\t\tpreDeployCommand?: pulumi.Input<string>;\n\t\t},\n\t\topts?: pulumi.CustomResourceOptions,\n\t) {\n\t\tsuper(\n\t\t\tnew RailwayServiceProvider(),\n\t\t\tname,\n\t\t\t{ ...args, serviceId: undefined },\n\t\t\topts,\n\t\t);\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":";;;;;UAIU,oBAAA;;EAET,KAAK;AAAA;;UAIW,oBAAA;EAJX;EAML,KAAA;EAFoC;EAKpC,SAAA;EAY6B;EAT7B,aAAA;EAHA;EAMA,IAAA;EAAA;EAGA,IAAA;EAGA;EAAA,MAAA,GAAS,oBAAoB;EAG7B;EAAA,OAAA;EAMA;EAHA,YAAA;EASA;EANA,YAAA;EAYA;EATA,iBAAA;EASgB;EANhB,eAAA;
|
|
1
|
+
{"version":3,"file":"service.d.cts","names":[],"sources":["../../src/railway/service.ts"],"mappings":";;;;;UAIU,oBAAA;;EAET,KAAK;AAAA;;UAIW,oBAAA;EAJX;EAML,KAAA;EAFoC;EAKpC,SAAA;EAY6B;EAT7B,aAAA;EAHA;EAMA,IAAA;EAAA;EAGA,IAAA;EAGA;EAAA,MAAA,GAAS,oBAAoB;EAG7B;EAAA,OAAA;EAMA;EAHA,YAAA;EASA;EANA,YAAA;EAYA;EATA,iBAAA;EASgB;EANhB,eAAA;EAiZ2B;EA9Y3B,kBAAA;EAgZmC;EA7YnC,gBAAA;AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;cA2YY,cAAA,SAAuB,MAAA,CAAO,OAAA,CAAQ,QAAA;EAWhD;EAAA,SATsB,SAAA,EAAW,MAAA,CAAO,MAAA;cAGzC,IAAA,UACA,IAAA;IAQC,gCANA,KAAA,EAAO,MAAA,CAAO,KAAA,UAMQ;IAHtB,SAAA,EAAW,MAAA,CAAO,KAAA,UAMZ;IAHN,aAAA,EAAe,MAAA,CAAO,KAAA,UAMtB;IAHA,IAAA,EAAM,MAAA,CAAO,KAAA,UAGC;IAAd,IAAA,GAAO,MAAA,CAAO,KAAA,UAGL;IAAT,MAAA,GAAS,MAAA,CAAO,KAAA;MAAQ,KAAA,EAAO,MAAA,CAAO,KAAA;IAAA,IAAA;IAGtC,OAAA,GAAU,MAAA,CAAO,KAAA,UAAP;IAGV,YAAA,GAAe,MAAA,CAAO,KAAA,UAAtB;IAGA,YAAA,GAAe,MAAA,CAAO,KAAA,UAHA;IAMtB,iBAAA,GAAoB,MAAA,CAAO,KAAA,UAHZ;IAMf,eAAA,GAAkB,MAAA,CAAO,KAAA,UAHzB;IAMA,kBAAA,GAAqB,MAAA,CAAO,KAAA,UAND;IAS3B,gBAAA,GAAmB,MAAA,CAAO,KAAA;EAAA,GAE3B,IAAA,GAAO,MAAA,CAAO,qBAAA;AAAA"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"service.d.mts","names":[],"sources":["../../src/railway/service.ts"],"mappings":";;;;;UAIU,oBAAA;;EAET,KAAK;AAAA;;UAIW,oBAAA;EAJX;EAML,KAAA;EAFoC;EAKpC,SAAA;EAY6B;EAT7B,aAAA;EAHA;EAMA,IAAA;EAAA;EAGA,IAAA;EAGA;EAAA,MAAA,GAAS,oBAAoB;EAG7B;EAAA,OAAA;EAMA;EAHA,YAAA;EASA;EANA,YAAA;EAYA;EATA,iBAAA;EASgB;EANhB,eAAA;
|
|
1
|
+
{"version":3,"file":"service.d.mts","names":[],"sources":["../../src/railway/service.ts"],"mappings":";;;;;UAIU,oBAAA;;EAET,KAAK;AAAA;;UAIW,oBAAA;EAJX;EAML,KAAA;EAFoC;EAKpC,SAAA;EAY6B;EAT7B,aAAA;EAHA;EAMA,IAAA;EAAA;EAGA,IAAA;EAGA;EAAA,MAAA,GAAS,oBAAoB;EAG7B;EAAA,OAAA;EAMA;EAHA,YAAA;EASA;EANA,YAAA;EAYA;EATA,iBAAA;EASgB;EANhB,eAAA;EAiZ2B;EA9Y3B,kBAAA;EAgZmC;EA7YnC,gBAAA;AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;cA2YY,cAAA,SAAuB,MAAA,CAAO,OAAA,CAAQ,QAAA;EAWhD;EAAA,SATsB,SAAA,EAAW,MAAA,CAAO,MAAA;cAGzC,IAAA,UACA,IAAA;IAQC,gCANA,KAAA,EAAO,MAAA,CAAO,KAAA,UAMQ;IAHtB,SAAA,EAAW,MAAA,CAAO,KAAA,UAMZ;IAHN,aAAA,EAAe,MAAA,CAAO,KAAA,UAMtB;IAHA,IAAA,EAAM,MAAA,CAAO,KAAA,UAGC;IAAd,IAAA,GAAO,MAAA,CAAO,KAAA,UAGL;IAAT,MAAA,GAAS,MAAA,CAAO,KAAA;MAAQ,KAAA,EAAO,MAAA,CAAO,KAAA;IAAA,IAAA;IAGtC,OAAA,GAAU,MAAA,CAAO,KAAA,UAAP;IAGV,YAAA,GAAe,MAAA,CAAO,KAAA,UAAtB;IAGA,YAAA,GAAe,MAAA,CAAO,KAAA,UAHA;IAMtB,iBAAA,GAAoB,MAAA,CAAO,KAAA,UAHZ;IAMf,eAAA,GAAkB,MAAA,CAAO,KAAA,UAHzB;IAMA,kBAAA,GAAqB,MAAA,CAAO,KAAA,UAND;IAS3B,gBAAA,GAAmB,MAAA,CAAO,KAAA;EAAA,GAE3B,IAAA,GAAO,MAAA,CAAO,qBAAA;AAAA"}
|
package/dist/railway/service.mjs
CHANGED
|
@@ -147,11 +147,14 @@ var RailwayServiceProvider = class {
|
|
|
147
147
|
* @param _olds Previous persisted state
|
|
148
148
|
* @param news New desired configuration
|
|
149
149
|
*/
|
|
150
|
-
async update(id,
|
|
150
|
+
async update(id, olds, news) {
|
|
151
151
|
const client = new RailwayClient(news.token);
|
|
152
|
-
|
|
152
|
+
const updateInput = {};
|
|
153
|
+
if (olds.name !== news.name) updateInput.name = news.name;
|
|
154
|
+
if (news.icon && olds.icon !== news.icon) updateInput.icon = news.icon;
|
|
155
|
+
if (Object.keys(updateInput).length > 0) await client.query(SERVICE_UPDATE, {
|
|
153
156
|
id,
|
|
154
|
-
input:
|
|
157
|
+
input: updateInput
|
|
155
158
|
});
|
|
156
159
|
await applyInstanceConfig(client, id, news.environmentId, news);
|
|
157
160
|
return { outs: {
|
|
@@ -198,8 +201,8 @@ var RailwayServiceProvider = class {
|
|
|
198
201
|
/**
|
|
199
202
|
* Compares old and new inputs to determine what changed.
|
|
200
203
|
*
|
|
201
|
-
*
|
|
202
|
-
*
|
|
204
|
+
* ProjectId and environmentId changes trigger replacement.
|
|
205
|
+
* Name, builder, commands, and healthcheck changes trigger in-place update.
|
|
203
206
|
*
|
|
204
207
|
* @param _id Current resource ID (unused)
|
|
205
208
|
* @param olds Previous persisted state
|
|
@@ -208,7 +211,7 @@ var RailwayServiceProvider = class {
|
|
|
208
211
|
async diff(_id, olds, news) {
|
|
209
212
|
const replaces = [];
|
|
210
213
|
const changes = [];
|
|
211
|
-
if (olds.name !== news.name)
|
|
214
|
+
if (olds.name !== news.name) changes.push("name");
|
|
212
215
|
if (olds.projectId !== news.projectId) replaces.push("projectId");
|
|
213
216
|
if (olds.environmentId !== news.environmentId) replaces.push("environmentId");
|
|
214
217
|
if (olds.builder !== news.builder) changes.push("builder");
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"service.mjs","names":[],"sources":["../../src/railway/service.ts"],"sourcesContent":["import * as pulumi from \"@pulumi/pulumi\";\nimport { RailwayClient } from \"./client.js\";\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. */\nexport interface RailwayServiceInputs {\n\t/** Railway API bearer token. */\n\ttoken: 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: `\"RAILPACK\"`, `\"NIXPACKS\"`, or `\"DOCKERFILE\"`. */\n\tbuilder?: string;\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: `\"ON_FAILURE\"`, `\"ALWAYS\"`, or `\"NEVER\"`. */\n\trestartPolicyType?: string;\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_DELETE = `\n mutation($id: String!) { serviceDelete(id: $id) }\n`;\n\n/**\n * Applies service instance configuration (builder, commands, healthcheck).\n * Retries without healthcheck fields if the first call fails.\n */\nasync function applyInstanceConfig(\n\tclient: RailwayClient,\n\tserviceId: string,\n\tenvironmentId: string,\n\tinputs: RailwayServiceInputs,\n): Promise<void> {\n\tconst instanceInput: Record<string, unknown> = {};\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;\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: instanceInput,\n\t\t});\n\t} catch (error) {\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\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 */\nclass RailwayServiceProvider implements pulumi.dynamic.ResourceProvider {\n\t/**\n\t * Creates or adopts a Railway service by name, then applies instance config.\n\t *\n\t * @param inputs Resolved service configuration\n\t * @returns The Railway service UUID as the resource ID\n\t */\n\tasync create(\n\t\tinputs: RailwayServiceInputs,\n\t): Promise<pulumi.dynamic.CreateResult> {\n\t\tconst client = new RailwayClient(inputs.token);\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\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 applyInstanceConfig(client, serviceId, inputs.environmentId, inputs);\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\t * @param id Railway service UUID\n\t * @param _olds Previous persisted state\n\t * @param news New desired configuration\n\t */\n\tasync update(\n\t\tid: string,\n\t\t_olds: RailwayServiceOutputs,\n\t\tnews: RailwayServiceInputs,\n\t): Promise<pulumi.dynamic.UpdateResult> {\n\t\tconst client = new RailwayClient(news.token);\n\n\t\tif (news.icon) {\n\t\t\tawait client.query(SERVICE_UPDATE, {\n\t\t\t\tid,\n\t\t\t\tinput: { icon: news.icon },\n\t\t\t});\n\t\t}\n\n\t\tawait applyInstanceConfig(client, id, news.environmentId, news);\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\t * @param id Railway service UUID\n\t * @param props Last known persisted state\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(props.token);\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\tthrow new Error(`Railway service \"${props.name}\" (${id}) not found`);\n\t\t}\n\n\t\treturn { id, props: { ...props, serviceId: id } };\n\t}\n\n\t/**\n\t * Deletes the Railway service. Silently succeeds if already deleted.\n\t *\n\t * @param id Railway service UUID to delete\n\t * @param props Last known persisted state (used for token and logging)\n\t */\n\tasync delete(id: string, props: RailwayServiceOutputs): Promise<void> {\n\t\tconst client = new RailwayClient(props.token);\n\n\t\ttry {\n\t\t\tawait client.query(SERVICE_DELETE, { id });\n\n\t\t\tpulumi.log.info(`Deleted Railway service \"${props.name}\" (${id})`);\n\t\t} catch {\n\t\t\tpulumi.log.warn(\n\t\t\t\t`Failed to delete Railway service \"${props.name}\" (${id}) — may already be deleted`,\n\t\t\t);\n\t\t}\n\t}\n\n\t/**\n\t * Compares old and new inputs to determine what changed.\n\t *\n\t * Name, projectId, and environmentId changes trigger replacement.\n\t * Builder, commands, and healthcheck changes trigger in-place update.\n\t *\n\t * @param _id Current resource ID (unused)\n\t * @param olds Previous persisted state\n\t * @param news New desired configuration\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\treplaces.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\tdeleteBeforeReplace: true,\n\t\t};\n\t}\n}\n\n/**\n * Manages a Railway service with adopt-or-create semantics.\n *\n * Queries existing services by project ID and name before creating new ones.\n * Service instance configuration (builder, start command, healthcheck) is\n * applied after creation via `serviceInstanceUpdate`.\n *\n * @example\n * ```typescript\n * const service = new RailwayService(\"railway-service-api\", {\n * token: project.projectToken,\n * projectId: project.projectId,\n * environmentId: project.productionEnvironmentId,\n * name: \"@my-app/api\",\n * builder: \"RAILPACK\",\n * startCommand: \"node dist/index.js\",\n * healthcheckPath: \"/health\",\n * });\n *\n * // Use serviceId downstream\n * new RailwayVariable(\"railway-variable-api\", {\n * serviceId: service.serviceId,\n * ...\n * });\n * ```\n */\nexport class RailwayService extends pulumi.dynamic.Resource {\n\t/** Railway service UUID. */\n\tpublic declare readonly serviceId: pulumi.Output<string>;\n\n\tconstructor(\n\t\tname: string,\n\t\targs: {\n\t\t\t/** Railway API bearer token. */\n\t\t\ttoken: pulumi.Input<string>;\n\n\t\t\t/** Railway project UUID. */\n\t\t\tprojectId: pulumi.Input<string>;\n\n\t\t\t/** Railway environment UUID (e.g. production). */\n\t\t\tenvironmentId: pulumi.Input<string>;\n\n\t\t\t/** Human-readable service name used for adopt-or-create matching. */\n\t\t\tname: pulumi.Input<string>;\n\n\t\t\t/** SVG icon URL displayed in the Railway dashboard. */\n\t\t\ticon?: pulumi.Input<string>;\n\n\t\t\t/** Docker image source for image-based services. */\n\t\t\tsource?: pulumi.Input<{ image: pulumi.Input<string> }>;\n\n\t\t\t/** Build system: `\"RAILPACK\"`, `\"NIXPACKS\"`, or `\"DOCKERFILE\"`. */\n\t\t\tbuilder?: pulumi.Input<string>;\n\n\t\t\t/** Shell command executed during the build phase. */\n\t\t\tbuildCommand?: pulumi.Input<string>;\n\n\t\t\t/** Shell command executed to start the service at runtime. */\n\t\t\tstartCommand?: pulumi.Input<string>;\n\n\t\t\t/** Restart behavior: `\"ON_FAILURE\"`, `\"ALWAYS\"`, or `\"NEVER\"`. */\n\t\t\trestartPolicyType?: pulumi.Input<string>;\n\n\t\t\t/** HTTP path polled for health checks (e.g. `\"/health-check\"`). */\n\t\t\thealthcheckPath?: pulumi.Input<string>;\n\n\t\t\t/** Seconds to wait for a healthy response before marking unhealthy. */\n\t\t\thealthcheckTimeout?: pulumi.Input<number>;\n\n\t\t\t/** Shell command executed before the main deploy (e.g. migrations). */\n\t\t\tpreDeployCommand?: pulumi.Input<string>;\n\t\t},\n\t\topts?: pulumi.CustomResourceOptions,\n\t) {\n\t\tsuper(\n\t\t\tnew RailwayServiceProvider(),\n\t\t\tname,\n\t\t\t{ ...args, serviceId: undefined },\n\t\t\topts,\n\t\t);\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 { RailwayClient } from \"./client.js\";\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. */\nexport interface RailwayServiceInputs {\n\t/** Railway API bearer token. */\n\ttoken: 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: `\"RAILPACK\"`, `\"NIXPACKS\"`, or `\"DOCKERFILE\"`. */\n\tbuilder?: string;\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: `\"ON_FAILURE\"`, `\"ALWAYS\"`, or `\"NEVER\"`. */\n\trestartPolicyType?: string;\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_DELETE = `\n mutation($id: String!) { serviceDelete(id: $id) }\n`;\n\n/**\n * Applies service instance configuration (builder, commands, healthcheck).\n * Retries without healthcheck fields if the first call fails.\n */\nasync function applyInstanceConfig(\n\tclient: RailwayClient,\n\tserviceId: string,\n\tenvironmentId: string,\n\tinputs: RailwayServiceInputs,\n): Promise<void> {\n\tconst instanceInput: Record<string, unknown> = {};\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;\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: instanceInput,\n\t\t});\n\t} catch (error) {\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\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 */\nclass RailwayServiceProvider implements pulumi.dynamic.ResourceProvider {\n\t/**\n\t * Creates or adopts a Railway service by name, then applies instance config.\n\t *\n\t * @param inputs Resolved service configuration\n\t * @returns The Railway service UUID as the resource ID\n\t */\n\tasync create(\n\t\tinputs: RailwayServiceInputs,\n\t): Promise<pulumi.dynamic.CreateResult> {\n\t\tconst client = new RailwayClient(inputs.token);\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\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 applyInstanceConfig(client, serviceId, inputs.environmentId, inputs);\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\t * @param id Railway service UUID\n\t * @param _olds Previous persisted state\n\t * @param news New desired 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(news.token);\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 applyInstanceConfig(client, id, news.environmentId, news);\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\t * @param id Railway service UUID\n\t * @param props Last known persisted state\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(props.token);\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\tthrow new Error(`Railway service \"${props.name}\" (${id}) not found`);\n\t\t}\n\n\t\treturn { id, props: { ...props, serviceId: id } };\n\t}\n\n\t/**\n\t * Deletes the Railway service. Silently succeeds if already deleted.\n\t *\n\t * @param id Railway service UUID to delete\n\t * @param props Last known persisted state (used for token and logging)\n\t */\n\tasync delete(id: string, props: RailwayServiceOutputs): Promise<void> {\n\t\tconst client = new RailwayClient(props.token);\n\n\t\ttry {\n\t\t\tawait client.query(SERVICE_DELETE, { id });\n\n\t\t\tpulumi.log.info(`Deleted Railway service \"${props.name}\" (${id})`);\n\t\t} catch {\n\t\t\tpulumi.log.warn(\n\t\t\t\t`Failed to delete Railway service \"${props.name}\" (${id}) — may already be deleted`,\n\t\t\t);\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 * Name, builder, commands, and healthcheck changes trigger in-place update.\n\t *\n\t * @param _id Current resource ID (unused)\n\t * @param olds Previous persisted state\n\t * @param news New desired configuration\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\tdeleteBeforeReplace: true,\n\t\t};\n\t}\n}\n\n/**\n * Manages a Railway service with adopt-or-create semantics.\n *\n * Queries existing services by project ID and name before creating new ones.\n * Service instance configuration (builder, start command, healthcheck) is\n * applied after creation via `serviceInstanceUpdate`.\n *\n * @example\n * ```typescript\n * const service = new RailwayService(\"railway-service-api\", {\n * token: project.projectToken,\n * projectId: project.projectId,\n * environmentId: project.productionEnvironmentId,\n * name: \"@my-app/api\",\n * builder: \"RAILPACK\",\n * startCommand: \"node dist/index.js\",\n * healthcheckPath: \"/health\",\n * });\n *\n * // Use serviceId downstream\n * new RailwayVariable(\"railway-variable-api\", {\n * serviceId: service.serviceId,\n * ...\n * });\n * ```\n */\nexport class RailwayService extends pulumi.dynamic.Resource {\n\t/** Railway service UUID. */\n\tpublic declare readonly serviceId: pulumi.Output<string>;\n\n\tconstructor(\n\t\tname: string,\n\t\targs: {\n\t\t\t/** Railway API bearer token. */\n\t\t\ttoken: pulumi.Input<string>;\n\n\t\t\t/** Railway project UUID. */\n\t\t\tprojectId: pulumi.Input<string>;\n\n\t\t\t/** Railway environment UUID (e.g. production). */\n\t\t\tenvironmentId: pulumi.Input<string>;\n\n\t\t\t/** Human-readable service name used for adopt-or-create matching. */\n\t\t\tname: pulumi.Input<string>;\n\n\t\t\t/** SVG icon URL displayed in the Railway dashboard. */\n\t\t\ticon?: pulumi.Input<string>;\n\n\t\t\t/** Docker image source for image-based services. */\n\t\t\tsource?: pulumi.Input<{ image: pulumi.Input<string> }>;\n\n\t\t\t/** Build system: `\"RAILPACK\"`, `\"NIXPACKS\"`, or `\"DOCKERFILE\"`. */\n\t\t\tbuilder?: pulumi.Input<string>;\n\n\t\t\t/** Shell command executed during the build phase. */\n\t\t\tbuildCommand?: pulumi.Input<string>;\n\n\t\t\t/** Shell command executed to start the service at runtime. */\n\t\t\tstartCommand?: pulumi.Input<string>;\n\n\t\t\t/** Restart behavior: `\"ON_FAILURE\"`, `\"ALWAYS\"`, or `\"NEVER\"`. */\n\t\t\trestartPolicyType?: pulumi.Input<string>;\n\n\t\t\t/** HTTP path polled for health checks (e.g. `\"/health-check\"`). */\n\t\t\thealthcheckPath?: pulumi.Input<string>;\n\n\t\t\t/** Seconds to wait for a healthy response before marking unhealthy. */\n\t\t\thealthcheckTimeout?: pulumi.Input<number>;\n\n\t\t\t/** Shell command executed before the main deploy (e.g. migrations). */\n\t\t\tpreDeployCommand?: pulumi.Input<string>;\n\t\t},\n\t\topts?: pulumi.CustomResourceOptions,\n\t) {\n\t\tsuper(\n\t\t\tnew RailwayServiceProvider(),\n\t\t\tname,\n\t\t\t{ ...args, serviceId: undefined },\n\t\t\topts,\n\t\t);\n\t}\n}\n"],"mappings":"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"}
|