@framers/agentos-ext-cloud-railway 0.1.0 → 0.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/manifest.json +4 -4
- package/package.json +15 -7
- package/src/RailwayService.ts +0 -415
- package/src/index.ts +0 -109
- package/src/tools/addDatabase.ts +0 -45
- package/src/tools/deployService.ts +0 -89
- package/src/tools/getLogs.ts +0 -50
- package/src/tools/listServices.ts +0 -44
- package/test/RailwayService.test.ts +0 -439
- package/test/index.test.ts +0 -49
- package/test/tools.test.ts +0 -310
- package/tsconfig.json +0 -22
package/manifest.json
CHANGED
|
@@ -2,16 +2,16 @@
|
|
|
2
2
|
"$schema": "https://agentos.sh/schemas/extension-manifest-v1.json",
|
|
3
3
|
"id": "com.framers.cloud.railway",
|
|
4
4
|
"name": "Railway Cloud Deployment",
|
|
5
|
-
"version": "0.1.
|
|
5
|
+
"version": "0.1.2",
|
|
6
6
|
"description": "Deploy services to Railway, manage projects and environments, provision database plugins (Postgres, Redis, MySQL, MongoDB), and stream deployment logs via the Railway GraphQL API.",
|
|
7
7
|
"author": {
|
|
8
8
|
"name": "Frame.dev",
|
|
9
9
|
"email": "support@frame.dev",
|
|
10
|
-
"url": "https://github.com/
|
|
10
|
+
"url": "https://github.com/framerslab"
|
|
11
11
|
},
|
|
12
|
-
"license": "
|
|
12
|
+
"license": "Apache-2.0",
|
|
13
13
|
"keywords": ["cloud", "railway", "deploy", "paas", "graphql", "database"],
|
|
14
|
-
"agentosVersion": "
|
|
14
|
+
"agentosVersion": ">=0.10.40",
|
|
15
15
|
"categories": ["tool", "cloud"],
|
|
16
16
|
"platforms": [],
|
|
17
17
|
"sdk": "none",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@framers/agentos-ext-cloud-railway",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.2",
|
|
4
4
|
"description": "Railway cloud deployment extension — deploy services, manage projects, provision databases, and retrieve logs via the Railway GraphQL API.",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
@@ -35,29 +35,37 @@
|
|
|
35
35
|
"url": "https://github.com/jddunn"
|
|
36
36
|
}
|
|
37
37
|
],
|
|
38
|
-
"license": "
|
|
38
|
+
"license": "Apache-2.0",
|
|
39
39
|
"repository": {
|
|
40
40
|
"type": "git",
|
|
41
|
-
"url": "https://github.com/
|
|
41
|
+
"url": "git+https://github.com/framerslab/agentos-extensions.git",
|
|
42
42
|
"directory": "registry/curated/cloud/railway"
|
|
43
43
|
},
|
|
44
44
|
"publishConfig": {
|
|
45
45
|
"access": "public"
|
|
46
46
|
},
|
|
47
47
|
"peerDependencies": {
|
|
48
|
-
"@framers/agentos": "
|
|
48
|
+
"@framers/agentos": ">=0.10.40"
|
|
49
49
|
},
|
|
50
50
|
"devDependencies": {
|
|
51
|
-
"@framers/agentos": "^0.6.6",
|
|
52
51
|
"@types/node": "^20.12.12",
|
|
53
52
|
"rimraf": "^5.0.7",
|
|
54
53
|
"typescript": "^5.4.5",
|
|
55
|
-
"vitest": "^
|
|
54
|
+
"vitest": "^3.2.7",
|
|
55
|
+
"@framers/agentos": "^0.13.40"
|
|
56
56
|
},
|
|
57
|
+
"homepage": "https://github.com/framerslab/agentos-extensions/tree/master/registry/curated/cloud/railway#readme",
|
|
58
|
+
"bugs": {
|
|
59
|
+
"url": "https://github.com/framerslab/agentos-extensions/issues"
|
|
60
|
+
},
|
|
61
|
+
"files": [
|
|
62
|
+
"dist",
|
|
63
|
+
"manifest.json"
|
|
64
|
+
],
|
|
57
65
|
"scripts": {
|
|
58
66
|
"build": "tsc",
|
|
59
67
|
"dev": "tsc --watch",
|
|
60
|
-
"test": "vitest run",
|
|
68
|
+
"test": "vitest run --passWithNoTests",
|
|
61
69
|
"test:watch": "vitest",
|
|
62
70
|
"typecheck": "tsc --noEmit",
|
|
63
71
|
"clean": "rimraf dist"
|
package/src/RailwayService.ts
DELETED
|
@@ -1,415 +0,0 @@
|
|
|
1
|
-
// @ts-nocheck
|
|
2
|
-
/**
|
|
3
|
-
* @fileoverview Railway GraphQL API service layer.
|
|
4
|
-
*
|
|
5
|
-
* Wraps the Railway GraphQL API v2 for project management, service deployment,
|
|
6
|
-
* database plugin provisioning, and deployment log retrieval.
|
|
7
|
-
*/
|
|
8
|
-
|
|
9
|
-
// ---------------------------------------------------------------------------
|
|
10
|
-
// Types
|
|
11
|
-
// ---------------------------------------------------------------------------
|
|
12
|
-
|
|
13
|
-
export interface RailwayConfig {
|
|
14
|
-
/** Railway API token (from https://railway.com/account/tokens) */
|
|
15
|
-
token: string;
|
|
16
|
-
/** GraphQL API base URL (defaults to https://backboard.railway.com/graphql/v2) */
|
|
17
|
-
baseUrl?: string;
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
export interface RailwayProject {
|
|
21
|
-
id: string;
|
|
22
|
-
name: string;
|
|
23
|
-
description: string;
|
|
24
|
-
createdAt: string;
|
|
25
|
-
updatedAt: string;
|
|
26
|
-
environments: RailwayEnvironment[];
|
|
27
|
-
services: RailwayService_[];
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
export interface RailwayEnvironment {
|
|
31
|
-
id: string;
|
|
32
|
-
name: string;
|
|
33
|
-
isEphemeral: boolean;
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
/** Underscore suffix to avoid clash with the service class name. */
|
|
37
|
-
export interface RailwayService_ {
|
|
38
|
-
id: string;
|
|
39
|
-
name: string;
|
|
40
|
-
projectId: string;
|
|
41
|
-
createdAt: string;
|
|
42
|
-
updatedAt: string;
|
|
43
|
-
icon?: string;
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
export interface RailwayDeployment {
|
|
47
|
-
id: string;
|
|
48
|
-
serviceId: string;
|
|
49
|
-
environmentId: string;
|
|
50
|
-
status: string;
|
|
51
|
-
createdAt: string;
|
|
52
|
-
url?: string;
|
|
53
|
-
staticUrl?: string;
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
export interface RailwayPlugin {
|
|
57
|
-
id: string;
|
|
58
|
-
name: string;
|
|
59
|
-
status: string;
|
|
60
|
-
friendlyName: string;
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
export interface RailwayLogEntry {
|
|
64
|
-
timestamp: string;
|
|
65
|
-
message: string;
|
|
66
|
-
severity: string;
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
// ---------------------------------------------------------------------------
|
|
70
|
-
// Service
|
|
71
|
-
// ---------------------------------------------------------------------------
|
|
72
|
-
|
|
73
|
-
export class RailwayService {
|
|
74
|
-
private config: RailwayConfig;
|
|
75
|
-
private running = false;
|
|
76
|
-
|
|
77
|
-
constructor(config: RailwayConfig) {
|
|
78
|
-
this.config = {
|
|
79
|
-
...config,
|
|
80
|
-
baseUrl: config.baseUrl ?? 'https://backboard.railway.com/graphql/v2',
|
|
81
|
-
};
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
async initialize(): Promise<void> {
|
|
85
|
-
// Validate token by fetching current user
|
|
86
|
-
const result = await this.gql<{ me: { id: string } }>(`query { me { id name email } }`);
|
|
87
|
-
if (!result.me?.id) {
|
|
88
|
-
throw new Error('Railway auth failed: could not retrieve user');
|
|
89
|
-
}
|
|
90
|
-
this.running = true;
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
async shutdown(): Promise<void> {
|
|
94
|
-
this.running = false;
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
get isRunning(): boolean {
|
|
98
|
-
return this.running;
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
// ── Projects ─────────────────────────────────────────────────────────────
|
|
102
|
-
|
|
103
|
-
/** List all projects. */
|
|
104
|
-
async listProjects(): Promise<RailwayProject[]> {
|
|
105
|
-
const result = await this.gql<{ projects: { edges: Array<{ node: any }> } }>(`
|
|
106
|
-
query {
|
|
107
|
-
projects {
|
|
108
|
-
edges {
|
|
109
|
-
node {
|
|
110
|
-
id
|
|
111
|
-
name
|
|
112
|
-
description
|
|
113
|
-
createdAt
|
|
114
|
-
updatedAt
|
|
115
|
-
environments {
|
|
116
|
-
edges { node { id name isEphemeral } }
|
|
117
|
-
}
|
|
118
|
-
services {
|
|
119
|
-
edges { node { id name projectId createdAt updatedAt icon } }
|
|
120
|
-
}
|
|
121
|
-
}
|
|
122
|
-
}
|
|
123
|
-
}
|
|
124
|
-
}
|
|
125
|
-
`);
|
|
126
|
-
|
|
127
|
-
return (result.projects?.edges ?? []).map((e: any) => this.mapProject(e.node));
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
/** Get a single project by ID. */
|
|
131
|
-
async getProject(projectId: string): Promise<RailwayProject> {
|
|
132
|
-
const result = await this.gql<{ project: any }>(`
|
|
133
|
-
query($id: String!) {
|
|
134
|
-
project(id: $id) {
|
|
135
|
-
id
|
|
136
|
-
name
|
|
137
|
-
description
|
|
138
|
-
createdAt
|
|
139
|
-
updatedAt
|
|
140
|
-
environments {
|
|
141
|
-
edges { node { id name isEphemeral } }
|
|
142
|
-
}
|
|
143
|
-
services {
|
|
144
|
-
edges { node { id name projectId createdAt updatedAt icon } }
|
|
145
|
-
}
|
|
146
|
-
}
|
|
147
|
-
}
|
|
148
|
-
`, { id: projectId });
|
|
149
|
-
|
|
150
|
-
return this.mapProject(result.project);
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
/** Create a new project. */
|
|
154
|
-
async createProject(name: string, description?: string): Promise<RailwayProject> {
|
|
155
|
-
const result = await this.gql<{ projectCreate: any }>(`
|
|
156
|
-
mutation($input: ProjectCreateInput!) {
|
|
157
|
-
projectCreate(input: $input) {
|
|
158
|
-
id
|
|
159
|
-
name
|
|
160
|
-
description
|
|
161
|
-
createdAt
|
|
162
|
-
updatedAt
|
|
163
|
-
environments {
|
|
164
|
-
edges { node { id name isEphemeral } }
|
|
165
|
-
}
|
|
166
|
-
services {
|
|
167
|
-
edges { node { id name projectId createdAt updatedAt icon } }
|
|
168
|
-
}
|
|
169
|
-
}
|
|
170
|
-
}
|
|
171
|
-
`, { input: { name, description: description ?? '' } });
|
|
172
|
-
|
|
173
|
-
return this.mapProject(result.projectCreate);
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
// ── Services ─────────────────────────────────────────────────────────────
|
|
177
|
-
|
|
178
|
-
/** Create a service in a project from a GitHub repo. */
|
|
179
|
-
async createService(projectId: string, opts: {
|
|
180
|
-
name?: string;
|
|
181
|
-
source?: { repo: string };
|
|
182
|
-
variables?: Record<string, string>;
|
|
183
|
-
}): Promise<RailwayService_> {
|
|
184
|
-
const result = await this.gql<{ serviceCreate: any }>(`
|
|
185
|
-
mutation($input: ServiceCreateInput!) {
|
|
186
|
-
serviceCreate(input: $input) {
|
|
187
|
-
id
|
|
188
|
-
name
|
|
189
|
-
projectId
|
|
190
|
-
createdAt
|
|
191
|
-
updatedAt
|
|
192
|
-
}
|
|
193
|
-
}
|
|
194
|
-
`, {
|
|
195
|
-
input: {
|
|
196
|
-
projectId,
|
|
197
|
-
name: opts.name,
|
|
198
|
-
source: opts.source ? { repo: opts.source.repo } : undefined,
|
|
199
|
-
},
|
|
200
|
-
});
|
|
201
|
-
|
|
202
|
-
const service = result.serviceCreate;
|
|
203
|
-
|
|
204
|
-
// Set env variables if provided
|
|
205
|
-
if (opts.variables && Object.keys(opts.variables).length > 0) {
|
|
206
|
-
// Get the production environment
|
|
207
|
-
const project = await this.getProject(projectId);
|
|
208
|
-
const prodEnv = project.environments.find(e => !e.isEphemeral) ?? project.environments[0];
|
|
209
|
-
if (prodEnv) {
|
|
210
|
-
await this.upsertVariables(service.id, prodEnv.id, opts.variables);
|
|
211
|
-
}
|
|
212
|
-
}
|
|
213
|
-
|
|
214
|
-
return {
|
|
215
|
-
id: service.id,
|
|
216
|
-
name: service.name ?? '',
|
|
217
|
-
projectId: service.projectId ?? projectId,
|
|
218
|
-
createdAt: service.createdAt ?? '',
|
|
219
|
-
updatedAt: service.updatedAt ?? '',
|
|
220
|
-
};
|
|
221
|
-
}
|
|
222
|
-
|
|
223
|
-
/** Trigger a redeployment for a service in a given environment. */
|
|
224
|
-
async redeployService(serviceId: string, environmentId: string): Promise<RailwayDeployment> {
|
|
225
|
-
const result = await this.gql<{ serviceInstanceRedeploy: boolean }>(`
|
|
226
|
-
mutation($serviceId: String!, $environmentId: String!) {
|
|
227
|
-
serviceInstanceRedeploy(serviceId: $serviceId, environmentId: $environmentId)
|
|
228
|
-
}
|
|
229
|
-
`, { serviceId, environmentId });
|
|
230
|
-
|
|
231
|
-
// The redeploy mutation returns a boolean; fetch latest deployment
|
|
232
|
-
return this.getLatestDeployment(serviceId, environmentId);
|
|
233
|
-
}
|
|
234
|
-
|
|
235
|
-
/** Set environment variables for a service instance. */
|
|
236
|
-
async upsertVariables(serviceId: string, environmentId: string, variables: Record<string, string>): Promise<boolean> {
|
|
237
|
-
const result = await this.gql<{ variableCollectionUpsert: boolean }>(`
|
|
238
|
-
mutation($input: VariableCollectionUpsertInput!) {
|
|
239
|
-
variableCollectionUpsert(input: $input)
|
|
240
|
-
}
|
|
241
|
-
`, {
|
|
242
|
-
input: {
|
|
243
|
-
serviceId,
|
|
244
|
-
environmentId,
|
|
245
|
-
variables,
|
|
246
|
-
},
|
|
247
|
-
});
|
|
248
|
-
|
|
249
|
-
return result.variableCollectionUpsert ?? false;
|
|
250
|
-
}
|
|
251
|
-
|
|
252
|
-
// ── Plugins (Databases) ──────────────────────────────────────────────────
|
|
253
|
-
|
|
254
|
-
/** Create a database plugin in a project. */
|
|
255
|
-
async createPlugin(projectId: string, plugin: 'postgresql' | 'redis' | 'mysql' | 'mongodb'): Promise<RailwayPlugin> {
|
|
256
|
-
const result = await this.gql<{ pluginCreate: any }>(`
|
|
257
|
-
mutation($input: PluginCreateInput!) {
|
|
258
|
-
pluginCreate(input: $input) {
|
|
259
|
-
id
|
|
260
|
-
name
|
|
261
|
-
status
|
|
262
|
-
friendlyName
|
|
263
|
-
}
|
|
264
|
-
}
|
|
265
|
-
`, {
|
|
266
|
-
input: {
|
|
267
|
-
projectId,
|
|
268
|
-
name: plugin,
|
|
269
|
-
},
|
|
270
|
-
});
|
|
271
|
-
|
|
272
|
-
const p = result.pluginCreate;
|
|
273
|
-
return {
|
|
274
|
-
id: p.id,
|
|
275
|
-
name: p.name ?? plugin,
|
|
276
|
-
status: p.status ?? 'provisioning',
|
|
277
|
-
friendlyName: p.friendlyName ?? plugin,
|
|
278
|
-
};
|
|
279
|
-
}
|
|
280
|
-
|
|
281
|
-
// ── Deployments ──────────────────────────────────────────────────────────
|
|
282
|
-
|
|
283
|
-
/** Get the latest deployment for a service in an environment. */
|
|
284
|
-
async getLatestDeployment(serviceId: string, environmentId: string): Promise<RailwayDeployment> {
|
|
285
|
-
const result = await this.gql<{ deployments: { edges: Array<{ node: any }> } }>(`
|
|
286
|
-
query($input: DeploymentListInput!) {
|
|
287
|
-
deployments(input: $input, first: 1) {
|
|
288
|
-
edges {
|
|
289
|
-
node {
|
|
290
|
-
id
|
|
291
|
-
serviceId
|
|
292
|
-
environmentId
|
|
293
|
-
status
|
|
294
|
-
createdAt
|
|
295
|
-
staticUrl
|
|
296
|
-
}
|
|
297
|
-
}
|
|
298
|
-
}
|
|
299
|
-
}
|
|
300
|
-
`, {
|
|
301
|
-
input: { serviceId, environmentId },
|
|
302
|
-
});
|
|
303
|
-
|
|
304
|
-
const edge = result.deployments?.edges?.[0];
|
|
305
|
-
if (!edge) {
|
|
306
|
-
return {
|
|
307
|
-
id: '',
|
|
308
|
-
serviceId,
|
|
309
|
-
environmentId,
|
|
310
|
-
status: 'unknown',
|
|
311
|
-
createdAt: '',
|
|
312
|
-
};
|
|
313
|
-
}
|
|
314
|
-
|
|
315
|
-
const d = edge.node;
|
|
316
|
-
return {
|
|
317
|
-
id: d.id,
|
|
318
|
-
serviceId: d.serviceId ?? serviceId,
|
|
319
|
-
environmentId: d.environmentId ?? environmentId,
|
|
320
|
-
status: d.status ?? 'unknown',
|
|
321
|
-
createdAt: d.createdAt ?? '',
|
|
322
|
-
url: d.url,
|
|
323
|
-
staticUrl: d.staticUrl,
|
|
324
|
-
};
|
|
325
|
-
}
|
|
326
|
-
|
|
327
|
-
// ── Logs ─────────────────────────────────────────────────────────────────
|
|
328
|
-
|
|
329
|
-
/** Get deployment logs. */
|
|
330
|
-
async getDeploymentLogs(deploymentId: string): Promise<RailwayLogEntry[]> {
|
|
331
|
-
const result = await this.gql<{ deploymentLogs: any[] }>(`
|
|
332
|
-
query($deploymentId: String!) {
|
|
333
|
-
deploymentLogs(deploymentId: $deploymentId) {
|
|
334
|
-
timestamp
|
|
335
|
-
message
|
|
336
|
-
severity
|
|
337
|
-
}
|
|
338
|
-
}
|
|
339
|
-
`, { deploymentId });
|
|
340
|
-
|
|
341
|
-
return (result.deploymentLogs ?? []).map((l: any) => ({
|
|
342
|
-
timestamp: l.timestamp ?? '',
|
|
343
|
-
message: l.message ?? '',
|
|
344
|
-
severity: l.severity ?? 'info',
|
|
345
|
-
}));
|
|
346
|
-
}
|
|
347
|
-
|
|
348
|
-
/** Get build logs for a deployment. */
|
|
349
|
-
async getBuildLogs(deploymentId: string): Promise<RailwayLogEntry[]> {
|
|
350
|
-
const result = await this.gql<{ buildLogs: any[] }>(`
|
|
351
|
-
query($deploymentId: String!) {
|
|
352
|
-
buildLogs(deploymentId: $deploymentId) {
|
|
353
|
-
timestamp
|
|
354
|
-
message
|
|
355
|
-
severity
|
|
356
|
-
}
|
|
357
|
-
}
|
|
358
|
-
`, { deploymentId });
|
|
359
|
-
|
|
360
|
-
return (result.buildLogs ?? []).map((l: any) => ({
|
|
361
|
-
timestamp: l.timestamp ?? '',
|
|
362
|
-
message: l.message ?? '',
|
|
363
|
-
severity: l.severity ?? 'info',
|
|
364
|
-
}));
|
|
365
|
-
}
|
|
366
|
-
|
|
367
|
-
// ── Helpers ──────────────────────────────────────────────────────────────
|
|
368
|
-
|
|
369
|
-
private async gql<T = any>(query: string, variables?: Record<string, unknown>): Promise<T> {
|
|
370
|
-
const res = await globalThis.fetch(this.config.baseUrl!, {
|
|
371
|
-
method: 'POST',
|
|
372
|
-
headers: {
|
|
373
|
-
'Authorization': `Bearer ${this.config.token}`,
|
|
374
|
-
'Content-Type': 'application/json',
|
|
375
|
-
},
|
|
376
|
-
body: JSON.stringify({ query, variables }),
|
|
377
|
-
});
|
|
378
|
-
|
|
379
|
-
if (!res.ok) {
|
|
380
|
-
const text = await res.text();
|
|
381
|
-
throw new Error(`Railway API ${res.status}: ${text.slice(0, 500)}`);
|
|
382
|
-
}
|
|
383
|
-
|
|
384
|
-
const json = await res.json() as { data?: T; errors?: Array<{ message: string }> };
|
|
385
|
-
|
|
386
|
-
if (json.errors && json.errors.length > 0) {
|
|
387
|
-
throw new Error(`Railway GraphQL: ${json.errors.map(e => e.message).join('; ')}`);
|
|
388
|
-
}
|
|
389
|
-
|
|
390
|
-
return json.data as T;
|
|
391
|
-
}
|
|
392
|
-
|
|
393
|
-
private mapProject(data: any): RailwayProject {
|
|
394
|
-
return {
|
|
395
|
-
id: data.id,
|
|
396
|
-
name: data.name ?? '',
|
|
397
|
-
description: data.description ?? '',
|
|
398
|
-
createdAt: data.createdAt ?? '',
|
|
399
|
-
updatedAt: data.updatedAt ?? '',
|
|
400
|
-
environments: (data.environments?.edges ?? []).map((e: any) => ({
|
|
401
|
-
id: e.node.id,
|
|
402
|
-
name: e.node.name ?? '',
|
|
403
|
-
isEphemeral: e.node.isEphemeral ?? false,
|
|
404
|
-
})),
|
|
405
|
-
services: (data.services?.edges ?? []).map((e: any) => ({
|
|
406
|
-
id: e.node.id,
|
|
407
|
-
name: e.node.name ?? '',
|
|
408
|
-
projectId: e.node.projectId ?? data.id,
|
|
409
|
-
createdAt: e.node.createdAt ?? '',
|
|
410
|
-
updatedAt: e.node.updatedAt ?? '',
|
|
411
|
-
icon: e.node.icon,
|
|
412
|
-
})),
|
|
413
|
-
};
|
|
414
|
-
}
|
|
415
|
-
}
|
package/src/index.ts
DELETED
|
@@ -1,109 +0,0 @@
|
|
|
1
|
-
// @ts-nocheck
|
|
2
|
-
/**
|
|
3
|
-
* @fileoverview Railway Cloud Extension for AgentOS.
|
|
4
|
-
*
|
|
5
|
-
* Provides 4 tools for deploying services, listing projects, provisioning
|
|
6
|
-
* databases, and retrieving logs via the Railway GraphQL API.
|
|
7
|
-
*
|
|
8
|
-
* @module @framers/agentos-ext-cloud-railway
|
|
9
|
-
*/
|
|
10
|
-
|
|
11
|
-
import { RailwayService } from './RailwayService.js';
|
|
12
|
-
import type { RailwayConfig } from './RailwayService.js';
|
|
13
|
-
import { RailwayDeployServiceTool } from './tools/deployService.js';
|
|
14
|
-
import { RailwayListServicesTool } from './tools/listServices.js';
|
|
15
|
-
import { RailwayAddDatabaseTool } from './tools/addDatabase.js';
|
|
16
|
-
import { RailwayGetLogsTool } from './tools/getLogs.js';
|
|
17
|
-
|
|
18
|
-
// ---------------------------------------------------------------------------
|
|
19
|
-
// Options
|
|
20
|
-
// ---------------------------------------------------------------------------
|
|
21
|
-
|
|
22
|
-
export interface RailwayCloudOptions {
|
|
23
|
-
token?: string;
|
|
24
|
-
secrets?: Record<string, string>;
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
// ---------------------------------------------------------------------------
|
|
28
|
-
// Secret Resolution
|
|
29
|
-
// ---------------------------------------------------------------------------
|
|
30
|
-
|
|
31
|
-
function resolveConfig(opts: RailwayCloudOptions, secrets: Record<string, string>): RailwayConfig {
|
|
32
|
-
return {
|
|
33
|
-
token:
|
|
34
|
-
opts.token ?? secrets['railway.token']
|
|
35
|
-
?? process.env.RAILWAY_TOKEN ?? '',
|
|
36
|
-
};
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
// ---------------------------------------------------------------------------
|
|
40
|
-
// Extension Context
|
|
41
|
-
// ---------------------------------------------------------------------------
|
|
42
|
-
|
|
43
|
-
export interface ExtensionContext {
|
|
44
|
-
options?: Record<string, unknown>;
|
|
45
|
-
secrets?: Record<string, string>;
|
|
46
|
-
getSecret?: (key: string) => string | undefined;
|
|
47
|
-
logger?: { info: (msg: string) => void };
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
export interface ExtensionPack {
|
|
51
|
-
name: string;
|
|
52
|
-
version: string;
|
|
53
|
-
descriptors: Array<{ id: string; kind: string; priority?: number; payload: unknown }>;
|
|
54
|
-
onActivate?: () => Promise<void>;
|
|
55
|
-
onDeactivate?: () => Promise<void>;
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
// ---------------------------------------------------------------------------
|
|
59
|
-
// Factory
|
|
60
|
-
// ---------------------------------------------------------------------------
|
|
61
|
-
|
|
62
|
-
export function createExtensionPack(context: ExtensionContext): ExtensionPack {
|
|
63
|
-
const opts = (context.options ?? {}) as RailwayCloudOptions;
|
|
64
|
-
const secrets = opts.secrets ?? context.secrets ?? {};
|
|
65
|
-
const config = resolveConfig(opts, secrets);
|
|
66
|
-
|
|
67
|
-
const service = new RailwayService(config);
|
|
68
|
-
|
|
69
|
-
const deployServiceTool = new RailwayDeployServiceTool(service);
|
|
70
|
-
const listServicesTool = new RailwayListServicesTool(service);
|
|
71
|
-
const addDatabaseTool = new RailwayAddDatabaseTool(service);
|
|
72
|
-
const getLogsTool = new RailwayGetLogsTool(service);
|
|
73
|
-
|
|
74
|
-
return {
|
|
75
|
-
name: '@framers/agentos-ext-cloud-railway',
|
|
76
|
-
version: '0.1.0',
|
|
77
|
-
descriptors: [
|
|
78
|
-
{ id: 'railwayDeployService', kind: 'tool', priority: 40, payload: deployServiceTool },
|
|
79
|
-
{ id: 'railwayListServices', kind: 'tool', priority: 40, payload: listServicesTool },
|
|
80
|
-
{ id: 'railwayAddDatabase', kind: 'tool', priority: 40, payload: addDatabaseTool },
|
|
81
|
-
{ id: 'railwayGetLogs', kind: 'tool', priority: 40, payload: getLogsTool },
|
|
82
|
-
],
|
|
83
|
-
onActivate: async () => {
|
|
84
|
-
if (!config.token) {
|
|
85
|
-
throw new Error(
|
|
86
|
-
'Railway: no API token provided. Set RAILWAY_TOKEN environment variable, '
|
|
87
|
-
+ 'or provide it via secrets["railway.token"].',
|
|
88
|
-
);
|
|
89
|
-
}
|
|
90
|
-
await service.initialize();
|
|
91
|
-
},
|
|
92
|
-
onDeactivate: async () => {
|
|
93
|
-
await service.shutdown();
|
|
94
|
-
},
|
|
95
|
-
};
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
// ---------------------------------------------------------------------------
|
|
99
|
-
// Public exports
|
|
100
|
-
// ---------------------------------------------------------------------------
|
|
101
|
-
|
|
102
|
-
export { RailwayService } from './RailwayService.js';
|
|
103
|
-
export type { RailwayConfig, RailwayProject, RailwayEnvironment, RailwayService_ as RailwayServiceInfo, RailwayDeployment, RailwayPlugin, RailwayLogEntry } from './RailwayService.js';
|
|
104
|
-
export { RailwayDeployServiceTool } from './tools/deployService.js';
|
|
105
|
-
export { RailwayListServicesTool } from './tools/listServices.js';
|
|
106
|
-
export { RailwayAddDatabaseTool } from './tools/addDatabase.js';
|
|
107
|
-
export { RailwayGetLogsTool } from './tools/getLogs.js';
|
|
108
|
-
|
|
109
|
-
export default createExtensionPack;
|
package/src/tools/addDatabase.ts
DELETED
|
@@ -1,45 +0,0 @@
|
|
|
1
|
-
// @ts-nocheck
|
|
2
|
-
/**
|
|
3
|
-
* @fileoverview RailwayAddDatabaseTool — provision a database plugin on Railway.
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
import type { RailwayService, RailwayPlugin } from '../RailwayService.js';
|
|
7
|
-
|
|
8
|
-
export interface RailwayAddDatabaseInput {
|
|
9
|
-
projectId: string;
|
|
10
|
-
plugin: 'postgresql' | 'redis' | 'mysql' | 'mongodb';
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
export class RailwayAddDatabaseTool {
|
|
14
|
-
readonly id = 'railwayAddDatabase';
|
|
15
|
-
readonly name = 'railwayAddDatabase';
|
|
16
|
-
readonly displayName = 'Add Railway Database';
|
|
17
|
-
readonly description = 'Provision a database plugin in a Railway project. Supports PostgreSQL, Redis, MySQL, and MongoDB. The database will be automatically connected to services in the same project via environment variables.';
|
|
18
|
-
readonly category = 'cloud';
|
|
19
|
-
readonly version = '0.1.0';
|
|
20
|
-
readonly hasSideEffects = true;
|
|
21
|
-
|
|
22
|
-
readonly inputSchema = {
|
|
23
|
-
type: 'object' as const,
|
|
24
|
-
properties: {
|
|
25
|
-
projectId: { type: 'string', description: 'Railway project ID to add the database to' },
|
|
26
|
-
plugin: {
|
|
27
|
-
type: 'string',
|
|
28
|
-
enum: ['postgresql', 'redis', 'mysql', 'mongodb'],
|
|
29
|
-
description: 'Database type to provision',
|
|
30
|
-
},
|
|
31
|
-
},
|
|
32
|
-
required: ['projectId', 'plugin'],
|
|
33
|
-
};
|
|
34
|
-
|
|
35
|
-
constructor(private service: RailwayService) {}
|
|
36
|
-
|
|
37
|
-
async execute(args: RailwayAddDatabaseInput): Promise<{ success: boolean; data?: RailwayPlugin; error?: string }> {
|
|
38
|
-
try {
|
|
39
|
-
const plugin = await this.service.createPlugin(args.projectId, args.plugin);
|
|
40
|
-
return { success: true, data: plugin };
|
|
41
|
-
} catch (err: any) {
|
|
42
|
-
return { success: false, error: err.message };
|
|
43
|
-
}
|
|
44
|
-
}
|
|
45
|
-
}
|