@freestyle-sh/with-nodejs
Advanced tools
+45
-1
@@ -14,7 +14,51 @@ import { VmWith, VmWithInstance, VmSpec } from 'freestyle-sandboxes'; | ||
| options: NodeJsResolvedOptions; | ||
| workspaces: NodeJsWorkspace[]; | ||
| constructor(options?: NodeJsOptions); | ||
| configureSnapshotSpec(spec: VmSpec): VmSpec; | ||
| createInstance(): NodeJsRuntimeInstance; | ||
| workspace(options: { | ||
| path: string; | ||
| install?: boolean; | ||
| }): NodeJsWorkspace; | ||
| installServiceName(): string; | ||
| } | ||
| declare class NodeJsWorkspace extends VmWith<NodeJsWorkspaceInstance> { | ||
| options: { | ||
| path: string; | ||
| install?: boolean; | ||
| }; | ||
| env?: Record<string, string>; | ||
| constructor(options: { | ||
| path: string; | ||
| install?: boolean; | ||
| }, env?: Record<string, string>); | ||
| task(name: string, options?: { | ||
| env?: Record<string, string>; | ||
| serviceName?: string; | ||
| }): NodeJsWorkspaceTask; | ||
| getInstallServiceName(): string; | ||
| configureSpec(spec: VmSpec): VmSpec; | ||
| createInstance(): NodeJsWorkspaceInstance; | ||
| } | ||
| declare class NodeJsWorkspaceInstance extends VmWithInstance { | ||
| } | ||
| declare class NodeJsWorkspaceTask extends VmWith<NodeJsWorkspaceTaskInstance> { | ||
| name: string; | ||
| workspace: NodeJsWorkspace; | ||
| env?: Record<string, string>; | ||
| serviceName?: string; | ||
| constructor(name: string, workspace: NodeJsWorkspace, env?: Record<string, string>, serviceName?: string); | ||
| getServiceName(): string; | ||
| configureSpec(spec: VmSpec): VmSpec; | ||
| createInstance(): NodeJsWorkspaceTaskInstance; | ||
| } | ||
| declare class NodeJsWorkspaceTaskInstance extends VmWithInstance { | ||
| name: string; | ||
| workspace: NodeJsWorkspace; | ||
| env?: Record<string, string>; | ||
| serviceName?: string; | ||
| constructor(name: string, workspace: NodeJsWorkspace, env?: Record<string, string>, serviceName?: string); | ||
| getServiceName(): string; | ||
| logs(): Promise<string[] | undefined>; | ||
| } | ||
| declare class NodeJsRuntimeInstance extends VmWithInstance implements VmJavaScriptRuntimeInstance { | ||
@@ -32,2 +76,2 @@ builder: VmNodeJs; | ||
| export { VmNodeJs }; | ||
| export { NodeJsWorkspace, NodeJsWorkspaceInstance, NodeJsWorkspaceTask, NodeJsWorkspaceTaskInstance, VmNodeJs }; |
+132
-1
@@ -5,2 +5,3 @@ import { VmWith, VmSpec, VmWithInstance } from 'freestyle-sandboxes'; | ||
| options; | ||
| workspaces = []; | ||
| constructor(options) { | ||
@@ -57,2 +58,7 @@ super(); | ||
| } | ||
| workspace(options) { | ||
| const workspace = new NodeJsWorkspace(options); | ||
| this.workspaces.push(workspace); | ||
| return workspace; | ||
| } | ||
| installServiceName() { | ||
@@ -62,2 +68,127 @@ return `install-nodejs.service`; | ||
| } | ||
| class NodeJsWorkspace extends VmWith { | ||
| options; | ||
| env; | ||
| constructor(options, env) { | ||
| super(); | ||
| this.options = options; | ||
| this.env = env; | ||
| } | ||
| task(name, options) { | ||
| return new NodeJsWorkspaceTask( | ||
| name, | ||
| this, | ||
| { | ||
| ...this.env, | ||
| ...options?.env | ||
| }, | ||
| options?.serviceName | ||
| ); | ||
| } | ||
| getInstallServiceName() { | ||
| return `nodejs-install-${this.options.path.replace(/\//g, "-")}`; | ||
| } | ||
| configureSpec(spec) { | ||
| if (this.options.install) { | ||
| return this.composeSpecs( | ||
| spec, | ||
| new VmSpec({ | ||
| systemd: { | ||
| services: [ | ||
| { | ||
| name: this.getInstallServiceName(), | ||
| mode: "oneshot", | ||
| exec: ['bash -lc "source /opt/nvm/nvm.sh && npm install"'], | ||
| workdir: this.options.path, | ||
| env: { | ||
| HOME: "/root", | ||
| NVM_DIR: "/opt/nvm", | ||
| ...this.env | ||
| }, | ||
| user: "root" | ||
| } | ||
| ] | ||
| } | ||
| }) | ||
| ); | ||
| } | ||
| return spec; | ||
| } | ||
| createInstance() { | ||
| return new NodeJsWorkspaceInstance(); | ||
| } | ||
| } | ||
| class NodeJsWorkspaceInstance extends VmWithInstance { | ||
| } | ||
| class NodeJsWorkspaceTask extends VmWith { | ||
| name; | ||
| workspace; | ||
| env; | ||
| serviceName; | ||
| constructor(name, workspace, env, serviceName) { | ||
| super(); | ||
| this.name = name; | ||
| this.workspace = workspace; | ||
| this.env = env; | ||
| this.serviceName = serviceName; | ||
| } | ||
| getServiceName() { | ||
| return this.serviceName ?? `nodejs-workspace-${this.workspace.options.path.replace(/\//g, "-")}-task-${this.name}`; | ||
| } | ||
| configureSpec(spec) { | ||
| return this.composeSpecs( | ||
| spec, | ||
| new VmSpec({ | ||
| systemd: { | ||
| services: [ | ||
| { | ||
| name: this.getServiceName(), | ||
| exec: [ | ||
| `bash -lc "source /opt/nvm/nvm.sh && npm run ${this.name}"` | ||
| ], | ||
| workdir: this.workspace.options.path, | ||
| after: this.workspace.options.install ? [this.workspace.getInstallServiceName()] : void 0, | ||
| requires: this.workspace.options.install ? [this.workspace.getInstallServiceName()] : void 0, | ||
| env: { | ||
| HOME: "/root", | ||
| NVM_DIR: "/opt/nvm", | ||
| ...this.env | ||
| }, | ||
| user: "root" | ||
| } | ||
| ] | ||
| } | ||
| }) | ||
| ); | ||
| } | ||
| createInstance() { | ||
| return new NodeJsWorkspaceTaskInstance( | ||
| this.name, | ||
| this.workspace, | ||
| this.env, | ||
| this.serviceName | ||
| ); | ||
| } | ||
| } | ||
| class NodeJsWorkspaceTaskInstance extends VmWithInstance { | ||
| name; | ||
| workspace; | ||
| env; | ||
| serviceName; | ||
| constructor(name, workspace, env, serviceName) { | ||
| super(); | ||
| this.name = name; | ||
| this.workspace = workspace; | ||
| this.env = env; | ||
| this.serviceName = serviceName; | ||
| } | ||
| getServiceName() { | ||
| return this.serviceName ?? `nodejs-workspace-${this.workspace.options.path.replace(/\//g, "-")}-task-${this.name}`; | ||
| } | ||
| logs() { | ||
| return this.vm.exec({ | ||
| command: `journalctl -u ${this.getServiceName()} --no-pager -n 30` | ||
| }).then((result) => result.stdout?.trim().split("\n")); | ||
| } | ||
| } | ||
| class NodeJsRuntimeInstance extends VmWithInstance { | ||
@@ -124,2 +255,2 @@ builder; | ||
| export { VmNodeJs }; | ||
| export { NodeJsWorkspace, NodeJsWorkspaceInstance, NodeJsWorkspaceTask, NodeJsWorkspaceTaskInstance, VmNodeJs }; |
+3
-3
| { | ||
| "name": "@freestyle-sh/with-nodejs", | ||
| "version": "0.2.8", | ||
| "version": "0.2.9", | ||
| "private": false, | ||
| "dependencies": { | ||
| "freestyle-sandboxes": "^0.1.28", | ||
| "@freestyle-sh/with-type-js": "^0.2.8" | ||
| "freestyle-sandboxes": "^0.1.41", | ||
| "@freestyle-sh/with-type-js": "^0.2.9" | ||
| }, | ||
@@ -9,0 +9,0 @@ "type": "module", |
+77
-0
@@ -94,2 +94,79 @@ # @freestyle-sh/with-nodejs | ||
| ## Workspaces and Tasks | ||
| Use the Node.js builder to attach a workspace and run an npm script as a managed systemd service. | ||
| ```typescript | ||
| import { freestyle, VmSpec } from "freestyle-sandboxes"; | ||
| import { VmNodeJs } from "@freestyle-sh/with-nodejs"; | ||
| const SOURCE_REPO = "https://github.com/freestyle-sh/freestyle-next"; | ||
| const node = new VmNodeJs(); | ||
| const workspace = node.workspace({ path: "/root/app", install: true }); | ||
| const appTask = workspace.task("dev", { | ||
| env: { | ||
| HOST: "0.0.0.0", | ||
| PORT: "3000", | ||
| }, | ||
| }); | ||
| const spec = new VmSpec() | ||
| .with("node", node) | ||
| .repo(SOURCE_REPO, "/root/app") | ||
| .with("workspace", workspace) | ||
| .with("app", appTask) | ||
| .snapshot() | ||
| .waitFor("curl http://localhost:3000") | ||
| .snapshot(); | ||
| const { repoId } = await freestyle.git.repos.create({ | ||
| source: { | ||
| url: SOURCE_REPO, | ||
| }, | ||
| }); | ||
| const domain = `${repoId}.style.dev`; | ||
| const { vm } = await freestyle.vms.create({ | ||
| spec, | ||
| domains: [{ domain, vmPort: 3000 }], | ||
| git: { | ||
| repos: [{ repo: repoId, path: "/root/app" }], | ||
| }, | ||
| }); | ||
| console.log(await vm.app.logs()); | ||
| ``` | ||
| ### Workspace API | ||
| ```typescript | ||
| const workspace = node.workspace({ | ||
| path: "/root/app", | ||
| install: true, | ||
| }); | ||
| ``` | ||
| - `path`: Working directory for `npm install` and task execution. | ||
| - `install`: When true, runs `npm install` in the workspace during VM startup. | ||
| ### Task API | ||
| ```typescript | ||
| const task = workspace.task("dev", { | ||
| env: { | ||
| HOST: "0.0.0.0", | ||
| PORT: "3000", | ||
| }, | ||
| serviceName: "my-node-app", | ||
| }); | ||
| ``` | ||
| - `name`: Script name from `package.json`. | ||
| - `env`: Optional environment variables for the task service. | ||
| - `serviceName`: Optional explicit systemd service name. | ||
| When added to the spec with `.with("app", task)`, you can access task logs with `vm.app.logs()`. | ||
| ## Documentation | ||
@@ -96,0 +173,0 @@ |
14324
89.95%323
118.24%176
77.78%