@infracraft/pulumi 0.1.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.

Files changed (151) hide show
  1. package/dist/chunk-BVYJZCqc.cjs +44 -0
  2. package/dist/chunk-OPjESj5l.mjs +9 -0
  3. package/dist/git-guard.cjs +62 -0
  4. package/dist/git-guard.cjs.map +1 -0
  5. package/dist/git-guard.d.cts +13 -0
  6. package/dist/git-guard.d.cts.map +1 -0
  7. package/dist/git-guard.d.mts +13 -0
  8. package/dist/git-guard.d.mts.map +1 -0
  9. package/dist/git-guard.mjs +56 -0
  10. package/dist/git-guard.mjs.map +1 -0
  11. package/dist/hash.cjs +40 -0
  12. package/dist/hash.cjs.map +1 -0
  13. package/dist/hash.d.cts +10 -0
  14. package/dist/hash.d.cts.map +1 -0
  15. package/dist/hash.d.mts +10 -0
  16. package/dist/hash.d.mts.map +1 -0
  17. package/dist/hash.mjs +36 -0
  18. package/dist/hash.mjs.map +1 -0
  19. package/dist/neon/branch.cjs +113 -0
  20. package/dist/neon/branch.cjs.map +1 -0
  21. package/dist/neon/branch.d.cts +40 -0
  22. package/dist/neon/branch.d.cts.map +1 -0
  23. package/dist/neon/branch.d.mts +40 -0
  24. package/dist/neon/branch.d.mts.map +1 -0
  25. package/dist/neon/branch.mjs +111 -0
  26. package/dist/neon/branch.mjs.map +1 -0
  27. package/dist/neon/client.cjs +85 -0
  28. package/dist/neon/client.cjs.map +1 -0
  29. package/dist/neon/client.d.cts +59 -0
  30. package/dist/neon/client.d.cts.map +1 -0
  31. package/dist/neon/client.d.mts +59 -0
  32. package/dist/neon/client.d.mts.map +1 -0
  33. package/dist/neon/client.mjs +84 -0
  34. package/dist/neon/client.mjs.map +1 -0
  35. package/dist/neon/database.cjs +117 -0
  36. package/dist/neon/database.cjs.map +1 -0
  37. package/dist/neon/database.d.cts +48 -0
  38. package/dist/neon/database.d.cts.map +1 -0
  39. package/dist/neon/database.d.mts +48 -0
  40. package/dist/neon/database.d.mts.map +1 -0
  41. package/dist/neon/database.mjs +115 -0
  42. package/dist/neon/database.mjs.map +1 -0
  43. package/dist/neon/endpoint.cjs +167 -0
  44. package/dist/neon/endpoint.cjs.map +1 -0
  45. package/dist/neon/endpoint.d.cts +57 -0
  46. package/dist/neon/endpoint.d.cts.map +1 -0
  47. package/dist/neon/endpoint.d.mts +57 -0
  48. package/dist/neon/endpoint.d.mts.map +1 -0
  49. package/dist/neon/endpoint.mjs +165 -0
  50. package/dist/neon/endpoint.mjs.map +1 -0
  51. package/dist/neon/index.cjs +14 -0
  52. package/dist/neon/index.d.cts +7 -0
  53. package/dist/neon/index.d.mts +7 -0
  54. package/dist/neon/index.mjs +8 -0
  55. package/dist/neon/project.cjs +120 -0
  56. package/dist/neon/project.cjs.map +1 -0
  57. package/dist/neon/project.d.cts +52 -0
  58. package/dist/neon/project.d.cts.map +1 -0
  59. package/dist/neon/project.d.mts +52 -0
  60. package/dist/neon/project.d.mts.map +1 -0
  61. package/dist/neon/project.mjs +118 -0
  62. package/dist/neon/project.mjs.map +1 -0
  63. package/dist/neon/role.cjs +135 -0
  64. package/dist/neon/role.cjs.map +1 -0
  65. package/dist/neon/role.d.cts +49 -0
  66. package/dist/neon/role.d.cts.map +1 -0
  67. package/dist/neon/role.d.mts +49 -0
  68. package/dist/neon/role.d.mts.map +1 -0
  69. package/dist/neon/role.mjs +133 -0
  70. package/dist/neon/role.mjs.map +1 -0
  71. package/dist/railway/client.cjs +59 -0
  72. package/dist/railway/client.cjs.map +1 -0
  73. package/dist/railway/client.d.cts +39 -0
  74. package/dist/railway/client.d.cts.map +1 -0
  75. package/dist/railway/client.d.mts +39 -0
  76. package/dist/railway/client.d.mts.map +1 -0
  77. package/dist/railway/client.mjs +58 -0
  78. package/dist/railway/client.mjs.map +1 -0
  79. package/dist/railway/deploy.cjs +40 -0
  80. package/dist/railway/deploy.cjs.map +1 -0
  81. package/dist/railway/deploy.d.cts +47 -0
  82. package/dist/railway/deploy.d.cts.map +1 -0
  83. package/dist/railway/deploy.d.mts +47 -0
  84. package/dist/railway/deploy.d.mts.map +1 -0
  85. package/dist/railway/deploy.mjs +37 -0
  86. package/dist/railway/deploy.mjs.map +1 -0
  87. package/dist/railway/domain.cjs +236 -0
  88. package/dist/railway/domain.cjs.map +1 -0
  89. package/dist/railway/domain.d.cts +52 -0
  90. package/dist/railway/domain.d.cts.map +1 -0
  91. package/dist/railway/domain.d.mts +52 -0
  92. package/dist/railway/domain.d.mts.map +1 -0
  93. package/dist/railway/domain.mjs +234 -0
  94. package/dist/railway/domain.mjs.map +1 -0
  95. package/dist/railway/index.cjs +16 -0
  96. package/dist/railway/index.d.cts +8 -0
  97. package/dist/railway/index.d.mts +8 -0
  98. package/dist/railway/index.mjs +9 -0
  99. package/dist/railway/project.cjs +231 -0
  100. package/dist/railway/project.cjs.map +1 -0
  101. package/dist/railway/project.d.cts +52 -0
  102. package/dist/railway/project.d.cts.map +1 -0
  103. package/dist/railway/project.d.mts +52 -0
  104. package/dist/railway/project.d.mts.map +1 -0
  105. package/dist/railway/project.mjs +229 -0
  106. package/dist/railway/project.mjs.map +1 -0
  107. package/dist/railway/service.cjs +268 -0
  108. package/dist/railway/service.cjs.map +1 -0
  109. package/dist/railway/service.d.cts +88 -0
  110. package/dist/railway/service.d.cts.map +1 -0
  111. package/dist/railway/service.d.mts +88 -0
  112. package/dist/railway/service.d.mts.map +1 -0
  113. package/dist/railway/service.mjs +266 -0
  114. package/dist/railway/service.mjs.map +1 -0
  115. package/dist/railway/variable.cjs +149 -0
  116. package/dist/railway/variable.cjs.map +1 -0
  117. package/dist/railway/variable.d.cts +52 -0
  118. package/dist/railway/variable.d.cts.map +1 -0
  119. package/dist/railway/variable.d.mts +52 -0
  120. package/dist/railway/variable.d.mts.map +1 -0
  121. package/dist/railway/variable.mjs +147 -0
  122. package/dist/railway/variable.mjs.map +1 -0
  123. package/dist/railway/volume.cjs +179 -0
  124. package/dist/railway/volume.cjs.map +1 -0
  125. package/dist/railway/volume.d.cts +52 -0
  126. package/dist/railway/volume.d.cts.map +1 -0
  127. package/dist/railway/volume.d.mts +52 -0
  128. package/dist/railway/volume.d.mts.map +1 -0
  129. package/dist/railway/volume.mjs +177 -0
  130. package/dist/railway/volume.mjs.map +1 -0
  131. package/dist/vercel/deploy.cjs +65 -0
  132. package/dist/vercel/deploy.cjs.map +1 -0
  133. package/dist/vercel/deploy.d.cts +54 -0
  134. package/dist/vercel/deploy.d.cts.map +1 -0
  135. package/dist/vercel/deploy.d.mts +54 -0
  136. package/dist/vercel/deploy.d.mts.map +1 -0
  137. package/dist/vercel/deploy.mjs +61 -0
  138. package/dist/vercel/deploy.mjs.map +1 -0
  139. package/dist/vercel/index.cjs +6 -0
  140. package/dist/vercel/index.d.cts +3 -0
  141. package/dist/vercel/index.d.mts +3 -0
  142. package/dist/vercel/index.mjs +4 -0
  143. package/dist/vercel/variable.cjs +280 -0
  144. package/dist/vercel/variable.cjs.map +1 -0
  145. package/dist/vercel/variable.d.cts +55 -0
  146. package/dist/vercel/variable.d.cts.map +1 -0
  147. package/dist/vercel/variable.d.mts +55 -0
  148. package/dist/vercel/variable.d.mts.map +1 -0
  149. package/dist/vercel/variable.mjs +278 -0
  150. package/dist/vercel/variable.mjs.map +1 -0
  151. package/package.json +62 -0
@@ -0,0 +1,8 @@
1
+ import { RailwayClient } from "./client.mjs";
2
+ import { RailwayDeploy, RailwayDeployConfig } from "./deploy.mjs";
3
+ import { RailwayDomain, RailwayDomainInputs } from "./domain.mjs";
4
+ import { RailwayProject, RailwayProjectInputs } from "./project.mjs";
5
+ import { RailwayService, RailwayServiceInputs } from "./service.mjs";
6
+ import { RailwayVariable, RailwayVariableInputs } from "./variable.mjs";
7
+ import { RailwayVolume, RailwayVolumeInputs } from "./volume.mjs";
8
+ export { RailwayClient, RailwayDeploy, type RailwayDeployConfig, RailwayDomain, type RailwayDomainInputs, RailwayProject, type RailwayProjectInputs, RailwayService, type RailwayServiceInputs, RailwayVariable, type RailwayVariableInputs, RailwayVolume, type RailwayVolumeInputs };
@@ -0,0 +1,9 @@
1
+ import { RailwayClient } from "./client.mjs";
2
+ import { RailwayDeploy } from "./deploy.mjs";
3
+ import { RailwayDomain } from "./domain.mjs";
4
+ import { RailwayProject } from "./project.mjs";
5
+ import { RailwayService } from "./service.mjs";
6
+ import { RailwayVariable } from "./variable.mjs";
7
+ import { RailwayVolume } from "./volume.mjs";
8
+
9
+ export { RailwayClient, RailwayDeploy, RailwayDomain, RailwayProject, RailwayService, RailwayVariable, RailwayVolume };
@@ -0,0 +1,231 @@
1
+ Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
2
+ const require_chunk = require('../chunk-BVYJZCqc.cjs');
3
+ const require_railway_client = require('./client.cjs');
4
+ let _pulumi_pulumi = require("@pulumi/pulumi");
5
+ _pulumi_pulumi = require_chunk.__toESM(_pulumi_pulumi, 1);
6
+
7
+ //#region src/railway/project.ts
8
+ const WORKSPACE_QUERY = `
9
+ query {
10
+ me {
11
+ workspaces {
12
+ id
13
+ name
14
+ projects {
15
+ edges {
16
+ node {
17
+ id
18
+ name
19
+ }
20
+ }
21
+ }
22
+ }
23
+ }
24
+ }
25
+ `;
26
+ const PROJECT_CREATE = `
27
+ mutation($input: ProjectCreateInput!) {
28
+ projectCreate(input: $input) {
29
+ id
30
+ name
31
+ }
32
+ }
33
+ `;
34
+ const PROJECT_UPDATE = `
35
+ mutation($id: String!, $input: ProjectUpdateInput!) {
36
+ projectUpdate(id: $id, input: $input) {
37
+ id
38
+ name
39
+ }
40
+ }
41
+ `;
42
+ const PROJECT_ENVIRONMENTS_QUERY = `
43
+ query($projectId: String!) {
44
+ project(id: $projectId) {
45
+ environments {
46
+ edges {
47
+ node {
48
+ id
49
+ name
50
+ }
51
+ }
52
+ }
53
+ }
54
+ }
55
+ `;
56
+ const PROJECT_TOKENS_QUERY = `
57
+ query($projectId: String!) {
58
+ projectTokens(projectId: $projectId) {
59
+ edges { node { id name } }
60
+ }
61
+ }
62
+ `;
63
+ const PROJECT_TOKEN_CREATE = `
64
+ mutation($input: ProjectTokenCreateInput!) {
65
+ projectTokenCreate(input: $input)
66
+ }
67
+ `;
68
+ const PROJECT_TOKEN_DELETE = `
69
+ mutation($id: String!) { projectTokenDelete(id: $id) }
70
+ `;
71
+ const PERMANENT_TOKEN_NAME = "pulumi";
72
+ /**
73
+ * Fetches all environments for a project and returns a name → UUID map.
74
+ */
75
+ async function fetchProjectEnvironments(client, projectId) {
76
+ const result = await client.query(PROJECT_ENVIRONMENTS_QUERY, { projectId });
77
+ const environments = {};
78
+ for (const edge of result.project.environments.edges) environments[edge.node.name] = edge.node.id;
79
+ return environments;
80
+ }
81
+ /**
82
+ * Gets or creates a permanent project-scoped token named "pulumi".
83
+ *
84
+ * Deletes any stale tokens with the same name before creating a new one,
85
+ * ensuring a single canonical token exists. Does NOT write to Pulumi config —
86
+ * the token is returned as a secret output for the consumer to manage.
87
+ */
88
+ async function getOrCreateProjectToken(client, projectId, environmentId) {
89
+ const stale = (await client.query(PROJECT_TOKENS_QUERY, { projectId })).projectTokens.edges.filter((edge) => edge.node.name === PERMANENT_TOKEN_NAME);
90
+ for (const entry of stale) await client.query(PROJECT_TOKEN_DELETE, { id: entry.node.id });
91
+ return (await client.query(PROJECT_TOKEN_CREATE, { input: {
92
+ projectId,
93
+ name: PERMANENT_TOKEN_NAME,
94
+ ...environmentId ? { environmentId } : {}
95
+ } })).projectTokenCreate;
96
+ }
97
+ /**
98
+ * Dynamic provider that adopts an existing Railway project by name, or creates one.
99
+ *
100
+ * On create:
101
+ * 1. Queries workspaces to find the project by name.
102
+ * 2. If found → adopts. If not → creates via `projectCreate`.
103
+ * 3. Fetches all environments and resolves the production environment ID.
104
+ * 4. Creates/reuses a project-scoped token named "pulumi".
105
+ *
106
+ * Deletion is a no-op (with a warning) to prevent accidental project removal.
107
+ * Name changes trigger replacement.
108
+ */
109
+ var RailwayProjectProvider = class {
110
+ async create(inputs) {
111
+ const client = new require_railway_client.RailwayClient(inputs.token);
112
+ const workspaces = (await client.query(WORKSPACE_QUERY)).me.workspaces;
113
+ if (workspaces.length === 0) throw new Error("No Railway workspace found — cannot create project");
114
+ let projectId;
115
+ for (const workspace of workspaces) {
116
+ const match = workspace.projects.edges.find((edge) => edge.node.name === inputs.name);
117
+ if (match) {
118
+ projectId = match.node.id;
119
+ break;
120
+ }
121
+ }
122
+ if (projectId) {
123
+ _pulumi_pulumi.log.info(`Adopted existing Railway project "${inputs.name}" (${projectId})`);
124
+ if (inputs.description) await client.query(PROJECT_UPDATE, {
125
+ id: projectId,
126
+ input: { description: inputs.description }
127
+ });
128
+ } else {
129
+ const workspace = workspaces[0];
130
+ projectId = (await client.query(PROJECT_CREATE, { input: {
131
+ name: inputs.name,
132
+ description: inputs.description,
133
+ workspaceId: workspace.id
134
+ } })).projectCreate.id;
135
+ _pulumi_pulumi.log.info(`Created Railway project "${inputs.name}" (${projectId})`);
136
+ }
137
+ if (!projectId) throw new Error(`Failed to find or create Railway project "${inputs.name}"`);
138
+ const productionEnvironmentId = (await fetchProjectEnvironments(client, projectId)).production ?? "";
139
+ const projectToken = await getOrCreateProjectToken(client, projectId, productionEnvironmentId || void 0);
140
+ const outs = {
141
+ ...inputs,
142
+ projectId,
143
+ productionEnvironmentId,
144
+ projectToken
145
+ };
146
+ return {
147
+ id: projectId,
148
+ outs
149
+ };
150
+ }
151
+ async update(id, _olds, news) {
152
+ const client = new require_railway_client.RailwayClient(news.token);
153
+ await client.query(PROJECT_UPDATE, {
154
+ id,
155
+ input: {
156
+ name: news.name,
157
+ description: news.description
158
+ }
159
+ });
160
+ const productionEnvironmentId = (await fetchProjectEnvironments(client, id)).production ?? "";
161
+ const projectToken = await getOrCreateProjectToken(client, id, productionEnvironmentId || void 0);
162
+ return { outs: {
163
+ ...news,
164
+ projectId: id,
165
+ productionEnvironmentId,
166
+ projectToken
167
+ } };
168
+ }
169
+ async read(id, props) {
170
+ const productionEnvironmentId = (await fetchProjectEnvironments(new require_railway_client.RailwayClient(props.token), id)).production ?? props.productionEnvironmentId;
171
+ return {
172
+ id,
173
+ props: {
174
+ ...props,
175
+ projectId: id,
176
+ productionEnvironmentId
177
+ }
178
+ };
179
+ }
180
+ async delete() {
181
+ _pulumi_pulumi.log.warn("Railway project deletion skipped — projects are not deleted by Pulumi");
182
+ }
183
+ async diff(_id, olds, news) {
184
+ const replaces = [];
185
+ const changes = [];
186
+ if (olds.name !== news.name) replaces.push("name");
187
+ if (olds.description !== news.description) changes.push("description");
188
+ return {
189
+ changes: replaces.length > 0 || changes.length > 0,
190
+ replaces,
191
+ deleteBeforeReplace: true
192
+ };
193
+ }
194
+ };
195
+ /**
196
+ * Manages a Railway project with adopt-or-create semantics.
197
+ *
198
+ * Discovers or creates the project, resolves the production environment ID,
199
+ * and provisions a project-scoped token named "pulumi" for CLI deploys.
200
+ * The token is exposed as a secret output — the consumer decides how to store it.
201
+ *
202
+ * @example
203
+ * ```typescript
204
+ * const project = new RailwayProject("railway-project", {
205
+ * token: railwayConfig.token,
206
+ * name: "my-app",
207
+ * description: "Railway services for my-app",
208
+ * });
209
+ *
210
+ * // Use outputs downstream
211
+ * const serviceVar = new RailwayVariable("...", {
212
+ * projectId: project.projectId,
213
+ * environmentId: project.productionEnvironmentId,
214
+ * ...
215
+ * });
216
+ * ```
217
+ */
218
+ var RailwayProject = class extends _pulumi_pulumi.dynamic.Resource {
219
+ constructor(name, args, opts) {
220
+ super(new RailwayProjectProvider(), name, {
221
+ ...args,
222
+ projectId: void 0,
223
+ productionEnvironmentId: void 0,
224
+ projectToken: _pulumi_pulumi.secret(void 0)
225
+ }, opts);
226
+ }
227
+ };
228
+
229
+ //#endregion
230
+ exports.RailwayProject = RailwayProject;
231
+ //# sourceMappingURL=project.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"project.cjs","names":["RailwayClient","pulumi"],"sources":["../../src/railway/project.ts"],"sourcesContent":["import * as pulumi from \"@pulumi/pulumi\";\nimport { RailwayClient } from \"./client.js\";\n\n/** Resolved inputs for the Railway project dynamic provider. */\nexport interface RailwayProjectInputs {\n\t/** Railway API bearer token. */\n\ttoken: string;\n\n\t/** Desired display name for the project in Railway's dashboard. */\n\tname: string;\n\n\t/** Optional description shown in Railway's dashboard. */\n\tdescription?: string;\n}\n\n/** Persisted state for the Railway project. */\ninterface RailwayProjectOutputs extends RailwayProjectInputs {\n\t/** Railway-assigned project UUID. */\n\tprojectId: string;\n\n\t/** Railway-assigned production environment UUID. */\n\tproductionEnvironmentId: string;\n\n\t/** Railway project-scoped token (auto-provisioned, exposed as secret output). */\n\tprojectToken: string;\n}\n\nconst WORKSPACE_QUERY = `\n query {\n me {\n workspaces {\n id\n name\n projects {\n edges {\n node {\n id\n name\n }\n }\n }\n }\n }\n }\n`;\n\nconst PROJECT_CREATE = `\n mutation($input: ProjectCreateInput!) {\n projectCreate(input: $input) {\n id\n name\n }\n }\n`;\n\nconst PROJECT_UPDATE = `\n mutation($id: String!, $input: ProjectUpdateInput!) {\n projectUpdate(id: $id, input: $input) {\n id\n name\n }\n }\n`;\n\nconst PROJECT_ENVIRONMENTS_QUERY = `\n query($projectId: String!) {\n project(id: $projectId) {\n environments {\n edges {\n node {\n id\n name\n }\n }\n }\n }\n }\n`;\n\nconst PROJECT_TOKENS_QUERY = `\n query($projectId: String!) {\n projectTokens(projectId: $projectId) {\n edges { node { id name } }\n }\n }\n`;\n\nconst PROJECT_TOKEN_CREATE = `\n mutation($input: ProjectTokenCreateInput!) {\n projectTokenCreate(input: $input)\n }\n`;\n\nconst PROJECT_TOKEN_DELETE = `\n mutation($id: String!) { projectTokenDelete(id: $id) }\n`;\n\nconst PERMANENT_TOKEN_NAME = \"pulumi\";\n\n/**\n * Fetches all environments for a project and returns a name → UUID map.\n */\nasync function fetchProjectEnvironments(\n\tclient: RailwayClient,\n\tprojectId: string,\n): Promise<Record<string, string>> {\n\tconst result = await client.query<{\n\t\tproject: {\n\t\t\tenvironments: {\n\t\t\t\tedges: Array<{ node: { id: string; name: string } }>;\n\t\t\t};\n\t\t};\n\t}>(PROJECT_ENVIRONMENTS_QUERY, { projectId });\n\n\tconst environments: Record<string, string> = {};\n\n\tfor (const edge of result.project.environments.edges) {\n\t\tenvironments[edge.node.name] = edge.node.id;\n\t}\n\n\treturn environments;\n}\n\n/**\n * Gets or creates a permanent project-scoped token named \"pulumi\".\n *\n * Deletes any stale tokens with the same name before creating a new one,\n * ensuring a single canonical token exists. Does NOT write to Pulumi config —\n * the token is returned as a secret output for the consumer to manage.\n */\nasync function getOrCreateProjectToken(\n\tclient: RailwayClient,\n\tprojectId: string,\n\tenvironmentId?: string,\n): Promise<string> {\n\tconst tokensResult = await client.query<{\n\t\tprojectTokens: {\n\t\t\tedges: Array<{ node: { id: string; name: string } }>;\n\t\t};\n\t}>(PROJECT_TOKENS_QUERY, { projectId });\n\n\tconst stale = tokensResult.projectTokens.edges.filter(\n\t\t(edge) => edge.node.name === PERMANENT_TOKEN_NAME,\n\t);\n\n\tfor (const entry of stale) {\n\t\tawait client.query(PROJECT_TOKEN_DELETE, { id: entry.node.id });\n\t}\n\n\tconst result = await client.query<{ projectTokenCreate: string }>(\n\t\tPROJECT_TOKEN_CREATE,\n\t\t{\n\t\t\tinput: {\n\t\t\t\tprojectId,\n\t\t\t\tname: PERMANENT_TOKEN_NAME,\n\t\t\t\t...(environmentId ? { environmentId } : {}),\n\t\t\t},\n\t\t},\n\t);\n\n\treturn result.projectTokenCreate;\n}\n\n/**\n * Dynamic provider that adopts an existing Railway project by name, or creates one.\n *\n * On create:\n * 1. Queries workspaces to find the project by name.\n * 2. If found → adopts. If not → creates via `projectCreate`.\n * 3. Fetches all environments and resolves the production environment ID.\n * 4. Creates/reuses a project-scoped token named \"pulumi\".\n *\n * Deletion is a no-op (with a warning) to prevent accidental project removal.\n * Name changes trigger replacement.\n */\nclass RailwayProjectProvider implements pulumi.dynamic.ResourceProvider {\n\tasync create(\n\t\tinputs: RailwayProjectInputs,\n\t): Promise<pulumi.dynamic.CreateResult> {\n\t\tconst client = new RailwayClient(inputs.token);\n\n\t\tconst workspaceResult = await client.query<{\n\t\t\tme: {\n\t\t\t\tworkspaces: Array<{\n\t\t\t\t\tid: string;\n\t\t\t\t\tname: string;\n\t\t\t\t\tprojects: {\n\t\t\t\t\t\tedges: Array<{ node: { id: string; name: string } }>;\n\t\t\t\t\t};\n\t\t\t\t}>;\n\t\t\t};\n\t\t}>(WORKSPACE_QUERY);\n\n\t\tconst workspaces = workspaceResult.me.workspaces;\n\n\t\tif (workspaces.length === 0) {\n\t\t\tthrow new Error(\"No Railway workspace found — cannot create project\");\n\t\t}\n\n\t\tlet projectId: string | undefined;\n\n\t\tfor (const workspace of workspaces) {\n\t\t\tconst match = workspace.projects.edges.find(\n\t\t\t\t(edge) => edge.node.name === inputs.name,\n\t\t\t);\n\n\t\t\tif (match) {\n\t\t\t\tprojectId = match.node.id;\n\n\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\n\t\tif (projectId) {\n\t\t\tpulumi.log.info(\n\t\t\t\t`Adopted existing Railway project \"${inputs.name}\" (${projectId})`,\n\t\t\t);\n\n\t\t\tif (inputs.description) {\n\t\t\t\tawait client.query(PROJECT_UPDATE, {\n\t\t\t\t\tid: projectId,\n\t\t\t\t\tinput: { description: inputs.description },\n\t\t\t\t});\n\t\t\t}\n\t\t} else {\n\t\t\tconst workspace = workspaces[0];\n\n\t\t\tconst created = await client.query<{\n\t\t\t\tprojectCreate: { id: string; name: string };\n\t\t\t}>(PROJECT_CREATE, {\n\t\t\t\tinput: {\n\t\t\t\t\tname: inputs.name,\n\t\t\t\t\tdescription: inputs.description,\n\t\t\t\t\tworkspaceId: workspace.id,\n\t\t\t\t},\n\t\t\t});\n\n\t\t\tprojectId = created.projectCreate.id;\n\n\t\t\tpulumi.log.info(\n\t\t\t\t`Created Railway project \"${inputs.name}\" (${projectId})`,\n\t\t\t);\n\t\t}\n\n\t\tif (!projectId) {\n\t\t\tthrow new Error(\n\t\t\t\t`Failed to find or create Railway project \"${inputs.name}\"`,\n\t\t\t);\n\t\t}\n\n\t\tconst environments = await fetchProjectEnvironments(client, projectId);\n\n\t\tconst productionEnvironmentId = environments.production ?? \"\";\n\n\t\tconst projectToken = await getOrCreateProjectToken(\n\t\t\tclient,\n\t\t\tprojectId,\n\t\t\tproductionEnvironmentId || undefined,\n\t\t);\n\n\t\tconst outs: RailwayProjectOutputs = {\n\t\t\t...inputs,\n\t\t\tprojectId,\n\t\t\tproductionEnvironmentId,\n\t\t\tprojectToken,\n\t\t};\n\n\t\treturn { id: projectId, outs };\n\t}\n\n\tasync update(\n\t\tid: string,\n\t\t_olds: RailwayProjectOutputs,\n\t\tnews: RailwayProjectInputs,\n\t): Promise<pulumi.dynamic.UpdateResult> {\n\t\tconst client = new RailwayClient(news.token);\n\n\t\tawait client.query(PROJECT_UPDATE, {\n\t\t\tid,\n\t\t\tinput: { name: news.name, description: news.description },\n\t\t});\n\n\t\tconst environments = await fetchProjectEnvironments(client, id);\n\n\t\tconst productionEnvironmentId = environments.production ?? \"\";\n\n\t\tconst projectToken = await getOrCreateProjectToken(\n\t\t\tclient,\n\t\t\tid,\n\t\t\tproductionEnvironmentId || undefined,\n\t\t);\n\n\t\tconst outs: RailwayProjectOutputs = {\n\t\t\t...news,\n\t\t\tprojectId: id,\n\t\t\tproductionEnvironmentId,\n\t\t\tprojectToken,\n\t\t};\n\n\t\treturn { outs };\n\t}\n\n\tasync read(\n\t\tid: string,\n\t\tprops: RailwayProjectOutputs,\n\t): Promise<pulumi.dynamic.ReadResult> {\n\t\tconst client = new RailwayClient(props.token);\n\n\t\tconst environments = await fetchProjectEnvironments(client, id);\n\n\t\tconst productionEnvironmentId =\n\t\t\tenvironments.production ?? props.productionEnvironmentId;\n\n\t\treturn {\n\t\t\tid,\n\t\t\tprops: { ...props, projectId: id, productionEnvironmentId },\n\t\t};\n\t}\n\n\tasync delete(): Promise<void> {\n\t\tpulumi.log.warn(\n\t\t\t\"Railway project deletion skipped — projects are not deleted by Pulumi\",\n\t\t);\n\t}\n\n\tasync diff(\n\t\t_id: string,\n\t\tolds: RailwayProjectOutputs,\n\t\tnews: RailwayProjectInputs,\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.description !== news.description) {\n\t\t\tchanges.push(\"description\");\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 project with adopt-or-create semantics.\n *\n * Discovers or creates the project, resolves the production environment ID,\n * and provisions a project-scoped token named \"pulumi\" for CLI deploys.\n * The token is exposed as a secret output — the consumer decides how to store it.\n *\n * @example\n * ```typescript\n * const project = new RailwayProject(\"railway-project\", {\n * token: railwayConfig.token,\n * name: \"my-app\",\n * description: \"Railway services for my-app\",\n * });\n *\n * // Use outputs downstream\n * const serviceVar = new RailwayVariable(\"...\", {\n * projectId: project.projectId,\n * environmentId: project.productionEnvironmentId,\n * ...\n * });\n * ```\n */\nexport class RailwayProject extends pulumi.dynamic.Resource {\n\t/** Railway project UUID. */\n\tpublic declare readonly projectId: pulumi.Output<string>;\n\n\t/** Railway production environment UUID. */\n\tpublic declare readonly productionEnvironmentId: pulumi.Output<string>;\n\n\t/** Railway project-scoped token (secret). */\n\tpublic declare readonly projectToken: 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/** Project display name to find and adopt or create. */\n\t\t\tname: pulumi.Input<string>;\n\n\t\t\t/** Optional description shown in Railway's dashboard. */\n\t\t\tdescription?: pulumi.Input<string>;\n\t\t},\n\t\topts?: pulumi.CustomResourceOptions,\n\t) {\n\t\tsuper(\n\t\t\tnew RailwayProjectProvider(),\n\t\t\tname,\n\t\t\t{\n\t\t\t\t...args,\n\t\t\t\tprojectId: undefined,\n\t\t\t\tproductionEnvironmentId: undefined,\n\t\t\t\tprojectToken: pulumi.secret(undefined as unknown as string),\n\t\t\t},\n\t\t\topts,\n\t\t);\n\t}\n}\n"],"mappings":"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"}
@@ -0,0 +1,52 @@
1
+ import { t as __name } from "../chunk-OPjESj5l.cjs";
2
+ import * as pulumi from "@pulumi/pulumi";
3
+
4
+ //#region src/railway/project.d.ts
5
+ /** Resolved inputs for the Railway project dynamic provider. */
6
+ interface RailwayProjectInputs {
7
+ /** Railway API bearer token. */
8
+ token: string;
9
+ /** Desired display name for the project in Railway's dashboard. */
10
+ name: string;
11
+ /** Optional description shown in Railway's dashboard. */
12
+ description?: string;
13
+ }
14
+ /**
15
+ * Manages a Railway project with adopt-or-create semantics.
16
+ *
17
+ * Discovers or creates the project, resolves the production environment ID,
18
+ * and provisions a project-scoped token named "pulumi" for CLI deploys.
19
+ * The token is exposed as a secret output — the consumer decides how to store it.
20
+ *
21
+ * @example
22
+ * ```typescript
23
+ * const project = new RailwayProject("railway-project", {
24
+ * token: railwayConfig.token,
25
+ * name: "my-app",
26
+ * description: "Railway services for my-app",
27
+ * });
28
+ *
29
+ * // Use outputs downstream
30
+ * const serviceVar = new RailwayVariable("...", {
31
+ * projectId: project.projectId,
32
+ * environmentId: project.productionEnvironmentId,
33
+ * ...
34
+ * });
35
+ * ```
36
+ */
37
+ declare class RailwayProject extends pulumi.dynamic.Resource {
38
+ /** Railway project UUID. */
39
+ readonly projectId: pulumi.Output<string>;
40
+ /** Railway production environment UUID. */
41
+ readonly productionEnvironmentId: pulumi.Output<string>;
42
+ /** Railway project-scoped token (secret). */
43
+ readonly projectToken: pulumi.Output<string>;
44
+ constructor(name: string, args: {
45
+ /** Railway API bearer token. */token: pulumi.Input<string>; /** Project display name to find and adopt or create. */
46
+ name: pulumi.Input<string>; /** Optional description shown in Railway's dashboard. */
47
+ description?: pulumi.Input<string>;
48
+ }, opts?: pulumi.CustomResourceOptions);
49
+ }
50
+ //#endregion
51
+ export { RailwayProject, RailwayProjectInputs };
52
+ //# sourceMappingURL=project.d.cts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"project.d.cts","names":[],"sources":["../../src/railway/project.ts"],"mappings":";;;;;UAIiB,oBAAA;;EAEhB,KAAA;EAFoC;EAKpC,IAAA;EALoC;EAQpC,WAAA;AAAA;;;AAAW;AAwWZ;;;;;;;;;;;;;;;;;;;;cAAa,cAAA,SAAuB,MAAA,CAAO,OAAA,CAAQ,QAAA;EAKM;EAAA,SAHhC,SAAA,EAAW,MAAA,CAAO,MAAA;EAMJ;EAAA,SAHd,uBAAA,EAAyB,MAAA,CAAO,MAAA;;WAGhC,YAAA,EAAc,MAAA,CAAO,MAAA;cAG5C,IAAA,UACA,IAAA;IAEQ,gCAAP,KAAA,EAAO,MAAA,CAAO,KAAA,UAGd;IAAA,IAAA,EAAM,MAAA,CAAO,KAAA,UAAA;IAGb,WAAA,GAAc,MAAA,CAAO,KAAA;EAAA,GAEtB,IAAA,GAAO,MAAA,CAAO,qBAAA;AAAA"}
@@ -0,0 +1,52 @@
1
+ import { t as __name } from "../chunk-OPjESj5l.mjs";
2
+ import * as pulumi from "@pulumi/pulumi";
3
+
4
+ //#region src/railway/project.d.ts
5
+ /** Resolved inputs for the Railway project dynamic provider. */
6
+ interface RailwayProjectInputs {
7
+ /** Railway API bearer token. */
8
+ token: string;
9
+ /** Desired display name for the project in Railway's dashboard. */
10
+ name: string;
11
+ /** Optional description shown in Railway's dashboard. */
12
+ description?: string;
13
+ }
14
+ /**
15
+ * Manages a Railway project with adopt-or-create semantics.
16
+ *
17
+ * Discovers or creates the project, resolves the production environment ID,
18
+ * and provisions a project-scoped token named "pulumi" for CLI deploys.
19
+ * The token is exposed as a secret output — the consumer decides how to store it.
20
+ *
21
+ * @example
22
+ * ```typescript
23
+ * const project = new RailwayProject("railway-project", {
24
+ * token: railwayConfig.token,
25
+ * name: "my-app",
26
+ * description: "Railway services for my-app",
27
+ * });
28
+ *
29
+ * // Use outputs downstream
30
+ * const serviceVar = new RailwayVariable("...", {
31
+ * projectId: project.projectId,
32
+ * environmentId: project.productionEnvironmentId,
33
+ * ...
34
+ * });
35
+ * ```
36
+ */
37
+ declare class RailwayProject extends pulumi.dynamic.Resource {
38
+ /** Railway project UUID. */
39
+ readonly projectId: pulumi.Output<string>;
40
+ /** Railway production environment UUID. */
41
+ readonly productionEnvironmentId: pulumi.Output<string>;
42
+ /** Railway project-scoped token (secret). */
43
+ readonly projectToken: pulumi.Output<string>;
44
+ constructor(name: string, args: {
45
+ /** Railway API bearer token. */token: pulumi.Input<string>; /** Project display name to find and adopt or create. */
46
+ name: pulumi.Input<string>; /** Optional description shown in Railway's dashboard. */
47
+ description?: pulumi.Input<string>;
48
+ }, opts?: pulumi.CustomResourceOptions);
49
+ }
50
+ //#endregion
51
+ export { RailwayProject, RailwayProjectInputs };
52
+ //# sourceMappingURL=project.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"project.d.mts","names":[],"sources":["../../src/railway/project.ts"],"mappings":";;;;;UAIiB,oBAAA;;EAEhB,KAAA;EAFoC;EAKpC,IAAA;EALoC;EAQpC,WAAA;AAAA;;;AAAW;AAwWZ;;;;;;;;;;;;;;;;;;;;cAAa,cAAA,SAAuB,MAAA,CAAO,OAAA,CAAQ,QAAA;EAKM;EAAA,SAHhC,SAAA,EAAW,MAAA,CAAO,MAAA;EAMJ;EAAA,SAHd,uBAAA,EAAyB,MAAA,CAAO,MAAA;;WAGhC,YAAA,EAAc,MAAA,CAAO,MAAA;cAG5C,IAAA,UACA,IAAA;IAEQ,gCAAP,KAAA,EAAO,MAAA,CAAO,KAAA,UAGd;IAAA,IAAA,EAAM,MAAA,CAAO,KAAA,UAAA;IAGb,WAAA,GAAc,MAAA,CAAO,KAAA;EAAA,GAEtB,IAAA,GAAO,MAAA,CAAO,qBAAA;AAAA"}
@@ -0,0 +1,229 @@
1
+ import { t as __name } from "../chunk-OPjESj5l.mjs";
2
+ import { RailwayClient } from "./client.mjs";
3
+ import * as pulumi from "@pulumi/pulumi";
4
+
5
+ //#region src/railway/project.ts
6
+ const WORKSPACE_QUERY = `
7
+ query {
8
+ me {
9
+ workspaces {
10
+ id
11
+ name
12
+ projects {
13
+ edges {
14
+ node {
15
+ id
16
+ name
17
+ }
18
+ }
19
+ }
20
+ }
21
+ }
22
+ }
23
+ `;
24
+ const PROJECT_CREATE = `
25
+ mutation($input: ProjectCreateInput!) {
26
+ projectCreate(input: $input) {
27
+ id
28
+ name
29
+ }
30
+ }
31
+ `;
32
+ const PROJECT_UPDATE = `
33
+ mutation($id: String!, $input: ProjectUpdateInput!) {
34
+ projectUpdate(id: $id, input: $input) {
35
+ id
36
+ name
37
+ }
38
+ }
39
+ `;
40
+ const PROJECT_ENVIRONMENTS_QUERY = `
41
+ query($projectId: String!) {
42
+ project(id: $projectId) {
43
+ environments {
44
+ edges {
45
+ node {
46
+ id
47
+ name
48
+ }
49
+ }
50
+ }
51
+ }
52
+ }
53
+ `;
54
+ const PROJECT_TOKENS_QUERY = `
55
+ query($projectId: String!) {
56
+ projectTokens(projectId: $projectId) {
57
+ edges { node { id name } }
58
+ }
59
+ }
60
+ `;
61
+ const PROJECT_TOKEN_CREATE = `
62
+ mutation($input: ProjectTokenCreateInput!) {
63
+ projectTokenCreate(input: $input)
64
+ }
65
+ `;
66
+ const PROJECT_TOKEN_DELETE = `
67
+ mutation($id: String!) { projectTokenDelete(id: $id) }
68
+ `;
69
+ const PERMANENT_TOKEN_NAME = "pulumi";
70
+ /**
71
+ * Fetches all environments for a project and returns a name → UUID map.
72
+ */
73
+ async function fetchProjectEnvironments(client, projectId) {
74
+ const result = await client.query(PROJECT_ENVIRONMENTS_QUERY, { projectId });
75
+ const environments = {};
76
+ for (const edge of result.project.environments.edges) environments[edge.node.name] = edge.node.id;
77
+ return environments;
78
+ }
79
+ /**
80
+ * Gets or creates a permanent project-scoped token named "pulumi".
81
+ *
82
+ * Deletes any stale tokens with the same name before creating a new one,
83
+ * ensuring a single canonical token exists. Does NOT write to Pulumi config —
84
+ * the token is returned as a secret output for the consumer to manage.
85
+ */
86
+ async function getOrCreateProjectToken(client, projectId, environmentId) {
87
+ const stale = (await client.query(PROJECT_TOKENS_QUERY, { projectId })).projectTokens.edges.filter((edge) => edge.node.name === PERMANENT_TOKEN_NAME);
88
+ for (const entry of stale) await client.query(PROJECT_TOKEN_DELETE, { id: entry.node.id });
89
+ return (await client.query(PROJECT_TOKEN_CREATE, { input: {
90
+ projectId,
91
+ name: PERMANENT_TOKEN_NAME,
92
+ ...environmentId ? { environmentId } : {}
93
+ } })).projectTokenCreate;
94
+ }
95
+ /**
96
+ * Dynamic provider that adopts an existing Railway project by name, or creates one.
97
+ *
98
+ * On create:
99
+ * 1. Queries workspaces to find the project by name.
100
+ * 2. If found → adopts. If not → creates via `projectCreate`.
101
+ * 3. Fetches all environments and resolves the production environment ID.
102
+ * 4. Creates/reuses a project-scoped token named "pulumi".
103
+ *
104
+ * Deletion is a no-op (with a warning) to prevent accidental project removal.
105
+ * Name changes trigger replacement.
106
+ */
107
+ var RailwayProjectProvider = class {
108
+ async create(inputs) {
109
+ const client = new RailwayClient(inputs.token);
110
+ const workspaces = (await client.query(WORKSPACE_QUERY)).me.workspaces;
111
+ if (workspaces.length === 0) throw new Error("No Railway workspace found — cannot create project");
112
+ let projectId;
113
+ for (const workspace of workspaces) {
114
+ const match = workspace.projects.edges.find((edge) => edge.node.name === inputs.name);
115
+ if (match) {
116
+ projectId = match.node.id;
117
+ break;
118
+ }
119
+ }
120
+ if (projectId) {
121
+ pulumi.log.info(`Adopted existing Railway project "${inputs.name}" (${projectId})`);
122
+ if (inputs.description) await client.query(PROJECT_UPDATE, {
123
+ id: projectId,
124
+ input: { description: inputs.description }
125
+ });
126
+ } else {
127
+ const workspace = workspaces[0];
128
+ projectId = (await client.query(PROJECT_CREATE, { input: {
129
+ name: inputs.name,
130
+ description: inputs.description,
131
+ workspaceId: workspace.id
132
+ } })).projectCreate.id;
133
+ pulumi.log.info(`Created Railway project "${inputs.name}" (${projectId})`);
134
+ }
135
+ if (!projectId) throw new Error(`Failed to find or create Railway project "${inputs.name}"`);
136
+ const productionEnvironmentId = (await fetchProjectEnvironments(client, projectId)).production ?? "";
137
+ const projectToken = await getOrCreateProjectToken(client, projectId, productionEnvironmentId || void 0);
138
+ const outs = {
139
+ ...inputs,
140
+ projectId,
141
+ productionEnvironmentId,
142
+ projectToken
143
+ };
144
+ return {
145
+ id: projectId,
146
+ outs
147
+ };
148
+ }
149
+ async update(id, _olds, news) {
150
+ const client = new RailwayClient(news.token);
151
+ await client.query(PROJECT_UPDATE, {
152
+ id,
153
+ input: {
154
+ name: news.name,
155
+ description: news.description
156
+ }
157
+ });
158
+ const productionEnvironmentId = (await fetchProjectEnvironments(client, id)).production ?? "";
159
+ const projectToken = await getOrCreateProjectToken(client, id, productionEnvironmentId || void 0);
160
+ return { outs: {
161
+ ...news,
162
+ projectId: id,
163
+ productionEnvironmentId,
164
+ projectToken
165
+ } };
166
+ }
167
+ async read(id, props) {
168
+ const productionEnvironmentId = (await fetchProjectEnvironments(new RailwayClient(props.token), id)).production ?? props.productionEnvironmentId;
169
+ return {
170
+ id,
171
+ props: {
172
+ ...props,
173
+ projectId: id,
174
+ productionEnvironmentId
175
+ }
176
+ };
177
+ }
178
+ async delete() {
179
+ pulumi.log.warn("Railway project deletion skipped — projects are not deleted by Pulumi");
180
+ }
181
+ async diff(_id, olds, news) {
182
+ const replaces = [];
183
+ const changes = [];
184
+ if (olds.name !== news.name) replaces.push("name");
185
+ if (olds.description !== news.description) changes.push("description");
186
+ return {
187
+ changes: replaces.length > 0 || changes.length > 0,
188
+ replaces,
189
+ deleteBeforeReplace: true
190
+ };
191
+ }
192
+ };
193
+ /**
194
+ * Manages a Railway project with adopt-or-create semantics.
195
+ *
196
+ * Discovers or creates the project, resolves the production environment ID,
197
+ * and provisions a project-scoped token named "pulumi" for CLI deploys.
198
+ * The token is exposed as a secret output — the consumer decides how to store it.
199
+ *
200
+ * @example
201
+ * ```typescript
202
+ * const project = new RailwayProject("railway-project", {
203
+ * token: railwayConfig.token,
204
+ * name: "my-app",
205
+ * description: "Railway services for my-app",
206
+ * });
207
+ *
208
+ * // Use outputs downstream
209
+ * const serviceVar = new RailwayVariable("...", {
210
+ * projectId: project.projectId,
211
+ * environmentId: project.productionEnvironmentId,
212
+ * ...
213
+ * });
214
+ * ```
215
+ */
216
+ var RailwayProject = class extends pulumi.dynamic.Resource {
217
+ constructor(name, args, opts) {
218
+ super(new RailwayProjectProvider(), name, {
219
+ ...args,
220
+ projectId: void 0,
221
+ productionEnvironmentId: void 0,
222
+ projectToken: pulumi.secret(void 0)
223
+ }, opts);
224
+ }
225
+ };
226
+
227
+ //#endregion
228
+ export { RailwayProject };
229
+ //# sourceMappingURL=project.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"project.mjs","names":[],"sources":["../../src/railway/project.ts"],"sourcesContent":["import * as pulumi from \"@pulumi/pulumi\";\nimport { RailwayClient } from \"./client.js\";\n\n/** Resolved inputs for the Railway project dynamic provider. */\nexport interface RailwayProjectInputs {\n\t/** Railway API bearer token. */\n\ttoken: string;\n\n\t/** Desired display name for the project in Railway's dashboard. */\n\tname: string;\n\n\t/** Optional description shown in Railway's dashboard. */\n\tdescription?: string;\n}\n\n/** Persisted state for the Railway project. */\ninterface RailwayProjectOutputs extends RailwayProjectInputs {\n\t/** Railway-assigned project UUID. */\n\tprojectId: string;\n\n\t/** Railway-assigned production environment UUID. */\n\tproductionEnvironmentId: string;\n\n\t/** Railway project-scoped token (auto-provisioned, exposed as secret output). */\n\tprojectToken: string;\n}\n\nconst WORKSPACE_QUERY = `\n query {\n me {\n workspaces {\n id\n name\n projects {\n edges {\n node {\n id\n name\n }\n }\n }\n }\n }\n }\n`;\n\nconst PROJECT_CREATE = `\n mutation($input: ProjectCreateInput!) {\n projectCreate(input: $input) {\n id\n name\n }\n }\n`;\n\nconst PROJECT_UPDATE = `\n mutation($id: String!, $input: ProjectUpdateInput!) {\n projectUpdate(id: $id, input: $input) {\n id\n name\n }\n }\n`;\n\nconst PROJECT_ENVIRONMENTS_QUERY = `\n query($projectId: String!) {\n project(id: $projectId) {\n environments {\n edges {\n node {\n id\n name\n }\n }\n }\n }\n }\n`;\n\nconst PROJECT_TOKENS_QUERY = `\n query($projectId: String!) {\n projectTokens(projectId: $projectId) {\n edges { node { id name } }\n }\n }\n`;\n\nconst PROJECT_TOKEN_CREATE = `\n mutation($input: ProjectTokenCreateInput!) {\n projectTokenCreate(input: $input)\n }\n`;\n\nconst PROJECT_TOKEN_DELETE = `\n mutation($id: String!) { projectTokenDelete(id: $id) }\n`;\n\nconst PERMANENT_TOKEN_NAME = \"pulumi\";\n\n/**\n * Fetches all environments for a project and returns a name → UUID map.\n */\nasync function fetchProjectEnvironments(\n\tclient: RailwayClient,\n\tprojectId: string,\n): Promise<Record<string, string>> {\n\tconst result = await client.query<{\n\t\tproject: {\n\t\t\tenvironments: {\n\t\t\t\tedges: Array<{ node: { id: string; name: string } }>;\n\t\t\t};\n\t\t};\n\t}>(PROJECT_ENVIRONMENTS_QUERY, { projectId });\n\n\tconst environments: Record<string, string> = {};\n\n\tfor (const edge of result.project.environments.edges) {\n\t\tenvironments[edge.node.name] = edge.node.id;\n\t}\n\n\treturn environments;\n}\n\n/**\n * Gets or creates a permanent project-scoped token named \"pulumi\".\n *\n * Deletes any stale tokens with the same name before creating a new one,\n * ensuring a single canonical token exists. Does NOT write to Pulumi config —\n * the token is returned as a secret output for the consumer to manage.\n */\nasync function getOrCreateProjectToken(\n\tclient: RailwayClient,\n\tprojectId: string,\n\tenvironmentId?: string,\n): Promise<string> {\n\tconst tokensResult = await client.query<{\n\t\tprojectTokens: {\n\t\t\tedges: Array<{ node: { id: string; name: string } }>;\n\t\t};\n\t}>(PROJECT_TOKENS_QUERY, { projectId });\n\n\tconst stale = tokensResult.projectTokens.edges.filter(\n\t\t(edge) => edge.node.name === PERMANENT_TOKEN_NAME,\n\t);\n\n\tfor (const entry of stale) {\n\t\tawait client.query(PROJECT_TOKEN_DELETE, { id: entry.node.id });\n\t}\n\n\tconst result = await client.query<{ projectTokenCreate: string }>(\n\t\tPROJECT_TOKEN_CREATE,\n\t\t{\n\t\t\tinput: {\n\t\t\t\tprojectId,\n\t\t\t\tname: PERMANENT_TOKEN_NAME,\n\t\t\t\t...(environmentId ? { environmentId } : {}),\n\t\t\t},\n\t\t},\n\t);\n\n\treturn result.projectTokenCreate;\n}\n\n/**\n * Dynamic provider that adopts an existing Railway project by name, or creates one.\n *\n * On create:\n * 1. Queries workspaces to find the project by name.\n * 2. If found → adopts. If not → creates via `projectCreate`.\n * 3. Fetches all environments and resolves the production environment ID.\n * 4. Creates/reuses a project-scoped token named \"pulumi\".\n *\n * Deletion is a no-op (with a warning) to prevent accidental project removal.\n * Name changes trigger replacement.\n */\nclass RailwayProjectProvider implements pulumi.dynamic.ResourceProvider {\n\tasync create(\n\t\tinputs: RailwayProjectInputs,\n\t): Promise<pulumi.dynamic.CreateResult> {\n\t\tconst client = new RailwayClient(inputs.token);\n\n\t\tconst workspaceResult = await client.query<{\n\t\t\tme: {\n\t\t\t\tworkspaces: Array<{\n\t\t\t\t\tid: string;\n\t\t\t\t\tname: string;\n\t\t\t\t\tprojects: {\n\t\t\t\t\t\tedges: Array<{ node: { id: string; name: string } }>;\n\t\t\t\t\t};\n\t\t\t\t}>;\n\t\t\t};\n\t\t}>(WORKSPACE_QUERY);\n\n\t\tconst workspaces = workspaceResult.me.workspaces;\n\n\t\tif (workspaces.length === 0) {\n\t\t\tthrow new Error(\"No Railway workspace found — cannot create project\");\n\t\t}\n\n\t\tlet projectId: string | undefined;\n\n\t\tfor (const workspace of workspaces) {\n\t\t\tconst match = workspace.projects.edges.find(\n\t\t\t\t(edge) => edge.node.name === inputs.name,\n\t\t\t);\n\n\t\t\tif (match) {\n\t\t\t\tprojectId = match.node.id;\n\n\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\n\t\tif (projectId) {\n\t\t\tpulumi.log.info(\n\t\t\t\t`Adopted existing Railway project \"${inputs.name}\" (${projectId})`,\n\t\t\t);\n\n\t\t\tif (inputs.description) {\n\t\t\t\tawait client.query(PROJECT_UPDATE, {\n\t\t\t\t\tid: projectId,\n\t\t\t\t\tinput: { description: inputs.description },\n\t\t\t\t});\n\t\t\t}\n\t\t} else {\n\t\t\tconst workspace = workspaces[0];\n\n\t\t\tconst created = await client.query<{\n\t\t\t\tprojectCreate: { id: string; name: string };\n\t\t\t}>(PROJECT_CREATE, {\n\t\t\t\tinput: {\n\t\t\t\t\tname: inputs.name,\n\t\t\t\t\tdescription: inputs.description,\n\t\t\t\t\tworkspaceId: workspace.id,\n\t\t\t\t},\n\t\t\t});\n\n\t\t\tprojectId = created.projectCreate.id;\n\n\t\t\tpulumi.log.info(\n\t\t\t\t`Created Railway project \"${inputs.name}\" (${projectId})`,\n\t\t\t);\n\t\t}\n\n\t\tif (!projectId) {\n\t\t\tthrow new Error(\n\t\t\t\t`Failed to find or create Railway project \"${inputs.name}\"`,\n\t\t\t);\n\t\t}\n\n\t\tconst environments = await fetchProjectEnvironments(client, projectId);\n\n\t\tconst productionEnvironmentId = environments.production ?? \"\";\n\n\t\tconst projectToken = await getOrCreateProjectToken(\n\t\t\tclient,\n\t\t\tprojectId,\n\t\t\tproductionEnvironmentId || undefined,\n\t\t);\n\n\t\tconst outs: RailwayProjectOutputs = {\n\t\t\t...inputs,\n\t\t\tprojectId,\n\t\t\tproductionEnvironmentId,\n\t\t\tprojectToken,\n\t\t};\n\n\t\treturn { id: projectId, outs };\n\t}\n\n\tasync update(\n\t\tid: string,\n\t\t_olds: RailwayProjectOutputs,\n\t\tnews: RailwayProjectInputs,\n\t): Promise<pulumi.dynamic.UpdateResult> {\n\t\tconst client = new RailwayClient(news.token);\n\n\t\tawait client.query(PROJECT_UPDATE, {\n\t\t\tid,\n\t\t\tinput: { name: news.name, description: news.description },\n\t\t});\n\n\t\tconst environments = await fetchProjectEnvironments(client, id);\n\n\t\tconst productionEnvironmentId = environments.production ?? \"\";\n\n\t\tconst projectToken = await getOrCreateProjectToken(\n\t\t\tclient,\n\t\t\tid,\n\t\t\tproductionEnvironmentId || undefined,\n\t\t);\n\n\t\tconst outs: RailwayProjectOutputs = {\n\t\t\t...news,\n\t\t\tprojectId: id,\n\t\t\tproductionEnvironmentId,\n\t\t\tprojectToken,\n\t\t};\n\n\t\treturn { outs };\n\t}\n\n\tasync read(\n\t\tid: string,\n\t\tprops: RailwayProjectOutputs,\n\t): Promise<pulumi.dynamic.ReadResult> {\n\t\tconst client = new RailwayClient(props.token);\n\n\t\tconst environments = await fetchProjectEnvironments(client, id);\n\n\t\tconst productionEnvironmentId =\n\t\t\tenvironments.production ?? props.productionEnvironmentId;\n\n\t\treturn {\n\t\t\tid,\n\t\t\tprops: { ...props, projectId: id, productionEnvironmentId },\n\t\t};\n\t}\n\n\tasync delete(): Promise<void> {\n\t\tpulumi.log.warn(\n\t\t\t\"Railway project deletion skipped — projects are not deleted by Pulumi\",\n\t\t);\n\t}\n\n\tasync diff(\n\t\t_id: string,\n\t\tolds: RailwayProjectOutputs,\n\t\tnews: RailwayProjectInputs,\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.description !== news.description) {\n\t\t\tchanges.push(\"description\");\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 project with adopt-or-create semantics.\n *\n * Discovers or creates the project, resolves the production environment ID,\n * and provisions a project-scoped token named \"pulumi\" for CLI deploys.\n * The token is exposed as a secret output — the consumer decides how to store it.\n *\n * @example\n * ```typescript\n * const project = new RailwayProject(\"railway-project\", {\n * token: railwayConfig.token,\n * name: \"my-app\",\n * description: \"Railway services for my-app\",\n * });\n *\n * // Use outputs downstream\n * const serviceVar = new RailwayVariable(\"...\", {\n * projectId: project.projectId,\n * environmentId: project.productionEnvironmentId,\n * ...\n * });\n * ```\n */\nexport class RailwayProject extends pulumi.dynamic.Resource {\n\t/** Railway project UUID. */\n\tpublic declare readonly projectId: pulumi.Output<string>;\n\n\t/** Railway production environment UUID. */\n\tpublic declare readonly productionEnvironmentId: pulumi.Output<string>;\n\n\t/** Railway project-scoped token (secret). */\n\tpublic declare readonly projectToken: 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/** Project display name to find and adopt or create. */\n\t\t\tname: pulumi.Input<string>;\n\n\t\t\t/** Optional description shown in Railway's dashboard. */\n\t\t\tdescription?: pulumi.Input<string>;\n\t\t},\n\t\topts?: pulumi.CustomResourceOptions,\n\t) {\n\t\tsuper(\n\t\t\tnew RailwayProjectProvider(),\n\t\t\tname,\n\t\t\t{\n\t\t\t\t...args,\n\t\t\t\tprojectId: undefined,\n\t\t\t\tproductionEnvironmentId: undefined,\n\t\t\t\tprojectToken: pulumi.secret(undefined as unknown as string),\n\t\t\t},\n\t\t\topts,\n\t\t);\n\t}\n}\n"],"mappings":"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"}