@computesdk/fly 1.1.37 → 1.1.38

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 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 image = options?.runtime ? RUNTIME_IMAGES[options.runtime] || RUNTIME_IMAGES.default : RUNTIME_IMAGES.default;
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 (options?.runtime === "node") {
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 (options?.runtime === "python") {
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 image = options?.runtime ? RUNTIME_IMAGES[options.runtime] || RUNTIME_IMAGES.default : RUNTIME_IMAGES.default;
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 (options?.runtime === "node") {
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 (options?.runtime === "python") {
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
  };
@@ -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.37",
3
+ "version": "1.1.38",
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.30",
22
- "computesdk": "2.5.0"
21
+ "@computesdk/provider": "1.0.31",
22
+ "computesdk": "2.5.1"
23
23
  },
24
24
  "keywords": [
25
25
  "computesdk",