@computesdk/aws-ecs 1.0.1

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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 computesdk
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,89 @@
1
+ # @computesdk/aws
2
+
3
+ AWS ECS Fargate provider for ComputeSDK that enables creating and managing containerized sandboxes on AWS infrastructure.
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ npm install @computesdk/aws
9
+ ```
10
+
11
+ ## Configuration
12
+
13
+ The AWS provider requires the following environment variables and AWS resources:
14
+
15
+ ```bash
16
+ # AWS Credentials (or use IAM roles/profiles)
17
+ AWS_ACCESS_KEY_ID=your_access_key
18
+ AWS_SECRET_ACCESS_KEY=your_secret_key
19
+ AWS_REGION=us-east-1
20
+
21
+ # Required AWS Resources
22
+ AWS_ECS_CLUSTER=your-ecs-cluster
23
+ AWS_TASK_DEFINITION=your-task-definition
24
+ AWS_SUBNETS=subnet-xxx,subnet-yyy,subnet-zzz
25
+ AWS_SECURITY_GROUPS=sg-xxx
26
+ ```
27
+
28
+ ## Usage
29
+
30
+ ```typescript
31
+ import { fargate } from '@computesdk/aws';
32
+
33
+ const provider = fargate({
34
+ cluster: 'my-ecs-cluster',
35
+ taskDefinition: 'my-task-definition',
36
+ subnets: ['subnet-12345', 'subnet-67890'],
37
+ securityGroups: ['sg-12345'],
38
+ region: 'us-east-1'
39
+ });
40
+
41
+ // Create a sandbox (ECS task)
42
+ const sandbox = await provider.sandbox.create({ runtime: 'node' });
43
+ console.log(`Created sandbox: ${sandbox.sandboxId}`);
44
+
45
+ // Get sandbox by ID
46
+ const retrieved = await provider.sandbox.getById(sandbox.sandboxId);
47
+
48
+ // List all running sandboxes
49
+ const sandboxes = await provider.sandbox.list();
50
+
51
+ // Destroy the sandbox
52
+ await provider.sandbox.destroy(sandbox.sandboxId);
53
+ ```
54
+
55
+ ## Currently Implemented
56
+
57
+ ### Sandbox Operations
58
+ - **create()** - Creates a new ECS Fargate task
59
+ - **getById()** - Retrieves a specific ECS task by ARN
60
+ - **list()** - Lists all running ECS tasks in the cluster
61
+ - **destroy()** - Stops an ECS task
62
+
63
+ ### Configuration Options
64
+ - **cluster** - ECS cluster name or ARN (required)
65
+ - **taskDefinition** - Task definition family name or ARN (required)
66
+ - **subnets** - VPC subnet IDs for task networking (required)
67
+ - **securityGroups** - Security group IDs for task networking (required)
68
+ - **accessKeyId** - AWS access key (optional - falls back to credential chain)
69
+ - **secretAccessKey** - AWS secret key (optional - falls back to credential chain)
70
+ - **region** - AWS region (optional - defaults to AWS_REGION env var or us-east-1)
71
+ - **assignPublicIp** - Whether to assign public IP (optional - defaults to true)
72
+ - **containerName** - Container name in task definition (optional - defaults to 'sandbox')
73
+
74
+ ## Prerequisites
75
+
76
+ Before using this provider, you need to set up:
77
+
78
+ 1. **ECS Cluster**: A Fargate-compatible ECS cluster
79
+ 2. **Task Definition**: A task definition with your desired container image
80
+ 3. **VPC Networking**: Subnets and security groups for task networking
81
+ 4. **IAM Permissions**: Appropriate permissions to create/manage ECS tasks
82
+
83
+ ## Notes
84
+
85
+ - Tasks use AWS Fargate launch type (serverless containers)
86
+ - Container images are specified in the task definition, not at runtime
87
+ - The `runtime` parameter is acknowledged but actual environment depends on task definition
88
+ - Tasks are immediately stopped when destroyed
89
+ - All operations use the AWS SDK v3 for ECS
@@ -0,0 +1,52 @@
1
+ import * as computesdk from 'computesdk';
2
+
3
+ /**
4
+ * AWS ECS Fargate Provider - Factory-based Implementation
5
+ */
6
+ /**
7
+ * Fargate sandbox interface
8
+ */
9
+ interface FargateSandbox {
10
+ taskArn: string;
11
+ clusterArn: string;
12
+ taskId: string;
13
+ }
14
+ interface FargateConfig {
15
+ /** AWS Access Key ID - falls back to AWS_ACCESS_KEY_ID env var or default credential chain */
16
+ accessKeyId?: string;
17
+ /** AWS Secret Access Key - falls back to AWS_SECRET_ACCESS_KEY env var or default credential chain */
18
+ secretAccessKey?: string;
19
+ /** AWS Region - falls back to AWS_REGION env var */
20
+ region?: string;
21
+ /** ECS Cluster name or ARN */
22
+ cluster: string;
23
+ /** Task definition family name or ARN */
24
+ taskDefinition: string;
25
+ /** VPC Subnet IDs for task networking */
26
+ subnets: string[];
27
+ /** Security Group IDs for task networking */
28
+ securityGroups: string[];
29
+ /** Whether to assign a public IP (default: true) */
30
+ assignPublicIp?: boolean;
31
+ /** Container name in the task definition (default: 'sandbox') */
32
+ containerName?: string;
33
+ }
34
+ declare const getAndValidateCredentials: (config: FargateConfig) => {
35
+ region: string;
36
+ cluster: string;
37
+ taskDefinition: string;
38
+ subnets: string[];
39
+ securityGroups: string[];
40
+ credentials: {
41
+ accessKeyId: string;
42
+ secretAccessKey: string;
43
+ } | undefined;
44
+ assignPublicIp: boolean;
45
+ containerName: string;
46
+ };
47
+ /**
48
+ * Create an AWS ECS Fargate provider instance using the factory pattern
49
+ */
50
+ declare const fargate: (config: FargateConfig) => computesdk.Provider<FargateSandbox, any, any>;
51
+
52
+ export { type FargateConfig, fargate, getAndValidateCredentials };
@@ -0,0 +1,52 @@
1
+ import * as computesdk from 'computesdk';
2
+
3
+ /**
4
+ * AWS ECS Fargate Provider - Factory-based Implementation
5
+ */
6
+ /**
7
+ * Fargate sandbox interface
8
+ */
9
+ interface FargateSandbox {
10
+ taskArn: string;
11
+ clusterArn: string;
12
+ taskId: string;
13
+ }
14
+ interface FargateConfig {
15
+ /** AWS Access Key ID - falls back to AWS_ACCESS_KEY_ID env var or default credential chain */
16
+ accessKeyId?: string;
17
+ /** AWS Secret Access Key - falls back to AWS_SECRET_ACCESS_KEY env var or default credential chain */
18
+ secretAccessKey?: string;
19
+ /** AWS Region - falls back to AWS_REGION env var */
20
+ region?: string;
21
+ /** ECS Cluster name or ARN */
22
+ cluster: string;
23
+ /** Task definition family name or ARN */
24
+ taskDefinition: string;
25
+ /** VPC Subnet IDs for task networking */
26
+ subnets: string[];
27
+ /** Security Group IDs for task networking */
28
+ securityGroups: string[];
29
+ /** Whether to assign a public IP (default: true) */
30
+ assignPublicIp?: boolean;
31
+ /** Container name in the task definition (default: 'sandbox') */
32
+ containerName?: string;
33
+ }
34
+ declare const getAndValidateCredentials: (config: FargateConfig) => {
35
+ region: string;
36
+ cluster: string;
37
+ taskDefinition: string;
38
+ subnets: string[];
39
+ securityGroups: string[];
40
+ credentials: {
41
+ accessKeyId: string;
42
+ secretAccessKey: string;
43
+ } | undefined;
44
+ assignPublicIp: boolean;
45
+ containerName: string;
46
+ };
47
+ /**
48
+ * Create an AWS ECS Fargate provider instance using the factory pattern
49
+ */
50
+ declare const fargate: (config: FargateConfig) => computesdk.Provider<FargateSandbox, any, any>;
51
+
52
+ export { type FargateConfig, fargate, getAndValidateCredentials };
package/dist/index.js ADDED
@@ -0,0 +1,250 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/index.ts
21
+ var index_exports = {};
22
+ __export(index_exports, {
23
+ fargate: () => fargate,
24
+ getAndValidateCredentials: () => getAndValidateCredentials
25
+ });
26
+ module.exports = __toCommonJS(index_exports);
27
+ var import_computesdk = require("computesdk");
28
+ var import_client_ecs = require("@aws-sdk/client-ecs");
29
+ var getAndValidateCredentials = (config) => {
30
+ const region = config.region || process.env?.AWS_REGION || "us-east-1";
31
+ const cluster = config.cluster;
32
+ const taskDefinition = config.taskDefinition;
33
+ const subnets = config.subnets;
34
+ const securityGroups = config.securityGroups;
35
+ if (!cluster) {
36
+ throw new Error("Missing ECS cluster. Provide cluster in config.");
37
+ }
38
+ if (!taskDefinition) {
39
+ throw new Error("Missing ECS task definition. Provide taskDefinition in config.");
40
+ }
41
+ if (!subnets || subnets.length === 0) {
42
+ throw new Error("Missing subnets. Provide at least one subnet in config.");
43
+ }
44
+ if (!securityGroups || securityGroups.length === 0) {
45
+ throw new Error("Missing security groups. Provide at least one security group in config.");
46
+ }
47
+ const credentials = config.accessKeyId && config.secretAccessKey ? {
48
+ accessKeyId: config.accessKeyId,
49
+ secretAccessKey: config.secretAccessKey
50
+ } : void 0;
51
+ return {
52
+ region,
53
+ cluster,
54
+ taskDefinition,
55
+ subnets,
56
+ securityGroups,
57
+ credentials,
58
+ assignPublicIp: config.assignPublicIp ?? true,
59
+ containerName: config.containerName || "sandbox"
60
+ };
61
+ };
62
+ var createECSClient = (config) => {
63
+ const { region, credentials } = getAndValidateCredentials(config);
64
+ return new import_client_ecs.ECSClient({
65
+ region,
66
+ ...credentials && { credentials }
67
+ });
68
+ };
69
+ var extractTaskId = (taskArn) => {
70
+ const parts = taskArn.split("/");
71
+ return parts[parts.length - 1];
72
+ };
73
+ var fargate = (0, import_computesdk.createProvider)({
74
+ name: "fargate",
75
+ methods: {
76
+ sandbox: {
77
+ // Collection operations (compute.sandbox.*)
78
+ create: async (config, options) => {
79
+ const {
80
+ cluster,
81
+ taskDefinition,
82
+ subnets,
83
+ securityGroups,
84
+ assignPublicIp,
85
+ containerName
86
+ } = getAndValidateCredentials(config);
87
+ const client = createECSClient(config);
88
+ try {
89
+ const command = new import_client_ecs.RunTaskCommand({
90
+ cluster,
91
+ taskDefinition,
92
+ launchType: "FARGATE",
93
+ networkConfiguration: {
94
+ awsvpcConfiguration: {
95
+ subnets,
96
+ securityGroups,
97
+ assignPublicIp: assignPublicIp ? "ENABLED" : "DISABLED"
98
+ }
99
+ },
100
+ // Container overrides for ECS are limited to environment variables,
101
+ // memory, CPU, and command - not the image itself
102
+ ...containerName && {
103
+ overrides: {
104
+ containerOverrides: [
105
+ {
106
+ name: containerName
107
+ // Additional container overrides can be added here if needed
108
+ // such as environment variables, memory, CPU, etc.
109
+ }
110
+ ]
111
+ }
112
+ }
113
+ });
114
+ const response = await client.send(command);
115
+ if (!response.tasks || response.tasks.length === 0) {
116
+ const failures = response.failures?.map((f) => `${f.reason}: ${f.detail}`).join(", ");
117
+ throw new Error(`No task created. Failures: ${failures || "unknown"}`);
118
+ }
119
+ const task = response.tasks[0];
120
+ if (!task.taskArn) {
121
+ throw new Error("Task ARN is undefined in response");
122
+ }
123
+ const fargateSandbox = {
124
+ taskArn: task.taskArn,
125
+ clusterArn: task.clusterArn || cluster,
126
+ taskId: extractTaskId(task.taskArn)
127
+ };
128
+ return {
129
+ sandbox: fargateSandbox,
130
+ sandboxId: task.taskArn
131
+ };
132
+ } catch (error) {
133
+ throw new Error(
134
+ `Failed to create Fargate sandbox: ${error instanceof Error ? error.message : String(error)}`
135
+ );
136
+ }
137
+ },
138
+ getById: async (config, sandboxId) => {
139
+ const { cluster } = getAndValidateCredentials(config);
140
+ const client = createECSClient(config);
141
+ try {
142
+ const command = new import_client_ecs.DescribeTasksCommand({
143
+ cluster,
144
+ tasks: [sandboxId]
145
+ });
146
+ const response = await client.send(command);
147
+ if (!response.tasks || response.tasks.length === 0) {
148
+ return null;
149
+ }
150
+ const task = response.tasks[0];
151
+ if (!task.taskArn) {
152
+ throw new Error("Task ARN is missing from response");
153
+ }
154
+ const fargateSandbox = {
155
+ taskArn: task.taskArn,
156
+ clusterArn: task.clusterArn || cluster,
157
+ taskId: extractTaskId(task.taskArn)
158
+ };
159
+ return {
160
+ sandbox: fargateSandbox,
161
+ sandboxId: task.taskArn
162
+ };
163
+ } catch (error) {
164
+ if (error instanceof Error && (error.message.includes("MISSING") || error.message.includes("not found"))) {
165
+ return null;
166
+ }
167
+ throw new Error(
168
+ `Failed to get Fargate sandbox: ${error instanceof Error ? error.message : String(error)}`
169
+ );
170
+ }
171
+ },
172
+ list: async (config) => {
173
+ const { cluster } = getAndValidateCredentials(config);
174
+ const client = createECSClient(config);
175
+ try {
176
+ const listCommand = new import_client_ecs.ListTasksCommand({
177
+ cluster,
178
+ desiredStatus: "RUNNING"
179
+ });
180
+ const listResponse = await client.send(listCommand);
181
+ const taskArns = listResponse.taskArns || [];
182
+ if (taskArns.length === 0) {
183
+ return [];
184
+ }
185
+ const describeCommand = new import_client_ecs.DescribeTasksCommand({
186
+ cluster,
187
+ tasks: taskArns
188
+ });
189
+ const describeResponse = await client.send(describeCommand);
190
+ const tasks = describeResponse.tasks || [];
191
+ const sandboxes = tasks.filter((task) => task.taskArn).map((task) => {
192
+ const fargateSandbox = {
193
+ taskArn: task.taskArn,
194
+ clusterArn: task.clusterArn || cluster,
195
+ taskId: extractTaskId(task.taskArn)
196
+ };
197
+ return {
198
+ sandbox: fargateSandbox,
199
+ sandboxId: task.taskArn
200
+ };
201
+ });
202
+ return sandboxes;
203
+ } catch (error) {
204
+ throw new Error(
205
+ `Failed to list Fargate sandboxes: ${error instanceof Error ? error.message : String(error)}`
206
+ );
207
+ }
208
+ },
209
+ destroy: async (config, sandboxId) => {
210
+ const { cluster } = getAndValidateCredentials(config);
211
+ const client = createECSClient(config);
212
+ try {
213
+ const command = new import_client_ecs.StopTaskCommand({
214
+ cluster,
215
+ task: sandboxId,
216
+ reason: "User requested termination"
217
+ });
218
+ await client.send(command);
219
+ } catch (error) {
220
+ if (error instanceof Error && (error.message.includes("MISSING") || error.message.includes("not found") || error.message.includes("STOPPED"))) {
221
+ console.warn(`Fargate destroy warning: Task already stopped or not found`);
222
+ return;
223
+ }
224
+ console.warn(
225
+ `Fargate destroy warning: ${error instanceof Error ? error.message : String(error)}`
226
+ );
227
+ }
228
+ },
229
+ // Instance operations (minimal stubs - not implemented yet)
230
+ runCode: async (_sandbox, _code, _runtime) => {
231
+ throw new Error("Fargate runCode method not implemented yet");
232
+ },
233
+ runCommand: async (_sandbox, _command, _args, _options) => {
234
+ throw new Error("Fargate runCommand method not implemented yet");
235
+ },
236
+ getInfo: async (_sandbox) => {
237
+ throw new Error("Fargate getInfo method not implemented yet");
238
+ },
239
+ getUrl: async (_sandbox, _options) => {
240
+ throw new Error("Fargate getUrl method not implemented yet");
241
+ }
242
+ }
243
+ }
244
+ });
245
+ // Annotate the CommonJS export names for ESM import in node:
246
+ 0 && (module.exports = {
247
+ fargate,
248
+ getAndValidateCredentials
249
+ });
250
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/index.ts"],"sourcesContent":["/**\n * AWS ECS Fargate Provider - Factory-based Implementation\n */\n\nimport { createProvider } from 'computesdk';\nimport type { Runtime, CreateSandboxOptions, RunCommandOptions } from 'computesdk';\nimport {\n ECSClient,\n RunTaskCommand,\n StopTaskCommand,\n DescribeTasksCommand,\n ListTasksCommand,\n} from '@aws-sdk/client-ecs';\n\n/**\n * Fargate sandbox interface\n */\ninterface FargateSandbox {\n taskArn: string;\n clusterArn: string;\n taskId: string;\n}\n\nexport interface FargateConfig {\n /** AWS Access Key ID - falls back to AWS_ACCESS_KEY_ID env var or default credential chain */\n accessKeyId?: string;\n /** AWS Secret Access Key - falls back to AWS_SECRET_ACCESS_KEY env var or default credential chain */\n secretAccessKey?: string;\n /** AWS Region - falls back to AWS_REGION env var */\n region?: string;\n /** ECS Cluster name or ARN */\n cluster: string;\n /** Task definition family name or ARN */\n taskDefinition: string;\n /** VPC Subnet IDs for task networking */\n subnets: string[];\n /** Security Group IDs for task networking */\n securityGroups: string[];\n /** Whether to assign a public IP (default: true) */\n assignPublicIp?: boolean;\n /** Container name in the task definition (default: 'sandbox') */\n containerName?: string;\n}\n\nexport const getAndValidateCredentials = (config: FargateConfig) => {\n const region = config.region || process.env?.AWS_REGION || 'us-east-1';\n const cluster = config.cluster;\n const taskDefinition = config.taskDefinition;\n const subnets = config.subnets;\n const securityGroups = config.securityGroups;\n\n if (!cluster) {\n throw new Error('Missing ECS cluster. Provide cluster in config.');\n }\n\n if (!taskDefinition) {\n throw new Error('Missing ECS task definition. Provide taskDefinition in config.');\n }\n\n if (!subnets || subnets.length === 0) {\n throw new Error('Missing subnets. Provide at least one subnet in config.');\n }\n\n if (!securityGroups || securityGroups.length === 0) {\n throw new Error('Missing security groups. Provide at least one security group in config.');\n }\n\n // Build credentials object only if explicitly provided\n // Otherwise, let the SDK use the default credential chain\n const credentials = config.accessKeyId && config.secretAccessKey\n ? {\n accessKeyId: config.accessKeyId,\n secretAccessKey: config.secretAccessKey,\n }\n : undefined;\n\n return {\n region,\n cluster,\n taskDefinition,\n subnets,\n securityGroups,\n credentials,\n assignPublicIp: config.assignPublicIp ?? true,\n containerName: config.containerName || 'sandbox',\n };\n};\n\n/**\n * Create an ECS client instance\n */\nconst createECSClient = (config: FargateConfig): ECSClient => {\n const { region, credentials } = getAndValidateCredentials(config);\n \n return new ECSClient({\n region,\n ...(credentials && { credentials }),\n });\n};\n\n/**\n * Extract task ID from task ARN\n * ARN format: arn:aws:ecs:region:account:task/cluster-name/task-id\n */\nconst extractTaskId = (taskArn: string): string => {\n const parts = taskArn.split('/');\n return parts[parts.length - 1];\n};\n\n/**\n * Normalize task identifier to full ARN if needed\n */\nconst normalizeTaskArn = (taskId: string, cluster: string, region: string, accountId?: string): string => {\n if (taskId.startsWith('arn:aws:ecs:')) {\n return taskId;\n }\n // If we don't have account ID, just return the task ID and let AWS resolve it\n return taskId;\n};\n\n/**\n * Create an AWS ECS Fargate provider instance using the factory pattern\n */\nexport const fargate = createProvider<FargateSandbox, FargateConfig>({\n name: 'fargate',\n methods: {\n sandbox: {\n // Collection operations (compute.sandbox.*)\n create: async (config: FargateConfig, options?: CreateSandboxOptions) => {\n const {\n cluster,\n taskDefinition,\n subnets,\n securityGroups,\n assignPublicIp,\n containerName,\n } = getAndValidateCredentials(config);\n \n const client = createECSClient(config);\n\n try {\n // Note: For AWS ECS Fargate, the image is specified in the task definition,\n // not as a runtime override. The task definition should be pre-configured\n // with the appropriate image for the desired runtime.\n // \n // The options?.runtime parameter is acknowledged but the actual runtime\n // environment is determined by the pre-configured task definition.\n \n const command = new RunTaskCommand({\n cluster,\n taskDefinition,\n launchType: 'FARGATE',\n networkConfiguration: {\n awsvpcConfiguration: {\n subnets,\n securityGroups,\n assignPublicIp: assignPublicIp ? 'ENABLED' : 'DISABLED',\n },\n },\n // Container overrides for ECS are limited to environment variables, \n // memory, CPU, and command - not the image itself\n ...(containerName && {\n overrides: {\n containerOverrides: [\n {\n name: containerName,\n // Additional container overrides can be added here if needed\n // such as environment variables, memory, CPU, etc.\n },\n ],\n },\n }),\n });\n\n const response = await client.send(command);\n\n if (!response.tasks || response.tasks.length === 0) {\n const failures = response.failures?.map(f => `${f.reason}: ${f.detail}`).join(', ');\n throw new Error(`No task created. Failures: ${failures || 'unknown'}`);\n }\n\n const task = response.tasks[0];\n \n if (!task.taskArn) {\n throw new Error('Task ARN is undefined in response');\n }\n\n const fargateSandbox: FargateSandbox = {\n taskArn: task.taskArn,\n clusterArn: task.clusterArn || cluster,\n taskId: extractTaskId(task.taskArn),\n };\n\n return {\n sandbox: fargateSandbox,\n sandboxId: task.taskArn,\n };\n } catch (error) {\n throw new Error(\n `Failed to create Fargate sandbox: ${error instanceof Error ? error.message : String(error)}`\n );\n }\n },\n\n getById: async (config: FargateConfig, sandboxId: string) => {\n const { cluster } = getAndValidateCredentials(config);\n const client = createECSClient(config);\n\n try {\n const command = new DescribeTasksCommand({\n cluster,\n tasks: [sandboxId],\n });\n\n const response = await client.send(command);\n\n if (!response.tasks || response.tasks.length === 0) {\n return null;\n }\n\n const task = response.tasks[0];\n \n if (!task.taskArn) {\n throw new Error('Task ARN is missing from response');\n }\n\n const fargateSandbox: FargateSandbox = {\n taskArn: task.taskArn,\n clusterArn: task.clusterArn || cluster,\n taskId: extractTaskId(task.taskArn),\n };\n\n return {\n sandbox: fargateSandbox,\n sandboxId: task.taskArn,\n };\n } catch (error) {\n // Handle task not found\n if (error instanceof Error && \n (error.message.includes('MISSING') || error.message.includes('not found'))) {\n return null;\n }\n throw new Error(\n `Failed to get Fargate sandbox: ${error instanceof Error ? error.message : String(error)}`\n );\n }\n },\n\n list: async (config: FargateConfig) => {\n const { cluster } = getAndValidateCredentials(config);\n const client = createECSClient(config);\n\n try {\n // Step 1: List task ARNs\n const listCommand = new ListTasksCommand({\n cluster,\n desiredStatus: 'RUNNING',\n });\n\n const listResponse = await client.send(listCommand);\n const taskArns = listResponse.taskArns || [];\n\n if (taskArns.length === 0) {\n return [];\n }\n\n // Step 2: Describe tasks to get full details\n const describeCommand = new DescribeTasksCommand({\n cluster,\n tasks: taskArns,\n });\n\n const describeResponse = await client.send(describeCommand);\n const tasks = describeResponse.tasks || [];\n\n // Transform each task into the expected format\n const sandboxes = tasks\n .filter(task => task.taskArn)\n .map(task => {\n const fargateSandbox: FargateSandbox = {\n taskArn: task.taskArn!,\n clusterArn: task.clusterArn || cluster,\n taskId: extractTaskId(task.taskArn!),\n };\n\n return {\n sandbox: fargateSandbox,\n sandboxId: task.taskArn!,\n };\n });\n\n return sandboxes;\n } catch (error) {\n throw new Error(\n `Failed to list Fargate sandboxes: ${error instanceof Error ? error.message : String(error)}`\n );\n }\n },\n\n destroy: async (config: FargateConfig, sandboxId: string) => {\n const { cluster } = getAndValidateCredentials(config);\n const client = createECSClient(config);\n\n try {\n const command = new StopTaskCommand({\n cluster,\n task: sandboxId,\n reason: 'User requested termination',\n });\n\n await client.send(command);\n } catch (error) {\n // For destroy operations, don't throw if task is already stopped/gone\n if (error instanceof Error && \n (error.message.includes('MISSING') || \n error.message.includes('not found') ||\n error.message.includes('STOPPED'))) {\n console.warn(`Fargate destroy warning: Task already stopped or not found`);\n return;\n }\n console.warn(\n `Fargate destroy warning: ${error instanceof Error ? error.message : String(error)}`\n );\n }\n },\n\n // Instance operations (minimal stubs - not implemented yet)\n runCode: async (_sandbox: FargateSandbox, _code: string, _runtime?: Runtime) => {\n throw new Error('Fargate runCode method not implemented yet');\n },\n\n runCommand: async (_sandbox: FargateSandbox, _command: string, _args?: string[], _options?: RunCommandOptions) => {\n throw new Error('Fargate runCommand method not implemented yet');\n },\n\n getInfo: async (_sandbox: FargateSandbox) => {\n throw new Error('Fargate getInfo method not implemented yet');\n },\n\n getUrl: async (_sandbox: FargateSandbox, _options: { port: number; protocol?: string }) => {\n throw new Error('Fargate getUrl method not implemented yet');\n },\n },\n },\n});"],"mappings":"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","names":[]}
package/dist/index.mjs ADDED
@@ -0,0 +1,230 @@
1
+ // src/index.ts
2
+ import { createProvider } from "computesdk";
3
+ import {
4
+ ECSClient,
5
+ RunTaskCommand,
6
+ StopTaskCommand,
7
+ DescribeTasksCommand,
8
+ ListTasksCommand
9
+ } from "@aws-sdk/client-ecs";
10
+ var getAndValidateCredentials = (config) => {
11
+ const region = config.region || process.env?.AWS_REGION || "us-east-1";
12
+ const cluster = config.cluster;
13
+ const taskDefinition = config.taskDefinition;
14
+ const subnets = config.subnets;
15
+ const securityGroups = config.securityGroups;
16
+ if (!cluster) {
17
+ throw new Error("Missing ECS cluster. Provide cluster in config.");
18
+ }
19
+ if (!taskDefinition) {
20
+ throw new Error("Missing ECS task definition. Provide taskDefinition in config.");
21
+ }
22
+ if (!subnets || subnets.length === 0) {
23
+ throw new Error("Missing subnets. Provide at least one subnet in config.");
24
+ }
25
+ if (!securityGroups || securityGroups.length === 0) {
26
+ throw new Error("Missing security groups. Provide at least one security group in config.");
27
+ }
28
+ const credentials = config.accessKeyId && config.secretAccessKey ? {
29
+ accessKeyId: config.accessKeyId,
30
+ secretAccessKey: config.secretAccessKey
31
+ } : void 0;
32
+ return {
33
+ region,
34
+ cluster,
35
+ taskDefinition,
36
+ subnets,
37
+ securityGroups,
38
+ credentials,
39
+ assignPublicIp: config.assignPublicIp ?? true,
40
+ containerName: config.containerName || "sandbox"
41
+ };
42
+ };
43
+ var createECSClient = (config) => {
44
+ const { region, credentials } = getAndValidateCredentials(config);
45
+ return new ECSClient({
46
+ region,
47
+ ...credentials && { credentials }
48
+ });
49
+ };
50
+ var extractTaskId = (taskArn) => {
51
+ const parts = taskArn.split("/");
52
+ return parts[parts.length - 1];
53
+ };
54
+ var fargate = createProvider({
55
+ name: "fargate",
56
+ methods: {
57
+ sandbox: {
58
+ // Collection operations (compute.sandbox.*)
59
+ create: async (config, options) => {
60
+ const {
61
+ cluster,
62
+ taskDefinition,
63
+ subnets,
64
+ securityGroups,
65
+ assignPublicIp,
66
+ containerName
67
+ } = getAndValidateCredentials(config);
68
+ const client = createECSClient(config);
69
+ try {
70
+ const command = new RunTaskCommand({
71
+ cluster,
72
+ taskDefinition,
73
+ launchType: "FARGATE",
74
+ networkConfiguration: {
75
+ awsvpcConfiguration: {
76
+ subnets,
77
+ securityGroups,
78
+ assignPublicIp: assignPublicIp ? "ENABLED" : "DISABLED"
79
+ }
80
+ },
81
+ // Container overrides for ECS are limited to environment variables,
82
+ // memory, CPU, and command - not the image itself
83
+ ...containerName && {
84
+ overrides: {
85
+ containerOverrides: [
86
+ {
87
+ name: containerName
88
+ // Additional container overrides can be added here if needed
89
+ // such as environment variables, memory, CPU, etc.
90
+ }
91
+ ]
92
+ }
93
+ }
94
+ });
95
+ const response = await client.send(command);
96
+ if (!response.tasks || response.tasks.length === 0) {
97
+ const failures = response.failures?.map((f) => `${f.reason}: ${f.detail}`).join(", ");
98
+ throw new Error(`No task created. Failures: ${failures || "unknown"}`);
99
+ }
100
+ const task = response.tasks[0];
101
+ if (!task.taskArn) {
102
+ throw new Error("Task ARN is undefined in response");
103
+ }
104
+ const fargateSandbox = {
105
+ taskArn: task.taskArn,
106
+ clusterArn: task.clusterArn || cluster,
107
+ taskId: extractTaskId(task.taskArn)
108
+ };
109
+ return {
110
+ sandbox: fargateSandbox,
111
+ sandboxId: task.taskArn
112
+ };
113
+ } catch (error) {
114
+ throw new Error(
115
+ `Failed to create Fargate sandbox: ${error instanceof Error ? error.message : String(error)}`
116
+ );
117
+ }
118
+ },
119
+ getById: async (config, sandboxId) => {
120
+ const { cluster } = getAndValidateCredentials(config);
121
+ const client = createECSClient(config);
122
+ try {
123
+ const command = new DescribeTasksCommand({
124
+ cluster,
125
+ tasks: [sandboxId]
126
+ });
127
+ const response = await client.send(command);
128
+ if (!response.tasks || response.tasks.length === 0) {
129
+ return null;
130
+ }
131
+ const task = response.tasks[0];
132
+ if (!task.taskArn) {
133
+ throw new Error("Task ARN is missing from response");
134
+ }
135
+ const fargateSandbox = {
136
+ taskArn: task.taskArn,
137
+ clusterArn: task.clusterArn || cluster,
138
+ taskId: extractTaskId(task.taskArn)
139
+ };
140
+ return {
141
+ sandbox: fargateSandbox,
142
+ sandboxId: task.taskArn
143
+ };
144
+ } catch (error) {
145
+ if (error instanceof Error && (error.message.includes("MISSING") || error.message.includes("not found"))) {
146
+ return null;
147
+ }
148
+ throw new Error(
149
+ `Failed to get Fargate sandbox: ${error instanceof Error ? error.message : String(error)}`
150
+ );
151
+ }
152
+ },
153
+ list: async (config) => {
154
+ const { cluster } = getAndValidateCredentials(config);
155
+ const client = createECSClient(config);
156
+ try {
157
+ const listCommand = new ListTasksCommand({
158
+ cluster,
159
+ desiredStatus: "RUNNING"
160
+ });
161
+ const listResponse = await client.send(listCommand);
162
+ const taskArns = listResponse.taskArns || [];
163
+ if (taskArns.length === 0) {
164
+ return [];
165
+ }
166
+ const describeCommand = new DescribeTasksCommand({
167
+ cluster,
168
+ tasks: taskArns
169
+ });
170
+ const describeResponse = await client.send(describeCommand);
171
+ const tasks = describeResponse.tasks || [];
172
+ const sandboxes = tasks.filter((task) => task.taskArn).map((task) => {
173
+ const fargateSandbox = {
174
+ taskArn: task.taskArn,
175
+ clusterArn: task.clusterArn || cluster,
176
+ taskId: extractTaskId(task.taskArn)
177
+ };
178
+ return {
179
+ sandbox: fargateSandbox,
180
+ sandboxId: task.taskArn
181
+ };
182
+ });
183
+ return sandboxes;
184
+ } catch (error) {
185
+ throw new Error(
186
+ `Failed to list Fargate sandboxes: ${error instanceof Error ? error.message : String(error)}`
187
+ );
188
+ }
189
+ },
190
+ destroy: async (config, sandboxId) => {
191
+ const { cluster } = getAndValidateCredentials(config);
192
+ const client = createECSClient(config);
193
+ try {
194
+ const command = new StopTaskCommand({
195
+ cluster,
196
+ task: sandboxId,
197
+ reason: "User requested termination"
198
+ });
199
+ await client.send(command);
200
+ } catch (error) {
201
+ if (error instanceof Error && (error.message.includes("MISSING") || error.message.includes("not found") || error.message.includes("STOPPED"))) {
202
+ console.warn(`Fargate destroy warning: Task already stopped or not found`);
203
+ return;
204
+ }
205
+ console.warn(
206
+ `Fargate destroy warning: ${error instanceof Error ? error.message : String(error)}`
207
+ );
208
+ }
209
+ },
210
+ // Instance operations (minimal stubs - not implemented yet)
211
+ runCode: async (_sandbox, _code, _runtime) => {
212
+ throw new Error("Fargate runCode method not implemented yet");
213
+ },
214
+ runCommand: async (_sandbox, _command, _args, _options) => {
215
+ throw new Error("Fargate runCommand method not implemented yet");
216
+ },
217
+ getInfo: async (_sandbox) => {
218
+ throw new Error("Fargate getInfo method not implemented yet");
219
+ },
220
+ getUrl: async (_sandbox, _options) => {
221
+ throw new Error("Fargate getUrl method not implemented yet");
222
+ }
223
+ }
224
+ }
225
+ });
226
+ export {
227
+ fargate,
228
+ getAndValidateCredentials
229
+ };
230
+ //# sourceMappingURL=index.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/index.ts"],"sourcesContent":["/**\n * AWS ECS Fargate Provider - Factory-based Implementation\n */\n\nimport { createProvider } from 'computesdk';\nimport type { Runtime, CreateSandboxOptions, RunCommandOptions } from 'computesdk';\nimport {\n ECSClient,\n RunTaskCommand,\n StopTaskCommand,\n DescribeTasksCommand,\n ListTasksCommand,\n} from '@aws-sdk/client-ecs';\n\n/**\n * Fargate sandbox interface\n */\ninterface FargateSandbox {\n taskArn: string;\n clusterArn: string;\n taskId: string;\n}\n\nexport interface FargateConfig {\n /** AWS Access Key ID - falls back to AWS_ACCESS_KEY_ID env var or default credential chain */\n accessKeyId?: string;\n /** AWS Secret Access Key - falls back to AWS_SECRET_ACCESS_KEY env var or default credential chain */\n secretAccessKey?: string;\n /** AWS Region - falls back to AWS_REGION env var */\n region?: string;\n /** ECS Cluster name or ARN */\n cluster: string;\n /** Task definition family name or ARN */\n taskDefinition: string;\n /** VPC Subnet IDs for task networking */\n subnets: string[];\n /** Security Group IDs for task networking */\n securityGroups: string[];\n /** Whether to assign a public IP (default: true) */\n assignPublicIp?: boolean;\n /** Container name in the task definition (default: 'sandbox') */\n containerName?: string;\n}\n\nexport const getAndValidateCredentials = (config: FargateConfig) => {\n const region = config.region || process.env?.AWS_REGION || 'us-east-1';\n const cluster = config.cluster;\n const taskDefinition = config.taskDefinition;\n const subnets = config.subnets;\n const securityGroups = config.securityGroups;\n\n if (!cluster) {\n throw new Error('Missing ECS cluster. Provide cluster in config.');\n }\n\n if (!taskDefinition) {\n throw new Error('Missing ECS task definition. Provide taskDefinition in config.');\n }\n\n if (!subnets || subnets.length === 0) {\n throw new Error('Missing subnets. Provide at least one subnet in config.');\n }\n\n if (!securityGroups || securityGroups.length === 0) {\n throw new Error('Missing security groups. Provide at least one security group in config.');\n }\n\n // Build credentials object only if explicitly provided\n // Otherwise, let the SDK use the default credential chain\n const credentials = config.accessKeyId && config.secretAccessKey\n ? {\n accessKeyId: config.accessKeyId,\n secretAccessKey: config.secretAccessKey,\n }\n : undefined;\n\n return {\n region,\n cluster,\n taskDefinition,\n subnets,\n securityGroups,\n credentials,\n assignPublicIp: config.assignPublicIp ?? true,\n containerName: config.containerName || 'sandbox',\n };\n};\n\n/**\n * Create an ECS client instance\n */\nconst createECSClient = (config: FargateConfig): ECSClient => {\n const { region, credentials } = getAndValidateCredentials(config);\n \n return new ECSClient({\n region,\n ...(credentials && { credentials }),\n });\n};\n\n/**\n * Extract task ID from task ARN\n * ARN format: arn:aws:ecs:region:account:task/cluster-name/task-id\n */\nconst extractTaskId = (taskArn: string): string => {\n const parts = taskArn.split('/');\n return parts[parts.length - 1];\n};\n\n/**\n * Normalize task identifier to full ARN if needed\n */\nconst normalizeTaskArn = (taskId: string, cluster: string, region: string, accountId?: string): string => {\n if (taskId.startsWith('arn:aws:ecs:')) {\n return taskId;\n }\n // If we don't have account ID, just return the task ID and let AWS resolve it\n return taskId;\n};\n\n/**\n * Create an AWS ECS Fargate provider instance using the factory pattern\n */\nexport const fargate = createProvider<FargateSandbox, FargateConfig>({\n name: 'fargate',\n methods: {\n sandbox: {\n // Collection operations (compute.sandbox.*)\n create: async (config: FargateConfig, options?: CreateSandboxOptions) => {\n const {\n cluster,\n taskDefinition,\n subnets,\n securityGroups,\n assignPublicIp,\n containerName,\n } = getAndValidateCredentials(config);\n \n const client = createECSClient(config);\n\n try {\n // Note: For AWS ECS Fargate, the image is specified in the task definition,\n // not as a runtime override. The task definition should be pre-configured\n // with the appropriate image for the desired runtime.\n // \n // The options?.runtime parameter is acknowledged but the actual runtime\n // environment is determined by the pre-configured task definition.\n \n const command = new RunTaskCommand({\n cluster,\n taskDefinition,\n launchType: 'FARGATE',\n networkConfiguration: {\n awsvpcConfiguration: {\n subnets,\n securityGroups,\n assignPublicIp: assignPublicIp ? 'ENABLED' : 'DISABLED',\n },\n },\n // Container overrides for ECS are limited to environment variables, \n // memory, CPU, and command - not the image itself\n ...(containerName && {\n overrides: {\n containerOverrides: [\n {\n name: containerName,\n // Additional container overrides can be added here if needed\n // such as environment variables, memory, CPU, etc.\n },\n ],\n },\n }),\n });\n\n const response = await client.send(command);\n\n if (!response.tasks || response.tasks.length === 0) {\n const failures = response.failures?.map(f => `${f.reason}: ${f.detail}`).join(', ');\n throw new Error(`No task created. Failures: ${failures || 'unknown'}`);\n }\n\n const task = response.tasks[0];\n \n if (!task.taskArn) {\n throw new Error('Task ARN is undefined in response');\n }\n\n const fargateSandbox: FargateSandbox = {\n taskArn: task.taskArn,\n clusterArn: task.clusterArn || cluster,\n taskId: extractTaskId(task.taskArn),\n };\n\n return {\n sandbox: fargateSandbox,\n sandboxId: task.taskArn,\n };\n } catch (error) {\n throw new Error(\n `Failed to create Fargate sandbox: ${error instanceof Error ? error.message : String(error)}`\n );\n }\n },\n\n getById: async (config: FargateConfig, sandboxId: string) => {\n const { cluster } = getAndValidateCredentials(config);\n const client = createECSClient(config);\n\n try {\n const command = new DescribeTasksCommand({\n cluster,\n tasks: [sandboxId],\n });\n\n const response = await client.send(command);\n\n if (!response.tasks || response.tasks.length === 0) {\n return null;\n }\n\n const task = response.tasks[0];\n \n if (!task.taskArn) {\n throw new Error('Task ARN is missing from response');\n }\n\n const fargateSandbox: FargateSandbox = {\n taskArn: task.taskArn,\n clusterArn: task.clusterArn || cluster,\n taskId: extractTaskId(task.taskArn),\n };\n\n return {\n sandbox: fargateSandbox,\n sandboxId: task.taskArn,\n };\n } catch (error) {\n // Handle task not found\n if (error instanceof Error && \n (error.message.includes('MISSING') || error.message.includes('not found'))) {\n return null;\n }\n throw new Error(\n `Failed to get Fargate sandbox: ${error instanceof Error ? error.message : String(error)}`\n );\n }\n },\n\n list: async (config: FargateConfig) => {\n const { cluster } = getAndValidateCredentials(config);\n const client = createECSClient(config);\n\n try {\n // Step 1: List task ARNs\n const listCommand = new ListTasksCommand({\n cluster,\n desiredStatus: 'RUNNING',\n });\n\n const listResponse = await client.send(listCommand);\n const taskArns = listResponse.taskArns || [];\n\n if (taskArns.length === 0) {\n return [];\n }\n\n // Step 2: Describe tasks to get full details\n const describeCommand = new DescribeTasksCommand({\n cluster,\n tasks: taskArns,\n });\n\n const describeResponse = await client.send(describeCommand);\n const tasks = describeResponse.tasks || [];\n\n // Transform each task into the expected format\n const sandboxes = tasks\n .filter(task => task.taskArn)\n .map(task => {\n const fargateSandbox: FargateSandbox = {\n taskArn: task.taskArn!,\n clusterArn: task.clusterArn || cluster,\n taskId: extractTaskId(task.taskArn!),\n };\n\n return {\n sandbox: fargateSandbox,\n sandboxId: task.taskArn!,\n };\n });\n\n return sandboxes;\n } catch (error) {\n throw new Error(\n `Failed to list Fargate sandboxes: ${error instanceof Error ? error.message : String(error)}`\n );\n }\n },\n\n destroy: async (config: FargateConfig, sandboxId: string) => {\n const { cluster } = getAndValidateCredentials(config);\n const client = createECSClient(config);\n\n try {\n const command = new StopTaskCommand({\n cluster,\n task: sandboxId,\n reason: 'User requested termination',\n });\n\n await client.send(command);\n } catch (error) {\n // For destroy operations, don't throw if task is already stopped/gone\n if (error instanceof Error && \n (error.message.includes('MISSING') || \n error.message.includes('not found') ||\n error.message.includes('STOPPED'))) {\n console.warn(`Fargate destroy warning: Task already stopped or not found`);\n return;\n }\n console.warn(\n `Fargate destroy warning: ${error instanceof Error ? error.message : String(error)}`\n );\n }\n },\n\n // Instance operations (minimal stubs - not implemented yet)\n runCode: async (_sandbox: FargateSandbox, _code: string, _runtime?: Runtime) => {\n throw new Error('Fargate runCode method not implemented yet');\n },\n\n runCommand: async (_sandbox: FargateSandbox, _command: string, _args?: string[], _options?: RunCommandOptions) => {\n throw new Error('Fargate runCommand method not implemented yet');\n },\n\n getInfo: async (_sandbox: FargateSandbox) => {\n throw new Error('Fargate getInfo method not implemented yet');\n },\n\n getUrl: async (_sandbox: FargateSandbox, _options: { port: number; protocol?: string }) => {\n throw new Error('Fargate getUrl method not implemented yet');\n },\n },\n },\n});"],"mappings":"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","names":[]}
package/package.json ADDED
@@ -0,0 +1,62 @@
1
+ {
2
+ "name": "@computesdk/aws-ecs",
3
+ "version": "1.0.1",
4
+ "description": "AWS ECS provider for ComputeSDK",
5
+ "author": "ComputeSDK Team",
6
+ "license": "MIT",
7
+ "main": "./dist/index.js",
8
+ "module": "./dist/index.mjs",
9
+ "types": "./dist/index.d.ts",
10
+ "exports": {
11
+ ".": {
12
+ "types": "./dist/index.d.ts",
13
+ "import": "./dist/index.mjs",
14
+ "require": "./dist/index.js"
15
+ }
16
+ },
17
+ "files": [
18
+ "dist"
19
+ ],
20
+ "dependencies": {
21
+ "@aws-sdk/client-ecs": "^3.943.0",
22
+ "computesdk": "1.8.8"
23
+ },
24
+ "keywords": [
25
+ "aws",
26
+ "sandbox",
27
+ "code-execution",
28
+ "cloud",
29
+ "compute",
30
+ "containers",
31
+ "ecs"
32
+ ],
33
+ "repository": {
34
+ "type": "git",
35
+ "url": "https://github.com/computesdk/computesdk.git",
36
+ "directory": "packages/aws-ecs"
37
+ },
38
+ "homepage": "https://github.com/computesdk/computesdk/tree/main/packages/aws-ecs",
39
+ "bugs": {
40
+ "url": "https://github.com/computesdk/computesdk/issues"
41
+ },
42
+ "devDependencies": {
43
+ "@types/node": "^20.0.0",
44
+ "@vitest/coverage-v8": "^1.0.0",
45
+ "eslint": "^8.37.0",
46
+ "rimraf": "^5.0.0",
47
+ "tsup": "^8.0.0",
48
+ "typescript": "^5.0.0",
49
+ "vitest": "^1.0.0",
50
+ "@computesdk/test-utils": "1.4.1"
51
+ },
52
+ "scripts": {
53
+ "build": "tsup",
54
+ "clean": "rimraf dist",
55
+ "dev": "tsup --watch",
56
+ "test": "vitest run",
57
+ "test:watch": "vitest watch",
58
+ "test:coverage": "vitest run --coverage",
59
+ "typecheck": "tsc --noEmit",
60
+ "lint": "eslint"
61
+ }
62
+ }