diff --git a/apps/web/src/app/docs/executors/page.tsx b/apps/web/src/app/docs/executors/page.tsx index 62b0197..62aeb1d 100644 --- a/apps/web/src/app/docs/executors/page.tsx +++ b/apps/web/src/app/docs/executors/page.tsx @@ -119,7 +119,54 @@ export default function ExecutorsDocsPage(): React.ReactElement { needs:

-
+
+ {/* Railway Card */} + +
+
+ + Railway logo + + +
+
+

+ Railway +

+

+ Always-on with auto-scaling and a $5/month free tier. One-click deploy. +

+
+ + Official + + + Free tier + + + Auto-scaling + +
+
+ +
+ + {/* Unsandbox Card */}
- - Recommended - No cold starts @@ -167,8 +211,10 @@ export default function ExecutorsDocsPage(): React.ReactElement { className="w-6 h-6 text-white" viewBox="0 0 76 65" fill="currentColor" - aria-label="Vercel logo" + role="img" + aria-labelledby="vercel-logo-title" > + Vercel logo
@@ -177,15 +223,14 @@ export default function ExecutorsDocsPage(): React.ReactElement { Vercel

- Serverless execution with VM-level isolation using Vercel Sandbox. One-click - deploy. + Serverless execution with VM-level isolation using Vercel Sandbox.

One-click deploy - Free tier available + Pay-per-use
@@ -211,6 +256,7 @@ export default function ExecutorsDocsPage(): React.ReactElement { Feature + Railway Unsandbox Vercel @@ -218,12 +264,14 @@ export default function ExecutorsDocsPage(): React.ReactElement { Deploy method + One-click / CLI CLI command One-click button Isolation Container-level + Container-level VM-level (Sandbox) @@ -231,6 +279,9 @@ export default function ExecutorsDocsPage(): React.ReactElement { None (always-on) + + None (always-on) + Yes (serverless) @@ -238,22 +289,44 @@ export default function ExecutorsDocsPage(): React.ReactElement { Unlimited + + Unlimited + 45min (Hobby) / 5hr (Pro) + + Free tier + + $5/month credit + + None + Limited + Pricing + Per usage Per uptime Per compute time + + Auto-scaling + + Yes + + Manual + Yes + Custom domains Yes + Yes Yes - Freeze/unfreeze - Yes (save costs) - N/A (serverless) + Docker support + Yes + Yes + No @@ -387,18 +460,18 @@ export default function ExecutorsDocsPage(): React.ReactElement { Which platform should I choose?

- Unsandbox is recommended for most use cases. It has no cold - starts, unlimited runtime, and simple CLI deployment. Use Vercel{' '} - if you're already on Vercel or prefer one-click deployment and pay-per-use - pricing. + Railway is our official recommendation. It offers one-click + deployment, no cold starts, auto-scaling, and a generous $5/month free tier. Use{' '} + Unsandbox if you prefer CLI deployment, or{' '} + Vercel if you're already on Vercel and prefer pay-per-use + serverless pricing.

Can I use other platforms?

Yes! Any platform that runs Node.js and exposes HTTP endpoints works. AWS Lambda, - Google Cloud Run, Railway, Render, Fly.io—just implement the API specification - above. + Google Cloud Run, Render, Fly.io—just implement the API specification above.

diff --git a/apps/web/src/app/docs/executors/railway/page.tsx b/apps/web/src/app/docs/executors/railway/page.tsx new file mode 100644 index 0000000..face0a8 --- /dev/null +++ b/apps/web/src/app/docs/executors/railway/page.tsx @@ -0,0 +1,442 @@ +import { Button } from '@tpmjs/ui/Button/Button'; +import { CodeBlock } from '@tpmjs/ui/CodeBlock/CodeBlock'; +import { Icon } from '@tpmjs/ui/Icon/Icon'; +import type { Metadata } from 'next'; +import Link from 'next/link'; + +import { AppFooter } from '~/components/AppFooter'; +import { AppHeader } from '~/components/AppHeader'; + +export const metadata: Metadata = { + title: 'Deploy to Railway - Custom Executors - TPMJS', + description: + 'Deploy a TPMJS executor to Railway with one click. Always-on, auto-scaling, with a generous free tier.', +}; + +const healthCheck = `curl https://your-executor.up.railway.app/health`; + +const healthResponse = `{ + "status": "ok", + "version": "1.0.0", + "info": { + "runtime": "railway", + "timestamp": "2024-01-01T00:00:00.000Z", + "region": "us-west1" + } +}`; + +const executeExample = `curl -X POST https://your-executor.up.railway.app/execute-tool \\ + -H "Content-Type: application/json" \\ + -H "Authorization: Bearer your-api-key" \\ + -d '{ + "packageName": "@tpmjs/hello", + "name": "helloWorldTool", + "version": "latest", + "params": { "includeTimestamp": true } + }'`; + +const cliDeploy = `# Clone the template +git clone https://github.com/tpmjs/tpmjs.git +cd tpmjs/templates/railway-executor + +# Install Railway CLI +npm install -g @railway/cli + +# Login to Railway +railway login + +# Create a new project and deploy +railway init +railway up`; + +const envVars = `# Set environment variables via CLI +railway variables set EXECUTOR_API_KEY=your-secure-key +railway variables set OPENAI_API_KEY=sk-xxx +railway variables set DATABASE_URL=postgres://...`; + +const localDev = `# Clone the repository +git clone https://github.com/tpmjs/tpmjs.git +cd tpmjs/templates/railway-executor + +# Run locally +PORT=3000 node index.js + +# Test health endpoint +curl http://localhost:3000/health`; + +const dockerDeploy = `# Build the image +docker build -t tpmjs-executor . + +# Run locally +docker run -p 3000:3000 -e EXECUTOR_API_KEY=your-key tpmjs-executor`; + +export default function RailwayExecutorPage(): React.ReactElement { + return ( +
+ + +
+
+ {/* Breadcrumb */} + + + {/* Header */} +
+
+
+ + + + + + + + +
+
+

Deploy to Railway

+

+ Always-on execution with auto-scaling and a free tier +

+
+
+
+ + {/* Why Railway */} +
+
+
+
$5
+
Free monthly credit
+
+
+
0ms
+
No cold starts
+
+
+
Auto
+
Scaling built-in
+
+
+
+ + {/* One-Click Deploy */} +
+

One-Click Deploy

+

+ Deploy the TPMJS executor to Railway with a single click: +

+ + + +

+ After deployment, your executor will be available at{' '} + + https://your-project.up.railway.app + +

+
+ + {/* CLI Deploy */} +
+

Deploy via CLI

+

+ Prefer the command line? Deploy with the Railway CLI: +

+ +
+ + {/* Test Your Deployment */} +
+

Test Your Deployment

+

Verify your executor is running:

+ +

Expected response:

+ +
+ + {/* Authentication */} +
+

Add Authentication

+
+

+ Important: Without an API key, anyone can execute tools on your + executor. Always set{' '} + EXECUTOR_API_KEY in production. +

+
+
    +
  1. + + 1 + + Go to your Railway project dashboard +
  2. +
  3. + + 2 + + Click on your service, then go to "Variables" +
  4. +
  5. + + 3 + + + Add EXECUTOR_API_KEY with a + secure random value + +
  6. +
  7. + + 4 + + Railway will automatically redeploy with the new variable +
  8. +
+
+ + {/* Environment Variables */} +
+

Environment Variables

+

+ Add environment variables via the Railway dashboard or CLI: +

+ +

+ These variables will be available during tool execution. +

+
+ + {/* Execute a Tool */} +
+

Execute a Tool

+

+ Test tool execution with a curl request: +

+ +
+ + {/* Local Development */} +
+

Local Development

+

Run the executor locally for testing:

+ +
+ + {/* Docker */} +
+

Docker Deployment

+

+ The template includes a Dockerfile for container deployments: +

+ +

+ Railway will automatically detect and use the Dockerfile if present. +

+
+ + {/* How It Works */} +
+

How It Works

+

+ The Railway executor runs as an always-on Node.js service: +

+
    +
  1. + + 1 + + + Receives tool execution request via HTTP POST to{' '} + /execute-tool + +
  2. +
  3. + + 2 + + Creates an isolated temporary directory for the execution +
  4. +
  5. + + 3 + + + Installs the npm package using{' '} + npm install + +
  6. +
  7. + + 4 + + + Loads the tool and calls its{' '} + execute() function + +
  8. +
  9. + + 5 + + Returns the result and cleans up the temporary directory +
  10. +
+
+ + {/* Security */} +
+

Security

+
    +
  • + + + Set EXECUTOR_API_KEY to require + authentication + +
  • +
  • + + Each tool execution uses an isolated temporary directory +
  • +
  • + + Environment variables stored encrypted by Railway +
  • +
  • + + All traffic encrypted via HTTPS +
  • +
  • + + Auto-restart on failure for high availability +
  • +
+
+ + {/* Pricing */} +
+

Pricing

+

+ Railway offers usage-based pricing with a generous free tier: +

+
+ + + + + + + + + + + + + + + + + + + + +
TierPriceIncludes
Free Tier$0/month$5 credit, enough for light usage
Usage-based~$0.000463/min0.5 vCPU, 512MB RAM
+
+

+ See{' '} + + Railway Pricing + {' '} + for current rates. +

+
+ + {/* Connect to TPMJS */} +
+

Connect to TPMJS

+
    +
  1. 1. Go to your collection or agent settings on TPMJS
  2. +
  3. 2. Select "Custom Executor" in Executor Configuration
  4. +
  5. + 3. Enter URL:{' '} + + https://your-project.up.railway.app + +
  6. +
  7. 4. Enter your API key (if configured)
  8. +
  9. 5. Click "Verify Connection"
  10. +
+
+ + {/* Navigation */} +
+ + + Back to Executors + + + Unsandbox Guide + + +
+
+
+ + +
+ ); +} diff --git a/apps/web/src/app/docs/executors/unsandbox/page.tsx b/apps/web/src/app/docs/executors/unsandbox/page.tsx index 715c2e5..795f120 100644 --- a/apps/web/src/app/docs/executors/unsandbox/page.tsx +++ b/apps/web/src/app/docs/executors/unsandbox/page.tsx @@ -397,11 +397,11 @@ export default function UnsandboxExecutorPage(): React.ReactElement { {/* Navigation */}
- Back to Executors + Railway Guide ` header. | + +### Setting Up API Key Authentication + +1. Go to your Railway project dashboard +2. Click on your service +3. Go to "Variables" tab +4. Add `EXECUTOR_API_KEY` with a secure random value +5. The service will automatically redeploy + +### Adding Tool Environment Variables + +Pass environment variables that your tools need: + +1. In Railway dashboard, go to "Variables" +2. Add your variables (e.g., `OPENAI_API_KEY`, `DATABASE_URL`) +3. These will be available during tool execution + +Or use the Railway CLI: + +```bash +railway variables set EXECUTOR_API_KEY=your-key +railway variables set OPENAI_API_KEY=sk-xxx +railway variables set DATABASE_URL=postgres://... +``` + +## Connecting to TPMJS + +1. Go to your TPMJS collection or agent settings +2. In "Executor Configuration", select "Custom Executor" +3. Enter your executor URL: `https://your-project.up.railway.app` +4. Enter your API key (if configured) +5. Click "Verify Connection" to test + +## Local Development + +```bash +# Clone the repository +git clone https://github.com/tpmjs/tpmjs.git +cd tpmjs/templates/railway-executor + +# Run locally +PORT=3000 node index.js + +# Or with an API key +EXECUTOR_API_KEY=test-key PORT=3000 node index.js + +# Test health endpoint +curl http://localhost:3000/health + +# Test tool execution +curl -X POST http://localhost:3000/execute-tool \ + -H "Content-Type: application/json" \ + -d '{ + "packageName": "@tpmjs/hello", + "name": "helloWorldTool", + "params": {} + }' +``` + +## Managing Your Service + +### View Logs + +```bash +railway logs +``` + +Or view in the Railway dashboard under "Deployments" → select deployment → "Logs" + +### Redeploy + +```bash +railway up +``` + +Or push to your connected GitHub repository for automatic deployments. + +### Scale Resources + +1. Go to Railway dashboard +2. Click on your service +3. Go to "Settings" tab +4. Adjust CPU and memory limits + +### Custom Domains + +1. Go to Railway dashboard +2. Click on your service +3. Go to "Settings" tab +4. Under "Domains", click "Generate Domain" or add a custom domain + +## Docker Deployment + +If you prefer Docker: + +```bash +# Build the image +docker build -t tpmjs-executor . + +# Run locally +docker run -p 3000:3000 -e EXECUTOR_API_KEY=your-key tpmjs-executor +``` + +Railway will automatically detect and use the Dockerfile if present. + +## Security + +- Set `EXECUTOR_API_KEY` to require authentication for all requests +- Tools run in isolated temporary directories +- Each execution uses a fresh npm install +- Environment variables are stored encrypted by Railway +- Network traffic is encrypted via HTTPS + +## Pricing + +Railway pricing is usage-based with a generous free tier: + +- **Free Tier**: $5/month credit (enough for light usage) +- **Pay-as-you-go**: ~$0.000463/min for 0.5 vCPU, 512MB RAM + +See [Railway Pricing](https://railway.app/pricing) for current rates. + +**Cost Optimization Tips:** +- Use the "Sleep" feature for dev environments +- Set memory limits appropriate for your tools +- Monitor usage in Railway dashboard + +## Comparison: Railway vs Other Platforms + +| Feature | Railway | Vercel | Unsandbox | +|---------|---------|--------|-----------| +| Deploy method | One-click / CLI | One-click | CLI | +| Cold starts | None (always-on) | Yes (serverless) | None | +| Max runtime | Unlimited | 45min / 5hr | Unlimited | +| Free tier | $5/month credit | Limited | None | +| Pricing | Per usage | Per compute | Per uptime | +| Docker support | Yes | No | Yes | +| Auto-scaling | Yes | Yes | Manual | + +## Troubleshooting + +### "Connection refused" errors +- Check that your service is running in Railway dashboard +- Verify the URL is correct (check "Domains" in settings) +- Ensure `EXECUTOR_API_KEY` matches if authentication is enabled + +### Tool installation failures +- Check Railway logs for npm errors +- Verify the package name and version are correct +- Some packages may need additional system dependencies + +### Timeout errors +- Railway has no timeout limit, but individual tool executions timeout at 2 minutes +- For longer-running tools, consider increasing the timeout in the executor code + +## Support + +- [TPMJS Custom Executors Documentation](https://tpmjs.com/docs/executors) +- [Railway Documentation](https://docs.railway.app) +- [GitHub Issues](https://github.com/tpmjs/tpmjs/issues) diff --git a/templates/railway-executor/index.js b/templates/railway-executor/index.js new file mode 100644 index 0000000..375fc7a --- /dev/null +++ b/templates/railway-executor/index.js @@ -0,0 +1,398 @@ +#!/usr/bin/env node +/** + * TPMJS Executor for Railway + * + * A lightweight HTTP server that executes TPMJS tools. + * Designed for deployment on Railway with zero dependencies. + * + * API-compatible with the Vercel and Unsandbox executors. + */ + +const http = require('node:http'); +const { execSync, spawn } = require('node:child_process'); +const fs = require('node:fs'); +const path = require('node:path'); + +const PORT = process.env.PORT || 3000; +const API_KEY = process.env.EXECUTOR_API_KEY || null; + +// CORS headers for cross-origin requests +const corsHeaders = { + 'Access-Control-Allow-Origin': '*', + 'Access-Control-Allow-Methods': 'GET, POST, OPTIONS', + 'Access-Control-Allow-Headers': 'Content-Type, Authorization', +}; + +/** + * Send JSON response with proper headers + */ +function jsonResponse(res, statusCode, data) { + res.writeHead(statusCode, { + 'Content-Type': 'application/json', + ...corsHeaders, + }); + res.end(JSON.stringify(data)); +} + +/** + * Validate API key if configured + */ +function checkAuth(req) { + if (!API_KEY) return true; + const authHeader = req.headers.authorization; + return authHeader === `Bearer ${API_KEY}`; +} + +/** + * Parse JSON request body + */ +function parseBody(req) { + return new Promise((resolve, reject) => { + let body = ''; + req.on('data', (chunk) => { + body += chunk; + }); + req.on('end', () => { + try { + resolve(body ? JSON.parse(body) : {}); + } catch (_e) { + reject(new Error('Invalid JSON')); + } + }); + req.on('error', reject); + }); +} + +/** + * GET /health - Health check endpoint + */ +function handleHealth(_req, res) { + jsonResponse(res, 200, { + status: 'ok', + version: '1.0.0', + info: { + runtime: 'railway', + timestamp: new Date().toISOString(), + region: process.env.RAILWAY_REGION || 'unknown', + }, + }); +} + +/** + * Create isolated work directory and package.json + */ +function createWorkDir() { + const workDir = `/tmp/tpmjs-exec-${Date.now()}-${Math.random().toString(36).slice(2)}`; + fs.mkdirSync(workDir, { recursive: true }); + fs.writeFileSync( + path.join(workDir, 'package.json'), + JSON.stringify({ + name: 'tpmjs-execution', + private: true, + type: 'commonjs', + }) + ); + return workDir; +} + +/** + * Install npm package in work directory + */ +function installPackage(workDir, packageSpec) { + execSync(`npm install --no-save --omit=dev --no-audit --no-fund ${packageSpec}`, { + cwd: workDir, + stdio: ['pipe', 'pipe', 'pipe'], + timeout: 60000, // 60s timeout for install + }); +} + +/** + * Generate the tool execution script + */ +function generateExecutionScript(packageName, name, params, env) { + const envSetup = env + ? Object.entries(env) + .map(([key, value]) => `process.env[${JSON.stringify(key)}] = ${JSON.stringify(value)};`) + .join('\n') + : ''; + + return ` +${envSetup} + +(async () => { + try { + const pkg = require(${JSON.stringify(packageName)}); + + // Find the tool export - check named export, default.name, or default + let tool = pkg[${JSON.stringify(name)}] || pkg.default?.[${JSON.stringify(name)}] || pkg.default; + + if (!tool) { + throw new Error(\`Tool "${name}" not found in package "${packageName}"\`); + } + + // Handle factory functions (tools that need to be instantiated) + if (typeof tool === 'function' && !tool.execute) { + const envVars = ${env ? JSON.stringify(env) : 'null'}; + + // Try no-arg call first + try { + const result = tool(); + if (result && typeof result.execute === 'function') { + tool = result; + } + } catch {} + + // Try with env config if still a function + if (typeof tool === 'function' && envVars) { + try { + const result = tool(envVars); + if (result && typeof result.execute === 'function') { + tool = result; + } + } catch {} + } + } + + if (!tool || typeof tool.execute !== 'function') { + throw new Error(\`Tool "${name}" does not have an execute() function\`); + } + + // Execute the tool + const result = await tool.execute(${JSON.stringify(params)}); + process.stdout.write(JSON.stringify({ __tpmjs_result__: result })); + } catch (err) { + process.stderr.write(JSON.stringify({ __tpmjs_error__: err.message || String(err) })); + process.exitCode = 1; + } +})(); +`.trim(); +} + +/** + * Run execution script and return results + */ +function runScript(workDir, env) { + return new Promise((resolve) => { + const child = spawn('node', ['execute.cjs'], { + cwd: workDir, + env: { ...process.env, ...env }, + timeout: 120000, // 2 minute timeout + }); + + let stdout = ''; + let stderr = ''; + + child.stdout.on('data', (data) => { + stdout += data; + }); + child.stderr.on('data', (data) => { + stderr += data; + }); + + child.on('close', (code) => { + resolve({ exitCode: code, stdout, stderr }); + }); + + child.on('error', (err) => { + resolve({ exitCode: 1, stdout: '', stderr: err.message }); + }); + }); +} + +/** + * Clean up work directory + */ +function cleanup(workDir) { + try { + fs.rmSync(workDir, { recursive: true, force: true }); + } catch (_e) { + // Ignore cleanup errors + } +} + +/** + * Parse execution result and determine response + */ +function parseExecutionResult(result, startTime) { + // Handle execution failure + if (result.exitCode !== 0) { + // Try to parse structured error from stderr + try { + const errorObj = JSON.parse(result.stderr); + if (errorObj.__tpmjs_error__) { + return { + success: false, + error: errorObj.__tpmjs_error__, + executionTimeMs: Date.now() - startTime, + }; + } + } catch (_e) { + // Not structured error + } + + return { + success: false, + error: result.stderr || `Script exited with code ${result.exitCode}`, + executionTimeMs: Date.now() - startTime, + }; + } + + // Parse the result + try { + const parsed = JSON.parse(result.stdout); + if (parsed.__tpmjs_result__ !== undefined) { + return { + success: true, + output: parsed.__tpmjs_result__, + executionTimeMs: Date.now() - startTime, + }; + } + } catch (_e) { + // Not structured result + } + + // Return raw output + return { + success: true, + output: result.stdout || null, + stderr: result.stderr || undefined, + executionTimeMs: Date.now() - startTime, + }; +} + +/** + * POST /execute-tool - Execute a TPMJS tool + */ +async function handleExecuteTool(req, res) { + const startTime = Date.now(); + + // Check authorization + if (!checkAuth(req)) { + return jsonResponse(res, 401, { + success: false, + error: 'Unauthorized', + executionTimeMs: Date.now() - startTime, + }); + } + + // Parse request body + let body; + try { + body = await parseBody(req); + } catch (_e) { + return jsonResponse(res, 400, { + success: false, + error: 'Invalid JSON body', + executionTimeMs: Date.now() - startTime, + }); + } + + const { packageName, name, version = 'latest', params = {}, env } = body; + + // Validate required fields + if (!packageName || !name) { + return jsonResponse(res, 400, { + success: false, + error: 'Missing required fields: packageName, name', + executionTimeMs: Date.now() - startTime, + }); + } + + const packageSpec = `${packageName}@${version}`; + const workDir = createWorkDir(); + + try { + // Install the npm package + console.log(`[executor] Installing ${packageSpec}...`); + const installStart = Date.now(); + + try { + installPackage(workDir, packageSpec); + } catch (installError) { + console.error(`[executor] npm install failed:`, installError.message); + cleanup(workDir); + return jsonResponse(res, 500, { + success: false, + error: `npm install failed: ${installError.message}`, + stderr: installError.stderr?.toString(), + executionTimeMs: Date.now() - startTime, + }); + } + + console.log(`[executor] npm install completed in ${Date.now() - installStart}ms`); + + // Generate and write execution script + const script = generateExecutionScript(packageName, name, params, env); + fs.writeFileSync(path.join(workDir, 'execute.cjs'), script); + + // Run the execution script + console.log(`[executor] Running tool ${packageName}/${name}...`); + const runStart = Date.now(); + const result = await runScript(workDir, env); + console.log( + `[executor] Tool execution completed in ${Date.now() - runStart}ms (exit: ${result.exitCode})` + ); + + // Cleanup and return result + cleanup(workDir); + return jsonResponse(res, 200, parseExecutionResult(result, startTime)); + } catch (error) { + cleanup(workDir); + return jsonResponse(res, 500, { + success: false, + error: error.message || String(error), + executionTimeMs: Date.now() - startTime, + }); + } +} + +/** + * Main HTTP server + */ +const server = http.createServer(async (req, res) => { + const url = new URL(req.url, `http://localhost:${PORT}`); + const pathname = url.pathname; + + // Handle CORS preflight + if (req.method === 'OPTIONS') { + res.writeHead(200, corsHeaders); + return res.end(); + } + + // Route requests (support both /api/path and /path) + if ((pathname === '/api/health' || pathname === '/health') && req.method === 'GET') { + return handleHealth(req, res); + } + + if ((pathname === '/api/execute-tool' || pathname === '/execute-tool') && req.method === 'POST') { + return handleExecuteTool(req, res); + } + + // Root path - simple info + if (pathname === '/' && req.method === 'GET') { + return jsonResponse(res, 200, { + name: 'TPMJS Executor', + version: '1.0.0', + runtime: 'railway', + endpoints: { + health: 'GET /health', + execute: 'POST /execute-tool', + }, + }); + } + + // 404 for unknown routes + jsonResponse(res, 404, { error: 'Not found' }); +}); + +// Start server +server.listen(PORT, () => { + console.log(`TPMJS Executor running on port ${PORT}`); + console.log(`Health: http://localhost:${PORT}/health`); + console.log(`Execute: POST http://localhost:${PORT}/execute-tool`); + if (API_KEY) { + console.log(`Authentication: Required (EXECUTOR_API_KEY is set)`); + } else { + console.log(`Authentication: None (set EXECUTOR_API_KEY to enable)`); + } +}); diff --git a/templates/railway-executor/package.json b/templates/railway-executor/package.json new file mode 100644 index 0000000..fd0c75f --- /dev/null +++ b/templates/railway-executor/package.json @@ -0,0 +1,20 @@ +{ + "name": "tpmjs-executor", + "version": "1.0.0", + "private": true, + "description": "TPMJS Tool Executor for Railway - Deploy your own executor on Railway", + "main": "index.js", + "scripts": { + "start": "node index.js", + "dev": "node index.js" + }, + "engines": { + "node": ">=18.0.0" + }, + "dependencies": {}, + "keywords": [ + "tpmjs", + "executor", + "railway" + ] +} diff --git a/templates/railway-executor/railway.json b/templates/railway-executor/railway.json new file mode 100644 index 0000000..22dda23 --- /dev/null +++ b/templates/railway-executor/railway.json @@ -0,0 +1,13 @@ +{ + "$schema": "https://railway.app/railway.schema.json", + "build": { + "builder": "NIXPACKS" + }, + "deploy": { + "startCommand": "node index.js", + "healthcheckPath": "/health", + "healthcheckTimeout": 30, + "restartPolicyType": "ON_FAILURE", + "restartPolicyMaxRetries": 3 + } +}