@freestyle-sh/with-nodejs 0.2.7 → 0.2.9

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 CHANGED
@@ -91,6 +91,83 @@ type InstallResult = {
91
91
  };
92
92
  ```
93
93
 
94
+ ## Workspaces and Tasks
95
+
96
+ Use the Node.js builder to attach a workspace and run an npm script as a managed systemd service.
97
+
98
+ ```typescript
99
+ import { freestyle, VmSpec } from "freestyle-sandboxes";
100
+ import { VmNodeJs } from "@freestyle-sh/with-nodejs";
101
+
102
+ const SOURCE_REPO = "https://github.com/freestyle-sh/freestyle-next";
103
+
104
+ const node = new VmNodeJs();
105
+ const workspace = node.workspace({ path: "/root/app", install: true });
106
+ const appTask = workspace.task("dev", {
107
+ env: {
108
+ HOST: "0.0.0.0",
109
+ PORT: "3000",
110
+ },
111
+ });
112
+
113
+ const spec = new VmSpec()
114
+ .with("node", node)
115
+ .repo(SOURCE_REPO, "/root/app")
116
+ .with("workspace", workspace)
117
+ .with("app", appTask)
118
+ .snapshot()
119
+ .waitFor("curl http://localhost:3000")
120
+ .snapshot();
121
+
122
+ const { repoId } = await freestyle.git.repos.create({
123
+ source: {
124
+ url: SOURCE_REPO,
125
+ },
126
+ });
127
+
128
+ const domain = `${repoId}.style.dev`;
129
+
130
+ const { vm } = await freestyle.vms.create({
131
+ spec,
132
+ domains: [{ domain, vmPort: 3000 }],
133
+ git: {
134
+ repos: [{ repo: repoId, path: "/root/app" }],
135
+ },
136
+ });
137
+
138
+ console.log(await vm.app.logs());
139
+ ```
140
+
141
+ ### Workspace API
142
+
143
+ ```typescript
144
+ const workspace = node.workspace({
145
+ path: "/root/app",
146
+ install: true,
147
+ });
148
+ ```
149
+
150
+ - `path`: Working directory for `npm install` and task execution.
151
+ - `install`: When true, runs `npm install` in the workspace during VM startup.
152
+
153
+ ### Task API
154
+
155
+ ```typescript
156
+ const task = workspace.task("dev", {
157
+ env: {
158
+ HOST: "0.0.0.0",
159
+ PORT: "3000",
160
+ },
161
+ serviceName: "my-node-app",
162
+ });
163
+ ```
164
+
165
+ - `name`: Script name from `package.json`.
166
+ - `env`: Optional environment variables for the task service.
167
+ - `serviceName`: Optional explicit systemd service name.
168
+
169
+ When added to the spec with `.with("app", task)`, you can access task logs with `vm.app.logs()`.
170
+
94
171
  ## Documentation
95
172
 
96
173
  - [Freestyle Documentation](https://docs.freestyle.sh)
package/dist/index.d.ts CHANGED
@@ -11,11 +11,55 @@ type NodeJsResolvedOptions = {
11
11
  };
12
12
  declare class VmNodeJs extends VmWith<NodeJsRuntimeInstance> implements VmJavaScriptRuntime<VmJavaScriptRuntimeInstance> {
13
13
  options: NodeJsResolvedOptions;
14
+ workspaces: NodeJsWorkspace[];
14
15
  constructor(options?: NodeJsOptions);
15
16
  configureSnapshotSpec(spec: VmSpec): VmSpec;
16
17
  createInstance(): NodeJsRuntimeInstance;
18
+ workspace(options: {
19
+ path: string;
20
+ install?: boolean;
21
+ }): NodeJsWorkspace;
17
22
  installServiceName(): string;
18
23
  }
24
+ declare class NodeJsWorkspace extends VmWith<NodeJsWorkspaceInstance> {
25
+ options: {
26
+ path: string;
27
+ install?: boolean;
28
+ };
29
+ env?: Record<string, string>;
30
+ constructor(options: {
31
+ path: string;
32
+ install?: boolean;
33
+ }, env?: Record<string, string>);
34
+ task(name: string, options?: {
35
+ env?: Record<string, string>;
36
+ serviceName?: string;
37
+ }): NodeJsWorkspaceTask;
38
+ getInstallServiceName(): string;
39
+ configureSpec(spec: VmSpec): VmSpec;
40
+ createInstance(): NodeJsWorkspaceInstance;
41
+ }
42
+ declare class NodeJsWorkspaceInstance extends VmWithInstance {
43
+ }
44
+ declare class NodeJsWorkspaceTask extends VmWith<NodeJsWorkspaceTaskInstance> {
45
+ name: string;
46
+ workspace: NodeJsWorkspace;
47
+ env?: Record<string, string>;
48
+ serviceName?: string;
49
+ constructor(name: string, workspace: NodeJsWorkspace, env?: Record<string, string>, serviceName?: string);
50
+ getServiceName(): string;
51
+ configureSpec(spec: VmSpec): VmSpec;
52
+ createInstance(): NodeJsWorkspaceTaskInstance;
53
+ }
54
+ declare class NodeJsWorkspaceTaskInstance extends VmWithInstance {
55
+ name: string;
56
+ workspace: NodeJsWorkspace;
57
+ env?: Record<string, string>;
58
+ serviceName?: string;
59
+ constructor(name: string, workspace: NodeJsWorkspace, env?: Record<string, string>, serviceName?: string);
60
+ getServiceName(): string;
61
+ logs(): Promise<string[] | undefined>;
62
+ }
19
63
  declare class NodeJsRuntimeInstance extends VmWithInstance implements VmJavaScriptRuntimeInstance {
20
64
  builder: VmNodeJs;
21
65
  constructor(builder: VmNodeJs);
@@ -28,4 +72,4 @@ declare class NodeJsRuntimeInstance extends VmWithInstance implements VmJavaScri
28
72
  install(options?: InstallOptions): Promise<InstallResult>;
29
73
  }
30
74
 
31
- export { VmNodeJs };
75
+ export { NodeJsWorkspace, NodeJsWorkspaceInstance, NodeJsWorkspaceTask, NodeJsWorkspaceTaskInstance, VmNodeJs };
package/dist/index.js CHANGED
@@ -2,6 +2,7 @@ import { VmWith, VmSpec, VmWithInstance } from 'freestyle-sandboxes';
2
2
 
3
3
  class VmNodeJs extends VmWith {
4
4
  options;
5
+ workspaces = [];
5
6
  constructor(options) {
6
7
  super();
7
8
  this.options = {
@@ -53,10 +54,140 @@ npm -v
53
54
  createInstance() {
54
55
  return new NodeJsRuntimeInstance(this);
55
56
  }
57
+ workspace(options) {
58
+ const workspace = new NodeJsWorkspace(options);
59
+ this.workspaces.push(workspace);
60
+ return workspace;
61
+ }
56
62
  installServiceName() {
57
63
  return `install-nodejs.service`;
58
64
  }
59
65
  }
66
+ class NodeJsWorkspace extends VmWith {
67
+ options;
68
+ env;
69
+ constructor(options, env) {
70
+ super();
71
+ this.options = options;
72
+ this.env = env;
73
+ }
74
+ task(name, options) {
75
+ return new NodeJsWorkspaceTask(
76
+ name,
77
+ this,
78
+ {
79
+ ...this.env,
80
+ ...options?.env
81
+ },
82
+ options?.serviceName
83
+ );
84
+ }
85
+ getInstallServiceName() {
86
+ return `nodejs-install-${this.options.path.replace(/\//g, "-")}`;
87
+ }
88
+ configureSpec(spec) {
89
+ if (this.options.install) {
90
+ return this.composeSpecs(
91
+ spec,
92
+ new VmSpec({
93
+ systemd: {
94
+ services: [
95
+ {
96
+ name: this.getInstallServiceName(),
97
+ mode: "oneshot",
98
+ exec: ['bash -lc "source /opt/nvm/nvm.sh && npm install"'],
99
+ workdir: this.options.path,
100
+ env: {
101
+ HOME: "/root",
102
+ NVM_DIR: "/opt/nvm",
103
+ ...this.env
104
+ },
105
+ user: "root"
106
+ }
107
+ ]
108
+ }
109
+ })
110
+ );
111
+ }
112
+ return spec;
113
+ }
114
+ createInstance() {
115
+ return new NodeJsWorkspaceInstance();
116
+ }
117
+ }
118
+ class NodeJsWorkspaceInstance extends VmWithInstance {
119
+ }
120
+ class NodeJsWorkspaceTask extends VmWith {
121
+ name;
122
+ workspace;
123
+ env;
124
+ serviceName;
125
+ constructor(name, workspace, env, serviceName) {
126
+ super();
127
+ this.name = name;
128
+ this.workspace = workspace;
129
+ this.env = env;
130
+ this.serviceName = serviceName;
131
+ }
132
+ getServiceName() {
133
+ return this.serviceName ?? `nodejs-workspace-${this.workspace.options.path.replace(/\//g, "-")}-task-${this.name}`;
134
+ }
135
+ configureSpec(spec) {
136
+ return this.composeSpecs(
137
+ spec,
138
+ new VmSpec({
139
+ systemd: {
140
+ services: [
141
+ {
142
+ name: this.getServiceName(),
143
+ exec: [
144
+ `bash -lc "source /opt/nvm/nvm.sh && npm run ${this.name}"`
145
+ ],
146
+ workdir: this.workspace.options.path,
147
+ after: this.workspace.options.install ? [this.workspace.getInstallServiceName()] : void 0,
148
+ requires: this.workspace.options.install ? [this.workspace.getInstallServiceName()] : void 0,
149
+ env: {
150
+ HOME: "/root",
151
+ NVM_DIR: "/opt/nvm",
152
+ ...this.env
153
+ },
154
+ user: "root"
155
+ }
156
+ ]
157
+ }
158
+ })
159
+ );
160
+ }
161
+ createInstance() {
162
+ return new NodeJsWorkspaceTaskInstance(
163
+ this.name,
164
+ this.workspace,
165
+ this.env,
166
+ this.serviceName
167
+ );
168
+ }
169
+ }
170
+ class NodeJsWorkspaceTaskInstance extends VmWithInstance {
171
+ name;
172
+ workspace;
173
+ env;
174
+ serviceName;
175
+ constructor(name, workspace, env, serviceName) {
176
+ super();
177
+ this.name = name;
178
+ this.workspace = workspace;
179
+ this.env = env;
180
+ this.serviceName = serviceName;
181
+ }
182
+ getServiceName() {
183
+ return this.serviceName ?? `nodejs-workspace-${this.workspace.options.path.replace(/\//g, "-")}-task-${this.name}`;
184
+ }
185
+ logs() {
186
+ return this.vm.exec({
187
+ command: `journalctl -u ${this.getServiceName()} --no-pager -n 30`
188
+ }).then((result) => result.stdout?.trim().split("\n"));
189
+ }
190
+ }
60
191
  class NodeJsRuntimeInstance extends VmWithInstance {
61
192
  builder;
62
193
  constructor(builder) {
@@ -118,4 +249,4 @@ class NodeJsRuntimeInstance extends VmWithInstance {
118
249
  }
119
250
  }
120
251
 
121
- export { VmNodeJs };
252
+ export { NodeJsWorkspace, NodeJsWorkspaceInstance, NodeJsWorkspaceTask, NodeJsWorkspaceTaskInstance, VmNodeJs };
package/package.json CHANGED
@@ -1,10 +1,10 @@
1
1
  {
2
2
  "name": "@freestyle-sh/with-nodejs",
3
- "version": "0.2.7",
3
+ "version": "0.2.9",
4
4
  "private": false,
5
5
  "dependencies": {
6
- "freestyle-sandboxes": "^0.1.28",
7
- "@freestyle-sh/with-type-js": "^0.2.7"
6
+ "freestyle-sandboxes": "^0.1.41",
7
+ "@freestyle-sh/with-type-js": "^0.2.9"
8
8
  },
9
9
  "type": "module",
10
10
  "main": "./dist/index.js",