@pixelbyte-software/pixcode 1.41.3 → 1.41.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-Bq2twGSW.js → index-BmK4VcVP.js} +52 -52
- package/dist/assets/index-CHa1760s.css +32 -0
- package/dist/index.html +2 -2
- package/dist-server/server/routes/live-view.js +44 -8
- package/dist-server/server/routes/live-view.js.map +1 -1
- package/dist-server/server/services/live-view.js +105 -100
- package/dist-server/server/services/live-view.js.map +1 -1
- package/dist-server/server/services/runtime-manager.js +312 -0
- package/dist-server/server/services/runtime-manager.js.map +1 -0
- package/package.json +2 -1
- package/scripts/smoke/live-view-diagnostics.mjs +2 -2
- package/scripts/smoke/live-view-environment.mjs +92 -0
- package/scripts/smoke/live-view-integration.mjs +58 -46
- package/scripts/smoke/runtime-manager.mjs +99 -0
- package/server/routes/live-view.js +45 -8
- package/server/services/live-view.js +110 -107
- package/server/services/runtime-manager.js +323 -0
- package/dist/assets/index-BC8CXTJj.css +0 -32
|
@@ -0,0 +1,312 @@
|
|
|
1
|
+
import { spawn } from 'node:child_process';
|
|
2
|
+
import fs from 'node:fs';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
import { buildCliSpawnEnv, findExecutableOnPath, resolveNpmCommand } from './install-jobs.js';
|
|
5
|
+
import { getManagedRuntimeStatus } from './managed-runtimes.js';
|
|
6
|
+
const RUNTIME_CHECK_TIMEOUT_MS = 1800;
|
|
7
|
+
export const RUNTIME_DEFINITIONS = {
|
|
8
|
+
node: {
|
|
9
|
+
id: 'node',
|
|
10
|
+
name: 'Node.js',
|
|
11
|
+
commands: ['node'],
|
|
12
|
+
versionArgs: ['--version'],
|
|
13
|
+
installAction: 'Install Node.js 22 or newer and ensure node is available on PATH.',
|
|
14
|
+
},
|
|
15
|
+
npm: {
|
|
16
|
+
id: 'npm',
|
|
17
|
+
name: 'Node package runner',
|
|
18
|
+
commands: ['npm'],
|
|
19
|
+
versionArgs: ['--version'],
|
|
20
|
+
managedRuntimeId: 'npm',
|
|
21
|
+
installAction: 'Install Node.js/npm or let Pixcode prepare its managed Node package runner.',
|
|
22
|
+
},
|
|
23
|
+
php: {
|
|
24
|
+
id: 'php',
|
|
25
|
+
name: 'PHP',
|
|
26
|
+
commands: ['php'],
|
|
27
|
+
versionArgs: ['--version'],
|
|
28
|
+
managedRuntimeId: 'frankenphp',
|
|
29
|
+
installAction: 'Install PHP or let Pixcode prepare its managed PHP runtime.',
|
|
30
|
+
},
|
|
31
|
+
python: {
|
|
32
|
+
id: 'python',
|
|
33
|
+
name: 'Python',
|
|
34
|
+
commands: process.platform === 'win32' ? ['python', 'py'] : ['python3', 'python'],
|
|
35
|
+
versionArgs: ['--version'],
|
|
36
|
+
installAction: 'Install Python 3 and ensure python3 or python is available on PATH.',
|
|
37
|
+
},
|
|
38
|
+
go: {
|
|
39
|
+
id: 'go',
|
|
40
|
+
name: 'Go',
|
|
41
|
+
commands: ['go'],
|
|
42
|
+
versionArgs: ['version'],
|
|
43
|
+
installAction: 'Install Go and ensure go is available on PATH.',
|
|
44
|
+
},
|
|
45
|
+
java: {
|
|
46
|
+
id: 'java',
|
|
47
|
+
name: 'Java',
|
|
48
|
+
commands: ['java'],
|
|
49
|
+
versionArgs: ['-version'],
|
|
50
|
+
installAction: 'Install a JDK and ensure java is available on PATH.',
|
|
51
|
+
},
|
|
52
|
+
rust: {
|
|
53
|
+
id: 'rust',
|
|
54
|
+
name: 'Rust',
|
|
55
|
+
commands: ['cargo', 'rustc'],
|
|
56
|
+
versionArgs: ['--version'],
|
|
57
|
+
installAction: 'Install Rust with rustup and ensure cargo is available on PATH.',
|
|
58
|
+
},
|
|
59
|
+
};
|
|
60
|
+
function runtimeDiagnostic(definition, status, detail) {
|
|
61
|
+
if (status === 'available') {
|
|
62
|
+
return {
|
|
63
|
+
message: `${definition.name} is available.`,
|
|
64
|
+
action: 'No action needed.',
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
if (status === 'too_old') {
|
|
68
|
+
return {
|
|
69
|
+
message: `${definition.name} is installed but does not meet the required version.`,
|
|
70
|
+
action: definition.installAction,
|
|
71
|
+
detail,
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
if (status === 'error') {
|
|
75
|
+
return {
|
|
76
|
+
message: `${definition.name} was found but could not be started.`,
|
|
77
|
+
action: definition.installAction,
|
|
78
|
+
detail,
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
return {
|
|
82
|
+
message: `${definition.name} is not available on this machine.`,
|
|
83
|
+
action: definition.installAction,
|
|
84
|
+
detail,
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
function normalizeVersion(output) {
|
|
88
|
+
const match = String(output || '').match(/(?:v|version\s*)?(\d+\.\d+\.\d+)/i);
|
|
89
|
+
return match?.[1];
|
|
90
|
+
}
|
|
91
|
+
function runVersion(command, args, env) {
|
|
92
|
+
return new Promise((resolve) => {
|
|
93
|
+
let output = '';
|
|
94
|
+
let settled = false;
|
|
95
|
+
const child = spawn(command, args, {
|
|
96
|
+
env,
|
|
97
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
98
|
+
windowsHide: true,
|
|
99
|
+
});
|
|
100
|
+
const finish = (result) => {
|
|
101
|
+
if (settled)
|
|
102
|
+
return;
|
|
103
|
+
settled = true;
|
|
104
|
+
clearTimeout(timer);
|
|
105
|
+
resolve(result);
|
|
106
|
+
};
|
|
107
|
+
const timer = setTimeout(() => {
|
|
108
|
+
try {
|
|
109
|
+
child.kill();
|
|
110
|
+
}
|
|
111
|
+
catch {
|
|
112
|
+
// Ignore a raced process exit.
|
|
113
|
+
}
|
|
114
|
+
finish({ ok: false, error: `${command} version check timed out.` });
|
|
115
|
+
}, RUNTIME_CHECK_TIMEOUT_MS);
|
|
116
|
+
child.stdout.on('data', (chunk) => {
|
|
117
|
+
output += chunk.toString();
|
|
118
|
+
});
|
|
119
|
+
child.stderr.on('data', (chunk) => {
|
|
120
|
+
output += chunk.toString();
|
|
121
|
+
});
|
|
122
|
+
child.on('error', (error) => {
|
|
123
|
+
finish({ ok: false, error: error.message });
|
|
124
|
+
});
|
|
125
|
+
child.on('exit', (code) => {
|
|
126
|
+
finish({
|
|
127
|
+
ok: code === 0,
|
|
128
|
+
output,
|
|
129
|
+
error: code === 0 ? undefined : `${command} exited with code ${code}.`,
|
|
130
|
+
});
|
|
131
|
+
});
|
|
132
|
+
});
|
|
133
|
+
}
|
|
134
|
+
function findExecutableOnDeclaredPath(name, env = process.env) {
|
|
135
|
+
const separator = process.platform === 'win32' ? ';' : ':';
|
|
136
|
+
const extensions = process.platform === 'win32'
|
|
137
|
+
? ['.cmd', '.exe', '.bat', '.ps1', '']
|
|
138
|
+
: [''];
|
|
139
|
+
const pathDirs = (env.PATH || env.Path || '').split(separator).filter(Boolean);
|
|
140
|
+
for (const dir of pathDirs) {
|
|
141
|
+
for (const extension of extensions) {
|
|
142
|
+
const candidate = path.join(dir, `${name}${extension}`);
|
|
143
|
+
try {
|
|
144
|
+
if (candidate && fs.existsSync(candidate))
|
|
145
|
+
return candidate;
|
|
146
|
+
}
|
|
147
|
+
catch {
|
|
148
|
+
// Ignore invalid PATH entries.
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
return null;
|
|
153
|
+
}
|
|
154
|
+
function commandPathForRuntime(id, definition, env, strictPath = false) {
|
|
155
|
+
const spawnEnv = buildCliSpawnEnv(env);
|
|
156
|
+
if (id === 'node')
|
|
157
|
+
return process.execPath;
|
|
158
|
+
if (id === 'npm')
|
|
159
|
+
return resolveNpmCommand(spawnEnv);
|
|
160
|
+
for (const command of definition.commands) {
|
|
161
|
+
const executable = strictPath
|
|
162
|
+
? findExecutableOnDeclaredPath(command, env)
|
|
163
|
+
: findExecutableOnPath(command, spawnEnv);
|
|
164
|
+
if (executable)
|
|
165
|
+
return executable;
|
|
166
|
+
}
|
|
167
|
+
return null;
|
|
168
|
+
}
|
|
169
|
+
async function discoverSystemRuntime(id, definition, env, options = {}) {
|
|
170
|
+
const executable = commandPathForRuntime(id, definition, env, Boolean(options.strictPath));
|
|
171
|
+
if (!executable) {
|
|
172
|
+
return {
|
|
173
|
+
id,
|
|
174
|
+
name: definition.name,
|
|
175
|
+
status: 'missing',
|
|
176
|
+
source: 'none',
|
|
177
|
+
installStatus: 'missing',
|
|
178
|
+
diagnostic: runtimeDiagnostic(definition, 'missing'),
|
|
179
|
+
};
|
|
180
|
+
}
|
|
181
|
+
const versionResult = await runVersion(executable, definition.versionArgs, env);
|
|
182
|
+
if (!versionResult.ok) {
|
|
183
|
+
return {
|
|
184
|
+
id,
|
|
185
|
+
name: definition.name,
|
|
186
|
+
status: 'error',
|
|
187
|
+
source: 'system',
|
|
188
|
+
path: executable,
|
|
189
|
+
installStatus: 'installed',
|
|
190
|
+
diagnostic: runtimeDiagnostic(definition, 'error', versionResult.error),
|
|
191
|
+
};
|
|
192
|
+
}
|
|
193
|
+
return {
|
|
194
|
+
id,
|
|
195
|
+
name: definition.name,
|
|
196
|
+
status: 'available',
|
|
197
|
+
source: 'system',
|
|
198
|
+
path: executable,
|
|
199
|
+
version: normalizeVersion(versionResult.output),
|
|
200
|
+
installStatus: 'installed',
|
|
201
|
+
diagnostic: runtimeDiagnostic(definition, 'available'),
|
|
202
|
+
};
|
|
203
|
+
}
|
|
204
|
+
async function discoverManagedRuntime(id, definition, env, preferManaged) {
|
|
205
|
+
if (!definition.managedRuntimeId || !preferManaged)
|
|
206
|
+
return null;
|
|
207
|
+
const managedRuntime = await getManagedRuntimeStatus(definition.managedRuntimeId, {
|
|
208
|
+
env,
|
|
209
|
+
preferManaged: true,
|
|
210
|
+
});
|
|
211
|
+
const available = managedRuntime.status === 'installed' || managedRuntime.status === 'system';
|
|
212
|
+
return {
|
|
213
|
+
id,
|
|
214
|
+
name: definition.name,
|
|
215
|
+
status: available ? 'available' : 'missing',
|
|
216
|
+
source: 'managed',
|
|
217
|
+
path: managedRuntime.executablePath,
|
|
218
|
+
version: managedRuntime.version,
|
|
219
|
+
installStatus: managedRuntime.status,
|
|
220
|
+
installable: managedRuntime.installable,
|
|
221
|
+
managedRuntime,
|
|
222
|
+
diagnostic: runtimeDiagnostic(definition, available ? 'available' : 'missing', managedRuntime.reason),
|
|
223
|
+
};
|
|
224
|
+
}
|
|
225
|
+
export async function discoverRuntime(id, options = {}) {
|
|
226
|
+
const definition = RUNTIME_DEFINITIONS[id];
|
|
227
|
+
if (!definition) {
|
|
228
|
+
return {
|
|
229
|
+
id,
|
|
230
|
+
name: id,
|
|
231
|
+
status: 'unsupported',
|
|
232
|
+
source: 'none',
|
|
233
|
+
installStatus: 'unsupported',
|
|
234
|
+
diagnostic: {
|
|
235
|
+
message: `${id} is not registered in the Pixcode runtime manager.`,
|
|
236
|
+
action: 'Add a runtime definition before using this stack.',
|
|
237
|
+
},
|
|
238
|
+
};
|
|
239
|
+
}
|
|
240
|
+
const env = options.env || process.env;
|
|
241
|
+
const managed = await discoverManagedRuntime(id, definition, env, Boolean(options.preferManaged));
|
|
242
|
+
if (managed)
|
|
243
|
+
return managed;
|
|
244
|
+
return discoverSystemRuntime(id, definition, env, options);
|
|
245
|
+
}
|
|
246
|
+
function isPackageManagerCommand(command) {
|
|
247
|
+
return command === 'npm' || command === 'pnpm' || command === 'yarn' || command === 'bun';
|
|
248
|
+
}
|
|
249
|
+
function runtimeIdForLiveViewCommand(command) {
|
|
250
|
+
if (!command)
|
|
251
|
+
return null;
|
|
252
|
+
if (isPackageManagerCommand(command.command) || command.packageManager)
|
|
253
|
+
return 'node';
|
|
254
|
+
if (command.framework === 'PHP' || command.command === 'php')
|
|
255
|
+
return 'php';
|
|
256
|
+
if (command.framework === 'Django' || command.framework === 'FastAPI' || command.framework === 'Flask')
|
|
257
|
+
return 'python';
|
|
258
|
+
if (command.framework === 'Go' || command.command === 'go')
|
|
259
|
+
return 'go';
|
|
260
|
+
if (command.framework === 'Rust' || command.command === 'cargo')
|
|
261
|
+
return 'rust';
|
|
262
|
+
if (command.framework === 'Java' || command.command === 'java')
|
|
263
|
+
return 'java';
|
|
264
|
+
return null;
|
|
265
|
+
}
|
|
266
|
+
export async function resolveLiveViewRuntime(command, options = {}) {
|
|
267
|
+
const env = options.env || process.env;
|
|
268
|
+
const runtimeId = runtimeIdForLiveViewCommand(command);
|
|
269
|
+
if (!runtimeId) {
|
|
270
|
+
return {
|
|
271
|
+
available: true,
|
|
272
|
+
reason: 'No dedicated runtime check is required for this Live View command.',
|
|
273
|
+
};
|
|
274
|
+
}
|
|
275
|
+
if (runtimeId === 'node') {
|
|
276
|
+
const runtime = await discoverRuntime('node', { env });
|
|
277
|
+
const managedRuntime = await getManagedRuntimeStatus('npm', {
|
|
278
|
+
env,
|
|
279
|
+
preferManaged: Boolean(options.preferManaged),
|
|
280
|
+
});
|
|
281
|
+
return {
|
|
282
|
+
available: true,
|
|
283
|
+
runtime,
|
|
284
|
+
managedRuntime,
|
|
285
|
+
reason: managedRuntime.reason || 'Pixcode will run this project with its managed Node package runner.',
|
|
286
|
+
};
|
|
287
|
+
}
|
|
288
|
+
if (runtimeId === 'php') {
|
|
289
|
+
const runtime = await discoverRuntime('php', {
|
|
290
|
+
env,
|
|
291
|
+
preferManaged: Boolean(options.preferManaged),
|
|
292
|
+
});
|
|
293
|
+
return {
|
|
294
|
+
available: Boolean(runtime.managedRuntime?.installable) || runtime.status === 'available',
|
|
295
|
+
runtime,
|
|
296
|
+
managedRuntime: runtime.managedRuntime,
|
|
297
|
+
reason: runtime.managedRuntime?.reason || runtime.diagnostic.message,
|
|
298
|
+
};
|
|
299
|
+
}
|
|
300
|
+
const runtime = await discoverRuntime(runtimeId, { env });
|
|
301
|
+
return {
|
|
302
|
+
available: runtime.status === 'available',
|
|
303
|
+
runtime,
|
|
304
|
+
reason: runtime.status === 'available' ? runtime.diagnostic.message : runtime.diagnostic.action,
|
|
305
|
+
};
|
|
306
|
+
}
|
|
307
|
+
export const runtimeManager = {
|
|
308
|
+
definitions: RUNTIME_DEFINITIONS,
|
|
309
|
+
discover: discoverRuntime,
|
|
310
|
+
resolveLiveViewRuntime,
|
|
311
|
+
};
|
|
312
|
+
//# sourceMappingURL=runtime-manager.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"runtime-manager.js","sourceRoot":"","sources":["../../../server/services/runtime-manager.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.41.
|
|
3
|
+
"version": "1.41.5",
|
|
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",
|
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
},
|
|
10
10
|
"files": [
|
|
11
11
|
"server/",
|
|
12
|
+
"!server/database/auth.json",
|
|
12
13
|
"shared/",
|
|
13
14
|
"dist/",
|
|
14
15
|
"dist-server/",
|
|
@@ -39,8 +39,8 @@ assert.match(payload.errorDetail, /spawn php ENOENT/);
|
|
|
39
39
|
assert.ok(payload.diagnostics.command.includes('php -S'), 'Payload should include the attempted PHP command.');
|
|
40
40
|
assert.ok(payload.diagnostics.logs.some((line) => line.includes('spawn php ENOENT')), 'Payload should include process logs.');
|
|
41
41
|
assert.ok(
|
|
42
|
-
payload.suggestions.some((suggestion) => /php/i.test(suggestion) && /
|
|
43
|
-
'PHP failures should
|
|
42
|
+
payload.suggestions.some((suggestion) => /php/i.test(suggestion) && /local PHP runtime/i.test(suggestion)),
|
|
43
|
+
'PHP failures should explain that Pixcode can prepare a local PHP runtime.',
|
|
44
44
|
);
|
|
45
45
|
assert.notEqual(payload.error, 'Live View session not found.', 'Existing failed sessions must not be hidden as missing sessions.');
|
|
46
46
|
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import assert from 'node:assert/strict';
|
|
4
|
+
import { mkdir, mkdtemp, writeFile } from 'node:fs/promises';
|
|
5
|
+
import { tmpdir } from 'node:os';
|
|
6
|
+
import path from 'node:path';
|
|
7
|
+
import { fileURLToPath } from 'node:url';
|
|
8
|
+
|
|
9
|
+
const repoRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '../..');
|
|
10
|
+
const read = async (relativePath) => {
|
|
11
|
+
const { readFile } = await import('node:fs/promises');
|
|
12
|
+
return readFile(path.join(repoRoot, relativePath), 'utf8');
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
const {
|
|
16
|
+
buildLiveViewEnvironment,
|
|
17
|
+
detectLiveViewTarget,
|
|
18
|
+
getLiveViewState,
|
|
19
|
+
} = await import('../../server/services/live-view.js');
|
|
20
|
+
|
|
21
|
+
assert.equal(typeof buildLiveViewEnvironment, 'function', 'Live View should expose a preview environment contract builder.');
|
|
22
|
+
|
|
23
|
+
const workspace = await mkdtemp(path.join(tmpdir(), 'pixcode-live-view-env-'));
|
|
24
|
+
const viteProject = path.join(workspace, 'vite');
|
|
25
|
+
const staticProject = path.join(workspace, 'static');
|
|
26
|
+
|
|
27
|
+
await mkdir(viteProject, { recursive: true });
|
|
28
|
+
await writeFile(path.join(viteProject, 'package.json'), JSON.stringify({
|
|
29
|
+
scripts: { dev: 'vite --host 0.0.0.0' },
|
|
30
|
+
dependencies: { vite: '^7.0.0' },
|
|
31
|
+
}, null, 2));
|
|
32
|
+
|
|
33
|
+
await mkdir(staticProject, { recursive: true });
|
|
34
|
+
await writeFile(path.join(staticProject, 'index.html'), '<main>static preview</main>');
|
|
35
|
+
|
|
36
|
+
const viteTarget = await detectLiveViewTarget(viteProject);
|
|
37
|
+
const readyEnvironment = buildLiveViewEnvironment({ target: viteTarget, session: null });
|
|
38
|
+
assert.equal(readyEnvironment.mode, 'local-process', 'Process targets should use a local-process preview environment.');
|
|
39
|
+
assert.equal(readyEnvironment.status, 'ready', 'Detected process targets should be ready before launch.');
|
|
40
|
+
assert.equal(readyEnvironment.framework, 'Vite', 'The environment should expose detected framework information.');
|
|
41
|
+
assert.equal(readyEnvironment.command.id, 'npm-dev-vite', 'The environment should expose the selected runner command.');
|
|
42
|
+
assert.equal(readyEnvironment.command.custom, false, 'Detected commands should not be marked as custom.');
|
|
43
|
+
assert.equal(readyEnvironment.diagnostics.runnerKind, 'process', 'Diagnostics should identify process runners.');
|
|
44
|
+
assert.equal(readyEnvironment.diagnostics.targetAvailable, true, 'Diagnostics should expose target availability.');
|
|
45
|
+
assert.ok(Array.isArray(readyEnvironment.logs), 'The environment should always expose logs.');
|
|
46
|
+
|
|
47
|
+
const customEnvironment = buildLiveViewEnvironment({
|
|
48
|
+
target: viteTarget,
|
|
49
|
+
session: {
|
|
50
|
+
status: 'starting',
|
|
51
|
+
kind: 'process',
|
|
52
|
+
framework: 'Custom',
|
|
53
|
+
label: 'Custom command',
|
|
54
|
+
command: {
|
|
55
|
+
id: 'custom',
|
|
56
|
+
label: 'Custom command',
|
|
57
|
+
displayCommand: 'npm run preview -- --host 127.0.0.1',
|
|
58
|
+
custom: true,
|
|
59
|
+
},
|
|
60
|
+
runtime: null,
|
|
61
|
+
managedRuntime: null,
|
|
62
|
+
port: 4173,
|
|
63
|
+
upstreamUrl: 'http://127.0.0.1:4173',
|
|
64
|
+
sharePath: '/live/custom-smoke/',
|
|
65
|
+
error: null,
|
|
66
|
+
log: ['$ npm run preview -- --host 127.0.0.1', 'Local: http://127.0.0.1:4173/'],
|
|
67
|
+
},
|
|
68
|
+
});
|
|
69
|
+
assert.equal(customEnvironment.status, 'starting', 'The environment should track the active session status.');
|
|
70
|
+
assert.equal(customEnvironment.command.custom, true, 'Custom commands should be visible in the environment model.');
|
|
71
|
+
assert.equal(customEnvironment.port, 4173, 'The environment should expose the active preview port.');
|
|
72
|
+
assert.equal(customEnvironment.upstreamUrl, 'http://127.0.0.1:4173', 'The environment should expose the active upstream URL.');
|
|
73
|
+
assert.deepEqual(customEnvironment.logs, customEnvironment.logs.slice(-40), 'The environment should expose bounded logs.');
|
|
74
|
+
|
|
75
|
+
const staticTarget = await detectLiveViewTarget(staticProject);
|
|
76
|
+
const staticEnvironment = buildLiveViewEnvironment({ target: staticTarget, session: null });
|
|
77
|
+
assert.equal(staticEnvironment.mode, 'static', 'Static projects should use a static preview environment.');
|
|
78
|
+
assert.equal(staticEnvironment.framework, 'Static HTML', 'Static framework detection should be visible.');
|
|
79
|
+
assert.equal(staticEnvironment.diagnostics.runnerKind, 'static', 'Static diagnostics should identify direct serving.');
|
|
80
|
+
|
|
81
|
+
const state = await getLiveViewState('vite-env-smoke', viteProject);
|
|
82
|
+
assert.equal(state.environment.mode, 'local-process', 'Live View state should return the unified environment.');
|
|
83
|
+
assert.equal(state.environment.command.id, 'npm-dev-vite', 'The state environment should use the detected command.');
|
|
84
|
+
|
|
85
|
+
const liveViewPanel = await read('src/components/live-view/LiveViewPanel.tsx');
|
|
86
|
+
assert.ok(liveViewPanel.includes('environment'), 'Live View panel should render the unified environment model.');
|
|
87
|
+
assert.ok(liveViewPanel.includes('liveView.environment'), 'Live View panel should label the environment surface.');
|
|
88
|
+
assert.ok(liveViewPanel.includes('command.custom'), 'Live View panel should make custom command state visible.');
|
|
89
|
+
assert.ok(liveViewPanel.includes('environment.logs'), 'Live View panel should render logs from the environment model.');
|
|
90
|
+
assert.ok(liveViewPanel.includes('environment.diagnostics'), 'Live View panel should render diagnostics from the environment model.');
|
|
91
|
+
|
|
92
|
+
console.log('live view environment smoke passed');
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { access, chmod, mkdtemp, readFile, writeFile } from 'node:fs/promises';
|
|
2
|
+
import net from 'node:net';
|
|
2
3
|
import { tmpdir } from 'node:os';
|
|
3
4
|
import path from 'node:path';
|
|
4
5
|
import { fileURLToPath } from 'node:url';
|
|
@@ -10,6 +11,13 @@ const read = async (relativePath) => {
|
|
|
10
11
|
return readFile(path.join(repoRoot, relativePath), 'utf8');
|
|
11
12
|
};
|
|
12
13
|
const fileExists = async (filePath) => access(filePath).then(() => true, () => false);
|
|
14
|
+
const canBindLoopback = () => new Promise((resolve) => {
|
|
15
|
+
const server = net.createServer();
|
|
16
|
+
server.once('error', () => resolve(false));
|
|
17
|
+
server.listen(0, '127.0.0.1', () => {
|
|
18
|
+
server.close(() => resolve(true));
|
|
19
|
+
});
|
|
20
|
+
});
|
|
13
21
|
|
|
14
22
|
const appTypes = await read('src/types/app.ts');
|
|
15
23
|
assert.ok(
|
|
@@ -386,53 +394,57 @@ const brokenStatus = await getManagedRuntimeStatus('frankenphp', {
|
|
|
386
394
|
});
|
|
387
395
|
assert.equal(brokenStatus.status, 'missing', 'Broken managed FrankenPHP manifests should be treated as missing so Pixcode can reinstall them.');
|
|
388
396
|
|
|
389
|
-
|
|
390
|
-
const
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
'
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
process.env.PIXCODE_MANAGED_RUNTIMES_HOME
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
397
|
+
if (await canBindLoopback()) {
|
|
398
|
+
const phpRuntimeEnvHome = path.join(workspace, 'php-runtime-env');
|
|
399
|
+
const phpRuntimeCurrent = path.join(phpRuntimeEnvHome, 'frankenphp', 'current');
|
|
400
|
+
await mkdir(phpRuntimeCurrent, { recursive: true });
|
|
401
|
+
const phpRuntimeExecutable = path.join(phpRuntimeCurrent, process.platform === 'win32' ? 'frankenphp.cmd' : 'frankenphp');
|
|
402
|
+
await writeFile(phpRuntimeExecutable, [
|
|
403
|
+
'#!/usr/bin/env node',
|
|
404
|
+
'const http = require("node:http");',
|
|
405
|
+
'const path = require("node:path");',
|
|
406
|
+
'const runtimeDir = __dirname;',
|
|
407
|
+
'if (process.argv.includes("version")) process.exit(0);',
|
|
408
|
+
'const pathValue = process.env.Path || process.env.PATH || "";',
|
|
409
|
+
'if (!pathValue.split(path.delimiter).includes(runtimeDir)) {',
|
|
410
|
+
' console.error("runtime path missing from PATH");',
|
|
411
|
+
' process.exit(1);',
|
|
412
|
+
'}',
|
|
413
|
+
'const port = Number(process.env.PORT || 0);',
|
|
414
|
+
'http.createServer((req, res) => res.end("php runtime ok")).listen(port, "127.0.0.1");',
|
|
415
|
+
'',
|
|
416
|
+
].join('\n'));
|
|
417
|
+
if (process.platform !== 'win32') {
|
|
418
|
+
await chmod(phpRuntimeExecutable, 0o755);
|
|
419
|
+
}
|
|
420
|
+
await writeFile(path.join(phpRuntimeEnvHome, 'frankenphp', 'pixcode-runtime.json'), JSON.stringify({
|
|
421
|
+
id: 'frankenphp',
|
|
422
|
+
label: 'Pixcode PHP runtime',
|
|
423
|
+
executablePath: phpRuntimeExecutable,
|
|
424
|
+
version: 'path-env-smoke',
|
|
425
|
+
}, null, 2));
|
|
426
|
+
const previousRuntimeHome = process.env.PIXCODE_MANAGED_RUNTIMES_HOME;
|
|
427
|
+
process.env.PIXCODE_MANAGED_RUNTIMES_HOME = phpRuntimeEnvHome;
|
|
428
|
+
try {
|
|
429
|
+
const phpRuntimeSession = await startLiveView('php-runtime-env-smoke', phpProject);
|
|
430
|
+
assert.equal(phpRuntimeSession.status, 'running', 'Managed PHP Live View should start with the runtime directory on PATH.');
|
|
431
|
+
await stopLiveView('php-runtime-env-smoke');
|
|
432
|
+
} finally {
|
|
433
|
+
if (previousRuntimeHome === undefined) {
|
|
434
|
+
delete process.env.PIXCODE_MANAGED_RUNTIMES_HOME;
|
|
435
|
+
} else {
|
|
436
|
+
process.env.PIXCODE_MANAGED_RUNTIMES_HOME = previousRuntimeHome;
|
|
437
|
+
}
|
|
428
438
|
}
|
|
429
|
-
}
|
|
430
439
|
|
|
431
|
-
const staticSession = await startLiveView('static-smoke', staticProject);
|
|
432
|
-
assert.equal(staticSession.status, 'running', 'Static Live View should start without a child process.');
|
|
433
|
-
assert.match(staticSession.sharePath, /^\/live\/[a-f0-9]{24}\/$/, 'Live View should expose a random public share path.');
|
|
434
|
-
const staticState = await getLiveViewState('static-smoke', staticProject);
|
|
435
|
-
assert.equal(staticState.session?.shareId, staticSession.shareId, 'Live View state should retain the active share session.');
|
|
436
|
-
await stopLiveView('static-smoke');
|
|
440
|
+
const staticSession = await startLiveView('static-smoke', staticProject);
|
|
441
|
+
assert.equal(staticSession.status, 'running', 'Static Live View should start without a child process.');
|
|
442
|
+
assert.match(staticSession.sharePath, /^\/live\/[a-f0-9]{24}\/$/, 'Live View should expose a random public share path.');
|
|
443
|
+
const staticState = await getLiveViewState('static-smoke', staticProject);
|
|
444
|
+
assert.equal(staticState.session?.shareId, staticSession.shareId, 'Live View state should retain the active share session.');
|
|
445
|
+
await stopLiveView('static-smoke');
|
|
446
|
+
} else {
|
|
447
|
+
console.warn('Skipping Live View launch smoke because this sandbox cannot bind 127.0.0.1.');
|
|
448
|
+
}
|
|
437
449
|
|
|
438
450
|
console.log('live view integration smoke passed');
|