@pixelbyte-software/pixcode 1.40.4 → 1.40.5
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/dist/assets/{index-pC1RxVq6.js → index-DROaSsD_.js} +44 -44
- package/dist/index.html +1 -1
- package/dist/landing.html +1 -1
- package/dist-server/server/services/install-jobs.js +1 -1
- package/dist-server/server/services/install-jobs.js.map +1 -1
- package/dist-server/server/services/live-view.js +75 -18
- package/dist-server/server/services/live-view.js.map +1 -1
- package/dist-server/server/services/managed-runtimes.js +106 -10
- package/dist-server/server/services/managed-runtimes.js.map +1 -1
- package/package.json +1 -1
- package/scripts/smoke/live-view-integration.mjs +46 -1
- package/server/services/install-jobs.js +1 -1
- package/server/services/live-view.js +79 -19
- package/server/services/managed-runtimes.js +116 -10
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { mkdtemp, writeFile } from 'node:fs/promises';
|
|
1
|
+
import { chmod, mkdtemp, writeFile } from 'node:fs/promises';
|
|
2
2
|
import { tmpdir } from 'node:os';
|
|
3
3
|
import path from 'node:path';
|
|
4
4
|
import { fileURLToPath } from 'node:url';
|
|
@@ -87,6 +87,10 @@ assert.ok(
|
|
|
87
87
|
liveViewPanel.includes('managedRuntime') && liveViewPanel.includes('liveView.managedRuntimePreparing'),
|
|
88
88
|
'Live View panel should explain that Pixcode can prepare managed runtimes automatically.',
|
|
89
89
|
);
|
|
90
|
+
assert.ok(
|
|
91
|
+
liveViewPanel.includes('isPreparingManagedRuntime') && liveViewPanel.includes('liveView.preparingRuntime'),
|
|
92
|
+
'Live View panel should show a visible in-progress state while Pixcode downloads and installs a managed runtime.',
|
|
93
|
+
);
|
|
90
94
|
assert.ok(
|
|
91
95
|
liveViewPanel.includes("runAction('restart')"),
|
|
92
96
|
'Live View panel should expose a restart action for failed process runners.',
|
|
@@ -101,6 +105,14 @@ assert.ok(
|
|
|
101
105
|
managedRuntimes.includes('extractZip') && managedRuntimes.includes('extractTarGz'),
|
|
102
106
|
'Managed runtime installation should handle common Windows zip and macOS/Linux tarball assets.',
|
|
103
107
|
);
|
|
108
|
+
assert.ok(
|
|
109
|
+
managedRuntimes.includes('preferManaged'),
|
|
110
|
+
'Managed PHP Live View should be able to skip external runtimes and prefer Pixcode-owned binaries.',
|
|
111
|
+
);
|
|
112
|
+
assert.ok(
|
|
113
|
+
managedRuntimes.includes("id === 'npm'") && managedRuntimes.includes('installNpmRuntime'),
|
|
114
|
+
'Managed runtimes should include a Pixcode-owned npm runner for JavaScript projects when npm is not on PATH.',
|
|
115
|
+
);
|
|
104
116
|
|
|
105
117
|
const serverIndex = await read('server/index.js');
|
|
106
118
|
assert.ok(
|
|
@@ -152,6 +164,18 @@ const viteTarget = await detectLiveViewTarget(viteProject);
|
|
|
152
164
|
assert.equal(viteTarget.available, true, 'Vite projects should be detected.');
|
|
153
165
|
assert.equal(viteTarget.command?.id, 'npm-dev-vite', 'Vite projects should get a Vite-aware command.');
|
|
154
166
|
|
|
167
|
+
const viteMissingNpmTarget = await detectLiveViewTarget(viteProject, {
|
|
168
|
+
env: {
|
|
169
|
+
...process.env,
|
|
170
|
+
PATH: '',
|
|
171
|
+
Path: '',
|
|
172
|
+
},
|
|
173
|
+
});
|
|
174
|
+
assert.equal(viteMissingNpmTarget.available, true, 'Vite projects should remain runnable through a Pixcode-managed package runner when npm is missing from PATH.');
|
|
175
|
+
assert.equal(viteMissingNpmTarget.command?.id, 'npm-dev-vite', 'Vite projects should keep the original Vite command identity.');
|
|
176
|
+
assert.equal(viteMissingNpmTarget.managedRuntime?.id, 'npm', 'Missing npm should select the Pixcode-managed npm runner.');
|
|
177
|
+
assert.equal(viteMissingNpmTarget.managedRuntime?.status, 'missing', 'Missing npm should report that the managed package runner still needs preparation.');
|
|
178
|
+
|
|
155
179
|
const djangoTarget = await detectLiveViewTarget(djangoProject);
|
|
156
180
|
assert.equal(djangoTarget.available, true, 'Django projects should be detected from manage.py.');
|
|
157
181
|
assert.equal(djangoTarget.command?.id, 'python-django', 'Django projects should get a runserver command.');
|
|
@@ -172,6 +196,27 @@ assert.ok(
|
|
|
172
196
|
'Missing PHP should use product language instead of exposing PATH setup as the primary message.',
|
|
173
197
|
);
|
|
174
198
|
|
|
199
|
+
const fakeBin = path.join(workspace, 'fake-bin');
|
|
200
|
+
await mkdir(fakeBin, { recursive: true });
|
|
201
|
+
const fakePhp = path.join(fakeBin, process.platform === 'win32' ? 'php.cmd' : 'php');
|
|
202
|
+
await writeFile(fakePhp, process.platform === 'win32' ? '@echo off\r\nexit /b 0\r\n' : '#!/bin/sh\nexit 0\n');
|
|
203
|
+
if (process.platform !== 'win32') {
|
|
204
|
+
await chmod(fakePhp, 0o755);
|
|
205
|
+
}
|
|
206
|
+
const fakePath = process.platform === 'win32'
|
|
207
|
+
? `${fakeBin};${process.env.PATH || ''}`
|
|
208
|
+
: `${fakeBin}:${process.env.PATH || ''}`;
|
|
209
|
+
const phpSystemRuntimeTarget = await detectLiveViewTarget(phpProject, {
|
|
210
|
+
env: {
|
|
211
|
+
...process.env,
|
|
212
|
+
PATH: fakePath,
|
|
213
|
+
Path: fakePath,
|
|
214
|
+
},
|
|
215
|
+
});
|
|
216
|
+
assert.equal(phpSystemRuntimeTarget.available, true, 'PHP projects should stay runnable when php exists on PATH.');
|
|
217
|
+
assert.equal(phpSystemRuntimeTarget.command?.id, 'frankenphp-php-server', 'PHP projects should still use the Pixcode-managed runtime even when external php exists.');
|
|
218
|
+
assert.equal(phpSystemRuntimeTarget.managedRuntime?.id, 'frankenphp', 'PHP projects should prefer the Pixcode-owned FrankenPHP runtime instead of external php.');
|
|
219
|
+
|
|
175
220
|
const staticSession = await startLiveView('static-smoke', staticProject);
|
|
176
221
|
assert.equal(staticSession.status, 'running', 'Static Live View should start without a child process.');
|
|
177
222
|
assert.match(staticSession.sharePath, /^\/live\/[a-f0-9]{24}\/$/, 'Live View should expose a random public share path.');
|
|
@@ -459,7 +459,7 @@ export function findExecutableOnPath(name, env = process.env) {
|
|
|
459
459
|
* more reliable than trusting PATH — when Pixcode runs as a daemon, PATH
|
|
460
460
|
* is often minimal and doesn't include the user's node install.
|
|
461
461
|
*/
|
|
462
|
-
function resolveNpmCommand(env = process.env) {
|
|
462
|
+
export function resolveNpmCommand(env = process.env) {
|
|
463
463
|
const nodeDir = path.dirname(process.execPath);
|
|
464
464
|
const isWindows = process.platform === 'win32';
|
|
465
465
|
const candidates = isWindows
|
|
@@ -4,6 +4,7 @@ import { promises as fs } from 'node:fs';
|
|
|
4
4
|
import net from 'node:net';
|
|
5
5
|
import path from 'node:path';
|
|
6
6
|
|
|
7
|
+
import { buildCliSpawnEnv } from './install-jobs.js';
|
|
7
8
|
import { ensureManagedRuntime, getManagedRuntimeStatus } from './managed-runtimes.js';
|
|
8
9
|
|
|
9
10
|
const sessionsByProject = new Map();
|
|
@@ -99,7 +100,7 @@ function runtimeMissingReason(command, framework) {
|
|
|
99
100
|
return 'Pixcode can prepare a local PHP runtime automatically before starting this project.';
|
|
100
101
|
}
|
|
101
102
|
if (command === 'npm' || command === 'pnpm' || command === 'yarn' || command === 'bun') {
|
|
102
|
-
return `${base} Pixcode
|
|
103
|
+
return `${base} Pixcode can prepare a local Node package runner automatically before starting this project.`;
|
|
103
104
|
}
|
|
104
105
|
if (command === 'python' || command === 'python3') {
|
|
105
106
|
return `${base} Pixcode does not have a managed Python runtime for this stack yet.`;
|
|
@@ -169,12 +170,44 @@ function buildPackageCommand(packageManager, scriptName, id, label, framework, e
|
|
|
169
170
|
id,
|
|
170
171
|
label,
|
|
171
172
|
framework,
|
|
173
|
+
packageManager,
|
|
174
|
+
scriptName,
|
|
175
|
+
extraArgs,
|
|
172
176
|
command: packageManager,
|
|
173
177
|
args,
|
|
174
178
|
displayCommand: buildDisplayCommand(packageManager, args),
|
|
175
179
|
};
|
|
176
180
|
}
|
|
177
181
|
|
|
182
|
+
function isPackageManagerCommand(command) {
|
|
183
|
+
return command === 'npm' || command === 'pnpm' || command === 'yarn' || command === 'bun';
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
function buildManagedPackageCommand(command, runtimeStatus) {
|
|
187
|
+
const npmArgs = command.scriptName
|
|
188
|
+
? packageRunArgs('npm', command.scriptName, command.extraArgs || [])
|
|
189
|
+
: command.args;
|
|
190
|
+
const runtimeExecutable = runtimeStatus?.executablePath || null;
|
|
191
|
+
const commandExecutable = runtimeExecutable
|
|
192
|
+
? (runtimeStatus?.runner === 'node' || runtimeExecutable.endsWith('.js') ? process.execPath : runtimeExecutable)
|
|
193
|
+
: command.command;
|
|
194
|
+
const args = runtimeExecutable && (runtimeStatus?.runner === 'node' || runtimeExecutable.endsWith('.js'))
|
|
195
|
+
? [runtimeExecutable, ...npmArgs]
|
|
196
|
+
: npmArgs;
|
|
197
|
+
|
|
198
|
+
return {
|
|
199
|
+
...command,
|
|
200
|
+
packageManager: 'npm',
|
|
201
|
+
command: commandExecutable,
|
|
202
|
+
args,
|
|
203
|
+
displayCommand: buildDisplayCommand('npm', npmArgs),
|
|
204
|
+
managedRuntime: {
|
|
205
|
+
id: 'npm',
|
|
206
|
+
status: runtimeStatus?.status || 'missing',
|
|
207
|
+
},
|
|
208
|
+
};
|
|
209
|
+
}
|
|
210
|
+
|
|
178
211
|
function buildManagedPhpCommand(runtimeStatus) {
|
|
179
212
|
const executable = runtimeStatus?.executablePath || 'frankenphp';
|
|
180
213
|
return {
|
|
@@ -414,24 +447,46 @@ export async function detectLiveViewTarget(projectPath, options = {}) {
|
|
|
414
447
|
|
|
415
448
|
const processCommand = await detectProcessCommand(projectPath);
|
|
416
449
|
if (processCommand) {
|
|
450
|
+
if (isPackageManagerCommand(processCommand.command)) {
|
|
451
|
+
const managedRuntime = await getManagedRuntimeStatus('npm', {
|
|
452
|
+
env: options.env || process.env,
|
|
453
|
+
preferManaged: true,
|
|
454
|
+
});
|
|
455
|
+
const command = buildManagedPackageCommand(processCommand, managedRuntime);
|
|
456
|
+
return {
|
|
457
|
+
available: true,
|
|
458
|
+
kind: 'process',
|
|
459
|
+
label: processCommand.label,
|
|
460
|
+
framework: processCommand.framework,
|
|
461
|
+
command,
|
|
462
|
+
managedRuntime,
|
|
463
|
+
reason: managedRuntime.status === 'missing'
|
|
464
|
+
? 'Pixcode will prepare a local Node package runner automatically before starting this project.'
|
|
465
|
+
: 'Pixcode will run this project with its managed Node package runner.',
|
|
466
|
+
};
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
if (processCommand.framework === 'PHP' || processCommand.command === 'php') {
|
|
470
|
+
const managedRuntime = await getManagedRuntimeStatus('frankenphp', {
|
|
471
|
+
env: options.env || process.env,
|
|
472
|
+
preferManaged: true,
|
|
473
|
+
});
|
|
474
|
+
const command = buildManagedPhpCommand(managedRuntime);
|
|
475
|
+
return {
|
|
476
|
+
available: true,
|
|
477
|
+
kind: 'process',
|
|
478
|
+
label: command.label,
|
|
479
|
+
framework: command.framework,
|
|
480
|
+
command,
|
|
481
|
+
managedRuntime,
|
|
482
|
+
reason: managedRuntime.status === 'missing'
|
|
483
|
+
? 'Pixcode will prepare a local PHP runtime automatically before starting this project.'
|
|
484
|
+
: 'Pixcode will run this project with its managed PHP runtime.',
|
|
485
|
+
};
|
|
486
|
+
}
|
|
487
|
+
|
|
417
488
|
const runtimeAvailable = await checkCommandAvailability(processCommand.command, options.env || process.env);
|
|
418
489
|
if (!runtimeAvailable) {
|
|
419
|
-
if (processCommand.framework === 'PHP' || processCommand.command === 'php') {
|
|
420
|
-
const managedRuntime = await getManagedRuntimeStatus('frankenphp', { env: options.env || process.env });
|
|
421
|
-
const command = buildManagedPhpCommand(managedRuntime);
|
|
422
|
-
return {
|
|
423
|
-
available: true,
|
|
424
|
-
kind: 'process',
|
|
425
|
-
label: command.label,
|
|
426
|
-
framework: command.framework,
|
|
427
|
-
command,
|
|
428
|
-
managedRuntime,
|
|
429
|
-
reason: managedRuntime.status === 'missing'
|
|
430
|
-
? 'Pixcode will prepare a local PHP runtime automatically before starting this project.'
|
|
431
|
-
: 'Pixcode will run this project with its managed PHP runtime.',
|
|
432
|
-
};
|
|
433
|
-
}
|
|
434
|
-
|
|
435
490
|
return {
|
|
436
491
|
available: false,
|
|
437
492
|
kind: 'process',
|
|
@@ -618,9 +673,13 @@ export async function startLiveView(projectName, projectPath, options = {}) {
|
|
|
618
673
|
let runtimeStatus = target.managedRuntime || target.command?.managedRuntime || null;
|
|
619
674
|
let targetCommand = target.command;
|
|
620
675
|
if (runtimeStatus?.id && runtimeStatus.status !== 'system' && runtimeStatus.status !== 'installed') {
|
|
621
|
-
runtimeStatus = await ensureManagedRuntime(runtimeStatus.id
|
|
676
|
+
runtimeStatus = await ensureManagedRuntime(runtimeStatus.id, {
|
|
677
|
+
preferManaged: runtimeStatus.id === 'frankenphp' || runtimeStatus.id === 'npm',
|
|
678
|
+
});
|
|
622
679
|
if (runtimeStatus.id === 'frankenphp') {
|
|
623
680
|
targetCommand = buildManagedPhpCommand(runtimeStatus);
|
|
681
|
+
} else if (runtimeStatus.id === 'npm') {
|
|
682
|
+
targetCommand = buildManagedPackageCommand(targetCommand, runtimeStatus);
|
|
624
683
|
}
|
|
625
684
|
}
|
|
626
685
|
|
|
@@ -649,8 +708,9 @@ export async function startLiveView(projectName, projectPath, options = {}) {
|
|
|
649
708
|
};
|
|
650
709
|
|
|
651
710
|
const env = {
|
|
652
|
-
...process.env,
|
|
711
|
+
...buildCliSpawnEnv(process.env),
|
|
653
712
|
...(command.env || {}),
|
|
713
|
+
...(process.versions.electron ? { ELECTRON_RUN_AS_NODE: '1' } : {}),
|
|
654
714
|
PORT: String(port),
|
|
655
715
|
HOST: '127.0.0.1',
|
|
656
716
|
VITE_HOST: '127.0.0.1',
|
|
@@ -3,7 +3,9 @@ import { promises as fs } from 'node:fs';
|
|
|
3
3
|
import os from 'node:os';
|
|
4
4
|
import path from 'node:path';
|
|
5
5
|
|
|
6
|
-
import
|
|
6
|
+
import * as tar from 'tar';
|
|
7
|
+
|
|
8
|
+
import { buildCliSpawnEnv, findExecutableOnPath, resolveNpmCommand } from './install-jobs.js';
|
|
7
9
|
|
|
8
10
|
const DEFAULT_RUNTIMES_HOME = path.join(os.homedir(), '.pixcode', 'runtimes');
|
|
9
11
|
const MANIFEST_FILE = 'pixcode-runtime.json';
|
|
@@ -161,7 +163,11 @@ async function extractZip(archivePath, targetDir, env = process.env) {
|
|
|
161
163
|
}
|
|
162
164
|
|
|
163
165
|
async function extractTarGz(archivePath, targetDir, env = process.env) {
|
|
164
|
-
|
|
166
|
+
void env;
|
|
167
|
+
await tar.x({
|
|
168
|
+
file: archivePath,
|
|
169
|
+
cwd: targetDir,
|
|
170
|
+
});
|
|
165
171
|
}
|
|
166
172
|
|
|
167
173
|
async function findRuntimeExecutable(searchRoot, binaryName) {
|
|
@@ -255,9 +261,53 @@ async function installFrankenPhp(env = process.env) {
|
|
|
255
261
|
return manifest;
|
|
256
262
|
}
|
|
257
263
|
|
|
264
|
+
async function installNpmRuntime(env = process.env) {
|
|
265
|
+
const registryUrl = env.PIXCODE_NPM_RUNTIME_REGISTRY
|
|
266
|
+
|| 'https://registry.npmjs.org/npm/latest';
|
|
267
|
+
const metadata = await fetchJson(registryUrl, env);
|
|
268
|
+
const tarballUrl = metadata?.dist?.tarball;
|
|
269
|
+
if (!tarballUrl) {
|
|
270
|
+
throw new Error('No npm runtime tarball is available from the npm registry.');
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
const baseDir = runtimeDir('npm', env);
|
|
274
|
+
const stagingDir = path.join(baseDir, `.staging-${Date.now()}`);
|
|
275
|
+
const currentDir = path.join(baseDir, 'current');
|
|
276
|
+
await fs.mkdir(stagingDir, { recursive: true });
|
|
277
|
+
|
|
278
|
+
const archivePath = path.join(stagingDir, 'npm-runtime.tgz');
|
|
279
|
+
await downloadFile(tarballUrl, archivePath, env);
|
|
280
|
+
await extractTarGz(archivePath, stagingDir, env);
|
|
281
|
+
|
|
282
|
+
const packageDir = path.join(stagingDir, 'package');
|
|
283
|
+
const executablePath = path.join(packageDir, 'bin', 'npm-cli.js');
|
|
284
|
+
if (!(await fileExists(executablePath))) {
|
|
285
|
+
throw new Error('Downloaded npm runtime did not contain bin/npm-cli.js.');
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
await fs.rm(currentDir, { recursive: true, force: true });
|
|
289
|
+
await fs.cp(packageDir, currentDir, { recursive: true, force: true });
|
|
290
|
+
|
|
291
|
+
const finalExecutable = path.join(currentDir, 'bin', 'npm-cli.js');
|
|
292
|
+
const manifest = {
|
|
293
|
+
id: 'npm',
|
|
294
|
+
label: 'Pixcode Node package runner',
|
|
295
|
+
provider: 'npm',
|
|
296
|
+
version: metadata?.version || 'latest',
|
|
297
|
+
executablePath: finalExecutable,
|
|
298
|
+
runner: 'node',
|
|
299
|
+
sourceUrl: tarballUrl,
|
|
300
|
+
installedAt: new Date().toISOString(),
|
|
301
|
+
};
|
|
302
|
+
await fs.writeFile(manifestPath('npm', env), JSON.stringify(manifest, null, 2));
|
|
303
|
+
await fs.rm(stagingDir, { recursive: true, force: true }).catch(() => undefined);
|
|
304
|
+
return manifest;
|
|
305
|
+
}
|
|
306
|
+
|
|
258
307
|
export async function getManagedRuntimeStatus(id, options = {}) {
|
|
259
308
|
const env = options.env || process.env;
|
|
260
|
-
|
|
309
|
+
const preferManaged = Boolean(options.preferManaged);
|
|
310
|
+
if (id !== 'frankenphp' && id !== 'npm') {
|
|
261
311
|
return {
|
|
262
312
|
id,
|
|
263
313
|
status: 'unsupported',
|
|
@@ -266,18 +316,59 @@ export async function getManagedRuntimeStatus(id, options = {}) {
|
|
|
266
316
|
};
|
|
267
317
|
}
|
|
268
318
|
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
319
|
+
if (id === 'npm') {
|
|
320
|
+
if (!preferManaged) {
|
|
321
|
+
const spawnEnv = buildCliSpawnEnv(env);
|
|
322
|
+
const npmExecutable = resolveNpmCommand(spawnEnv);
|
|
323
|
+
if (npmExecutable) {
|
|
324
|
+
return {
|
|
325
|
+
id,
|
|
326
|
+
label: 'npm',
|
|
327
|
+
status: 'system',
|
|
328
|
+
installable: true,
|
|
329
|
+
executablePath: npmExecutable,
|
|
330
|
+
runner: npmExecutable.endsWith('.js') ? 'node' : undefined,
|
|
331
|
+
};
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
const manifest = await readManifest(id, env);
|
|
336
|
+
if (manifest) {
|
|
337
|
+
return {
|
|
338
|
+
id,
|
|
339
|
+
label: manifest.label || 'Pixcode Node package runner',
|
|
340
|
+
status: 'installed',
|
|
341
|
+
installable: true,
|
|
342
|
+
executablePath: manifest.executablePath,
|
|
343
|
+
runner: manifest.runner || 'node',
|
|
344
|
+
version: manifest.version,
|
|
345
|
+
};
|
|
346
|
+
}
|
|
347
|
+
|
|
272
348
|
return {
|
|
273
349
|
id,
|
|
274
|
-
label: '
|
|
275
|
-
|
|
350
|
+
label: 'Pixcode Node package runner',
|
|
351
|
+
provider: 'npm',
|
|
352
|
+
status: 'missing',
|
|
276
353
|
installable: true,
|
|
277
|
-
|
|
354
|
+
reason: 'Pixcode will prepare a local Node package runner automatically before starting this project.',
|
|
278
355
|
};
|
|
279
356
|
}
|
|
280
357
|
|
|
358
|
+
if (!preferManaged) {
|
|
359
|
+
const spawnEnv = buildCliSpawnEnv(env);
|
|
360
|
+
const systemExecutable = findExecutableOnPath('frankenphp', spawnEnv);
|
|
361
|
+
if (systemExecutable) {
|
|
362
|
+
return {
|
|
363
|
+
id,
|
|
364
|
+
label: 'FrankenPHP',
|
|
365
|
+
status: 'system',
|
|
366
|
+
installable: true,
|
|
367
|
+
executablePath: systemExecutable,
|
|
368
|
+
};
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
|
|
281
372
|
const manifest = await readManifest(id, env);
|
|
282
373
|
if (manifest) {
|
|
283
374
|
return {
|
|
@@ -302,7 +393,10 @@ export async function getManagedRuntimeStatus(id, options = {}) {
|
|
|
302
393
|
|
|
303
394
|
export async function ensureManagedRuntime(id, options = {}) {
|
|
304
395
|
const env = options.env || process.env;
|
|
305
|
-
const status = await getManagedRuntimeStatus(id, {
|
|
396
|
+
const status = await getManagedRuntimeStatus(id, {
|
|
397
|
+
env,
|
|
398
|
+
preferManaged: options.preferManaged,
|
|
399
|
+
});
|
|
306
400
|
if (status.executablePath) return status;
|
|
307
401
|
if (!status.installable) {
|
|
308
402
|
throw new Error(status.reason || 'This runtime cannot be prepared automatically.');
|
|
@@ -323,6 +417,18 @@ export async function ensureManagedRuntime(id, options = {}) {
|
|
|
323
417
|
version: manifest.version,
|
|
324
418
|
};
|
|
325
419
|
}
|
|
420
|
+
if (id === 'npm') {
|
|
421
|
+
const manifest = await installNpmRuntime(env);
|
|
422
|
+
return {
|
|
423
|
+
id,
|
|
424
|
+
label: manifest.label,
|
|
425
|
+
status: 'installed',
|
|
426
|
+
installable: true,
|
|
427
|
+
executablePath: manifest.executablePath,
|
|
428
|
+
runner: manifest.runner || 'node',
|
|
429
|
+
version: manifest.version,
|
|
430
|
+
};
|
|
431
|
+
}
|
|
326
432
|
throw new Error(`Unsupported managed runtime: ${id}`);
|
|
327
433
|
})().finally(() => {
|
|
328
434
|
installLocks.delete(lockKey);
|