@freestyle-sh/with-deno 0.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/README.md +123 -0
- package/dist/index.d.ts +33 -0
- package/dist/index.js +124 -0
- package/package.json +30 -0
package/README.md
ADDED
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
# @freestyle-sh/with-deno
|
|
2
|
+
|
|
3
|
+
Deno runtime for [Freestyle](https://freestyle.sh) VMs.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install @freestyle-sh/with-deno freestyle-sandboxes
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Usage
|
|
12
|
+
|
|
13
|
+
```typescript
|
|
14
|
+
import { freestyle, VmSpec } from "freestyle-sandboxes";
|
|
15
|
+
import { VmDeno } from "@freestyle-sh/with-deno";
|
|
16
|
+
|
|
17
|
+
const spec = new VmSpec({
|
|
18
|
+
with: {
|
|
19
|
+
deno: new VmDeno(),
|
|
20
|
+
},
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
const { vm } = await freestyle.vms.create({ spec });
|
|
24
|
+
|
|
25
|
+
const res = await vm.deno.runCode({
|
|
26
|
+
code: "console.log(JSON.stringify({ hello: 'world', runtime: 'deno' }));",
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
console.log(res);
|
|
30
|
+
// { result: { hello: 'world', runtime: 'deno' }, stdout: '{"hello":"world","runtime":"deno"}\n', statusCode: 0 }
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
## Options
|
|
34
|
+
|
|
35
|
+
```typescript
|
|
36
|
+
new VmDeno({
|
|
37
|
+
version: "2.0.0", // Optional: specific Deno version (default: latest)
|
|
38
|
+
})
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
| Option | Type | Default | Description |
|
|
42
|
+
|--------|------|---------|-------------|
|
|
43
|
+
| `version` | `string` | `undefined` | Deno version to install. If not specified, installs the latest version. |
|
|
44
|
+
|
|
45
|
+
## API
|
|
46
|
+
|
|
47
|
+
### `vm.deno.runCode({ code: string })`
|
|
48
|
+
|
|
49
|
+
Executes JavaScript/TypeScript code using `deno eval`.
|
|
50
|
+
|
|
51
|
+
**Returns:** `Promise<RunCodeResponse>`
|
|
52
|
+
|
|
53
|
+
```typescript
|
|
54
|
+
type RunCodeResponse<Result> = {
|
|
55
|
+
result: Result; // Parsed JSON from stdout (if valid JSON)
|
|
56
|
+
stdout?: string; // Raw stdout output
|
|
57
|
+
stderr?: string; // Raw stderr output
|
|
58
|
+
statusCode?: number; // Exit code
|
|
59
|
+
};
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
### `vm.deno.install(options?)`
|
|
63
|
+
|
|
64
|
+
Installs packages using Deno. Supports both npm packages and JSR (Deno's native registry).
|
|
65
|
+
|
|
66
|
+
```typescript
|
|
67
|
+
// Install from deno.json in current directory
|
|
68
|
+
await vm.deno.install();
|
|
69
|
+
|
|
70
|
+
// Install from deno.json in specific directory
|
|
71
|
+
await vm.deno.install({ directory: "/app" });
|
|
72
|
+
|
|
73
|
+
// Install npm packages (auto-prefixed with npm:)
|
|
74
|
+
await vm.deno.install({ deps: ["lodash-es", "express"] });
|
|
75
|
+
|
|
76
|
+
// Install JSR packages (use jsr: prefix)
|
|
77
|
+
await vm.deno.install({ deps: ["jsr:@std/path", "jsr:@std/fs"] });
|
|
78
|
+
|
|
79
|
+
// Install with specific versions
|
|
80
|
+
await vm.deno.install({ deps: { "lodash-es": "^4.0.0" } });
|
|
81
|
+
|
|
82
|
+
// Install as dev dependencies
|
|
83
|
+
await vm.deno.install({ deps: ["typescript"], dev: true });
|
|
84
|
+
|
|
85
|
+
// Install globally
|
|
86
|
+
await vm.deno.install({ global: true, deps: ["jsr:@std/cli"] });
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
**Returns:** `Promise<InstallResult>`
|
|
90
|
+
|
|
91
|
+
```typescript
|
|
92
|
+
type InstallResult = {
|
|
93
|
+
success: boolean;
|
|
94
|
+
stdout?: string;
|
|
95
|
+
stderr?: string;
|
|
96
|
+
};
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
## JSR Packages
|
|
100
|
+
|
|
101
|
+
Deno has native support for [JSR](https://jsr.io) (JavaScript Registry), which hosts TypeScript-first packages including the Deno standard library.
|
|
102
|
+
|
|
103
|
+
```typescript
|
|
104
|
+
// Install @std/path from JSR
|
|
105
|
+
await vm.deno.install({ deps: ["jsr:@std/path"] });
|
|
106
|
+
|
|
107
|
+
// Use it in code
|
|
108
|
+
const res = await vm.deno.runCode({
|
|
109
|
+
code: `
|
|
110
|
+
import * as path from "jsr:@std/path";
|
|
111
|
+
console.log(JSON.stringify({
|
|
112
|
+
join: path.join("foo", "bar", "baz"),
|
|
113
|
+
basename: path.basename("/home/user/file.txt"),
|
|
114
|
+
}));
|
|
115
|
+
`,
|
|
116
|
+
});
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
## Documentation
|
|
120
|
+
|
|
121
|
+
- [Freestyle Documentation](https://docs.freestyle.sh)
|
|
122
|
+
- [Deno Documentation](https://docs.deno.com)
|
|
123
|
+
- [JSR Registry](https://jsr.io)
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { VmWith, VmWithInstance, VmSpec } from 'freestyle-sandboxes';
|
|
2
|
+
import { VmJavaScriptRuntimeInstance, JSONValue, RunCodeResponse, InstallOptions, InstallResult, VmJavaScriptRuntime } from '@freestyle-sh/with-type-js';
|
|
3
|
+
|
|
4
|
+
type DenoOptions = {
|
|
5
|
+
version?: string;
|
|
6
|
+
workdir?: string;
|
|
7
|
+
deleteAfterSuccess?: boolean;
|
|
8
|
+
};
|
|
9
|
+
type DenoResolvedOptions = {
|
|
10
|
+
version?: string;
|
|
11
|
+
workdir?: string;
|
|
12
|
+
deleteAfterSuccess: boolean;
|
|
13
|
+
};
|
|
14
|
+
declare class VmDeno extends VmWith<VmDenoInstance> implements VmJavaScriptRuntime<VmJavaScriptRuntimeInstance> {
|
|
15
|
+
options: DenoResolvedOptions;
|
|
16
|
+
constructor(options?: DenoOptions);
|
|
17
|
+
configureSnapshotSpec(spec: VmSpec): VmSpec;
|
|
18
|
+
createInstance(): VmDenoInstance;
|
|
19
|
+
installServiceName(): string;
|
|
20
|
+
}
|
|
21
|
+
declare class VmDenoInstance extends VmWithInstance implements VmJavaScriptRuntimeInstance {
|
|
22
|
+
builder: VmDeno;
|
|
23
|
+
constructor(builder: VmDeno);
|
|
24
|
+
runCode<Result extends JSONValue = any>(args: string | {
|
|
25
|
+
code: string;
|
|
26
|
+
argv?: string[];
|
|
27
|
+
env?: Record<string, string>;
|
|
28
|
+
workdir?: string;
|
|
29
|
+
}): Promise<RunCodeResponse<Result>>;
|
|
30
|
+
install(options?: InstallOptions): Promise<InstallResult>;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export { VmDeno };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
import { VmWith, VmSpec, VmWithInstance } from 'freestyle-sandboxes';
|
|
2
|
+
|
|
3
|
+
class VmDeno extends VmWith {
|
|
4
|
+
options;
|
|
5
|
+
constructor(options) {
|
|
6
|
+
super();
|
|
7
|
+
this.options = {
|
|
8
|
+
version: options?.version,
|
|
9
|
+
workdir: options?.workdir,
|
|
10
|
+
deleteAfterSuccess: options?.deleteAfterSuccess ?? true
|
|
11
|
+
};
|
|
12
|
+
}
|
|
13
|
+
configureSnapshotSpec(spec) {
|
|
14
|
+
const versionArg = this.options.version ? ` v${this.options.version}` : "";
|
|
15
|
+
const installScript = `#!/bin/bash
|
|
16
|
+
set -e
|
|
17
|
+
export DENO_INSTALL="/opt/deno"
|
|
18
|
+
curl -fsSL https://deno.land/install.sh | sh -s -- --yes${versionArg}
|
|
19
|
+
$DENO_INSTALL/bin/deno --version
|
|
20
|
+
`;
|
|
21
|
+
const denoInit = `export DENO_INSTALL="/opt/deno"
|
|
22
|
+
export PATH="$DENO_INSTALL/bin:$PATH"
|
|
23
|
+
`;
|
|
24
|
+
return this.composeSpecs(
|
|
25
|
+
spec,
|
|
26
|
+
new VmSpec({
|
|
27
|
+
additionalFiles: {
|
|
28
|
+
"/opt/install-deno.sh": {
|
|
29
|
+
content: installScript
|
|
30
|
+
},
|
|
31
|
+
"/etc/profile.d/deno.sh": {
|
|
32
|
+
content: denoInit
|
|
33
|
+
}
|
|
34
|
+
},
|
|
35
|
+
systemd: {
|
|
36
|
+
services: [
|
|
37
|
+
{
|
|
38
|
+
name: "install-deno",
|
|
39
|
+
mode: "oneshot",
|
|
40
|
+
deleteAfterSuccess: this.options.deleteAfterSuccess,
|
|
41
|
+
env: {
|
|
42
|
+
HOME: "/root"
|
|
43
|
+
},
|
|
44
|
+
exec: ["bash /opt/install-deno.sh"],
|
|
45
|
+
timeoutSec: 300
|
|
46
|
+
}
|
|
47
|
+
]
|
|
48
|
+
}
|
|
49
|
+
})
|
|
50
|
+
);
|
|
51
|
+
}
|
|
52
|
+
createInstance() {
|
|
53
|
+
return new VmDenoInstance(this);
|
|
54
|
+
}
|
|
55
|
+
installServiceName() {
|
|
56
|
+
return "install-deno.service";
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
class VmDenoInstance extends VmWithInstance {
|
|
60
|
+
builder;
|
|
61
|
+
constructor(builder) {
|
|
62
|
+
super();
|
|
63
|
+
this.builder = builder;
|
|
64
|
+
}
|
|
65
|
+
async runCode(args) {
|
|
66
|
+
const options = typeof args === "string" ? { code: args } : args;
|
|
67
|
+
const { code, argv, env, workdir } = options;
|
|
68
|
+
const shellEscape = (value) => `'${value.replace(/'/g, "'\\''")}'`;
|
|
69
|
+
const argvArgs = argv?.map(shellEscape).join(" ");
|
|
70
|
+
const envPrefix = env ? `${Object.entries(env).map(([key, value]) => `${key}=${shellEscape(value)}`).join(" ")} ` : "";
|
|
71
|
+
const cdPrefix = workdir ? `cd ${shellEscape(workdir)} && ` : "";
|
|
72
|
+
const command = `${cdPrefix}${envPrefix}/opt/deno/bin/deno eval "${code.replace(/"/g, '\\"')}"${argvArgs ? ` -- ${argvArgs}` : ""}`;
|
|
73
|
+
const result = await this.vm.exec({
|
|
74
|
+
command
|
|
75
|
+
});
|
|
76
|
+
let parsedResult = void 0;
|
|
77
|
+
if (result.stdout) {
|
|
78
|
+
const lines = result.stdout.split(/\r?\n/).map((line) => line.trim()).filter((line) => line.length > 0);
|
|
79
|
+
const lastLine = lines[lines.length - 1];
|
|
80
|
+
if (lastLine) {
|
|
81
|
+
try {
|
|
82
|
+
parsedResult = JSON.parse(lastLine);
|
|
83
|
+
} catch (e) {
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
return {
|
|
88
|
+
result: parsedResult,
|
|
89
|
+
stdout: result.stdout ?? void 0,
|
|
90
|
+
stderr: result.stderr ?? void 0,
|
|
91
|
+
statusCode: result.statusCode ?? -1
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
async install(options) {
|
|
95
|
+
let command;
|
|
96
|
+
const prefixDep = (dep) => {
|
|
97
|
+
if (dep.startsWith("npm:") || dep.startsWith("jsr:")) {
|
|
98
|
+
return dep;
|
|
99
|
+
}
|
|
100
|
+
return `npm:${dep}`;
|
|
101
|
+
};
|
|
102
|
+
if (options?.global) {
|
|
103
|
+
const deps = options.deps.map(prefixDep);
|
|
104
|
+
command = `/opt/deno/bin/deno install --global --allow-all ${deps.join(" ")}`;
|
|
105
|
+
} else {
|
|
106
|
+
const cdPrefix = options?.directory ? `cd ${options.directory} && ` : "";
|
|
107
|
+
if (!options?.deps) {
|
|
108
|
+
command = `${cdPrefix}/opt/deno/bin/deno install`;
|
|
109
|
+
} else {
|
|
110
|
+
const deps = Array.isArray(options.deps) ? options.deps.map(prefixDep) : Object.entries(options.deps).map(([pkg, ver]) => `${prefixDep(pkg)}@${ver}`);
|
|
111
|
+
const devFlag = options.dev ? " --dev" : "";
|
|
112
|
+
command = `${cdPrefix}/opt/deno/bin/deno add${devFlag} ${deps.join(" ")}`;
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
const result = await this.vm.exec({ command });
|
|
116
|
+
return {
|
|
117
|
+
success: result.statusCode === 0,
|
|
118
|
+
stdout: result.stdout ?? void 0,
|
|
119
|
+
stderr: result.stderr ?? void 0
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
export { VmDeno };
|
package/package.json
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@freestyle-sh/with-deno",
|
|
3
|
+
"version": "0.0.1",
|
|
4
|
+
"private": false,
|
|
5
|
+
"dependencies": {
|
|
6
|
+
"freestyle-sandboxes": "^0.1.14",
|
|
7
|
+
"@freestyle-sh/with-type-js": "0.2.8"
|
|
8
|
+
},
|
|
9
|
+
"devDependencies": {
|
|
10
|
+
"@types/node": "^22.0.0",
|
|
11
|
+
"pkgroll": "^2.11.2",
|
|
12
|
+
"typescript": "^5.8.3"
|
|
13
|
+
},
|
|
14
|
+
"type": "module",
|
|
15
|
+
"main": "./dist/index.js",
|
|
16
|
+
"types": "./dist/index.d.ts",
|
|
17
|
+
"exports": {
|
|
18
|
+
".": {
|
|
19
|
+
"types": "./dist/index.d.ts",
|
|
20
|
+
"import": "./dist/index.js"
|
|
21
|
+
}
|
|
22
|
+
},
|
|
23
|
+
"source": "./src/index.ts",
|
|
24
|
+
"files": [
|
|
25
|
+
"dist"
|
|
26
|
+
],
|
|
27
|
+
"scripts": {
|
|
28
|
+
"build": "pkgroll"
|
|
29
|
+
}
|
|
30
|
+
}
|