unitup 0.0.8 → 0.0.10

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/src/doctor.js CHANGED
@@ -9,6 +9,7 @@ import {
9
9
  checkNodeDiagnostics
10
10
  } from './systemd.js';
11
11
  import { getUserUnitDir } from './unit.js';
12
+ import { findRuntimeExecutable } from './runtimes/common.js';
12
13
 
13
14
  /**
14
15
  * Runs system diagnostic checks and returns structured data.
@@ -24,14 +25,54 @@ export async function getDoctorInfo() {
24
25
  const lingering = await checkUserLinger();
25
26
  const nodeDiag = await checkNodeDiagnostics();
26
27
 
28
+ const nodePath = nodeDiag.execPath || (await findRuntimeExecutable(['node']));
29
+ const pythonPath = await findRuntimeExecutable(['python3', 'python']);
30
+ const rubyPath = await findRuntimeExecutable(['ruby']);
31
+ const phpPath = await findRuntimeExecutable(['php']);
32
+ const bunPath = await findRuntimeExecutable(['bun']);
33
+ const denoPath = await findRuntimeExecutable(['deno']);
34
+ const goPath = await findRuntimeExecutable(['go']);
35
+ const elixirPath = await findRuntimeExecutable(['elixir']);
36
+
37
+ const runtimes = {
38
+ 'Node.js': nodePath,
39
+ 'Python': pythonPath,
40
+ 'Ruby': rubyPath,
41
+ 'PHP': phpPath,
42
+ 'Bun': bunPath,
43
+ 'Deno': denoPath,
44
+ 'Go': goPath,
45
+ 'Elixir': elixirPath
46
+ };
47
+
48
+ let cgroupV2 = false;
49
+ let memoryController = false;
50
+ try {
51
+ if (fs.existsSync('/sys/fs/cgroup/cgroup.controllers')) {
52
+ cgroupV2 = true;
53
+ const controllers = fs.readFileSync('/sys/fs/cgroup/cgroup.controllers', 'utf8');
54
+ memoryController = controllers.includes('memory');
55
+ }
56
+ } catch {
57
+ // ignore
58
+ }
59
+
60
+ const memoryMaxSupported = linux && (cgroupV2 || userSystemd);
61
+ const memorySwapMaxSupported = linux && cgroupV2 && memoryController;
62
+
27
63
  return {
28
64
  linux,
29
65
  systemctl,
30
66
  systemdRunning: pid1,
31
67
  userSystemdAvailable: userSystemd,
68
+ cgroupV2,
69
+ memoryController,
70
+ memoryMaxSupported,
71
+ memorySwapMaxSupported,
32
72
  nodeDiag,
33
- nodePath: nodeDiag.execPath || process.execPath,
73
+ nodePath: nodePath || process.execPath,
34
74
  nodeVersion: nodeDiag.version,
75
+ runtimes,
35
76
  unitDir: getUserUnitDir(),
36
77
  unitDirWritable,
37
78
  lingering,
@@ -71,24 +112,42 @@ export async function runDoctor() {
71
112
  console.log('! systemd user services unavailable');
72
113
  }
73
114
 
74
- const n = info.nodeDiag;
75
- if (n.found && n.executable) {
76
- console.log(`✓ Node.js: ${n.execPath}`);
77
- if (n.version) {
78
- console.log(`✓ Node version: ${n.version}`);
79
- }
80
- if (n.inPath) {
81
- console.log('✓ PATH includes node');
115
+ console.log('\nDetected runtimes:');
116
+ for (const [name, path] of Object.entries(info.runtimes)) {
117
+ if (path) {
118
+ console.log(`✓ ${name}: ${path}`);
82
119
  } else {
83
- console.log('! node is not in standard PATH');
120
+ console.log(`- ${name}: not found`);
84
121
  }
122
+ }
123
+
124
+ console.log('\nMemory Controller Diagnostics:');
125
+ if (info.cgroupV2) {
126
+ console.log('✓ cgroup v2 detected');
85
127
  } else {
86
- console.log(`✗ ${n.error || 'Node.js executable could not be resolved'}`);
87
- if (n.solution) {
88
- console.log(`\n${n.solution}\n`);
89
- }
128
+ console.log('- cgroup v2 unavailable');
90
129
  }
91
130
 
131
+ if (info.memoryController) {
132
+ console.log('✓ Memory controller available');
133
+ } else {
134
+ console.log('- Memory controller unavailable');
135
+ }
136
+
137
+ if (info.memoryMaxSupported) {
138
+ console.log('✓ MemoryMax supported');
139
+ } else {
140
+ console.log('- MemoryMax unavailable');
141
+ }
142
+
143
+ if (info.memorySwapMaxSupported) {
144
+ console.log('✓ MemorySwapMax supported');
145
+ } else {
146
+ console.log('- MemorySwapMax unavailable');
147
+ }
148
+
149
+ console.log('');
150
+
92
151
  if (info.unitDirWritable) {
93
152
  console.log('✓ Unit directory writable');
94
153
  } else {
package/src/index.js CHANGED
@@ -7,7 +7,12 @@ import {
7
7
  getServiceStatus,
8
8
  getServiceStatusRaw,
9
9
  listServices,
10
+ inspectService,
11
+ getServiceFailures,
12
+ getServicesByGroup,
10
13
  runJournalctlLogs,
14
+ setServiceLimits,
15
+ executeJournalctlMaintenance,
11
16
  isLinux,
12
17
  isSystemctlAvailable,
13
18
  isSystemdPID1,
@@ -28,8 +33,15 @@ import {
28
33
  import {
29
34
  sanitizeServiceName,
30
35
  getUnitFilename,
31
- getServiceNameFromUnit
36
+ getServiceNameFromUnit,
37
+ readAppMetadata,
38
+ getAppMetadataPath,
39
+ getAppsDir,
40
+ getUnitupDir,
41
+ validateMemorySize,
42
+ formatMemoryBytes
32
43
  } from './utils.js';
44
+ import { detectRuntime, resolveRuntimeConfig } from './runtimes/index.js';
33
45
 
34
46
  export {
35
47
  // Main programmatic service management API
@@ -42,8 +54,27 @@ export {
42
54
  getServiceStatus,
43
55
  getServiceStatusRaw,
44
56
  listServices,
57
+ inspectService,
58
+ getServiceFailures,
59
+ getServicesByGroup,
60
+ setServiceLimits,
61
+ executeJournalctlMaintenance as executeJournalctlMaintenance,
45
62
  runJournalctlLogs as getServiceLogs,
46
63
 
64
+ // Memory & Validation helpers
65
+ validateMemorySize,
66
+ formatMemoryBytes,
67
+
68
+ // Runtime detection & resolution
69
+ detectRuntime,
70
+ resolveRuntimeConfig,
71
+
72
+ // Metadata helpers
73
+ readAppMetadata,
74
+ getAppMetadataPath,
75
+ getAppsDir,
76
+ getUnitupDir,
77
+
47
78
  // System & Doctor checks
48
79
  isSystemctlAvailable as isSystemdAvailable,
49
80
  isSystemctlAvailable,
@@ -0,0 +1,46 @@
1
+ import path from 'node:path';
2
+ import fs from 'node:fs';
3
+ import { findRuntimeExecutable, getBinaryVersion } from './common.js';
4
+
5
+ export async function createBunAdapter(opts = {}) {
6
+ const customPath = opts.command;
7
+ let execPath = null;
8
+
9
+ if (customPath) {
10
+ const absPath = path.resolve(process.cwd(), customPath);
11
+ if (fs.existsSync(absPath)) {
12
+ try {
13
+ fs.accessSync(absPath, fs.constants.X_OK);
14
+ execPath = absPath;
15
+ } catch {}
16
+ }
17
+ }
18
+
19
+ if (!execPath) {
20
+ execPath = await findRuntimeExecutable(['bun']);
21
+ }
22
+
23
+ if (!execPath) {
24
+ throw new Error(
25
+ 'Bun runtime could not be found.\n\nInstall Bun or specify its path:\n unitup add server.ts --command /usr/bin/bun'
26
+ );
27
+ }
28
+
29
+ const version = await getBinaryVersion(execPath, '-v');
30
+ const scriptPath = opts.script ? path.resolve(process.cwd(), opts.script) : null;
31
+ const runtimeArgs = Array.isArray(opts.runtimeArgs) ? opts.runtimeArgs : [];
32
+ const scriptArgs = Array.isArray(opts.args) ? opts.args : [];
33
+
34
+ const args = [...runtimeArgs];
35
+ if (scriptPath) {
36
+ args.push(scriptPath);
37
+ }
38
+ args.push(...scriptArgs);
39
+
40
+ return {
41
+ command: execPath,
42
+ args,
43
+ runtime: 'bun',
44
+ version
45
+ };
46
+ }
@@ -0,0 +1,73 @@
1
+ import path from 'node:path';
2
+ import fs from 'node:fs';
3
+ import { runCommand } from '../systemd.js';
4
+
5
+ export function findExecutableInPath(binaryName) {
6
+ // 1. Check standard PATH env
7
+ const envPath = process.env.PATH || '';
8
+ const dirs = envPath.split(path.delimiter).filter(Boolean);
9
+
10
+ for (const dir of dirs) {
11
+ const full = path.join(dir, binaryName);
12
+ try {
13
+ if (fs.existsSync(full)) {
14
+ fs.accessSync(full, fs.constants.X_OK);
15
+ return full;
16
+ }
17
+ } catch {
18
+ // ignore
19
+ }
20
+ }
21
+ return null;
22
+ }
23
+
24
+ export async function findRuntimeExecutable(binaryNames) {
25
+ const names = Array.isArray(binaryNames) ? binaryNames : [binaryNames];
26
+
27
+ for (const name of names) {
28
+ if (path.isAbsolute(name)) {
29
+ if (fs.existsSync(name)) {
30
+ try {
31
+ fs.accessSync(name, fs.constants.X_OK);
32
+ return name;
33
+ } catch {
34
+ // ignore
35
+ }
36
+ }
37
+ continue;
38
+ }
39
+
40
+ // Try finding in system PATH directly
41
+ const foundInPath = findExecutableInPath(name);
42
+ if (foundInPath) return foundInPath;
43
+
44
+ // Fallback to which/where system call
45
+ try {
46
+ const whichCmd = process.platform === 'win32' ? 'where' : 'which';
47
+ const res = await runCommand(whichCmd, [name]);
48
+ if (res.code === 0 && res.stdout.trim()) {
49
+ const p = res.stdout.trim().split('\n')[0].trim();
50
+ if (fs.existsSync(p)) {
51
+ fs.accessSync(p, fs.constants.X_OK);
52
+ return p;
53
+ }
54
+ }
55
+ } catch {
56
+ // ignore
57
+ }
58
+ }
59
+
60
+ return null;
61
+ }
62
+
63
+ export async function getBinaryVersion(execPath, versionFlag = '-v') {
64
+ try {
65
+ const res = await runCommand(execPath, [versionFlag]);
66
+ if (res.code === 0 && res.stdout.trim()) {
67
+ return res.stdout.trim().split('\n')[0].trim();
68
+ }
69
+ } catch {
70
+ // ignore
71
+ }
72
+ return 'unknown';
73
+ }
@@ -0,0 +1,46 @@
1
+ import path from 'node:path';
2
+ import fs from 'node:fs';
3
+ import { findRuntimeExecutable, getBinaryVersion } from './common.js';
4
+
5
+ export async function createDenoAdapter(opts = {}) {
6
+ const customPath = opts.command;
7
+ let execPath = null;
8
+
9
+ if (customPath) {
10
+ const absPath = path.resolve(process.cwd(), customPath);
11
+ if (fs.existsSync(absPath)) {
12
+ try {
13
+ fs.accessSync(absPath, fs.constants.X_OK);
14
+ execPath = absPath;
15
+ } catch {}
16
+ }
17
+ }
18
+
19
+ if (!execPath) {
20
+ execPath = await findRuntimeExecutable(['deno']);
21
+ }
22
+
23
+ if (!execPath) {
24
+ throw new Error(
25
+ 'Deno runtime could not be found.\n\nInstall Deno or specify its path:\n unitup add server.ts --command /usr/bin/deno'
26
+ );
27
+ }
28
+
29
+ const version = await getBinaryVersion(execPath, '--version');
30
+ const scriptPath = opts.script ? path.resolve(process.cwd(), opts.script) : null;
31
+ const runtimeArgs = Array.isArray(opts.runtimeArgs) ? opts.runtimeArgs : [];
32
+ const scriptArgs = Array.isArray(opts.args) ? opts.args : [];
33
+
34
+ const args = ['run', ...runtimeArgs];
35
+ if (scriptPath) {
36
+ args.push(scriptPath);
37
+ }
38
+ args.push(...scriptArgs);
39
+
40
+ return {
41
+ command: execPath,
42
+ args,
43
+ runtime: 'deno',
44
+ version
45
+ };
46
+ }
@@ -0,0 +1,46 @@
1
+ import path from 'node:path';
2
+ import fs from 'node:fs';
3
+ import { findRuntimeExecutable, getBinaryVersion } from './common.js';
4
+
5
+ export async function createElixirAdapter(opts = {}) {
6
+ const customPath = opts.command;
7
+ let execPath = null;
8
+
9
+ if (customPath) {
10
+ const absPath = path.resolve(process.cwd(), customPath);
11
+ if (fs.existsSync(absPath)) {
12
+ try {
13
+ fs.accessSync(absPath, fs.constants.X_OK);
14
+ execPath = absPath;
15
+ } catch {}
16
+ }
17
+ }
18
+
19
+ if (!execPath) {
20
+ execPath = await findRuntimeExecutable(['elixir']);
21
+ }
22
+
23
+ if (!execPath) {
24
+ throw new Error(
25
+ 'Elixir runtime could not be found.\n\nInstall Elixir or specify its path:\n unitup add app.exs --command /usr/bin/elixir'
26
+ );
27
+ }
28
+
29
+ const version = await getBinaryVersion(execPath, '-v');
30
+ const scriptPath = opts.script ? path.resolve(process.cwd(), opts.script) : null;
31
+ const runtimeArgs = Array.isArray(opts.runtimeArgs) ? opts.runtimeArgs : [];
32
+ const scriptArgs = Array.isArray(opts.args) ? opts.args : [];
33
+
34
+ const args = [...runtimeArgs];
35
+ if (scriptPath) {
36
+ args.push(scriptPath);
37
+ }
38
+ args.push(...scriptArgs);
39
+
40
+ return {
41
+ command: execPath,
42
+ args,
43
+ runtime: 'elixir',
44
+ version
45
+ };
46
+ }
@@ -0,0 +1,46 @@
1
+ import path from 'node:path';
2
+ import fs from 'node:fs';
3
+ import { findRuntimeExecutable, getBinaryVersion } from './common.js';
4
+
5
+ export async function createGoAdapter(opts = {}) {
6
+ const customPath = opts.command;
7
+ let execPath = null;
8
+
9
+ if (customPath) {
10
+ const absPath = path.resolve(process.cwd(), customPath);
11
+ if (fs.existsSync(absPath)) {
12
+ try {
13
+ fs.accessSync(absPath, fs.constants.X_OK);
14
+ execPath = absPath;
15
+ } catch {}
16
+ }
17
+ }
18
+
19
+ if (!execPath) {
20
+ execPath = await findRuntimeExecutable(['go']);
21
+ }
22
+
23
+ if (!execPath) {
24
+ throw new Error(
25
+ 'Go runtime could not be found.\n\nInstall Go or specify its path:\n unitup add main.go --command /usr/local/go/bin/go'
26
+ );
27
+ }
28
+
29
+ const version = await getBinaryVersion(execPath, 'version');
30
+ const scriptPath = opts.script ? path.resolve(process.cwd(), opts.script) : null;
31
+ const runtimeArgs = Array.isArray(opts.runtimeArgs) ? opts.runtimeArgs : [];
32
+ const scriptArgs = Array.isArray(opts.args) ? opts.args : [];
33
+
34
+ const args = ['run', ...runtimeArgs];
35
+ if (scriptPath) {
36
+ args.push(scriptPath);
37
+ }
38
+ args.push(...scriptArgs);
39
+
40
+ return {
41
+ command: execPath,
42
+ args,
43
+ runtime: 'go',
44
+ version
45
+ };
46
+ }
@@ -0,0 +1,155 @@
1
+ import path from 'node:path';
2
+ import fs from 'node:fs';
3
+ import { createNodeAdapter } from './node.js';
4
+ import { createPythonAdapter } from './python.js';
5
+ import { createRubyAdapter } from './ruby.js';
6
+ import { createPhpAdapter } from './php.js';
7
+ import { createBunAdapter } from './bun.js';
8
+ import { createDenoAdapter } from './deno.js';
9
+ import { createShellAdapter } from './shell.js';
10
+ import { createNativeAdapter } from './native.js';
11
+ import { createGoAdapter } from './go.js';
12
+ import { createElixirAdapter } from './elixir.js';
13
+
14
+ export function detectRuntime(filepath) {
15
+ if (!filepath) {
16
+ throw new Error('Script path or command is required for runtime detection.');
17
+ }
18
+
19
+ const baseName = path.basename(filepath);
20
+ const ext = path.extname(baseName).toLowerCase();
21
+
22
+ if (ext === '.js' || ext === '.mjs' || ext === '.cjs') {
23
+ return 'node';
24
+ }
25
+ if (ext === '.py') {
26
+ return 'python';
27
+ }
28
+ if (ext === '.rb') {
29
+ return 'ruby';
30
+ }
31
+ if (ext === '.php') {
32
+ return 'php';
33
+ }
34
+ if (ext === '.sh') {
35
+ return 'shell';
36
+ }
37
+ if (ext === '.go') {
38
+ return 'go';
39
+ }
40
+ if (ext === '.ex' || ext === '.exs') {
41
+ return 'elixir';
42
+ }
43
+
44
+ // Inspect shebang header if file exists
45
+ const absPath = path.resolve(process.cwd(), filepath);
46
+ if (fs.existsSync(absPath)) {
47
+ try {
48
+ const fd = fs.openSync(absPath, 'r');
49
+ const buffer = Buffer.alloc(256);
50
+ const bytesRead = fs.readSync(fd, buffer, 0, 256, 0);
51
+ fs.closeSync(fd);
52
+
53
+ const header = buffer.toString('utf8', 0, bytesRead);
54
+ const firstLine = header.split('\n')[0].trim();
55
+
56
+ if (firstLine.startsWith('#!')) {
57
+ if (firstLine.includes('python')) return 'python';
58
+ if (firstLine.includes('node')) return 'node';
59
+ if (firstLine.includes('ruby')) return 'ruby';
60
+ if (firstLine.includes('bash') || firstLine.includes('/sh')) return 'shell';
61
+ if (firstLine.includes('bun')) return 'bun';
62
+ if (firstLine.includes('deno')) return 'deno';
63
+ if (firstLine.includes('php')) return 'php';
64
+ if (firstLine.includes('go')) return 'go';
65
+ if (firstLine.includes('elixir')) return 'elixir';
66
+ }
67
+ } catch {
68
+ // ignore
69
+ }
70
+ }
71
+
72
+ if (ext === '.ts') {
73
+ throw new Error(
74
+ `Could not determine runtime for ${baseName}.\n\nSpecify one:\n unitup add ${baseName} --runtime bun\n unitup add ${baseName} --runtime deno\n unitup add ${baseName} --runtime node`
75
+ );
76
+ }
77
+
78
+ if (ext === '') {
79
+ return 'node';
80
+ }
81
+
82
+ throw new Error(
83
+ `Could not determine runtime for ${baseName}.\n\nSpecify runtime with --runtime <name> or custom executable with --command <path>`
84
+ );
85
+ }
86
+
87
+ export async function resolveRuntimeConfig(opts = {}) {
88
+ // 1. If explicit --command is provided, bypass runtime auto-detection
89
+ if (opts.command) {
90
+ const absCommand = path.resolve(process.cwd(), opts.command);
91
+ if (!fs.existsSync(absCommand)) {
92
+ // If binary doesn't exist as exact file path, check if it's in PATH or native executable
93
+ const { findRuntimeExecutable } = await import('./common.js');
94
+ const resolved = await findRuntimeExecutable([opts.command]);
95
+ if (!resolved) {
96
+ throw new Error(`Command executable could not be resolved: ${opts.command}`);
97
+ }
98
+ const args = Array.isArray(opts.args) ? opts.args : [];
99
+ return {
100
+ command: resolved,
101
+ args,
102
+ runtime: opts.runtime || 'custom',
103
+ version: 'custom'
104
+ };
105
+ }
106
+
107
+ const args = Array.isArray(opts.args) ? opts.args : [];
108
+ return {
109
+ command: absCommand,
110
+ args,
111
+ runtime: opts.runtime || 'custom',
112
+ version: 'custom'
113
+ };
114
+ }
115
+
116
+ // 2. Resolve target runtime name
117
+ let runtimeName = opts.runtime;
118
+ if (!runtimeName) {
119
+ runtimeName = detectRuntime(opts.script);
120
+ }
121
+
122
+ const cleanRuntime = runtimeName.toLowerCase();
123
+
124
+ switch (cleanRuntime) {
125
+ case 'node':
126
+ case 'nodejs':
127
+ return createNodeAdapter(opts);
128
+ case 'python':
129
+ case 'python3':
130
+ return createPythonAdapter(opts);
131
+ case 'ruby':
132
+ return createRubyAdapter(opts);
133
+ case 'php':
134
+ return createPhpAdapter(opts);
135
+ case 'bun':
136
+ return createBunAdapter(opts);
137
+ case 'deno':
138
+ return createDenoAdapter(opts);
139
+ case 'shell':
140
+ case 'bash':
141
+ case 'sh':
142
+ return createShellAdapter(opts);
143
+ case 'go':
144
+ case 'golang':
145
+ return createGoAdapter(opts);
146
+ case 'elixir':
147
+ case 'ex':
148
+ case 'exs':
149
+ return createElixirAdapter(opts);
150
+ case 'native':
151
+ return createNativeAdapter(opts);
152
+ default:
153
+ throw new Error(`Unsupported runtime: "${runtimeName}"`);
154
+ }
155
+ }
@@ -0,0 +1,38 @@
1
+ import path from 'node:path';
2
+ import fs from 'node:fs';
3
+
4
+ export async function createNativeAdapter(opts = {}) {
5
+ const targetPath = opts.script || opts.command;
6
+
7
+ if (!targetPath) {
8
+ throw new Error('Native executable path is required.');
9
+ }
10
+
11
+ const absPath = path.resolve(process.cwd(), targetPath);
12
+
13
+ if (!fs.existsSync(absPath)) {
14
+ throw new Error(`Executable file does not exist: ${absPath}`);
15
+ }
16
+
17
+ const stat = fs.statSync(absPath);
18
+ if (!stat.isFile()) {
19
+ throw new Error(`Executable path is not a file: ${absPath}`);
20
+ }
21
+
22
+ try {
23
+ fs.accessSync(absPath, fs.constants.X_OK);
24
+ } catch {
25
+ throw new Error(
26
+ `The executable is not runnable.\n\nRun:\n chmod +x ${absPath}`
27
+ );
28
+ }
29
+
30
+ const scriptArgs = Array.isArray(opts.args) ? opts.args : [];
31
+
32
+ return {
33
+ command: absPath,
34
+ args: scriptArgs,
35
+ runtime: 'native',
36
+ version: 'native'
37
+ };
38
+ }
@@ -0,0 +1,57 @@
1
+ import path from 'node:path';
2
+ import fs from 'node:fs';
3
+ import { findRuntimeExecutable, getBinaryVersion } from './common.js';
4
+
5
+ export async function createNodeAdapter(opts = {}) {
6
+ const customPath = opts.command || opts.nodePath;
7
+ let execPath = null;
8
+
9
+ if (customPath) {
10
+ const absPath = path.resolve(process.cwd(), customPath);
11
+ if (fs.existsSync(absPath)) {
12
+ try {
13
+ fs.accessSync(absPath, fs.constants.X_OK);
14
+ execPath = absPath;
15
+ } catch {
16
+ execPath = null;
17
+ }
18
+ }
19
+ }
20
+
21
+ if (!execPath) {
22
+ if (process.execPath && fs.existsSync(process.execPath)) {
23
+ try {
24
+ fs.accessSync(process.execPath, fs.constants.X_OK);
25
+ execPath = process.execPath;
26
+ } catch {}
27
+ }
28
+ }
29
+
30
+ if (!execPath) {
31
+ execPath = await findRuntimeExecutable(['node']);
32
+ }
33
+
34
+ if (!execPath) {
35
+ throw new Error(
36
+ 'Node.js runtime could not be found.\n\nInstall Node.js or specify its path:\n unitup add script.js --command /usr/bin/node'
37
+ );
38
+ }
39
+
40
+ const version = await getBinaryVersion(execPath, '-v');
41
+ const scriptPath = opts.script ? path.resolve(process.cwd(), opts.script) : null;
42
+ const runtimeArgs = Array.isArray(opts.runtimeArgs) ? opts.runtimeArgs : [];
43
+ const scriptArgs = Array.isArray(opts.args) ? opts.args : [];
44
+
45
+ const args = [...runtimeArgs];
46
+ if (scriptPath) {
47
+ args.push(scriptPath);
48
+ }
49
+ args.push(...scriptArgs);
50
+
51
+ return {
52
+ command: execPath,
53
+ args,
54
+ runtime: 'node',
55
+ version
56
+ };
57
+ }