@freestyle-sh/with-nodejs 0.2.0 → 0.2.2

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 ADDED
@@ -0,0 +1,98 @@
1
+ # @freestyle-sh/with-nodejs
2
+
3
+ Node.js runtime via [NVM](https://github.com/nvm-sh/nvm) for [Freestyle](https://freestyle.sh) VMs.
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ npm install @freestyle-sh/with-nodejs freestyle-sandboxes
9
+ ```
10
+
11
+ ## Usage
12
+
13
+ ```typescript
14
+ import { freestyle } from "freestyle-sandboxes";
15
+ import { VmNodeJs } from "@freestyle-sh/with-nodejs";
16
+
17
+ const { vm } = await freestyle.vms.create({
18
+ with: {
19
+ node: new VmNodeJs(),
20
+ },
21
+ });
22
+
23
+ const res = await vm.node.runCode({
24
+ code: "console.log(JSON.stringify({ hello: 'world' }));"
25
+ });
26
+
27
+ console.log(res);
28
+ // { result: { hello: 'world' }, stdout: '{"hello":"world"}\n', statusCode: 0 }
29
+ ```
30
+
31
+ ## Options
32
+
33
+ ```typescript
34
+ new VmNodeJs({
35
+ version: "22", // Optional: Node.js version (default: "24")
36
+ })
37
+ ```
38
+
39
+ | Option | Type | Default | Description |
40
+ |--------|------|---------|-------------|
41
+ | `version` | `string` | `"24"` | Node.js version to install via NVM. |
42
+
43
+ ## API
44
+
45
+ ### `vm.node.runCode({ code: string })`
46
+
47
+ Executes JavaScript code in the Node.js runtime.
48
+
49
+ **Returns:** `Promise<RunCodeResponse>`
50
+
51
+ ```typescript
52
+ type RunCodeResponse<Result> = {
53
+ result: Result; // Parsed JSON from stdout (if valid JSON)
54
+ stdout?: string; // Raw stdout output
55
+ stderr?: string; // Raw stderr output
56
+ statusCode?: number; // Exit code
57
+ };
58
+ ```
59
+
60
+ ### `vm.node.install(options?)`
61
+
62
+ Installs npm packages.
63
+
64
+ ```typescript
65
+ // Install from package.json in current directory
66
+ await vm.node.install();
67
+
68
+ // Install from package.json in specific directory
69
+ await vm.node.install({ directory: "/app" });
70
+
71
+ // Install specific packages
72
+ await vm.node.install({ deps: ["lodash", "express"] });
73
+
74
+ // Install with specific versions
75
+ await vm.node.install({ deps: { "lodash": "^4.0.0", "express": "~5.0.0" } });
76
+
77
+ // Install as dev dependencies
78
+ await vm.node.install({ deps: ["typescript"], dev: true });
79
+
80
+ // Install globally
81
+ await vm.node.install({ global: true, deps: ["typescript"] });
82
+ ```
83
+
84
+ **Returns:** `Promise<InstallResult>`
85
+
86
+ ```typescript
87
+ type InstallResult = {
88
+ success: boolean;
89
+ stdout?: string;
90
+ stderr?: string;
91
+ };
92
+ ```
93
+
94
+ ## Documentation
95
+
96
+ - [Freestyle Documentation](https://docs.freestyle.sh)
97
+ - [Node.js Documentation](https://nodejs.org/docs)
98
+ - [NVM Documentation](https://github.com/nvm-sh/nvm)
package/dist/index.d.ts CHANGED
@@ -1,5 +1,5 @@
1
- import { VmWith, VmWithInstance, CreateVmOptions } from 'freestyle-sandboxes';
2
- import { VmJavaScriptRuntimeInstance, JSONValue, RunCodeResponse, VmJavaScriptRuntime } from '@freestyle-sh/with-type-js';
1
+ import { VmWith, VmWithInstance, VmSpec } from 'freestyle-sandboxes';
2
+ import { VmJavaScriptRuntimeInstance, JSONValue, RunCodeResponse, InstallOptions, InstallResult, VmJavaScriptRuntime } from '@freestyle-sh/with-type-js';
3
3
 
4
4
  type NodeJsOptions = {
5
5
  version?: string;
@@ -12,16 +12,20 @@ type NodeJsResolvedOptions = {
12
12
  declare class VmNodeJs extends VmWith<NodeJsRuntimeInstance> implements VmJavaScriptRuntime<VmJavaScriptRuntimeInstance> {
13
13
  options: NodeJsResolvedOptions;
14
14
  constructor(options?: NodeJsOptions);
15
- configure(existingConfig: CreateVmOptions): CreateVmOptions | Promise<CreateVmOptions>;
15
+ configureSnapshotSpec(spec: VmSpec): VmSpec;
16
16
  createInstance(): NodeJsRuntimeInstance;
17
17
  installServiceName(): string;
18
18
  }
19
19
  declare class NodeJsRuntimeInstance extends VmWithInstance implements VmJavaScriptRuntimeInstance {
20
20
  builder: VmNodeJs;
21
21
  constructor(builder: VmNodeJs);
22
- runCode<Result extends JSONValue = any>({ code, }: {
22
+ runCode<Result extends JSONValue = any>(args: string | {
23
23
  code: string;
24
+ argv?: string[];
25
+ env?: Record<string, string>;
26
+ workdir?: string;
24
27
  }): Promise<RunCodeResponse<Result>>;
28
+ install(options?: InstallOptions): Promise<InstallResult>;
25
29
  }
26
30
 
27
31
  export { VmNodeJs };
package/dist/index.js CHANGED
@@ -1,4 +1,4 @@
1
- import { VmWith, VmTemplate, VmWithInstance } from 'freestyle-sandboxes';
1
+ import { VmWith, VmSpec, VmWithInstance } from 'freestyle-sandboxes';
2
2
 
3
3
  class VmNodeJs extends VmWith {
4
4
  options;
@@ -9,7 +9,7 @@ class VmNodeJs extends VmWith {
9
9
  workdir: options?.workdir
10
10
  };
11
11
  }
12
- configure(existingConfig) {
12
+ configureSnapshotSpec(spec) {
13
13
  const installScript = `#!/bin/bash
14
14
  set -e
15
15
  export NVM_DIR="/opt/nvm"
@@ -25,8 +25,9 @@ npm -v
25
25
  [ -s "$NVM_DIR/nvm.sh" ] && . "$NVM_DIR/nvm.sh"
26
26
  [ -s "$NVM_DIR/bash_completion" ] && . "$NVM_DIR/bash_completion"
27
27
  `;
28
- const nodeJsConfig = {
29
- template: new VmTemplate({
28
+ return this.composeSpecs(
29
+ spec,
30
+ new VmSpec({
30
31
  additionalFiles: {
31
32
  "/opt/install-nodejs.sh": {
32
33
  content: installScript
@@ -47,8 +48,7 @@ npm -v
47
48
  ]
48
49
  }
49
50
  })
50
- };
51
- return this.compose(existingConfig, nodeJsConfig);
51
+ );
52
52
  }
53
53
  createInstance() {
54
54
  return new NodeJsRuntimeInstance(this);
@@ -63,19 +63,28 @@ class NodeJsRuntimeInstance extends VmWithInstance {
63
63
  super();
64
64
  this.builder = builder;
65
65
  }
66
- async runCode({
67
- code
68
- }) {
66
+ async runCode(args) {
67
+ const options = typeof args === "string" ? { code: args } : args;
68
+ const { code, argv, env, workdir } = options;
69
+ const shellEscape = (value) => `'${value.replace(/'/g, "'\\''")}'`;
70
+ const argvArgs = argv?.map(shellEscape).join(" ");
71
+ const envPrefix = env ? `${Object.entries(env).map(([key, value]) => `${key}=${shellEscape(value)}`).join(" ")} ` : "";
72
+ const cdPrefix = workdir ? `cd ${shellEscape(workdir)} && ` : "";
73
+ const command = `${cdPrefix}${envPrefix}node -e "${code.replace(/"/g, '\\"')}"${argvArgs ? ` -- ${argvArgs}` : ""}`;
69
74
  const result = await this.vm.exec({
70
- command: `node -e "${code.replace(/"/g, '\\"')}"`
75
+ command
71
76
  });
72
77
  let parsedResult = void 0;
73
78
  if (result.stdout) {
74
- try {
75
- parsedResult = JSON.parse(result.stdout);
76
- } catch (e) {
77
- if (result.stderr) {
78
- `Failed to parse JSON output. Stderr: ${result.stderr}`;
79
+ const lines = result.stdout.split(/\r?\n/).map((line) => line.trim()).filter((line) => line.length > 0);
80
+ const lastLine = lines[lines.length - 1];
81
+ if (lastLine) {
82
+ try {
83
+ parsedResult = JSON.parse(lastLine);
84
+ } catch (e) {
85
+ if (result.stderr) {
86
+ `Failed to parse JSON output. Stderr: ${result.stderr}`;
87
+ }
79
88
  }
80
89
  }
81
90
  }
@@ -86,6 +95,27 @@ class NodeJsRuntimeInstance extends VmWithInstance {
86
95
  statusCode: result.statusCode ?? -1
87
96
  };
88
97
  }
98
+ async install(options) {
99
+ let command;
100
+ if (options?.global) {
101
+ command = `npm install -g ${options.deps.join(" ")}`;
102
+ } else {
103
+ const cdPrefix = options?.directory ? `cd ${options.directory} && ` : "";
104
+ if (!options?.deps) {
105
+ command = `${cdPrefix}npm install`;
106
+ } else {
107
+ const deps = Array.isArray(options.deps) ? options.deps : Object.entries(options.deps).map(([pkg, ver]) => `${pkg}@${ver}`);
108
+ const devFlag = options.dev ? " --save-dev" : "";
109
+ command = `${cdPrefix}npm install${devFlag} ${deps.join(" ")}`;
110
+ }
111
+ }
112
+ const result = await this.vm.exec({ command });
113
+ return {
114
+ success: result.statusCode === 0,
115
+ stdout: result.stdout ?? void 0,
116
+ stderr: result.stderr ?? void 0
117
+ };
118
+ }
89
119
  }
90
120
 
91
121
  export { VmNodeJs };
package/package.json CHANGED
@@ -1,10 +1,10 @@
1
1
  {
2
2
  "name": "@freestyle-sh/with-nodejs",
3
- "version": "0.2.0",
3
+ "version": "0.2.2",
4
4
  "private": false,
5
5
  "dependencies": {
6
- "freestyle-sandboxes": "^0.1.2",
7
- "@freestyle-sh/with-type-js": "^0.2.0"
6
+ "freestyle-sandboxes": "^0.1.8",
7
+ "@freestyle-sh/with-type-js": "^0.2.2"
8
8
  },
9
9
  "type": "module",
10
10
  "main": "./dist/index.js",