@pixelbyte-software/pixcode 1.40.3 → 1.40.4
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 +3 -0
- package/dist/assets/index-LZgOC7Q_.css +32 -0
- package/dist/assets/{index-Cc_ji8dI.js → index-pC1RxVq6.js} +48 -48
- package/dist/index.html +2 -2
- package/dist/landing.html +1 -1
- package/dist-server/server/routes/live-view.js +2 -2
- package/dist-server/server/routes/live-view.js.map +1 -1
- package/dist-server/server/services/live-view.js +67 -7
- package/dist-server/server/services/live-view.js.map +1 -1
- package/dist-server/server/services/managed-runtimes.js +313 -0
- package/dist-server/server/services/managed-runtimes.js.map +1 -0
- package/package.json +1 -1
- package/scripts/smoke/live-view-integration.mjs +21 -5
- package/server/routes/live-view.js +2 -2
- package/server/services/live-view.js +69 -7
- package/server/services/managed-runtimes.js +333 -0
- package/dist/assets/index-I_EBn-XU.css +0 -32
|
@@ -0,0 +1,313 @@
|
|
|
1
|
+
import { spawn } from 'node:child_process';
|
|
2
|
+
import { promises as fs } from 'node:fs';
|
|
3
|
+
import os from 'node:os';
|
|
4
|
+
import path from 'node:path';
|
|
5
|
+
import { buildCliSpawnEnv, findExecutableOnPath } from './install-jobs.js';
|
|
6
|
+
const DEFAULT_RUNTIMES_HOME = path.join(os.homedir(), '.pixcode', 'runtimes');
|
|
7
|
+
const MANIFEST_FILE = 'pixcode-runtime.json';
|
|
8
|
+
const installLocks = new Map();
|
|
9
|
+
function runtimesHome(env = process.env) {
|
|
10
|
+
return env.PIXCODE_MANAGED_RUNTIMES_HOME || DEFAULT_RUNTIMES_HOME;
|
|
11
|
+
}
|
|
12
|
+
function runtimeDir(id, env = process.env) {
|
|
13
|
+
return path.join(runtimesHome(env), id);
|
|
14
|
+
}
|
|
15
|
+
function manifestPath(id, env = process.env) {
|
|
16
|
+
return path.join(runtimeDir(id, env), MANIFEST_FILE);
|
|
17
|
+
}
|
|
18
|
+
async function fileExists(filePath) {
|
|
19
|
+
try {
|
|
20
|
+
const stats = await fs.stat(filePath);
|
|
21
|
+
return stats.isFile();
|
|
22
|
+
}
|
|
23
|
+
catch {
|
|
24
|
+
return false;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
async function readManifest(id, env = process.env) {
|
|
28
|
+
try {
|
|
29
|
+
const content = await fs.readFile(manifestPath(id, env), 'utf8');
|
|
30
|
+
const manifest = JSON.parse(content);
|
|
31
|
+
if (manifest?.executablePath && await fileExists(manifest.executablePath)) {
|
|
32
|
+
return manifest;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
catch {
|
|
36
|
+
// Missing or malformed manifests are treated as not installed.
|
|
37
|
+
}
|
|
38
|
+
return null;
|
|
39
|
+
}
|
|
40
|
+
function platformTokens() {
|
|
41
|
+
if (process.platform === 'win32')
|
|
42
|
+
return ['windows'];
|
|
43
|
+
if (process.platform === 'darwin')
|
|
44
|
+
return ['mac', 'darwin'];
|
|
45
|
+
if (process.platform === 'linux')
|
|
46
|
+
return ['linux'];
|
|
47
|
+
return [process.platform];
|
|
48
|
+
}
|
|
49
|
+
function archTokens() {
|
|
50
|
+
if (process.arch === 'x64')
|
|
51
|
+
return ['x86_64', 'amd64', 'x64'];
|
|
52
|
+
if (process.arch === 'arm64')
|
|
53
|
+
return ['aarch64', 'arm64'];
|
|
54
|
+
return [process.arch];
|
|
55
|
+
}
|
|
56
|
+
function scoreFrankenPhpAsset(assetName) {
|
|
57
|
+
const name = assetName.toLowerCase();
|
|
58
|
+
if (!name.includes('frankenphp'))
|
|
59
|
+
return -1;
|
|
60
|
+
if (name.includes('debug'))
|
|
61
|
+
return -1;
|
|
62
|
+
if (!platformTokens().some((token) => name.includes(token)))
|
|
63
|
+
return -1;
|
|
64
|
+
if (!archTokens().some((token) => name.includes(token)))
|
|
65
|
+
return -1;
|
|
66
|
+
let score = 10;
|
|
67
|
+
if (process.platform === 'win32' && name.endsWith('.zip'))
|
|
68
|
+
score += 10;
|
|
69
|
+
if (process.platform !== 'win32' && !name.endsWith('.zip'))
|
|
70
|
+
score += 10;
|
|
71
|
+
if (!name.includes('gnu'))
|
|
72
|
+
score += 2;
|
|
73
|
+
if (name.endsWith('.tar.gz') || name.endsWith('.tgz'))
|
|
74
|
+
score += 1;
|
|
75
|
+
return score;
|
|
76
|
+
}
|
|
77
|
+
function selectFrankenPhpAsset(release) {
|
|
78
|
+
const assets = Array.isArray(release?.assets) ? release.assets : [];
|
|
79
|
+
const candidates = assets
|
|
80
|
+
.map((asset) => ({ asset, score: scoreFrankenPhpAsset(asset.name || '') }))
|
|
81
|
+
.filter((entry) => entry.score >= 0)
|
|
82
|
+
.sort((a, b) => b.score - a.score);
|
|
83
|
+
return candidates[0]?.asset || null;
|
|
84
|
+
}
|
|
85
|
+
async function fetchJson(url, env = process.env) {
|
|
86
|
+
const headers = {
|
|
87
|
+
Accept: 'application/vnd.github+json',
|
|
88
|
+
'User-Agent': 'Pixcode Live View',
|
|
89
|
+
};
|
|
90
|
+
if (env.GITHUB_TOKEN)
|
|
91
|
+
headers.Authorization = `Bearer ${env.GITHUB_TOKEN}`;
|
|
92
|
+
const response = await fetch(url, { headers });
|
|
93
|
+
if (!response.ok) {
|
|
94
|
+
throw new Error(`Runtime metadata request failed with HTTP ${response.status}`);
|
|
95
|
+
}
|
|
96
|
+
return response.json();
|
|
97
|
+
}
|
|
98
|
+
async function downloadFile(url, targetFile, env = process.env) {
|
|
99
|
+
const headers = { 'User-Agent': 'Pixcode Live View' };
|
|
100
|
+
if (env.GITHUB_TOKEN && url.includes('github.com'))
|
|
101
|
+
headers.Authorization = `Bearer ${env.GITHUB_TOKEN}`;
|
|
102
|
+
const response = await fetch(url, { headers });
|
|
103
|
+
if (!response.ok) {
|
|
104
|
+
throw new Error(`Runtime download failed with HTTP ${response.status}`);
|
|
105
|
+
}
|
|
106
|
+
const buffer = Buffer.from(await response.arrayBuffer());
|
|
107
|
+
await fs.writeFile(targetFile, buffer);
|
|
108
|
+
}
|
|
109
|
+
function runProcess(command, args, options = {}) {
|
|
110
|
+
return new Promise((resolve, reject) => {
|
|
111
|
+
let stderr = '';
|
|
112
|
+
const child = spawn(command, args, {
|
|
113
|
+
...options,
|
|
114
|
+
stdio: ['ignore', 'ignore', 'pipe'],
|
|
115
|
+
windowsHide: true,
|
|
116
|
+
});
|
|
117
|
+
child.stderr.on('data', (chunk) => {
|
|
118
|
+
stderr += chunk.toString();
|
|
119
|
+
});
|
|
120
|
+
child.on('error', reject);
|
|
121
|
+
child.on('close', (code) => {
|
|
122
|
+
if (code === 0) {
|
|
123
|
+
resolve();
|
|
124
|
+
return;
|
|
125
|
+
}
|
|
126
|
+
reject(new Error(`${command} exited with code ${code}${stderr ? `: ${stderr.trim()}` : ''}`));
|
|
127
|
+
});
|
|
128
|
+
});
|
|
129
|
+
}
|
|
130
|
+
async function extractZip(archivePath, targetDir, env = process.env) {
|
|
131
|
+
if (process.platform === 'win32') {
|
|
132
|
+
const shell = env.ComSpec || process.env.ComSpec || 'powershell.exe';
|
|
133
|
+
const isCmd = shell.toLowerCase().endsWith('cmd.exe');
|
|
134
|
+
if (isCmd) {
|
|
135
|
+
await runProcess('powershell.exe', [
|
|
136
|
+
'-NoProfile',
|
|
137
|
+
'-ExecutionPolicy',
|
|
138
|
+
'Bypass',
|
|
139
|
+
'-Command',
|
|
140
|
+
'Expand-Archive -Force -LiteralPath $args[0] -DestinationPath $args[1]',
|
|
141
|
+
archivePath,
|
|
142
|
+
targetDir,
|
|
143
|
+
], { env });
|
|
144
|
+
return;
|
|
145
|
+
}
|
|
146
|
+
await runProcess(shell, [
|
|
147
|
+
'-NoProfile',
|
|
148
|
+
'-ExecutionPolicy',
|
|
149
|
+
'Bypass',
|
|
150
|
+
'-Command',
|
|
151
|
+
'Expand-Archive -Force -LiteralPath $args[0] -DestinationPath $args[1]',
|
|
152
|
+
archivePath,
|
|
153
|
+
targetDir,
|
|
154
|
+
], { env });
|
|
155
|
+
return;
|
|
156
|
+
}
|
|
157
|
+
await runProcess('unzip', ['-q', archivePath, '-d', targetDir], { env });
|
|
158
|
+
}
|
|
159
|
+
async function extractTarGz(archivePath, targetDir, env = process.env) {
|
|
160
|
+
await runProcess('tar', ['-xzf', archivePath, '-C', targetDir], { env });
|
|
161
|
+
}
|
|
162
|
+
async function findRuntimeExecutable(searchRoot, binaryName) {
|
|
163
|
+
const expectedNames = process.platform === 'win32'
|
|
164
|
+
? [`${binaryName}.exe`, binaryName]
|
|
165
|
+
: [binaryName];
|
|
166
|
+
const stack = [searchRoot];
|
|
167
|
+
while (stack.length > 0) {
|
|
168
|
+
const current = stack.pop();
|
|
169
|
+
const entries = await fs.readdir(current, { withFileTypes: true }).catch(() => []);
|
|
170
|
+
for (const entry of entries) {
|
|
171
|
+
const full = path.join(current, entry.name);
|
|
172
|
+
if (entry.isDirectory()) {
|
|
173
|
+
stack.push(full);
|
|
174
|
+
continue;
|
|
175
|
+
}
|
|
176
|
+
if (entry.isFile() && expectedNames.includes(entry.name)) {
|
|
177
|
+
return full;
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
return null;
|
|
182
|
+
}
|
|
183
|
+
async function installFrankenPhp(env = process.env) {
|
|
184
|
+
const releaseApiUrl = env.PIXCODE_FRANKENPHP_RELEASE_API
|
|
185
|
+
|| 'https://api.github.com/repos/php/frankenphp/releases/latest';
|
|
186
|
+
const release = env.PIXCODE_FRANKENPHP_URL ? null : await fetchJson(releaseApiUrl, env);
|
|
187
|
+
const asset = env.PIXCODE_FRANKENPHP_URL
|
|
188
|
+
? {
|
|
189
|
+
name: path.basename(new URL(env.PIXCODE_FRANKENPHP_URL).pathname),
|
|
190
|
+
browser_download_url: env.PIXCODE_FRANKENPHP_URL,
|
|
191
|
+
}
|
|
192
|
+
: selectFrankenPhpAsset(release);
|
|
193
|
+
if (!asset?.browser_download_url) {
|
|
194
|
+
throw new Error('No FrankenPHP binary is available for this operating system and CPU architecture.');
|
|
195
|
+
}
|
|
196
|
+
const baseDir = runtimeDir('frankenphp', env);
|
|
197
|
+
const stagingDir = path.join(baseDir, `.staging-${Date.now()}`);
|
|
198
|
+
const currentDir = path.join(baseDir, 'current');
|
|
199
|
+
await fs.mkdir(stagingDir, { recursive: true });
|
|
200
|
+
const archivePath = path.join(stagingDir, asset.name || 'frankenphp');
|
|
201
|
+
await downloadFile(asset.browser_download_url, archivePath, env);
|
|
202
|
+
let executablePath = archivePath;
|
|
203
|
+
const assetName = (asset.name || '').toLowerCase();
|
|
204
|
+
if (assetName.endsWith('.zip')) {
|
|
205
|
+
await extractZip(archivePath, stagingDir, env);
|
|
206
|
+
executablePath = await findRuntimeExecutable(stagingDir, 'frankenphp');
|
|
207
|
+
if (!executablePath) {
|
|
208
|
+
throw new Error('Downloaded FrankenPHP archive did not contain a frankenphp executable.');
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
else if (assetName.endsWith('.tar.gz') || assetName.endsWith('.tgz')) {
|
|
212
|
+
await extractTarGz(archivePath, stagingDir, env);
|
|
213
|
+
executablePath = await findRuntimeExecutable(stagingDir, 'frankenphp');
|
|
214
|
+
if (!executablePath) {
|
|
215
|
+
throw new Error('Downloaded FrankenPHP archive did not contain a frankenphp executable.');
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
if (process.platform !== 'win32') {
|
|
219
|
+
await fs.chmod(executablePath, 0o755).catch(() => undefined);
|
|
220
|
+
}
|
|
221
|
+
await fs.rm(currentDir, { recursive: true, force: true });
|
|
222
|
+
await fs.mkdir(currentDir, { recursive: true });
|
|
223
|
+
const finalName = process.platform === 'win32' ? 'frankenphp.exe' : 'frankenphp';
|
|
224
|
+
const finalExecutable = path.join(currentDir, finalName);
|
|
225
|
+
await fs.copyFile(executablePath, finalExecutable);
|
|
226
|
+
if (process.platform !== 'win32') {
|
|
227
|
+
await fs.chmod(finalExecutable, 0o755).catch(() => undefined);
|
|
228
|
+
}
|
|
229
|
+
const manifest = {
|
|
230
|
+
id: 'frankenphp',
|
|
231
|
+
label: 'Pixcode PHP runtime',
|
|
232
|
+
provider: 'FrankenPHP',
|
|
233
|
+
version: release?.tag_name || 'custom',
|
|
234
|
+
executablePath: finalExecutable,
|
|
235
|
+
sourceUrl: asset.browser_download_url,
|
|
236
|
+
installedAt: new Date().toISOString(),
|
|
237
|
+
};
|
|
238
|
+
await fs.writeFile(manifestPath('frankenphp', env), JSON.stringify(manifest, null, 2));
|
|
239
|
+
await fs.rm(stagingDir, { recursive: true, force: true }).catch(() => undefined);
|
|
240
|
+
return manifest;
|
|
241
|
+
}
|
|
242
|
+
export async function getManagedRuntimeStatus(id, options = {}) {
|
|
243
|
+
const env = options.env || process.env;
|
|
244
|
+
if (id !== 'frankenphp') {
|
|
245
|
+
return {
|
|
246
|
+
id,
|
|
247
|
+
status: 'unsupported',
|
|
248
|
+
installable: false,
|
|
249
|
+
reason: 'Pixcode does not have a managed runtime for this stack yet.',
|
|
250
|
+
};
|
|
251
|
+
}
|
|
252
|
+
const spawnEnv = buildCliSpawnEnv(env);
|
|
253
|
+
const systemExecutable = findExecutableOnPath('frankenphp', spawnEnv);
|
|
254
|
+
if (systemExecutable) {
|
|
255
|
+
return {
|
|
256
|
+
id,
|
|
257
|
+
label: 'FrankenPHP',
|
|
258
|
+
status: 'system',
|
|
259
|
+
installable: true,
|
|
260
|
+
executablePath: systemExecutable,
|
|
261
|
+
};
|
|
262
|
+
}
|
|
263
|
+
const manifest = await readManifest(id, env);
|
|
264
|
+
if (manifest) {
|
|
265
|
+
return {
|
|
266
|
+
id,
|
|
267
|
+
label: manifest.label || 'Pixcode PHP runtime',
|
|
268
|
+
status: 'installed',
|
|
269
|
+
installable: true,
|
|
270
|
+
executablePath: manifest.executablePath,
|
|
271
|
+
version: manifest.version,
|
|
272
|
+
};
|
|
273
|
+
}
|
|
274
|
+
return {
|
|
275
|
+
id,
|
|
276
|
+
label: 'Pixcode PHP runtime',
|
|
277
|
+
provider: 'FrankenPHP',
|
|
278
|
+
status: 'missing',
|
|
279
|
+
installable: true,
|
|
280
|
+
reason: 'Pixcode will prepare a local PHP runtime automatically before starting this project.',
|
|
281
|
+
};
|
|
282
|
+
}
|
|
283
|
+
export async function ensureManagedRuntime(id, options = {}) {
|
|
284
|
+
const env = options.env || process.env;
|
|
285
|
+
const status = await getManagedRuntimeStatus(id, { env });
|
|
286
|
+
if (status.executablePath)
|
|
287
|
+
return status;
|
|
288
|
+
if (!status.installable) {
|
|
289
|
+
throw new Error(status.reason || 'This runtime cannot be prepared automatically.');
|
|
290
|
+
}
|
|
291
|
+
const lockKey = `${runtimesHome(env)}:${id}`;
|
|
292
|
+
if (installLocks.has(lockKey))
|
|
293
|
+
return installLocks.get(lockKey);
|
|
294
|
+
const installPromise = (async () => {
|
|
295
|
+
if (id === 'frankenphp') {
|
|
296
|
+
const manifest = await installFrankenPhp(env);
|
|
297
|
+
return {
|
|
298
|
+
id,
|
|
299
|
+
label: manifest.label,
|
|
300
|
+
status: 'installed',
|
|
301
|
+
installable: true,
|
|
302
|
+
executablePath: manifest.executablePath,
|
|
303
|
+
version: manifest.version,
|
|
304
|
+
};
|
|
305
|
+
}
|
|
306
|
+
throw new Error(`Unsupported managed runtime: ${id}`);
|
|
307
|
+
})().finally(() => {
|
|
308
|
+
installLocks.delete(lockKey);
|
|
309
|
+
});
|
|
310
|
+
installLocks.set(lockKey, installPromise);
|
|
311
|
+
return installPromise;
|
|
312
|
+
}
|
|
313
|
+
//# sourceMappingURL=managed-runtimes.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"managed-runtimes.js","sourceRoot":"","sources":["../../../server/services/managed-runtimes.js"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pixelbyte-software/pixcode",
|
|
3
|
-
"version": "1.40.
|
|
3
|
+
"version": "1.40.4",
|
|
4
4
|
"description": "Self-hosted AI coding agent control room for Claude Code, Cursor CLI, OpenAI Codex, Gemini CLI, Qwen Code, and OpenCode with chat, files, shell, Git, orchestration, API keys, Telegram, MCP, plugins, themes, and desktop/server deployment.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist-server/server/index.js",
|
|
@@ -83,11 +83,25 @@ assert.ok(
|
|
|
83
83
|
liveViewPanel.includes('targetUnavailableReason') && liveViewPanel.includes('liveView.runnerUnavailable'),
|
|
84
84
|
'Live View panel should show a clear unavailable-runner message before launching a missing runtime.',
|
|
85
85
|
);
|
|
86
|
+
assert.ok(
|
|
87
|
+
liveViewPanel.includes('managedRuntime') && liveViewPanel.includes('liveView.managedRuntimePreparing'),
|
|
88
|
+
'Live View panel should explain that Pixcode can prepare managed runtimes automatically.',
|
|
89
|
+
);
|
|
86
90
|
assert.ok(
|
|
87
91
|
liveViewPanel.includes("runAction('restart')"),
|
|
88
92
|
'Live View panel should expose a restart action for failed process runners.',
|
|
89
93
|
);
|
|
90
94
|
|
|
95
|
+
const managedRuntimes = await read('server/services/managed-runtimes.js');
|
|
96
|
+
assert.ok(
|
|
97
|
+
managedRuntimes.includes("process.platform === 'win32'") && managedRuntimes.includes("process.platform === 'darwin'") && managedRuntimes.includes("process.platform === 'linux'"),
|
|
98
|
+
'Managed runtime selection should explicitly handle Windows, macOS, and Linux assets.',
|
|
99
|
+
);
|
|
100
|
+
assert.ok(
|
|
101
|
+
managedRuntimes.includes('extractZip') && managedRuntimes.includes('extractTarGz'),
|
|
102
|
+
'Managed runtime installation should handle common Windows zip and macOS/Linux tarball assets.',
|
|
103
|
+
);
|
|
104
|
+
|
|
91
105
|
const serverIndex = await read('server/index.js');
|
|
92
106
|
assert.ok(
|
|
93
107
|
serverIndex.includes("app.use('/api/live-view', authenticateToken, liveViewRoutes)"),
|
|
@@ -148,12 +162,14 @@ const phpMissingRuntimeTarget = await detectLiveViewTarget(phpProject, {
|
|
|
148
162
|
PATH: '',
|
|
149
163
|
},
|
|
150
164
|
});
|
|
151
|
-
assert.equal(phpMissingRuntimeTarget.available,
|
|
165
|
+
assert.equal(phpMissingRuntimeTarget.available, true, 'PHP projects should remain runnable through a Pixcode-managed runtime when php is missing from PATH.');
|
|
152
166
|
assert.equal(phpMissingRuntimeTarget.framework, 'PHP', 'Missing PHP runtime diagnostics should keep the detected framework.');
|
|
153
|
-
assert.
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
167
|
+
assert.equal(phpMissingRuntimeTarget.managedRuntime?.id, 'frankenphp', 'Missing PHP should select the Pixcode-managed FrankenPHP runtime.');
|
|
168
|
+
assert.equal(phpMissingRuntimeTarget.managedRuntime?.status, 'missing', 'Missing PHP should report that the managed runtime still needs preparation.');
|
|
169
|
+
assert.equal(phpMissingRuntimeTarget.command?.id, 'frankenphp-php-server', 'Missing PHP should use a managed FrankenPHP server command.');
|
|
170
|
+
assert.ok(
|
|
171
|
+
!/PATH/i.test(phpMissingRuntimeTarget.reason || ''),
|
|
172
|
+
'Missing PHP should use product language instead of exposing PATH setup as the primary message.',
|
|
157
173
|
);
|
|
158
174
|
|
|
159
175
|
const staticSession = await startLiveView('static-smoke', staticProject);
|
|
@@ -50,8 +50,8 @@ function buildLiveViewSuggestions(session, reason) {
|
|
|
50
50
|
const suggestions = [];
|
|
51
51
|
|
|
52
52
|
if (framework.includes('php')) {
|
|
53
|
-
suggestions.push('
|
|
54
|
-
suggestions.push('If
|
|
53
|
+
suggestions.push('Pixcode can prepare a local PHP runtime automatically and keep it under your user profile.');
|
|
54
|
+
suggestions.push('If the automatic runtime download fails, check the Live View panel logs and retry.');
|
|
55
55
|
suggestions.push('Check that the project has an index.php or a valid PHP router file in the selected project root.');
|
|
56
56
|
} else if (
|
|
57
57
|
framework.includes('javascript')
|
|
@@ -4,6 +4,8 @@ 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 { ensureManagedRuntime, getManagedRuntimeStatus } from './managed-runtimes.js';
|
|
8
|
+
|
|
7
9
|
const sessionsByProject = new Map();
|
|
8
10
|
const sessionsByShareId = new Map();
|
|
9
11
|
const READY_TIMEOUT_MS = 12000;
|
|
@@ -92,17 +94,17 @@ function isPathLikeCommand(command) {
|
|
|
92
94
|
}
|
|
93
95
|
|
|
94
96
|
function runtimeMissingReason(command, framework) {
|
|
95
|
-
const base = `${command}
|
|
97
|
+
const base = `${command} is not available on this machine.`;
|
|
96
98
|
if (framework === 'PHP' || command === 'php') {
|
|
97
|
-
return
|
|
99
|
+
return 'Pixcode can prepare a local PHP runtime automatically before starting this project.';
|
|
98
100
|
}
|
|
99
101
|
if (command === 'npm' || command === 'pnpm' || command === 'yarn' || command === 'bun') {
|
|
100
|
-
return `${base}
|
|
102
|
+
return `${base} Pixcode will use its bundled Node runtime when possible; otherwise install the package manager or use a custom command.`;
|
|
101
103
|
}
|
|
102
104
|
if (command === 'python' || command === 'python3') {
|
|
103
|
-
return `${base}
|
|
105
|
+
return `${base} Pixcode does not have a managed Python runtime for this stack yet.`;
|
|
104
106
|
}
|
|
105
|
-
return `${base}
|
|
107
|
+
return `${base} Pixcode does not have a managed ${framework || command} runtime for this stack yet.`;
|
|
106
108
|
}
|
|
107
109
|
|
|
108
110
|
async function checkCommandAvailability(command, env = process.env) {
|
|
@@ -173,6 +175,25 @@ function buildPackageCommand(packageManager, scriptName, id, label, framework, e
|
|
|
173
175
|
};
|
|
174
176
|
}
|
|
175
177
|
|
|
178
|
+
function buildManagedPhpCommand(runtimeStatus) {
|
|
179
|
+
const executable = runtimeStatus?.executablePath || 'frankenphp';
|
|
180
|
+
return {
|
|
181
|
+
id: 'frankenphp-php-server',
|
|
182
|
+
label: 'Pixcode PHP runtime',
|
|
183
|
+
framework: 'PHP',
|
|
184
|
+
command: executable,
|
|
185
|
+
args: ['php-server', '-r', '.'],
|
|
186
|
+
displayCommand: `${executable} php-server -r .`,
|
|
187
|
+
env: {
|
|
188
|
+
SERVER_NAME: 'http://127.0.0.1:$PORT',
|
|
189
|
+
},
|
|
190
|
+
managedRuntime: {
|
|
191
|
+
id: 'frankenphp',
|
|
192
|
+
status: runtimeStatus?.status || 'missing',
|
|
193
|
+
},
|
|
194
|
+
};
|
|
195
|
+
}
|
|
196
|
+
|
|
176
197
|
function detectPackageCommand(packageJson, packageManager) {
|
|
177
198
|
const scripts = packageJson.scripts || {};
|
|
178
199
|
const devScript = String(scripts.dev || '');
|
|
@@ -243,9 +264,22 @@ function withPort(command, port) {
|
|
|
243
264
|
...command,
|
|
244
265
|
args: command.args.map((arg) => arg.replaceAll('$PORT', String(port))),
|
|
245
266
|
displayCommand: command.displayCommand.replaceAll('$PORT', String(port)),
|
|
267
|
+
env: command.env
|
|
268
|
+
? Object.fromEntries(Object.entries(command.env).map(([key, value]) => [
|
|
269
|
+
key,
|
|
270
|
+
String(value).replaceAll('$PORT', String(port)),
|
|
271
|
+
]))
|
|
272
|
+
: undefined,
|
|
246
273
|
};
|
|
247
274
|
}
|
|
248
275
|
|
|
276
|
+
function shouldUseShell(command) {
|
|
277
|
+
if (command.shell) return true;
|
|
278
|
+
if (process.platform !== 'win32') return false;
|
|
279
|
+
if (path.isAbsolute(command.command) && command.command.toLowerCase().endsWith('.exe')) return false;
|
|
280
|
+
return true;
|
|
281
|
+
}
|
|
282
|
+
|
|
249
283
|
async function detectStaticRoot(projectPath) {
|
|
250
284
|
const candidates = [
|
|
251
285
|
projectPath,
|
|
@@ -382,6 +416,22 @@ export async function detectLiveViewTarget(projectPath, options = {}) {
|
|
|
382
416
|
if (processCommand) {
|
|
383
417
|
const runtimeAvailable = await checkCommandAvailability(processCommand.command, options.env || process.env);
|
|
384
418
|
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
|
+
|
|
385
435
|
return {
|
|
386
436
|
available: false,
|
|
387
437
|
kind: 'process',
|
|
@@ -483,6 +533,7 @@ function publicSession(session) {
|
|
|
483
533
|
label: session.command.label,
|
|
484
534
|
displayCommand: session.command.displayCommand,
|
|
485
535
|
} : null,
|
|
536
|
+
managedRuntime: session.managedRuntime || null,
|
|
486
537
|
port: session.port,
|
|
487
538
|
upstreamUrl: session.upstreamUrl,
|
|
488
539
|
startedAt: session.startedAt,
|
|
@@ -564,7 +615,16 @@ export async function startLiveView(projectName, projectPath, options = {}) {
|
|
|
564
615
|
}
|
|
565
616
|
|
|
566
617
|
const port = await findFreePort();
|
|
567
|
-
|
|
618
|
+
let runtimeStatus = target.managedRuntime || target.command?.managedRuntime || null;
|
|
619
|
+
let targetCommand = target.command;
|
|
620
|
+
if (runtimeStatus?.id && runtimeStatus.status !== 'system' && runtimeStatus.status !== 'installed') {
|
|
621
|
+
runtimeStatus = await ensureManagedRuntime(runtimeStatus.id);
|
|
622
|
+
if (runtimeStatus.id === 'frankenphp') {
|
|
623
|
+
targetCommand = buildManagedPhpCommand(runtimeStatus);
|
|
624
|
+
}
|
|
625
|
+
}
|
|
626
|
+
|
|
627
|
+
const command = withPort(targetCommand, port);
|
|
568
628
|
const session = {
|
|
569
629
|
projectName,
|
|
570
630
|
projectPath,
|
|
@@ -574,6 +634,7 @@ export async function startLiveView(projectName, projectPath, options = {}) {
|
|
|
574
634
|
framework: target.framework,
|
|
575
635
|
label: target.label,
|
|
576
636
|
command,
|
|
637
|
+
managedRuntime: runtimeStatus,
|
|
577
638
|
port,
|
|
578
639
|
host: '127.0.0.1',
|
|
579
640
|
upstreamUrl: `http://127.0.0.1:${port}`,
|
|
@@ -589,6 +650,7 @@ export async function startLiveView(projectName, projectPath, options = {}) {
|
|
|
589
650
|
|
|
590
651
|
const env = {
|
|
591
652
|
...process.env,
|
|
653
|
+
...(command.env || {}),
|
|
592
654
|
PORT: String(port),
|
|
593
655
|
HOST: '127.0.0.1',
|
|
594
656
|
VITE_HOST: '127.0.0.1',
|
|
@@ -599,7 +661,7 @@ export async function startLiveView(projectName, projectPath, options = {}) {
|
|
|
599
661
|
const child = spawn(command.command, command.args, {
|
|
600
662
|
cwd: projectPath,
|
|
601
663
|
env,
|
|
602
|
-
shell:
|
|
664
|
+
shell: shouldUseShell(command),
|
|
603
665
|
stdio: ['ignore', 'pipe', 'pipe'],
|
|
604
666
|
});
|
|
605
667
|
|