@computesdk/fly 1.1.37 → 1.1.39
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/dist/index.d.mts +2 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +37 -6
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +37 -6
- package/dist/index.mjs.map +1 -1
- package/package.json +3 -3
package/dist/index.d.mts
CHANGED
|
@@ -28,6 +28,8 @@ interface FlyConfig {
|
|
|
28
28
|
apiHostname?: string;
|
|
29
29
|
/** App name - if not provided, defaults to 'computesdk' */
|
|
30
30
|
appName?: string;
|
|
31
|
+
/** Execution timeout in milliseconds */
|
|
32
|
+
timeout?: number;
|
|
31
33
|
}
|
|
32
34
|
declare const getAndValidateCredentials: (config: FlyConfig) => {
|
|
33
35
|
apiToken: string;
|
package/dist/index.d.ts
CHANGED
|
@@ -28,6 +28,8 @@ interface FlyConfig {
|
|
|
28
28
|
apiHostname?: string;
|
|
29
29
|
/** App name - if not provided, defaults to 'computesdk' */
|
|
30
30
|
appName?: string;
|
|
31
|
+
/** Execution timeout in milliseconds */
|
|
32
|
+
timeout?: number;
|
|
31
33
|
}
|
|
32
34
|
declare const getAndValidateCredentials: (config: FlyConfig) => {
|
|
33
35
|
apiToken: string;
|
package/dist/index.js
CHANGED
|
@@ -105,10 +105,24 @@ var fly = (0, import_provider.defineProvider)({
|
|
|
105
105
|
const { apiToken, org, region, apiHostname, appName } = getAndValidateCredentials(config);
|
|
106
106
|
try {
|
|
107
107
|
await ensureAppExists(apiToken, apiHostname, appName, org);
|
|
108
|
-
const
|
|
108
|
+
const {
|
|
109
|
+
runtime: optRuntime,
|
|
110
|
+
timeout: optTimeout,
|
|
111
|
+
envs,
|
|
112
|
+
name,
|
|
113
|
+
metadata,
|
|
114
|
+
templateId: _templateId,
|
|
115
|
+
snapshotId: _snapshotId,
|
|
116
|
+
sandboxId: _sandboxId,
|
|
117
|
+
namespace: _namespace,
|
|
118
|
+
directory: _directory,
|
|
119
|
+
overlays: _overlays,
|
|
120
|
+
servers: _servers,
|
|
121
|
+
...providerOptions
|
|
122
|
+
} = options || {};
|
|
123
|
+
const image = optRuntime ? RUNTIME_IMAGES[optRuntime] || RUNTIME_IMAGES.default : RUNTIME_IMAGES.default;
|
|
109
124
|
const machineConfig = {
|
|
110
|
-
name: `machine-${Date.now()}`,
|
|
111
|
-
// Unique machine name
|
|
125
|
+
name: name || `machine-${Date.now()}`,
|
|
112
126
|
region,
|
|
113
127
|
config: {
|
|
114
128
|
image,
|
|
@@ -116,14 +130,31 @@ var fly = (0, import_provider.defineProvider)({
|
|
|
116
130
|
cpu_kind: "shared",
|
|
117
131
|
cpus: 1,
|
|
118
132
|
memory_mb: 256
|
|
119
|
-
}
|
|
133
|
+
},
|
|
134
|
+
...providerOptions
|
|
135
|
+
// Spread provider-specific options into machine config
|
|
120
136
|
}
|
|
121
137
|
};
|
|
122
|
-
if (
|
|
138
|
+
if (envs && Object.keys(envs).length > 0) {
|
|
139
|
+
machineConfig.config.env = envs;
|
|
140
|
+
}
|
|
141
|
+
if (metadata && typeof metadata === "object") {
|
|
142
|
+
machineConfig.config.metadata = Object.fromEntries(
|
|
143
|
+
Object.entries(metadata).map(([k, v]) => [k, typeof v === "string" ? v : JSON.stringify(v)])
|
|
144
|
+
);
|
|
145
|
+
}
|
|
146
|
+
const timeout = optTimeout ?? config.timeout;
|
|
147
|
+
if (timeout) {
|
|
148
|
+
const timeoutSeconds = Math.ceil(timeout / 1e3);
|
|
149
|
+
machineConfig.config.auto_destroy = true;
|
|
150
|
+
machineConfig.config.restart = { policy: "no" };
|
|
151
|
+
machineConfig.config.stop_config = { timeout: timeoutSeconds };
|
|
152
|
+
}
|
|
153
|
+
if (optRuntime === "node") {
|
|
123
154
|
machineConfig.config.init = {
|
|
124
155
|
cmd: ["node", "-e", 'require("http").createServer((req,res)=>{res.end("ok")}).listen(80)']
|
|
125
156
|
};
|
|
126
|
-
} else if (
|
|
157
|
+
} else if (optRuntime === "python") {
|
|
127
158
|
machineConfig.config.init = {
|
|
128
159
|
cmd: ["python", "-c", 'import http.server;http.server.HTTPServer(("",80),http.server.SimpleHTTPRequestHandler).serve_forever()']
|
|
129
160
|
};
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts"],"sourcesContent":["/**\n * Fly.io Provider - Factory-based Implementation\n * FLY_API_TOKEN=\n * FLY_API_HOSTNAME=\"https://api.machines.dev\"\n * FLY_APP_NAME=\n * FLY_ORG=\n * FLY_REGION=\n */\n\nimport { defineProvider } from '@computesdk/provider';\n\nimport type { Runtime, CodeResult, CommandResult, SandboxInfo, CreateSandboxOptions, RunCommandOptions } from '@computesdk/provider';\n\n/**\n * Fly.io sandbox interface\n */\ninterface FlyMachine {\n machineId: string;\n appName: string;\n region: string;\n privateIp?: string;\n}\n\nexport interface FlyConfig {\n /** Fly.io API token - if not provided, will fallback to FLY_API_TOKEN environment variable */\n apiToken?: string;\n /** Fly.io organization slug - defaults to 'personal' */\n org?: string;\n /** Fly.io region - defaults to 'iad' */\n region?: string;\n /** Base API hostname - defaults to public endpoint */\n apiHostname?: string;\n /** App name - if not provided, defaults to 'computesdk' */\n appName?: string;\n}\n\nexport const getAndValidateCredentials = (config: FlyConfig) => {\n const apiToken = config.apiToken || (typeof process !== 'undefined' && process.env?.FLY_API_TOKEN) || '';\n const org = config.org || (typeof process !== 'undefined' && process.env?.FLY_ORG) || 'personal';\n const region = config.region || (typeof process !== 'undefined' && process.env?.FLY_REGION) || 'iad';\n const apiHostname = config.apiHostname || 'https://api.machines.dev';\n const appName = config.appName || (typeof process !== 'undefined' && process.env?.FLY_APP_NAME) || 'compute-sdk';\n\n if (!apiToken) {\n throw new Error(\n 'Missing Fly.io API token. Provide apiToken in config or set FLY_API_TOKEN environment variable.'\n );\n }\n\n return { apiToken, org, region, apiHostname, appName };\n};\n\nconst RUNTIME_IMAGES: Record<string, string> = {\n node: 'node:alpine',\n python: 'python:alpine',\n default: 'docker.io/traefik/whoami'\n};\n\n/**\n * Fetch from Fly.io Machines REST API\n */\nexport const fetchMachinesApi = async (\n apiToken: string,\n apiHostname: string,\n endpoint: string,\n options: RequestInit = {}\n) => {\n const response = await fetch(`${apiHostname}${endpoint}`, {\n ...options,\n headers: {\n 'Content-Type': 'application/json',\n 'Authorization': `Bearer ${apiToken}`,\n ...options.headers\n }\n });\n\n // Handle DELETE which may return empty body\n if (response.status === 200 && options.method === 'DELETE') {\n return { ok: true };\n }\n\n if (!response.ok) {\n const errorText = await response.text();\n throw new Error(`Fly.io API error: ${response.status} ${response.statusText} - ${errorText}`);\n }\n\n const text = await response.text();\n return text ? JSON.parse(text) : {};\n};\n\n/**\n * Ensure the app exists, create it if it doesn't\n */\nconst ensureAppExists = async (\n apiToken: string,\n apiHostname: string,\n appName: string,\n org: string\n): Promise<void> => {\n try {\n // Check if app exists\n await fetchMachinesApi(apiToken, apiHostname, `/v1/apps/${appName}`, {\n method: 'GET'\n });\n } catch (error) {\n if (error instanceof Error && error.message.includes('404')) {\n // App doesn't exist, create it\n await fetchMachinesApi(apiToken, apiHostname, '/v1/apps', {\n method: 'POST',\n body: JSON.stringify({\n app_name: appName,\n org_slug: org\n })\n });\n } else {\n throw error; // Re-throw other errors\n }\n }\n};\n\n/**\n * Wait for a machine to reach a specific state\n */\nconst waitForMachineState = async (\n apiToken: string,\n apiHostname: string,\n appName: string,\n machineId: string,\n targetState: string,\n maxWaitMs: number = 30000\n): Promise<void> => {\n const startTime = Date.now();\n \n while (Date.now() - startTime < maxWaitMs) {\n try {\n const machine = await fetchMachinesApi(apiToken, apiHostname, `/v1/apps/${appName}/machines/${machineId}`);\n if (machine.state === targetState) {\n return;\n }\n await new Promise(resolve => setTimeout(resolve, 1000)); // Wait 1 second\n } catch {\n // If machine not found, consider it stopped/deleted\n return;\n }\n }\n throw new Error(`Machine ${machineId} did not reach state ${targetState} within ${maxWaitMs}ms`);\n};\n\n/**\n * Create a Fly.io provider instance using the factory pattern\n */\nexport const fly = defineProvider<FlyMachine, FlyConfig>({\n name: 'fly',\n methods: {\n sandbox: {\n create: async (config: FlyConfig, options?: CreateSandboxOptions) => {\n const { apiToken, org, region, apiHostname, appName } = getAndValidateCredentials(config);\n\n try {\n // 1. Ensure the app exists (create if needed)\n await ensureAppExists(apiToken, apiHostname, appName, org);\n\n // 2. Determine the image based on runtime\n const image = options?.runtime \n ? (RUNTIME_IMAGES[options.runtime] || RUNTIME_IMAGES.default)\n : RUNTIME_IMAGES.default;\n\n // 3. Create the machine (no app creation here)\n const machineConfig: any = {\n name: `machine-${Date.now()}`, // Unique machine name\n region,\n config: {\n image,\n guest: {\n cpu_kind: 'shared',\n cpus: 1,\n memory_mb: 256\n }\n }\n };\n\n // Add init command for node/python to keep container running\n if (options?.runtime === 'node') {\n machineConfig.config.init = {\n cmd: ['node', '-e', 'require(\"http\").createServer((req,res)=>{res.end(\"ok\")}).listen(80)']\n };\n } else if (options?.runtime === 'python') {\n machineConfig.config.init = {\n cmd: ['python', '-c', 'import http.server;http.server.HTTPServer((\"\",80),http.server.SimpleHTTPRequestHandler).serve_forever()']\n };\n }\n\n const machine = await fetchMachinesApi(apiToken, apiHostname, `/v1/apps/${appName}/machines`, {\n method: 'POST',\n body: JSON.stringify(machineConfig)\n });\n\n const flyMachine: FlyMachine = {\n machineId: machine.id,\n appName,\n region: machine.region,\n privateIp: machine.private_ip\n };\n\n return {\n sandbox: flyMachine,\n sandboxId: `${appName}:${machine.id}`\n };\n } catch (error) {\n throw new Error(\n `Failed to create Fly.io machine: ${error instanceof Error ? error.message : String(error)}`\n );\n }\n },\n\n getById: async (config: FlyConfig, sandboxId: string) => {\n const { apiToken, apiHostname } = getAndValidateCredentials(config);\n const [appName, machineId] = sandboxId.split(':');\n\n if (!appName || !machineId) {\n throw new Error('Invalid sandboxId format. Expected \"appName:machineId\"');\n }\n\n try {\n const machine = await fetchMachinesApi(apiToken, apiHostname, `/v1/apps/${appName}/machines/${machineId}`, {\n method: 'GET'\n });\n\n const flyMachine: FlyMachine = {\n machineId: machine.id,\n appName,\n region: machine.region,\n privateIp: machine.private_ip\n };\n\n return {\n sandbox: flyMachine,\n sandboxId\n };\n } catch (error) {\n if (error instanceof Error && error.message.includes('404')) {\n return null;\n }\n throw new Error(\n `Failed to get Fly.io sandbox: ${error instanceof Error ? error.message : String(error)}`\n );\n }\n },\n\n list: async (config: FlyConfig) => {\n const { apiToken, apiHostname, appName } = getAndValidateCredentials(config);\n\n try {\n // Get machines for the specific app only\n const machines = await fetchMachinesApi(\n apiToken,\n apiHostname,\n `/v1/apps/${appName}/machines`,\n { method: 'GET' }\n );\n\n const machineList = Array.isArray(machines) ? machines : [];\n \n return machineList.map(machine => ({\n sandbox: {\n machineId: machine.id,\n appName,\n region: machine.region,\n privateIp: machine.private_ip\n },\n sandboxId: `${appName}:${machine.id}`\n }));\n } catch (error) {\n throw new Error(\n `Failed to list Fly.io machines: ${error instanceof Error ? error.message : String(error)}`\n );\n }\n },\n\n destroy: async (config: FlyConfig, sandboxId: string) => {\n const { apiToken, apiHostname } = getAndValidateCredentials(config);\n const [appName, machineId] = sandboxId.split(':');\n\n if (!machineId) {\n console.warn('Invalid sandboxId format for destroy');\n return;\n }\n\n try {\n // 1. Check current machine state\n let machine;\n try {\n machine = await fetchMachinesApi(apiToken, apiHostname, `/v1/apps/${appName}/machines/${machineId}`);\n } catch (error) {\n if (error instanceof Error && error.message.includes('404')) {\n // Machine already deleted\n return;\n }\n throw error;\n }\n\n const currentState = machine.state;\n \n // 2. Handle based on current state\n if (currentState === 'created') {\n // Machine is in created state, try to start it first, then stop it\n try {\n await fetchMachinesApi(apiToken, apiHostname, `/v1/apps/${appName}/machines/${machineId}/start`, {\n method: 'POST'\n });\n // Wait a moment for it to start\n await new Promise(resolve => setTimeout(resolve, 2000));\n } catch {\n // If start fails, machine might be in a weird state, try deletion anyway\n }\n }\n \n if (currentState !== 'stopped' && currentState !== 'failed' && currentState !== 'destroyed') {\n\n try {\n await fetchMachinesApi(apiToken, apiHostname, `/v1/apps/${appName}/machines/${machineId}/stop`, {\n method: 'POST',\n body: JSON.stringify({ signal: 'SIGTERM' })\n });\n\n // Give it 5 seconds to stop gracefully before checking state\n await new Promise(resolve => setTimeout(resolve, 5000));\n \n // Wait for machine to actually stop\n await waitForMachineState(apiToken, apiHostname, appName, machineId, 'stopped', 15000);\n } catch (stopError) {\n // Try force stop if graceful stop failed\n try {\n await fetchMachinesApi(apiToken, apiHostname, `/v1/apps/${appName}/machines/${machineId}/stop`, {\n method: 'POST',\n body: JSON.stringify({ signal: 'SIGKILL' })\n });\n \n await new Promise(resolve => setTimeout(resolve, 5000));\n \n await waitForMachineState(apiToken, apiHostname, appName, machineId, 'stopped', 10000);\n\n } catch (forceStopError) {\n // Force stop errors are ignored because the machine may already be stopped or in a terminal state.\n // Log at debug level for troubleshooting.\n console.debug(`Fly.io force stop warning: ${forceStopError instanceof Error ? forceStopError.message : String(forceStopError)}`);\n }\n }\n }\n\n // 5. Delete the machine, with force if necessary\n try {\n await fetchMachinesApi(apiToken, apiHostname, `/v1/apps/${appName}/machines/${machineId}`, {\n method: 'DELETE'\n });\n } catch (deleteError) {\n if (deleteError instanceof Error && deleteError.message.includes('412')) {\n // Try force delete if normal delete fails with precondition\n await fetchMachinesApi(apiToken, apiHostname, `/v1/apps/${appName}/machines/${machineId}?force=true`, {\n method: 'DELETE'\n });\n } else {\n throw deleteError;\n }\n }\n } catch (error) {\n console.warn(`Fly.io destroy warning: ${error instanceof Error ? error.message : String(error)}`);\n }\n },\n\n runCode: async (_sandbox: FlyMachine, _code: string, _runtime?: Runtime) => {\n throw new Error('Fly.io runCode method not implemented yet');\n },\n\n runCommand: async (_sandbox: FlyMachine, _command: string, _options?: RunCommandOptions) => {\n throw new Error('Fly.io runCommand method not implemented yet');\n },\n\n getInfo: async (sandbox: FlyMachine) => {\n throw new Error('Fly.io getInfo method not implemented yet');\n },\n\n getUrl: async (sandbox: FlyMachine, options: { port: number; protocol?: string }) => {\n throw new Error('Fly.io getUrl method not implemented yet');\n },\n },\n },\n});\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../src/index.ts"],"sourcesContent":["/**\n * Fly.io Provider - Factory-based Implementation\n * FLY_API_TOKEN=\n * FLY_API_HOSTNAME=\"https://api.machines.dev\"\n * FLY_APP_NAME=\n * FLY_ORG=\n * FLY_REGION=\n */\n\nimport { defineProvider } from '@computesdk/provider';\n\nimport type { Runtime, CodeResult, CommandResult, SandboxInfo, CreateSandboxOptions, RunCommandOptions } from '@computesdk/provider';\n\n/**\n * Fly.io sandbox interface\n */\ninterface FlyMachine {\n machineId: string;\n appName: string;\n region: string;\n privateIp?: string;\n}\n\nexport interface FlyConfig {\n /** Fly.io API token - if not provided, will fallback to FLY_API_TOKEN environment variable */\n apiToken?: string;\n /** Fly.io organization slug - defaults to 'personal' */\n org?: string;\n /** Fly.io region - defaults to 'iad' */\n region?: string;\n /** Base API hostname - defaults to public endpoint */\n apiHostname?: string;\n /** App name - if not provided, defaults to 'computesdk' */\n appName?: string;\n /** Execution timeout in milliseconds */\n timeout?: number;\n}\n\nexport const getAndValidateCredentials = (config: FlyConfig) => {\n const apiToken = config.apiToken || (typeof process !== 'undefined' && process.env?.FLY_API_TOKEN) || '';\n const org = config.org || (typeof process !== 'undefined' && process.env?.FLY_ORG) || 'personal';\n const region = config.region || (typeof process !== 'undefined' && process.env?.FLY_REGION) || 'iad';\n const apiHostname = config.apiHostname || 'https://api.machines.dev';\n const appName = config.appName || (typeof process !== 'undefined' && process.env?.FLY_APP_NAME) || 'compute-sdk';\n\n if (!apiToken) {\n throw new Error(\n 'Missing Fly.io API token. Provide apiToken in config or set FLY_API_TOKEN environment variable.'\n );\n }\n\n return { apiToken, org, region, apiHostname, appName };\n};\n\nconst RUNTIME_IMAGES: Record<string, string> = {\n node: 'node:alpine',\n python: 'python:alpine',\n default: 'docker.io/traefik/whoami'\n};\n\n/**\n * Fetch from Fly.io Machines REST API\n */\nexport const fetchMachinesApi = async (\n apiToken: string,\n apiHostname: string,\n endpoint: string,\n options: RequestInit = {}\n) => {\n const response = await fetch(`${apiHostname}${endpoint}`, {\n ...options,\n headers: {\n 'Content-Type': 'application/json',\n 'Authorization': `Bearer ${apiToken}`,\n ...options.headers\n }\n });\n\n // Handle DELETE which may return empty body\n if (response.status === 200 && options.method === 'DELETE') {\n return { ok: true };\n }\n\n if (!response.ok) {\n const errorText = await response.text();\n throw new Error(`Fly.io API error: ${response.status} ${response.statusText} - ${errorText}`);\n }\n\n const text = await response.text();\n return text ? JSON.parse(text) : {};\n};\n\n/**\n * Ensure the app exists, create it if it doesn't\n */\nconst ensureAppExists = async (\n apiToken: string,\n apiHostname: string,\n appName: string,\n org: string\n): Promise<void> => {\n try {\n // Check if app exists\n await fetchMachinesApi(apiToken, apiHostname, `/v1/apps/${appName}`, {\n method: 'GET'\n });\n } catch (error) {\n if (error instanceof Error && error.message.includes('404')) {\n // App doesn't exist, create it\n await fetchMachinesApi(apiToken, apiHostname, '/v1/apps', {\n method: 'POST',\n body: JSON.stringify({\n app_name: appName,\n org_slug: org\n })\n });\n } else {\n throw error; // Re-throw other errors\n }\n }\n};\n\n/**\n * Wait for a machine to reach a specific state\n */\nconst waitForMachineState = async (\n apiToken: string,\n apiHostname: string,\n appName: string,\n machineId: string,\n targetState: string,\n maxWaitMs: number = 30000\n): Promise<void> => {\n const startTime = Date.now();\n \n while (Date.now() - startTime < maxWaitMs) {\n try {\n const machine = await fetchMachinesApi(apiToken, apiHostname, `/v1/apps/${appName}/machines/${machineId}`);\n if (machine.state === targetState) {\n return;\n }\n await new Promise(resolve => setTimeout(resolve, 1000)); // Wait 1 second\n } catch {\n // If machine not found, consider it stopped/deleted\n return;\n }\n }\n throw new Error(`Machine ${machineId} did not reach state ${targetState} within ${maxWaitMs}ms`);\n};\n\n/**\n * Create a Fly.io provider instance using the factory pattern\n */\nexport const fly = defineProvider<FlyMachine, FlyConfig>({\n name: 'fly',\n methods: {\n sandbox: {\n create: async (config: FlyConfig, options?: CreateSandboxOptions) => {\n const { apiToken, org, region, apiHostname, appName } = getAndValidateCredentials(config);\n\n try {\n // 1. Ensure the app exists (create if needed)\n await ensureAppExists(apiToken, apiHostname, appName, org);\n\n // Destructure known ComputeSDK fields, collect the rest for passthrough\n const {\n runtime: optRuntime,\n timeout: optTimeout,\n envs,\n name,\n metadata,\n templateId: _templateId,\n snapshotId: _snapshotId,\n sandboxId: _sandboxId,\n namespace: _namespace,\n directory: _directory,\n overlays: _overlays,\n servers: _servers,\n ...providerOptions\n } = options || {};\n\n // 2. Determine the image based on runtime\n const image = optRuntime \n ? (RUNTIME_IMAGES[optRuntime] || RUNTIME_IMAGES.default)\n : RUNTIME_IMAGES.default;\n\n // 3. Create the machine (no app creation here)\n const machineConfig: any = {\n name: name || `machine-${Date.now()}`,\n region,\n config: {\n image,\n guest: {\n cpu_kind: 'shared',\n cpus: 1,\n memory_mb: 256\n },\n ...providerOptions, // Spread provider-specific options into machine config\n }\n };\n\n // Add environment variables if provided\n if (envs && Object.keys(envs).length > 0) {\n machineConfig.config.env = envs;\n }\n\n // Add metadata as labels\n if (metadata && typeof metadata === 'object') {\n machineConfig.config.metadata = Object.fromEntries(\n Object.entries(metadata).map(([k, v]) => [k, typeof v === 'string' ? v : JSON.stringify(v)])\n );\n }\n\n // Configure auto-stop timeout if specified (convert ms to seconds)\n const timeout = optTimeout ?? config.timeout;\n if (timeout) {\n const timeoutSeconds = Math.ceil(timeout / 1000);\n machineConfig.config.auto_destroy = true;\n machineConfig.config.restart = { policy: 'no' };\n machineConfig.config.stop_config = { timeout: timeoutSeconds };\n }\n\n // Add init command for node/python to keep container running\n if (optRuntime === 'node') {\n machineConfig.config.init = {\n cmd: ['node', '-e', 'require(\"http\").createServer((req,res)=>{res.end(\"ok\")}).listen(80)']\n };\n } else if (optRuntime === 'python') {\n machineConfig.config.init = {\n cmd: ['python', '-c', 'import http.server;http.server.HTTPServer((\"\",80),http.server.SimpleHTTPRequestHandler).serve_forever()']\n };\n }\n\n const machine = await fetchMachinesApi(apiToken, apiHostname, `/v1/apps/${appName}/machines`, {\n method: 'POST',\n body: JSON.stringify(machineConfig)\n });\n\n const flyMachine: FlyMachine = {\n machineId: machine.id,\n appName,\n region: machine.region,\n privateIp: machine.private_ip\n };\n\n return {\n sandbox: flyMachine,\n sandboxId: `${appName}:${machine.id}`\n };\n } catch (error) {\n throw new Error(\n `Failed to create Fly.io machine: ${error instanceof Error ? error.message : String(error)}`\n );\n }\n },\n\n getById: async (config: FlyConfig, sandboxId: string) => {\n const { apiToken, apiHostname } = getAndValidateCredentials(config);\n const [appName, machineId] = sandboxId.split(':');\n\n if (!appName || !machineId) {\n throw new Error('Invalid sandboxId format. Expected \"appName:machineId\"');\n }\n\n try {\n const machine = await fetchMachinesApi(apiToken, apiHostname, `/v1/apps/${appName}/machines/${machineId}`, {\n method: 'GET'\n });\n\n const flyMachine: FlyMachine = {\n machineId: machine.id,\n appName,\n region: machine.region,\n privateIp: machine.private_ip\n };\n\n return {\n sandbox: flyMachine,\n sandboxId\n };\n } catch (error) {\n if (error instanceof Error && error.message.includes('404')) {\n return null;\n }\n throw new Error(\n `Failed to get Fly.io sandbox: ${error instanceof Error ? error.message : String(error)}`\n );\n }\n },\n\n list: async (config: FlyConfig) => {\n const { apiToken, apiHostname, appName } = getAndValidateCredentials(config);\n\n try {\n // Get machines for the specific app only\n const machines = await fetchMachinesApi(\n apiToken,\n apiHostname,\n `/v1/apps/${appName}/machines`,\n { method: 'GET' }\n );\n\n const machineList = Array.isArray(machines) ? machines : [];\n \n return machineList.map(machine => ({\n sandbox: {\n machineId: machine.id,\n appName,\n region: machine.region,\n privateIp: machine.private_ip\n },\n sandboxId: `${appName}:${machine.id}`\n }));\n } catch (error) {\n throw new Error(\n `Failed to list Fly.io machines: ${error instanceof Error ? error.message : String(error)}`\n );\n }\n },\n\n destroy: async (config: FlyConfig, sandboxId: string) => {\n const { apiToken, apiHostname } = getAndValidateCredentials(config);\n const [appName, machineId] = sandboxId.split(':');\n\n if (!machineId) {\n console.warn('Invalid sandboxId format for destroy');\n return;\n }\n\n try {\n // 1. Check current machine state\n let machine;\n try {\n machine = await fetchMachinesApi(apiToken, apiHostname, `/v1/apps/${appName}/machines/${machineId}`);\n } catch (error) {\n if (error instanceof Error && error.message.includes('404')) {\n // Machine already deleted\n return;\n }\n throw error;\n }\n\n const currentState = machine.state;\n \n // 2. Handle based on current state\n if (currentState === 'created') {\n // Machine is in created state, try to start it first, then stop it\n try {\n await fetchMachinesApi(apiToken, apiHostname, `/v1/apps/${appName}/machines/${machineId}/start`, {\n method: 'POST'\n });\n // Wait a moment for it to start\n await new Promise(resolve => setTimeout(resolve, 2000));\n } catch {\n // If start fails, machine might be in a weird state, try deletion anyway\n }\n }\n \n if (currentState !== 'stopped' && currentState !== 'failed' && currentState !== 'destroyed') {\n\n try {\n await fetchMachinesApi(apiToken, apiHostname, `/v1/apps/${appName}/machines/${machineId}/stop`, {\n method: 'POST',\n body: JSON.stringify({ signal: 'SIGTERM' })\n });\n\n // Give it 5 seconds to stop gracefully before checking state\n await new Promise(resolve => setTimeout(resolve, 5000));\n \n // Wait for machine to actually stop\n await waitForMachineState(apiToken, apiHostname, appName, machineId, 'stopped', 15000);\n } catch (stopError) {\n // Try force stop if graceful stop failed\n try {\n await fetchMachinesApi(apiToken, apiHostname, `/v1/apps/${appName}/machines/${machineId}/stop`, {\n method: 'POST',\n body: JSON.stringify({ signal: 'SIGKILL' })\n });\n \n await new Promise(resolve => setTimeout(resolve, 5000));\n \n await waitForMachineState(apiToken, apiHostname, appName, machineId, 'stopped', 10000);\n\n } catch (forceStopError) {\n // Force stop errors are ignored because the machine may already be stopped or in a terminal state.\n // Log at debug level for troubleshooting.\n console.debug(`Fly.io force stop warning: ${forceStopError instanceof Error ? forceStopError.message : String(forceStopError)}`);\n }\n }\n }\n\n // 5. Delete the machine, with force if necessary\n try {\n await fetchMachinesApi(apiToken, apiHostname, `/v1/apps/${appName}/machines/${machineId}`, {\n method: 'DELETE'\n });\n } catch (deleteError) {\n if (deleteError instanceof Error && deleteError.message.includes('412')) {\n // Try force delete if normal delete fails with precondition\n await fetchMachinesApi(apiToken, apiHostname, `/v1/apps/${appName}/machines/${machineId}?force=true`, {\n method: 'DELETE'\n });\n } else {\n throw deleteError;\n }\n }\n } catch (error) {\n console.warn(`Fly.io destroy warning: ${error instanceof Error ? error.message : String(error)}`);\n }\n },\n\n runCode: async (_sandbox: FlyMachine, _code: string, _runtime?: Runtime) => {\n throw new Error('Fly.io runCode method not implemented yet');\n },\n\n runCommand: async (_sandbox: FlyMachine, _command: string, _options?: RunCommandOptions) => {\n throw new Error('Fly.io runCommand method not implemented yet');\n },\n\n getInfo: async (sandbox: FlyMachine) => {\n throw new Error('Fly.io getInfo method not implemented yet');\n },\n\n getUrl: async (sandbox: FlyMachine, options: { port: number; protocol?: string }) => {\n throw new Error('Fly.io getUrl method not implemented yet');\n },\n },\n },\n});\n"],"mappings":"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","names":[]}
|
package/dist/index.mjs
CHANGED
|
@@ -79,10 +79,24 @@ var fly = defineProvider({
|
|
|
79
79
|
const { apiToken, org, region, apiHostname, appName } = getAndValidateCredentials(config);
|
|
80
80
|
try {
|
|
81
81
|
await ensureAppExists(apiToken, apiHostname, appName, org);
|
|
82
|
-
const
|
|
82
|
+
const {
|
|
83
|
+
runtime: optRuntime,
|
|
84
|
+
timeout: optTimeout,
|
|
85
|
+
envs,
|
|
86
|
+
name,
|
|
87
|
+
metadata,
|
|
88
|
+
templateId: _templateId,
|
|
89
|
+
snapshotId: _snapshotId,
|
|
90
|
+
sandboxId: _sandboxId,
|
|
91
|
+
namespace: _namespace,
|
|
92
|
+
directory: _directory,
|
|
93
|
+
overlays: _overlays,
|
|
94
|
+
servers: _servers,
|
|
95
|
+
...providerOptions
|
|
96
|
+
} = options || {};
|
|
97
|
+
const image = optRuntime ? RUNTIME_IMAGES[optRuntime] || RUNTIME_IMAGES.default : RUNTIME_IMAGES.default;
|
|
83
98
|
const machineConfig = {
|
|
84
|
-
name: `machine-${Date.now()}`,
|
|
85
|
-
// Unique machine name
|
|
99
|
+
name: name || `machine-${Date.now()}`,
|
|
86
100
|
region,
|
|
87
101
|
config: {
|
|
88
102
|
image,
|
|
@@ -90,14 +104,31 @@ var fly = defineProvider({
|
|
|
90
104
|
cpu_kind: "shared",
|
|
91
105
|
cpus: 1,
|
|
92
106
|
memory_mb: 256
|
|
93
|
-
}
|
|
107
|
+
},
|
|
108
|
+
...providerOptions
|
|
109
|
+
// Spread provider-specific options into machine config
|
|
94
110
|
}
|
|
95
111
|
};
|
|
96
|
-
if (
|
|
112
|
+
if (envs && Object.keys(envs).length > 0) {
|
|
113
|
+
machineConfig.config.env = envs;
|
|
114
|
+
}
|
|
115
|
+
if (metadata && typeof metadata === "object") {
|
|
116
|
+
machineConfig.config.metadata = Object.fromEntries(
|
|
117
|
+
Object.entries(metadata).map(([k, v]) => [k, typeof v === "string" ? v : JSON.stringify(v)])
|
|
118
|
+
);
|
|
119
|
+
}
|
|
120
|
+
const timeout = optTimeout ?? config.timeout;
|
|
121
|
+
if (timeout) {
|
|
122
|
+
const timeoutSeconds = Math.ceil(timeout / 1e3);
|
|
123
|
+
machineConfig.config.auto_destroy = true;
|
|
124
|
+
machineConfig.config.restart = { policy: "no" };
|
|
125
|
+
machineConfig.config.stop_config = { timeout: timeoutSeconds };
|
|
126
|
+
}
|
|
127
|
+
if (optRuntime === "node") {
|
|
97
128
|
machineConfig.config.init = {
|
|
98
129
|
cmd: ["node", "-e", 'require("http").createServer((req,res)=>{res.end("ok")}).listen(80)']
|
|
99
130
|
};
|
|
100
|
-
} else if (
|
|
131
|
+
} else if (optRuntime === "python") {
|
|
101
132
|
machineConfig.config.init = {
|
|
102
133
|
cmd: ["python", "-c", 'import http.server;http.server.HTTPServer(("",80),http.server.SimpleHTTPRequestHandler).serve_forever()']
|
|
103
134
|
};
|
package/dist/index.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts"],"sourcesContent":["/**\n * Fly.io Provider - Factory-based Implementation\n * FLY_API_TOKEN=\n * FLY_API_HOSTNAME=\"https://api.machines.dev\"\n * FLY_APP_NAME=\n * FLY_ORG=\n * FLY_REGION=\n */\n\nimport { defineProvider } from '@computesdk/provider';\n\nimport type { Runtime, CodeResult, CommandResult, SandboxInfo, CreateSandboxOptions, RunCommandOptions } from '@computesdk/provider';\n\n/**\n * Fly.io sandbox interface\n */\ninterface FlyMachine {\n machineId: string;\n appName: string;\n region: string;\n privateIp?: string;\n}\n\nexport interface FlyConfig {\n /** Fly.io API token - if not provided, will fallback to FLY_API_TOKEN environment variable */\n apiToken?: string;\n /** Fly.io organization slug - defaults to 'personal' */\n org?: string;\n /** Fly.io region - defaults to 'iad' */\n region?: string;\n /** Base API hostname - defaults to public endpoint */\n apiHostname?: string;\n /** App name - if not provided, defaults to 'computesdk' */\n appName?: string;\n}\n\nexport const getAndValidateCredentials = (config: FlyConfig) => {\n const apiToken = config.apiToken || (typeof process !== 'undefined' && process.env?.FLY_API_TOKEN) || '';\n const org = config.org || (typeof process !== 'undefined' && process.env?.FLY_ORG) || 'personal';\n const region = config.region || (typeof process !== 'undefined' && process.env?.FLY_REGION) || 'iad';\n const apiHostname = config.apiHostname || 'https://api.machines.dev';\n const appName = config.appName || (typeof process !== 'undefined' && process.env?.FLY_APP_NAME) || 'compute-sdk';\n\n if (!apiToken) {\n throw new Error(\n 'Missing Fly.io API token. Provide apiToken in config or set FLY_API_TOKEN environment variable.'\n );\n }\n\n return { apiToken, org, region, apiHostname, appName };\n};\n\nconst RUNTIME_IMAGES: Record<string, string> = {\n node: 'node:alpine',\n python: 'python:alpine',\n default: 'docker.io/traefik/whoami'\n};\n\n/**\n * Fetch from Fly.io Machines REST API\n */\nexport const fetchMachinesApi = async (\n apiToken: string,\n apiHostname: string,\n endpoint: string,\n options: RequestInit = {}\n) => {\n const response = await fetch(`${apiHostname}${endpoint}`, {\n ...options,\n headers: {\n 'Content-Type': 'application/json',\n 'Authorization': `Bearer ${apiToken}`,\n ...options.headers\n }\n });\n\n // Handle DELETE which may return empty body\n if (response.status === 200 && options.method === 'DELETE') {\n return { ok: true };\n }\n\n if (!response.ok) {\n const errorText = await response.text();\n throw new Error(`Fly.io API error: ${response.status} ${response.statusText} - ${errorText}`);\n }\n\n const text = await response.text();\n return text ? JSON.parse(text) : {};\n};\n\n/**\n * Ensure the app exists, create it if it doesn't\n */\nconst ensureAppExists = async (\n apiToken: string,\n apiHostname: string,\n appName: string,\n org: string\n): Promise<void> => {\n try {\n // Check if app exists\n await fetchMachinesApi(apiToken, apiHostname, `/v1/apps/${appName}`, {\n method: 'GET'\n });\n } catch (error) {\n if (error instanceof Error && error.message.includes('404')) {\n // App doesn't exist, create it\n await fetchMachinesApi(apiToken, apiHostname, '/v1/apps', {\n method: 'POST',\n body: JSON.stringify({\n app_name: appName,\n org_slug: org\n })\n });\n } else {\n throw error; // Re-throw other errors\n }\n }\n};\n\n/**\n * Wait for a machine to reach a specific state\n */\nconst waitForMachineState = async (\n apiToken: string,\n apiHostname: string,\n appName: string,\n machineId: string,\n targetState: string,\n maxWaitMs: number = 30000\n): Promise<void> => {\n const startTime = Date.now();\n \n while (Date.now() - startTime < maxWaitMs) {\n try {\n const machine = await fetchMachinesApi(apiToken, apiHostname, `/v1/apps/${appName}/machines/${machineId}`);\n if (machine.state === targetState) {\n return;\n }\n await new Promise(resolve => setTimeout(resolve, 1000)); // Wait 1 second\n } catch {\n // If machine not found, consider it stopped/deleted\n return;\n }\n }\n throw new Error(`Machine ${machineId} did not reach state ${targetState} within ${maxWaitMs}ms`);\n};\n\n/**\n * Create a Fly.io provider instance using the factory pattern\n */\nexport const fly = defineProvider<FlyMachine, FlyConfig>({\n name: 'fly',\n methods: {\n sandbox: {\n create: async (config: FlyConfig, options?: CreateSandboxOptions) => {\n const { apiToken, org, region, apiHostname, appName } = getAndValidateCredentials(config);\n\n try {\n // 1. Ensure the app exists (create if needed)\n await ensureAppExists(apiToken, apiHostname, appName, org);\n\n // 2. Determine the image based on runtime\n const image = options?.runtime \n ? (RUNTIME_IMAGES[options.runtime] || RUNTIME_IMAGES.default)\n : RUNTIME_IMAGES.default;\n\n // 3. Create the machine (no app creation here)\n const machineConfig: any = {\n name: `machine-${Date.now()}`, // Unique machine name\n region,\n config: {\n image,\n guest: {\n cpu_kind: 'shared',\n cpus: 1,\n memory_mb: 256\n }\n }\n };\n\n // Add init command for node/python to keep container running\n if (options?.runtime === 'node') {\n machineConfig.config.init = {\n cmd: ['node', '-e', 'require(\"http\").createServer((req,res)=>{res.end(\"ok\")}).listen(80)']\n };\n } else if (options?.runtime === 'python') {\n machineConfig.config.init = {\n cmd: ['python', '-c', 'import http.server;http.server.HTTPServer((\"\",80),http.server.SimpleHTTPRequestHandler).serve_forever()']\n };\n }\n\n const machine = await fetchMachinesApi(apiToken, apiHostname, `/v1/apps/${appName}/machines`, {\n method: 'POST',\n body: JSON.stringify(machineConfig)\n });\n\n const flyMachine: FlyMachine = {\n machineId: machine.id,\n appName,\n region: machine.region,\n privateIp: machine.private_ip\n };\n\n return {\n sandbox: flyMachine,\n sandboxId: `${appName}:${machine.id}`\n };\n } catch (error) {\n throw new Error(\n `Failed to create Fly.io machine: ${error instanceof Error ? error.message : String(error)}`\n );\n }\n },\n\n getById: async (config: FlyConfig, sandboxId: string) => {\n const { apiToken, apiHostname } = getAndValidateCredentials(config);\n const [appName, machineId] = sandboxId.split(':');\n\n if (!appName || !machineId) {\n throw new Error('Invalid sandboxId format. Expected \"appName:machineId\"');\n }\n\n try {\n const machine = await fetchMachinesApi(apiToken, apiHostname, `/v1/apps/${appName}/machines/${machineId}`, {\n method: 'GET'\n });\n\n const flyMachine: FlyMachine = {\n machineId: machine.id,\n appName,\n region: machine.region,\n privateIp: machine.private_ip\n };\n\n return {\n sandbox: flyMachine,\n sandboxId\n };\n } catch (error) {\n if (error instanceof Error && error.message.includes('404')) {\n return null;\n }\n throw new Error(\n `Failed to get Fly.io sandbox: ${error instanceof Error ? error.message : String(error)}`\n );\n }\n },\n\n list: async (config: FlyConfig) => {\n const { apiToken, apiHostname, appName } = getAndValidateCredentials(config);\n\n try {\n // Get machines for the specific app only\n const machines = await fetchMachinesApi(\n apiToken,\n apiHostname,\n `/v1/apps/${appName}/machines`,\n { method: 'GET' }\n );\n\n const machineList = Array.isArray(machines) ? machines : [];\n \n return machineList.map(machine => ({\n sandbox: {\n machineId: machine.id,\n appName,\n region: machine.region,\n privateIp: machine.private_ip\n },\n sandboxId: `${appName}:${machine.id}`\n }));\n } catch (error) {\n throw new Error(\n `Failed to list Fly.io machines: ${error instanceof Error ? error.message : String(error)}`\n );\n }\n },\n\n destroy: async (config: FlyConfig, sandboxId: string) => {\n const { apiToken, apiHostname } = getAndValidateCredentials(config);\n const [appName, machineId] = sandboxId.split(':');\n\n if (!machineId) {\n console.warn('Invalid sandboxId format for destroy');\n return;\n }\n\n try {\n // 1. Check current machine state\n let machine;\n try {\n machine = await fetchMachinesApi(apiToken, apiHostname, `/v1/apps/${appName}/machines/${machineId}`);\n } catch (error) {\n if (error instanceof Error && error.message.includes('404')) {\n // Machine already deleted\n return;\n }\n throw error;\n }\n\n const currentState = machine.state;\n \n // 2. Handle based on current state\n if (currentState === 'created') {\n // Machine is in created state, try to start it first, then stop it\n try {\n await fetchMachinesApi(apiToken, apiHostname, `/v1/apps/${appName}/machines/${machineId}/start`, {\n method: 'POST'\n });\n // Wait a moment for it to start\n await new Promise(resolve => setTimeout(resolve, 2000));\n } catch {\n // If start fails, machine might be in a weird state, try deletion anyway\n }\n }\n \n if (currentState !== 'stopped' && currentState !== 'failed' && currentState !== 'destroyed') {\n\n try {\n await fetchMachinesApi(apiToken, apiHostname, `/v1/apps/${appName}/machines/${machineId}/stop`, {\n method: 'POST',\n body: JSON.stringify({ signal: 'SIGTERM' })\n });\n\n // Give it 5 seconds to stop gracefully before checking state\n await new Promise(resolve => setTimeout(resolve, 5000));\n \n // Wait for machine to actually stop\n await waitForMachineState(apiToken, apiHostname, appName, machineId, 'stopped', 15000);\n } catch (stopError) {\n // Try force stop if graceful stop failed\n try {\n await fetchMachinesApi(apiToken, apiHostname, `/v1/apps/${appName}/machines/${machineId}/stop`, {\n method: 'POST',\n body: JSON.stringify({ signal: 'SIGKILL' })\n });\n \n await new Promise(resolve => setTimeout(resolve, 5000));\n \n await waitForMachineState(apiToken, apiHostname, appName, machineId, 'stopped', 10000);\n\n } catch (forceStopError) {\n // Force stop errors are ignored because the machine may already be stopped or in a terminal state.\n // Log at debug level for troubleshooting.\n console.debug(`Fly.io force stop warning: ${forceStopError instanceof Error ? forceStopError.message : String(forceStopError)}`);\n }\n }\n }\n\n // 5. Delete the machine, with force if necessary\n try {\n await fetchMachinesApi(apiToken, apiHostname, `/v1/apps/${appName}/machines/${machineId}`, {\n method: 'DELETE'\n });\n } catch (deleteError) {\n if (deleteError instanceof Error && deleteError.message.includes('412')) {\n // Try force delete if normal delete fails with precondition\n await fetchMachinesApi(apiToken, apiHostname, `/v1/apps/${appName}/machines/${machineId}?force=true`, {\n method: 'DELETE'\n });\n } else {\n throw deleteError;\n }\n }\n } catch (error) {\n console.warn(`Fly.io destroy warning: ${error instanceof Error ? error.message : String(error)}`);\n }\n },\n\n runCode: async (_sandbox: FlyMachine, _code: string, _runtime?: Runtime) => {\n throw new Error('Fly.io runCode method not implemented yet');\n },\n\n runCommand: async (_sandbox: FlyMachine, _command: string, _options?: RunCommandOptions) => {\n throw new Error('Fly.io runCommand method not implemented yet');\n },\n\n getInfo: async (sandbox: FlyMachine) => {\n throw new Error('Fly.io getInfo method not implemented yet');\n },\n\n getUrl: async (sandbox: FlyMachine, options: { port: number; protocol?: string }) => {\n throw new Error('Fly.io getUrl method not implemented yet');\n },\n },\n },\n});\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../src/index.ts"],"sourcesContent":["/**\n * Fly.io Provider - Factory-based Implementation\n * FLY_API_TOKEN=\n * FLY_API_HOSTNAME=\"https://api.machines.dev\"\n * FLY_APP_NAME=\n * FLY_ORG=\n * FLY_REGION=\n */\n\nimport { defineProvider } from '@computesdk/provider';\n\nimport type { Runtime, CodeResult, CommandResult, SandboxInfo, CreateSandboxOptions, RunCommandOptions } from '@computesdk/provider';\n\n/**\n * Fly.io sandbox interface\n */\ninterface FlyMachine {\n machineId: string;\n appName: string;\n region: string;\n privateIp?: string;\n}\n\nexport interface FlyConfig {\n /** Fly.io API token - if not provided, will fallback to FLY_API_TOKEN environment variable */\n apiToken?: string;\n /** Fly.io organization slug - defaults to 'personal' */\n org?: string;\n /** Fly.io region - defaults to 'iad' */\n region?: string;\n /** Base API hostname - defaults to public endpoint */\n apiHostname?: string;\n /** App name - if not provided, defaults to 'computesdk' */\n appName?: string;\n /** Execution timeout in milliseconds */\n timeout?: number;\n}\n\nexport const getAndValidateCredentials = (config: FlyConfig) => {\n const apiToken = config.apiToken || (typeof process !== 'undefined' && process.env?.FLY_API_TOKEN) || '';\n const org = config.org || (typeof process !== 'undefined' && process.env?.FLY_ORG) || 'personal';\n const region = config.region || (typeof process !== 'undefined' && process.env?.FLY_REGION) || 'iad';\n const apiHostname = config.apiHostname || 'https://api.machines.dev';\n const appName = config.appName || (typeof process !== 'undefined' && process.env?.FLY_APP_NAME) || 'compute-sdk';\n\n if (!apiToken) {\n throw new Error(\n 'Missing Fly.io API token. Provide apiToken in config or set FLY_API_TOKEN environment variable.'\n );\n }\n\n return { apiToken, org, region, apiHostname, appName };\n};\n\nconst RUNTIME_IMAGES: Record<string, string> = {\n node: 'node:alpine',\n python: 'python:alpine',\n default: 'docker.io/traefik/whoami'\n};\n\n/**\n * Fetch from Fly.io Machines REST API\n */\nexport const fetchMachinesApi = async (\n apiToken: string,\n apiHostname: string,\n endpoint: string,\n options: RequestInit = {}\n) => {\n const response = await fetch(`${apiHostname}${endpoint}`, {\n ...options,\n headers: {\n 'Content-Type': 'application/json',\n 'Authorization': `Bearer ${apiToken}`,\n ...options.headers\n }\n });\n\n // Handle DELETE which may return empty body\n if (response.status === 200 && options.method === 'DELETE') {\n return { ok: true };\n }\n\n if (!response.ok) {\n const errorText = await response.text();\n throw new Error(`Fly.io API error: ${response.status} ${response.statusText} - ${errorText}`);\n }\n\n const text = await response.text();\n return text ? JSON.parse(text) : {};\n};\n\n/**\n * Ensure the app exists, create it if it doesn't\n */\nconst ensureAppExists = async (\n apiToken: string,\n apiHostname: string,\n appName: string,\n org: string\n): Promise<void> => {\n try {\n // Check if app exists\n await fetchMachinesApi(apiToken, apiHostname, `/v1/apps/${appName}`, {\n method: 'GET'\n });\n } catch (error) {\n if (error instanceof Error && error.message.includes('404')) {\n // App doesn't exist, create it\n await fetchMachinesApi(apiToken, apiHostname, '/v1/apps', {\n method: 'POST',\n body: JSON.stringify({\n app_name: appName,\n org_slug: org\n })\n });\n } else {\n throw error; // Re-throw other errors\n }\n }\n};\n\n/**\n * Wait for a machine to reach a specific state\n */\nconst waitForMachineState = async (\n apiToken: string,\n apiHostname: string,\n appName: string,\n machineId: string,\n targetState: string,\n maxWaitMs: number = 30000\n): Promise<void> => {\n const startTime = Date.now();\n \n while (Date.now() - startTime < maxWaitMs) {\n try {\n const machine = await fetchMachinesApi(apiToken, apiHostname, `/v1/apps/${appName}/machines/${machineId}`);\n if (machine.state === targetState) {\n return;\n }\n await new Promise(resolve => setTimeout(resolve, 1000)); // Wait 1 second\n } catch {\n // If machine not found, consider it stopped/deleted\n return;\n }\n }\n throw new Error(`Machine ${machineId} did not reach state ${targetState} within ${maxWaitMs}ms`);\n};\n\n/**\n * Create a Fly.io provider instance using the factory pattern\n */\nexport const fly = defineProvider<FlyMachine, FlyConfig>({\n name: 'fly',\n methods: {\n sandbox: {\n create: async (config: FlyConfig, options?: CreateSandboxOptions) => {\n const { apiToken, org, region, apiHostname, appName } = getAndValidateCredentials(config);\n\n try {\n // 1. Ensure the app exists (create if needed)\n await ensureAppExists(apiToken, apiHostname, appName, org);\n\n // Destructure known ComputeSDK fields, collect the rest for passthrough\n const {\n runtime: optRuntime,\n timeout: optTimeout,\n envs,\n name,\n metadata,\n templateId: _templateId,\n snapshotId: _snapshotId,\n sandboxId: _sandboxId,\n namespace: _namespace,\n directory: _directory,\n overlays: _overlays,\n servers: _servers,\n ...providerOptions\n } = options || {};\n\n // 2. Determine the image based on runtime\n const image = optRuntime \n ? (RUNTIME_IMAGES[optRuntime] || RUNTIME_IMAGES.default)\n : RUNTIME_IMAGES.default;\n\n // 3. Create the machine (no app creation here)\n const machineConfig: any = {\n name: name || `machine-${Date.now()}`,\n region,\n config: {\n image,\n guest: {\n cpu_kind: 'shared',\n cpus: 1,\n memory_mb: 256\n },\n ...providerOptions, // Spread provider-specific options into machine config\n }\n };\n\n // Add environment variables if provided\n if (envs && Object.keys(envs).length > 0) {\n machineConfig.config.env = envs;\n }\n\n // Add metadata as labels\n if (metadata && typeof metadata === 'object') {\n machineConfig.config.metadata = Object.fromEntries(\n Object.entries(metadata).map(([k, v]) => [k, typeof v === 'string' ? v : JSON.stringify(v)])\n );\n }\n\n // Configure auto-stop timeout if specified (convert ms to seconds)\n const timeout = optTimeout ?? config.timeout;\n if (timeout) {\n const timeoutSeconds = Math.ceil(timeout / 1000);\n machineConfig.config.auto_destroy = true;\n machineConfig.config.restart = { policy: 'no' };\n machineConfig.config.stop_config = { timeout: timeoutSeconds };\n }\n\n // Add init command for node/python to keep container running\n if (optRuntime === 'node') {\n machineConfig.config.init = {\n cmd: ['node', '-e', 'require(\"http\").createServer((req,res)=>{res.end(\"ok\")}).listen(80)']\n };\n } else if (optRuntime === 'python') {\n machineConfig.config.init = {\n cmd: ['python', '-c', 'import http.server;http.server.HTTPServer((\"\",80),http.server.SimpleHTTPRequestHandler).serve_forever()']\n };\n }\n\n const machine = await fetchMachinesApi(apiToken, apiHostname, `/v1/apps/${appName}/machines`, {\n method: 'POST',\n body: JSON.stringify(machineConfig)\n });\n\n const flyMachine: FlyMachine = {\n machineId: machine.id,\n appName,\n region: machine.region,\n privateIp: machine.private_ip\n };\n\n return {\n sandbox: flyMachine,\n sandboxId: `${appName}:${machine.id}`\n };\n } catch (error) {\n throw new Error(\n `Failed to create Fly.io machine: ${error instanceof Error ? error.message : String(error)}`\n );\n }\n },\n\n getById: async (config: FlyConfig, sandboxId: string) => {\n const { apiToken, apiHostname } = getAndValidateCredentials(config);\n const [appName, machineId] = sandboxId.split(':');\n\n if (!appName || !machineId) {\n throw new Error('Invalid sandboxId format. Expected \"appName:machineId\"');\n }\n\n try {\n const machine = await fetchMachinesApi(apiToken, apiHostname, `/v1/apps/${appName}/machines/${machineId}`, {\n method: 'GET'\n });\n\n const flyMachine: FlyMachine = {\n machineId: machine.id,\n appName,\n region: machine.region,\n privateIp: machine.private_ip\n };\n\n return {\n sandbox: flyMachine,\n sandboxId\n };\n } catch (error) {\n if (error instanceof Error && error.message.includes('404')) {\n return null;\n }\n throw new Error(\n `Failed to get Fly.io sandbox: ${error instanceof Error ? error.message : String(error)}`\n );\n }\n },\n\n list: async (config: FlyConfig) => {\n const { apiToken, apiHostname, appName } = getAndValidateCredentials(config);\n\n try {\n // Get machines for the specific app only\n const machines = await fetchMachinesApi(\n apiToken,\n apiHostname,\n `/v1/apps/${appName}/machines`,\n { method: 'GET' }\n );\n\n const machineList = Array.isArray(machines) ? machines : [];\n \n return machineList.map(machine => ({\n sandbox: {\n machineId: machine.id,\n appName,\n region: machine.region,\n privateIp: machine.private_ip\n },\n sandboxId: `${appName}:${machine.id}`\n }));\n } catch (error) {\n throw new Error(\n `Failed to list Fly.io machines: ${error instanceof Error ? error.message : String(error)}`\n );\n }\n },\n\n destroy: async (config: FlyConfig, sandboxId: string) => {\n const { apiToken, apiHostname } = getAndValidateCredentials(config);\n const [appName, machineId] = sandboxId.split(':');\n\n if (!machineId) {\n console.warn('Invalid sandboxId format for destroy');\n return;\n }\n\n try {\n // 1. Check current machine state\n let machine;\n try {\n machine = await fetchMachinesApi(apiToken, apiHostname, `/v1/apps/${appName}/machines/${machineId}`);\n } catch (error) {\n if (error instanceof Error && error.message.includes('404')) {\n // Machine already deleted\n return;\n }\n throw error;\n }\n\n const currentState = machine.state;\n \n // 2. Handle based on current state\n if (currentState === 'created') {\n // Machine is in created state, try to start it first, then stop it\n try {\n await fetchMachinesApi(apiToken, apiHostname, `/v1/apps/${appName}/machines/${machineId}/start`, {\n method: 'POST'\n });\n // Wait a moment for it to start\n await new Promise(resolve => setTimeout(resolve, 2000));\n } catch {\n // If start fails, machine might be in a weird state, try deletion anyway\n }\n }\n \n if (currentState !== 'stopped' && currentState !== 'failed' && currentState !== 'destroyed') {\n\n try {\n await fetchMachinesApi(apiToken, apiHostname, `/v1/apps/${appName}/machines/${machineId}/stop`, {\n method: 'POST',\n body: JSON.stringify({ signal: 'SIGTERM' })\n });\n\n // Give it 5 seconds to stop gracefully before checking state\n await new Promise(resolve => setTimeout(resolve, 5000));\n \n // Wait for machine to actually stop\n await waitForMachineState(apiToken, apiHostname, appName, machineId, 'stopped', 15000);\n } catch (stopError) {\n // Try force stop if graceful stop failed\n try {\n await fetchMachinesApi(apiToken, apiHostname, `/v1/apps/${appName}/machines/${machineId}/stop`, {\n method: 'POST',\n body: JSON.stringify({ signal: 'SIGKILL' })\n });\n \n await new Promise(resolve => setTimeout(resolve, 5000));\n \n await waitForMachineState(apiToken, apiHostname, appName, machineId, 'stopped', 10000);\n\n } catch (forceStopError) {\n // Force stop errors are ignored because the machine may already be stopped or in a terminal state.\n // Log at debug level for troubleshooting.\n console.debug(`Fly.io force stop warning: ${forceStopError instanceof Error ? forceStopError.message : String(forceStopError)}`);\n }\n }\n }\n\n // 5. Delete the machine, with force if necessary\n try {\n await fetchMachinesApi(apiToken, apiHostname, `/v1/apps/${appName}/machines/${machineId}`, {\n method: 'DELETE'\n });\n } catch (deleteError) {\n if (deleteError instanceof Error && deleteError.message.includes('412')) {\n // Try force delete if normal delete fails with precondition\n await fetchMachinesApi(apiToken, apiHostname, `/v1/apps/${appName}/machines/${machineId}?force=true`, {\n method: 'DELETE'\n });\n } else {\n throw deleteError;\n }\n }\n } catch (error) {\n console.warn(`Fly.io destroy warning: ${error instanceof Error ? error.message : String(error)}`);\n }\n },\n\n runCode: async (_sandbox: FlyMachine, _code: string, _runtime?: Runtime) => {\n throw new Error('Fly.io runCode method not implemented yet');\n },\n\n runCommand: async (_sandbox: FlyMachine, _command: string, _options?: RunCommandOptions) => {\n throw new Error('Fly.io runCommand method not implemented yet');\n },\n\n getInfo: async (sandbox: FlyMachine) => {\n throw new Error('Fly.io getInfo method not implemented yet');\n },\n\n getUrl: async (sandbox: FlyMachine, options: { port: number; protocol?: string }) => {\n throw new Error('Fly.io getUrl method not implemented yet');\n },\n },\n },\n});\n"],"mappings":"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","names":[]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@computesdk/fly",
|
|
3
|
-
"version": "1.1.
|
|
3
|
+
"version": "1.1.39",
|
|
4
4
|
"description": "Fly.io provider for ComputeSDK - globally distributed sandboxes using Fly Machines",
|
|
5
5
|
"author": "ComputeSDK Team",
|
|
6
6
|
"license": "MIT",
|
|
@@ -18,8 +18,8 @@
|
|
|
18
18
|
"dist"
|
|
19
19
|
],
|
|
20
20
|
"dependencies": {
|
|
21
|
-
"@computesdk/provider": "1.0.
|
|
22
|
-
"computesdk": "2.5.
|
|
21
|
+
"@computesdk/provider": "1.0.32",
|
|
22
|
+
"computesdk": "2.5.2"
|
|
23
23
|
},
|
|
24
24
|
"keywords": [
|
|
25
25
|
"computesdk",
|