freestyle-sandboxes 0.1.17 → 0.1.18
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/README.md +96 -0
- package/cli.mjs +512 -0
- package/index.cjs +969 -941
- package/index.d.cts +759 -727
- package/index.d.mts +759 -727
- package/index.mjs +971 -943
- package/package.json +6 -2
package/README.md
CHANGED
|
@@ -2,6 +2,102 @@
|
|
|
2
2
|
|
|
3
3
|
Learn more at [docs.freestyle.sh](https://docs.freestyle.sh)
|
|
4
4
|
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install freestyle-sandboxes
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## CLI Usage
|
|
12
|
+
|
|
13
|
+
The Freestyle SDK includes a command-line interface for managing your Freestyle resources.
|
|
14
|
+
|
|
15
|
+
### Setup
|
|
16
|
+
|
|
17
|
+
Set the environment variable with your API key:
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
export FREESTYLE_API_KEY="your-api-key"
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
Or create a `.env` file in your project directory:
|
|
24
|
+
|
|
25
|
+
```
|
|
26
|
+
FREESTYLE_API_KEY=your-api-key
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
### Commands
|
|
30
|
+
|
|
31
|
+
#### Virtual Machines
|
|
32
|
+
|
|
33
|
+
```bash
|
|
34
|
+
# Create a new VM
|
|
35
|
+
freestyle vm create --name my-vm
|
|
36
|
+
|
|
37
|
+
# Create a VM from a snapshot (for debugging)
|
|
38
|
+
freestyle vm create --snapshot <snapshot-id>
|
|
39
|
+
|
|
40
|
+
# Create a VM with domain
|
|
41
|
+
freestyle vm create --domain myapp.example.com --port 3000
|
|
42
|
+
|
|
43
|
+
# Create VM and SSH into it (auto-deletes on exit)
|
|
44
|
+
freestyle vm create --ssh
|
|
45
|
+
|
|
46
|
+
# Create VM from snapshot and SSH into it
|
|
47
|
+
freestyle vm create --snapshot <snapshot-id> --ssh
|
|
48
|
+
|
|
49
|
+
# List all VMs
|
|
50
|
+
freestyle vm list
|
|
51
|
+
|
|
52
|
+
# SSH into a VM
|
|
53
|
+
freestyle vm ssh <vmId>
|
|
54
|
+
|
|
55
|
+
# SSH into a VM and delete it on exit
|
|
56
|
+
freestyle vm ssh <vmId> --delete-on-exit
|
|
57
|
+
|
|
58
|
+
# Execute a command on a VM
|
|
59
|
+
freestyle vm exec <vmId> "ls -la"
|
|
60
|
+
|
|
61
|
+
# Delete a VM
|
|
62
|
+
freestyle vm delete <vmId>
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
#### Serverless Deployments
|
|
66
|
+
|
|
67
|
+
```bash
|
|
68
|
+
# Deploy from inline code
|
|
69
|
+
freestyle deploy --code "export default () => 'Hello World'"
|
|
70
|
+
|
|
71
|
+
# Deploy from a file
|
|
72
|
+
freestyle deploy --file ./my-function.js
|
|
73
|
+
|
|
74
|
+
# Deploy from a Git repository
|
|
75
|
+
freestyle deploy --repo <repoId>
|
|
76
|
+
|
|
77
|
+
# Add environment variables
|
|
78
|
+
freestyle deploy --code "..." --env API_KEY=secret --env DEBUG=true
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
#### Serverless Runs
|
|
82
|
+
|
|
83
|
+
```bash
|
|
84
|
+
# Execute a one-off function from inline code
|
|
85
|
+
freestyle run --code "console.log('Hello!')"
|
|
86
|
+
|
|
87
|
+
# Execute from a file
|
|
88
|
+
freestyle run --file ./script.js
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
#### Utilities
|
|
92
|
+
|
|
93
|
+
```bash
|
|
94
|
+
# Get help for any command
|
|
95
|
+
freestyle --help
|
|
96
|
+
freestyle vm --help
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
## SDK Usage
|
|
100
|
+
|
|
5
101
|
```ts
|
|
6
102
|
import { freestyle } from "freestyle-sandboxes";
|
|
7
103
|
|
package/cli.mjs
ADDED
|
@@ -0,0 +1,512 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import 'dotenv/config';
|
|
3
|
+
import yargs from 'yargs';
|
|
4
|
+
import { hideBin } from 'yargs/helpers';
|
|
5
|
+
import { Freestyle, VmSpec } from './index.mjs';
|
|
6
|
+
import * as fs from 'fs';
|
|
7
|
+
import * as path from 'path';
|
|
8
|
+
import * as dotenv from 'dotenv';
|
|
9
|
+
import { spawn } from 'child_process';
|
|
10
|
+
|
|
11
|
+
function getFreestyleClient() {
|
|
12
|
+
const apiKey = process.env.FREESTYLE_API_KEY;
|
|
13
|
+
const baseUrl = process.env.FREESTYLE_API_URL;
|
|
14
|
+
if (!apiKey) {
|
|
15
|
+
console.error("Error: FREESTYLE_API_KEY environment variable is not set.");
|
|
16
|
+
console.error('Run "freestyle init" to set it up, or set it manually.');
|
|
17
|
+
process.exit(1);
|
|
18
|
+
}
|
|
19
|
+
return new Freestyle({ apiKey, baseUrl });
|
|
20
|
+
}
|
|
21
|
+
function handleError(error) {
|
|
22
|
+
if (error.response) {
|
|
23
|
+
console.error("API Error:", error.response.data);
|
|
24
|
+
} else if (error.message) {
|
|
25
|
+
console.error("Error:", error.message);
|
|
26
|
+
} else {
|
|
27
|
+
console.error("Error:", error);
|
|
28
|
+
}
|
|
29
|
+
process.exit(1);
|
|
30
|
+
}
|
|
31
|
+
function loadEnv() {
|
|
32
|
+
const envPath = path.join(process.cwd(), ".env");
|
|
33
|
+
if (fs.existsSync(envPath)) {
|
|
34
|
+
dotenv.config({ path: envPath });
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
function formatTable(headers, rows) {
|
|
38
|
+
const colWidths = headers.map((h, i) => {
|
|
39
|
+
const maxRowWidth = Math.max(...rows.map((r) => (r[i] || "").length));
|
|
40
|
+
return Math.max(h.length, maxRowWidth);
|
|
41
|
+
});
|
|
42
|
+
const headerRow = headers.map((h, i) => h.padEnd(colWidths[i])).join(" ");
|
|
43
|
+
const separator = colWidths.map((w) => "-".repeat(w)).join(" ");
|
|
44
|
+
console.log(headerRow);
|
|
45
|
+
console.log(separator);
|
|
46
|
+
rows.forEach((row) => {
|
|
47
|
+
console.log(row.map((cell, i) => (cell || "").padEnd(colWidths[i])).join(" "));
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
async function sshIntoVm(vmId, options = {}) {
|
|
52
|
+
const freestyle = getFreestyleClient();
|
|
53
|
+
console.log("Setting up SSH connection...");
|
|
54
|
+
const { identity, identityId } = await freestyle.identities.create();
|
|
55
|
+
console.log(`Created identity: ${identityId}`);
|
|
56
|
+
await identity.permissions.vms.grant({ vmId });
|
|
57
|
+
const { token, tokenId } = await identity.tokens.create();
|
|
58
|
+
const sshCommand = `ssh ${vmId}:${token}@vm-ssh.freestyle.sh -p 22`;
|
|
59
|
+
console.log(`Connecting to VM ${vmId}...`);
|
|
60
|
+
console.log(`Command: ${sshCommand}
|
|
61
|
+
`);
|
|
62
|
+
return new Promise((resolve, reject) => {
|
|
63
|
+
const sshProcess = spawn(sshCommand, {
|
|
64
|
+
shell: true,
|
|
65
|
+
stdio: "inherit"
|
|
66
|
+
});
|
|
67
|
+
sshProcess.on("close", async (code) => {
|
|
68
|
+
console.log("\nSSH session ended.");
|
|
69
|
+
try {
|
|
70
|
+
console.log("Cleaning up identity and token...");
|
|
71
|
+
await identity.tokens.revoke({ tokenId });
|
|
72
|
+
await freestyle.identities.delete({ identityId });
|
|
73
|
+
console.log("\u2713 Cleanup complete");
|
|
74
|
+
if (options.deleteOnExit) {
|
|
75
|
+
console.log(`Deleting VM ${vmId}...`);
|
|
76
|
+
await freestyle.vms.delete({ vmId });
|
|
77
|
+
console.log("\u2713 VM deleted");
|
|
78
|
+
}
|
|
79
|
+
resolve();
|
|
80
|
+
} catch (error) {
|
|
81
|
+
console.error("Error during cleanup:", error);
|
|
82
|
+
reject(error);
|
|
83
|
+
}
|
|
84
|
+
});
|
|
85
|
+
sshProcess.on("error", (error) => {
|
|
86
|
+
console.error("Error starting SSH:", error);
|
|
87
|
+
reject(error);
|
|
88
|
+
});
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
const vmCommand = {
|
|
92
|
+
command: "vm <action>",
|
|
93
|
+
describe: "Manage Virtual Machines",
|
|
94
|
+
builder: (yargs) => {
|
|
95
|
+
return yargs.command(
|
|
96
|
+
"create",
|
|
97
|
+
"Create a new VM",
|
|
98
|
+
(yargs2) => {
|
|
99
|
+
return yargs2.option("name", {
|
|
100
|
+
alias: "n",
|
|
101
|
+
type: "string",
|
|
102
|
+
description: "VM name/discriminator"
|
|
103
|
+
}).option("domain", {
|
|
104
|
+
alias: "d",
|
|
105
|
+
type: "string",
|
|
106
|
+
description: "Custom domain to attach"
|
|
107
|
+
}).option("port", {
|
|
108
|
+
alias: "p",
|
|
109
|
+
type: "number",
|
|
110
|
+
description: "VM port to expose (default: 3000)",
|
|
111
|
+
default: 3e3
|
|
112
|
+
}).option("apt", {
|
|
113
|
+
type: "array",
|
|
114
|
+
description: "APT packages to install",
|
|
115
|
+
default: []
|
|
116
|
+
}).option("snapshot", {
|
|
117
|
+
alias: "s",
|
|
118
|
+
type: "string",
|
|
119
|
+
description: "Snapshot ID to create VM from"
|
|
120
|
+
}).option("exec", {
|
|
121
|
+
alias: "e",
|
|
122
|
+
type: "string",
|
|
123
|
+
description: "Execute a command on the VM after creation"
|
|
124
|
+
}).option("ssh", {
|
|
125
|
+
type: "boolean",
|
|
126
|
+
description: "SSH into VM after creation and delete VM on exit (for debugging)",
|
|
127
|
+
default: false
|
|
128
|
+
}).option("delete", {
|
|
129
|
+
type: "boolean",
|
|
130
|
+
description: "Delete VM after exec completes or when SSH session ends",
|
|
131
|
+
default: false
|
|
132
|
+
}).option("json", {
|
|
133
|
+
type: "boolean",
|
|
134
|
+
description: "Output as JSON",
|
|
135
|
+
default: false
|
|
136
|
+
});
|
|
137
|
+
},
|
|
138
|
+
async (argv) => {
|
|
139
|
+
loadEnv();
|
|
140
|
+
const args = argv;
|
|
141
|
+
try {
|
|
142
|
+
const freestyle = getFreestyleClient();
|
|
143
|
+
let createOptions = {};
|
|
144
|
+
if (args.snapshot) {
|
|
145
|
+
createOptions.snapshotId = args.snapshot;
|
|
146
|
+
} else {
|
|
147
|
+
const spec = new VmSpec({
|
|
148
|
+
discriminator: args.name,
|
|
149
|
+
aptDeps: args.apt
|
|
150
|
+
});
|
|
151
|
+
createOptions.snapshot = spec;
|
|
152
|
+
}
|
|
153
|
+
if (args.domain) {
|
|
154
|
+
createOptions.domains = [
|
|
155
|
+
{
|
|
156
|
+
domain: args.domain,
|
|
157
|
+
vmPort: args.port
|
|
158
|
+
}
|
|
159
|
+
];
|
|
160
|
+
}
|
|
161
|
+
console.log("Creating VM...");
|
|
162
|
+
const result = await freestyle.vms.create(createOptions);
|
|
163
|
+
let execResult;
|
|
164
|
+
if (args.exec) {
|
|
165
|
+
const vm = freestyle.vms.ref({ vmId: result.vmId });
|
|
166
|
+
console.log(`Executing command on VM ${result.vmId}...`);
|
|
167
|
+
execResult = await vm.exec({
|
|
168
|
+
command: args.exec
|
|
169
|
+
});
|
|
170
|
+
}
|
|
171
|
+
if (args.json && !args.ssh) {
|
|
172
|
+
if (execResult) {
|
|
173
|
+
console.log(
|
|
174
|
+
JSON.stringify(
|
|
175
|
+
{
|
|
176
|
+
vm: result,
|
|
177
|
+
exec: execResult
|
|
178
|
+
},
|
|
179
|
+
null,
|
|
180
|
+
2
|
|
181
|
+
)
|
|
182
|
+
);
|
|
183
|
+
} else {
|
|
184
|
+
console.log(JSON.stringify(result, null, 2));
|
|
185
|
+
}
|
|
186
|
+
} else {
|
|
187
|
+
console.log("\n\u2713 VM created successfully!");
|
|
188
|
+
console.log(` VM ID: ${result.vmId}`);
|
|
189
|
+
const domainStr = result.domains?.[0];
|
|
190
|
+
if (domainStr) {
|
|
191
|
+
console.log(` Domain: https://${domainStr}`);
|
|
192
|
+
}
|
|
193
|
+
if (execResult) {
|
|
194
|
+
if (execResult.stdout) {
|
|
195
|
+
console.log("\nExec output:");
|
|
196
|
+
console.log(execResult.stdout);
|
|
197
|
+
}
|
|
198
|
+
if (execResult.stderr) {
|
|
199
|
+
console.error("\nExec errors:");
|
|
200
|
+
console.error(execResult.stderr);
|
|
201
|
+
}
|
|
202
|
+
console.log(`
|
|
203
|
+
Exec exit code: ${execResult.statusCode || 0}`);
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
if (args.ssh) {
|
|
207
|
+
console.log("");
|
|
208
|
+
await sshIntoVm(result.vmId, { deleteOnExit: args.delete });
|
|
209
|
+
} else if (args.delete) {
|
|
210
|
+
console.log(`Deleting VM ${result.vmId}...`);
|
|
211
|
+
await freestyle.vms.delete({ vmId: result.vmId });
|
|
212
|
+
console.log("\u2713 VM deleted");
|
|
213
|
+
}
|
|
214
|
+
} catch (error) {
|
|
215
|
+
handleError(error);
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
).command(
|
|
219
|
+
"list",
|
|
220
|
+
"List all VMs",
|
|
221
|
+
(yargs2) => {
|
|
222
|
+
return yargs2.option("json", {
|
|
223
|
+
type: "boolean",
|
|
224
|
+
description: "Output as JSON",
|
|
225
|
+
default: false
|
|
226
|
+
});
|
|
227
|
+
},
|
|
228
|
+
async (argv) => {
|
|
229
|
+
loadEnv();
|
|
230
|
+
const args = argv;
|
|
231
|
+
try {
|
|
232
|
+
const freestyle = getFreestyleClient();
|
|
233
|
+
const vms = await freestyle.vms.list();
|
|
234
|
+
if (args.json) {
|
|
235
|
+
console.log(JSON.stringify(vms, null, 2));
|
|
236
|
+
} else {
|
|
237
|
+
if (vms.vms.length === 0) {
|
|
238
|
+
console.log("No VMs found.");
|
|
239
|
+
return;
|
|
240
|
+
}
|
|
241
|
+
const rows = vms.vms.map((vm) => [
|
|
242
|
+
vm.id,
|
|
243
|
+
vm.state || "unknown",
|
|
244
|
+
vm.createdAt ? new Date(vm.createdAt).toLocaleString() : "N/A"
|
|
245
|
+
]);
|
|
246
|
+
formatTable(["VM ID", "Status", "Created"], rows);
|
|
247
|
+
}
|
|
248
|
+
} catch (error) {
|
|
249
|
+
handleError(error);
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
).command(
|
|
253
|
+
"ssh <vmId>",
|
|
254
|
+
"SSH into a VM",
|
|
255
|
+
(yargs2) => {
|
|
256
|
+
return yargs2.positional("vmId", {
|
|
257
|
+
type: "string",
|
|
258
|
+
description: "VM ID to SSH into",
|
|
259
|
+
demandOption: true
|
|
260
|
+
}).option("delete", {
|
|
261
|
+
type: "boolean",
|
|
262
|
+
description: "Delete VM when SSH session ends",
|
|
263
|
+
default: false
|
|
264
|
+
});
|
|
265
|
+
},
|
|
266
|
+
async (argv) => {
|
|
267
|
+
loadEnv();
|
|
268
|
+
const args = argv;
|
|
269
|
+
try {
|
|
270
|
+
await sshIntoVm(args.vmId, {
|
|
271
|
+
deleteOnExit: args.delete
|
|
272
|
+
});
|
|
273
|
+
} catch (error) {
|
|
274
|
+
handleError(error);
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
).command(
|
|
278
|
+
"exec <vmId> <command>",
|
|
279
|
+
"Execute a command on a VM",
|
|
280
|
+
(yargs2) => {
|
|
281
|
+
return yargs2.positional("vmId", {
|
|
282
|
+
type: "string",
|
|
283
|
+
description: "VM ID",
|
|
284
|
+
demandOption: true
|
|
285
|
+
}).positional("command", {
|
|
286
|
+
type: "string",
|
|
287
|
+
description: "Command to execute",
|
|
288
|
+
demandOption: true
|
|
289
|
+
}).option("json", {
|
|
290
|
+
type: "boolean",
|
|
291
|
+
description: "Output as JSON",
|
|
292
|
+
default: false
|
|
293
|
+
});
|
|
294
|
+
},
|
|
295
|
+
async (argv) => {
|
|
296
|
+
loadEnv();
|
|
297
|
+
const args = argv;
|
|
298
|
+
try {
|
|
299
|
+
const freestyle = getFreestyleClient();
|
|
300
|
+
const vm = freestyle.vms.ref({ vmId: args.vmId });
|
|
301
|
+
console.log(`Executing command on VM ${args.vmId}...`);
|
|
302
|
+
const result = await vm.exec({
|
|
303
|
+
command: args.command
|
|
304
|
+
});
|
|
305
|
+
if (args.json) {
|
|
306
|
+
console.log(JSON.stringify(result, null, 2));
|
|
307
|
+
} else {
|
|
308
|
+
if (result.stdout) {
|
|
309
|
+
console.log("\nOutput:");
|
|
310
|
+
console.log(result.stdout);
|
|
311
|
+
}
|
|
312
|
+
if (result.stderr) {
|
|
313
|
+
console.error("\nErrors:");
|
|
314
|
+
console.error(result.stderr);
|
|
315
|
+
}
|
|
316
|
+
console.log(`
|
|
317
|
+
Exit code: ${result.statusCode || 0}`);
|
|
318
|
+
}
|
|
319
|
+
} catch (error) {
|
|
320
|
+
handleError(error);
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
).command(
|
|
324
|
+
"delete <vmId>",
|
|
325
|
+
"Delete a VM",
|
|
326
|
+
(yargs2) => {
|
|
327
|
+
return yargs2.positional("vmId", {
|
|
328
|
+
type: "string",
|
|
329
|
+
description: "VM ID to delete",
|
|
330
|
+
demandOption: true
|
|
331
|
+
});
|
|
332
|
+
},
|
|
333
|
+
async (argv) => {
|
|
334
|
+
loadEnv();
|
|
335
|
+
const args = argv;
|
|
336
|
+
try {
|
|
337
|
+
const freestyle = getFreestyleClient();
|
|
338
|
+
console.log(`Deleting VM ${args.vmId}...`);
|
|
339
|
+
await freestyle.vms.delete({ vmId: args.vmId });
|
|
340
|
+
console.log("\u2713 VM deleted successfully!");
|
|
341
|
+
} catch (error) {
|
|
342
|
+
handleError(error);
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
).demandCommand(1, "You need to specify a vm action");
|
|
346
|
+
},
|
|
347
|
+
handler: () => {
|
|
348
|
+
}
|
|
349
|
+
};
|
|
350
|
+
|
|
351
|
+
const deployCommand = {
|
|
352
|
+
command: "deploy",
|
|
353
|
+
describe: "Deploy a serverless function",
|
|
354
|
+
builder: (yargs) => {
|
|
355
|
+
return yargs.option("code", {
|
|
356
|
+
alias: "c",
|
|
357
|
+
type: "string",
|
|
358
|
+
description: "Inline code to deploy"
|
|
359
|
+
}).option("file", {
|
|
360
|
+
alias: "f",
|
|
361
|
+
type: "string",
|
|
362
|
+
description: "File path containing code to deploy"
|
|
363
|
+
}).option("repo", {
|
|
364
|
+
alias: "r",
|
|
365
|
+
type: "string",
|
|
366
|
+
description: "Git repository ID to deploy"
|
|
367
|
+
}).option("env", {
|
|
368
|
+
alias: "e",
|
|
369
|
+
type: "array",
|
|
370
|
+
description: "Environment variables (KEY=VALUE)",
|
|
371
|
+
default: []
|
|
372
|
+
}).option("json", {
|
|
373
|
+
type: "boolean",
|
|
374
|
+
description: "Output as JSON",
|
|
375
|
+
default: false
|
|
376
|
+
}).check((argv) => {
|
|
377
|
+
const hasCode = !!argv.code;
|
|
378
|
+
const hasFile = !!argv.file;
|
|
379
|
+
const hasRepo = !!argv.repo;
|
|
380
|
+
if (!hasCode && !hasFile && !hasRepo) {
|
|
381
|
+
throw new Error(
|
|
382
|
+
"You must specify one of --code, --file, or --repo"
|
|
383
|
+
);
|
|
384
|
+
}
|
|
385
|
+
if ([hasCode, hasFile, hasRepo].filter(Boolean).length > 1) {
|
|
386
|
+
throw new Error(
|
|
387
|
+
"You can only specify one of --code, --file, or --repo"
|
|
388
|
+
);
|
|
389
|
+
}
|
|
390
|
+
return true;
|
|
391
|
+
});
|
|
392
|
+
},
|
|
393
|
+
handler: async (argv) => {
|
|
394
|
+
loadEnv();
|
|
395
|
+
const args = argv;
|
|
396
|
+
try {
|
|
397
|
+
const freestyle = getFreestyleClient();
|
|
398
|
+
let code;
|
|
399
|
+
let repo;
|
|
400
|
+
if (args.code) {
|
|
401
|
+
code = args.code;
|
|
402
|
+
} else if (args.file) {
|
|
403
|
+
code = fs.readFileSync(args.file, "utf-8");
|
|
404
|
+
} else if (args.repo) {
|
|
405
|
+
repo = args.repo;
|
|
406
|
+
}
|
|
407
|
+
const env = {};
|
|
408
|
+
if (args.env) {
|
|
409
|
+
for (const envVar of args.env) {
|
|
410
|
+
const [key, ...valueParts] = envVar.split("=");
|
|
411
|
+
if (key) {
|
|
412
|
+
env[key] = valueParts.join("=");
|
|
413
|
+
}
|
|
414
|
+
}
|
|
415
|
+
}
|
|
416
|
+
console.log("Creating deployment...");
|
|
417
|
+
const result = await freestyle.serverless.deployments.create({
|
|
418
|
+
...code ? { code } : {},
|
|
419
|
+
...repo ? { repo } : {},
|
|
420
|
+
env: Object.keys(env).length > 0 ? env : void 0
|
|
421
|
+
});
|
|
422
|
+
if (args.json) {
|
|
423
|
+
console.log(JSON.stringify(result, null, 2));
|
|
424
|
+
} else {
|
|
425
|
+
console.log("\n\u2713 Deployment created successfully!");
|
|
426
|
+
console.log(` Deployment ID: ${result.deploymentId}`);
|
|
427
|
+
if (result.url) {
|
|
428
|
+
console.log(` URL: ${result.url}`);
|
|
429
|
+
}
|
|
430
|
+
}
|
|
431
|
+
} catch (error) {
|
|
432
|
+
handleError(error);
|
|
433
|
+
}
|
|
434
|
+
}
|
|
435
|
+
};
|
|
436
|
+
|
|
437
|
+
const runCommand = {
|
|
438
|
+
command: "run",
|
|
439
|
+
describe: "Execute a one-off serverless function",
|
|
440
|
+
builder: (yargs) => {
|
|
441
|
+
return yargs.option("code", {
|
|
442
|
+
alias: "c",
|
|
443
|
+
type: "string",
|
|
444
|
+
description: "Inline code to execute"
|
|
445
|
+
}).option("file", {
|
|
446
|
+
alias: "f",
|
|
447
|
+
type: "string",
|
|
448
|
+
description: "File path containing code to execute"
|
|
449
|
+
}).option("env", {
|
|
450
|
+
alias: "e",
|
|
451
|
+
type: "array",
|
|
452
|
+
description: "Environment variables (KEY=VALUE)",
|
|
453
|
+
default: []
|
|
454
|
+
}).option("json", {
|
|
455
|
+
type: "boolean",
|
|
456
|
+
description: "Output as JSON",
|
|
457
|
+
default: false
|
|
458
|
+
}).check((argv) => {
|
|
459
|
+
const hasCode = !!argv.code;
|
|
460
|
+
const hasFile = !!argv.file;
|
|
461
|
+
if (!hasCode && !hasFile) {
|
|
462
|
+
throw new Error("You must specify either --code or --file");
|
|
463
|
+
}
|
|
464
|
+
if (hasCode && hasFile) {
|
|
465
|
+
throw new Error("You can only specify one of --code or --file");
|
|
466
|
+
}
|
|
467
|
+
return true;
|
|
468
|
+
});
|
|
469
|
+
},
|
|
470
|
+
handler: async (argv) => {
|
|
471
|
+
loadEnv();
|
|
472
|
+
const args = argv;
|
|
473
|
+
try {
|
|
474
|
+
const freestyle = getFreestyleClient();
|
|
475
|
+
let code;
|
|
476
|
+
if (args.code) {
|
|
477
|
+
code = args.code;
|
|
478
|
+
} else if (args.file) {
|
|
479
|
+
code = fs.readFileSync(args.file, "utf-8");
|
|
480
|
+
} else {
|
|
481
|
+
throw new Error("Code is required");
|
|
482
|
+
}
|
|
483
|
+
const env = {};
|
|
484
|
+
if (args.env) {
|
|
485
|
+
for (const envVar of args.env) {
|
|
486
|
+
const [key, ...valueParts] = envVar.split("=");
|
|
487
|
+
if (key) {
|
|
488
|
+
env[key] = valueParts.join("=");
|
|
489
|
+
}
|
|
490
|
+
}
|
|
491
|
+
}
|
|
492
|
+
console.log("Executing serverless function...");
|
|
493
|
+
const result = await freestyle.serverless.runs.create({
|
|
494
|
+
code,
|
|
495
|
+
env: Object.keys(env).length > 0 ? env : void 0
|
|
496
|
+
});
|
|
497
|
+
if (args.json) {
|
|
498
|
+
console.log(JSON.stringify(result, null, 2));
|
|
499
|
+
} else {
|
|
500
|
+
console.log("\n\u2713 Function executed successfully!");
|
|
501
|
+
console.log(` Run ID: ${result.runId}`);
|
|
502
|
+
if (result.output) {
|
|
503
|
+
console.log(` Output: ${result.output}`);
|
|
504
|
+
}
|
|
505
|
+
}
|
|
506
|
+
} catch (error) {
|
|
507
|
+
handleError(error);
|
|
508
|
+
}
|
|
509
|
+
}
|
|
510
|
+
};
|
|
511
|
+
|
|
512
|
+
yargs(hideBin(process.argv)).scriptName("freestyle").usage("$0 <command> [options]").command(vmCommand).command(deployCommand).command(runCommand).demandCommand(1, "You need to specify a command").help().alias("help", "h").version().alias("version", "v").strict().parse();
|