@pixelbyte-software/pixcode 1.41.2 → 1.41.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.
Files changed (27) hide show
  1. package/dist/assets/index-BC8CXTJj.css +32 -0
  2. package/dist/assets/{index-BC6Knu5B.js → index-CIEN0bZ-.js} +169 -169
  3. package/dist/index.html +2 -2
  4. package/dist-server/server/modules/orchestration/index.js.map +1 -1
  5. package/dist-server/server/modules/orchestration/workflows/workflow-runner.js +1 -0
  6. package/dist-server/server/modules/orchestration/workflows/workflow-runner.js.map +1 -1
  7. package/dist-server/server/modules/orchestration/workflows/workflow-trace.js +236 -0
  8. package/dist-server/server/modules/orchestration/workflows/workflow-trace.js.map +1 -0
  9. package/dist-server/server/modules/orchestration/workflows/workflow.routes.js +12 -0
  10. package/dist-server/server/modules/orchestration/workflows/workflow.routes.js.map +1 -1
  11. package/dist-server/server/services/live-view.js +19 -94
  12. package/dist-server/server/services/live-view.js.map +1 -1
  13. package/dist-server/server/services/runtime-manager.js +312 -0
  14. package/dist-server/server/services/runtime-manager.js.map +1 -0
  15. package/package.json +1 -1
  16. package/scripts/smoke/live-view-diagnostics.mjs +2 -2
  17. package/scripts/smoke/live-view-integration.mjs +58 -46
  18. package/scripts/smoke/runtime-manager.mjs +99 -0
  19. package/scripts/smoke/workflow-trace-timeline.mjs +46 -0
  20. package/server/modules/orchestration/index.ts +1 -0
  21. package/server/modules/orchestration/workflows/workflow-runner.ts +1 -0
  22. package/server/modules/orchestration/workflows/workflow-trace.ts +270 -0
  23. package/server/modules/orchestration/workflows/workflow.routes.ts +14 -0
  24. package/server/modules/orchestration/workflows/workflow.types.ts +21 -0
  25. package/server/services/live-view.js +20 -101
  26. package/server/services/runtime-manager.js +323 -0
  27. package/dist/assets/index-LZgOC7Q_.css +0 -32
@@ -5,13 +5,13 @@ import net from 'node:net';
5
5
  import path from 'node:path';
6
6
 
7
7
  import { buildCliSpawnEnv } from './install-jobs.js';
8
- import { ensureManagedRuntime, getManagedRuntimeStatus } from './managed-runtimes.js';
8
+ import { ensureManagedRuntime } from './managed-runtimes.js';
9
+ import { resolveLiveViewRuntime } from './runtime-manager.js';
9
10
 
10
11
  const sessionsByProject = new Map();
11
12
  const sessionsByShareId = new Map();
12
13
  const READY_TIMEOUT_MS = 12000;
13
14
  const LOG_LIMIT = 200;
14
- const RUNTIME_CHECK_TIMEOUT_MS = 1800;
15
15
 
16
16
  const localUrlRegex = /https?:\/\/(?:localhost|127\.0\.0\.1|0\.0\.0\.0|\[[^\]]+\])(?::(\d+))?[^\s"'<>]*/i;
17
17
 
@@ -82,88 +82,6 @@ function buildDisplayCommand(command, args) {
82
82
  return [command, ...args].join(' ');
83
83
  }
84
84
 
85
- function quoteForPosixShell(value) {
86
- return `'${String(value).replaceAll("'", "'\\''")}'`;
87
- }
88
-
89
- function quoteForWindowsShell(value) {
90
- return `"${String(value).replaceAll('"', '""')}"`;
91
- }
92
-
93
- function isPathLikeCommand(command) {
94
- return path.isAbsolute(command) || command.includes('/') || command.includes('\\');
95
- }
96
-
97
- function runtimeMissingReason(command, framework) {
98
- const base = `${command} is not available on this machine.`;
99
- if (framework === 'PHP' || command === 'php') {
100
- return 'Pixcode can prepare a local PHP runtime automatically before starting this project.';
101
- }
102
- if (command === 'npm' || command === 'pnpm' || command === 'yarn' || command === 'bun') {
103
- return `${base} Pixcode can prepare a local Node package runner automatically before starting this project.`;
104
- }
105
- if (command === 'python' || command === 'python3') {
106
- return `${base} Pixcode does not have a managed Python runtime for this stack yet.`;
107
- }
108
- return `${base} Pixcode does not have a managed ${framework || command} runtime for this stack yet.`;
109
- }
110
-
111
- async function checkCommandAvailability(command, env = process.env) {
112
- if (!command || command.includes('\n') || command.includes('\r')) return true;
113
-
114
- if (isPathLikeCommand(command)) {
115
- try {
116
- await fs.access(command);
117
- return true;
118
- } catch {
119
- return false;
120
- }
121
- }
122
-
123
- const checker = process.platform === 'win32'
124
- ? {
125
- command: process.env.ComSpec || 'cmd.exe',
126
- args: ['/d', '/s', '/c', `where ${quoteForWindowsShell(command)}`],
127
- }
128
- : {
129
- command: '/bin/sh',
130
- args: ['-lc', `command -v ${quoteForPosixShell(command)}`],
131
- };
132
-
133
- return new Promise((resolve) => {
134
- let settled = false;
135
- let child = null;
136
- const finish = (available) => {
137
- if (settled) return;
138
- settled = true;
139
- clearTimeout(timer);
140
- resolve(available);
141
- };
142
-
143
- const timer = setTimeout(() => {
144
- try {
145
- child?.kill();
146
- } catch {
147
- // Ignore a raced process exit.
148
- }
149
- finish(true);
150
- }, RUNTIME_CHECK_TIMEOUT_MS);
151
-
152
- child = spawn(checker.command, checker.args, {
153
- env,
154
- stdio: 'ignore',
155
- windowsHide: true,
156
- });
157
-
158
- child.on('error', (error) => {
159
- finish(error?.code === 'ENOENT' ? false : true);
160
- });
161
- child.on('exit', (code) => {
162
- finish(code === 0);
163
- });
164
- });
165
- }
166
-
167
85
  function buildPackageCommand(packageManager, scriptName, id, label, framework, extraArgs = []) {
168
86
  const args = packageRunArgs(packageManager, scriptName, extraArgs);
169
87
  return {
@@ -596,11 +514,13 @@ export async function detectLiveViewTarget(projectPath, options = {}) {
596
514
 
597
515
  const processCommand = await detectProcessCommand(projectPath);
598
516
  if (processCommand) {
517
+ const runtimeResolution = await resolveLiveViewRuntime(processCommand, {
518
+ env: options.env || process.env,
519
+ preferManaged: true,
520
+ });
521
+
599
522
  if (isPackageManagerCommand(processCommand.command)) {
600
- const managedRuntime = await getManagedRuntimeStatus('npm', {
601
- env: options.env || process.env,
602
- preferManaged: true,
603
- });
523
+ const managedRuntime = runtimeResolution.managedRuntime;
604
524
  const command = buildManagedPackageCommand(processCommand, managedRuntime);
605
525
  return {
606
526
  available: true,
@@ -609,17 +529,13 @@ export async function detectLiveViewTarget(projectPath, options = {}) {
609
529
  framework: processCommand.framework,
610
530
  command,
611
531
  managedRuntime,
612
- reason: managedRuntime.status === 'missing'
613
- ? 'Pixcode will prepare a local Node package runner automatically before starting this project.'
614
- : 'Pixcode will run this project with its managed Node package runner.',
532
+ runtime: runtimeResolution.runtime,
533
+ reason: runtimeResolution.reason,
615
534
  };
616
535
  }
617
536
 
618
537
  if (processCommand.framework === 'PHP' || processCommand.command === 'php') {
619
- const managedRuntime = await getManagedRuntimeStatus('frankenphp', {
620
- env: options.env || process.env,
621
- preferManaged: true,
622
- });
538
+ const managedRuntime = runtimeResolution.managedRuntime;
623
539
  const command = buildManagedPhpCommand(managedRuntime);
624
540
  return {
625
541
  available: true,
@@ -628,14 +544,12 @@ export async function detectLiveViewTarget(projectPath, options = {}) {
628
544
  framework: command.framework,
629
545
  command,
630
546
  managedRuntime,
631
- reason: managedRuntime.status === 'missing'
632
- ? 'Pixcode will prepare a local PHP runtime automatically before starting this project.'
633
- : 'Pixcode will run this project with its managed PHP runtime.',
547
+ runtime: runtimeResolution.runtime,
548
+ reason: runtimeResolution.reason,
634
549
  };
635
550
  }
636
551
 
637
- const runtimeAvailable = await checkCommandAvailability(processCommand.command, options.env || process.env);
638
- if (!runtimeAvailable) {
552
+ if (!runtimeResolution.available) {
639
553
  return {
640
554
  available: false,
641
555
  kind: 'process',
@@ -643,7 +557,8 @@ export async function detectLiveViewTarget(projectPath, options = {}) {
643
557
  framework: processCommand.framework,
644
558
  command: processCommand,
645
559
  missingRuntime: processCommand.command,
646
- reason: runtimeMissingReason(processCommand.command, processCommand.framework),
560
+ runtime: runtimeResolution.runtime,
561
+ reason: runtimeResolution.reason,
647
562
  };
648
563
  }
649
564
 
@@ -653,6 +568,7 @@ export async function detectLiveViewTarget(projectPath, options = {}) {
653
568
  label: processCommand.label,
654
569
  framework: processCommand.framework,
655
570
  command: processCommand,
571
+ runtime: runtimeResolution.runtime,
656
572
  };
657
573
  }
658
574
 
@@ -737,6 +653,7 @@ function publicSession(session) {
737
653
  label: session.command.label,
738
654
  displayCommand: session.command.displayCommand,
739
655
  } : null,
656
+ runtime: session.runtime || null,
740
657
  managedRuntime: session.managedRuntime || null,
741
658
  port: session.port,
742
659
  upstreamUrl: session.upstreamUrl,
@@ -803,6 +720,7 @@ export async function startLiveView(projectName, projectPath, options = {}) {
803
720
  label: target.label,
804
721
  staticRoot: target.staticRoot,
805
722
  command: null,
723
+ runtime: null,
806
724
  port: null,
807
725
  upstreamUrl: null,
808
726
  startedAt: new Date().toISOString(),
@@ -842,6 +760,7 @@ export async function startLiveView(projectName, projectPath, options = {}) {
842
760
  framework: target.framework,
843
761
  label: target.label,
844
762
  command,
763
+ runtime: target.runtime || null,
845
764
  managedRuntime: runtimeStatus,
846
765
  port,
847
766
  host: '127.0.0.1',
@@ -0,0 +1,323 @@
1
+ import { spawn } from 'node:child_process';
2
+ import fs from 'node:fs';
3
+ import path from 'node:path';
4
+
5
+ import { buildCliSpawnEnv, findExecutableOnPath, resolveNpmCommand } from './install-jobs.js';
6
+ import { getManagedRuntimeStatus } from './managed-runtimes.js';
7
+
8
+ const RUNTIME_CHECK_TIMEOUT_MS = 1800;
9
+
10
+ export const RUNTIME_DEFINITIONS = {
11
+ node: {
12
+ id: 'node',
13
+ name: 'Node.js',
14
+ commands: ['node'],
15
+ versionArgs: ['--version'],
16
+ installAction: 'Install Node.js 22 or newer and ensure node is available on PATH.',
17
+ },
18
+ npm: {
19
+ id: 'npm',
20
+ name: 'Node package runner',
21
+ commands: ['npm'],
22
+ versionArgs: ['--version'],
23
+ managedRuntimeId: 'npm',
24
+ installAction: 'Install Node.js/npm or let Pixcode prepare its managed Node package runner.',
25
+ },
26
+ php: {
27
+ id: 'php',
28
+ name: 'PHP',
29
+ commands: ['php'],
30
+ versionArgs: ['--version'],
31
+ managedRuntimeId: 'frankenphp',
32
+ installAction: 'Install PHP or let Pixcode prepare its managed PHP runtime.',
33
+ },
34
+ python: {
35
+ id: 'python',
36
+ name: 'Python',
37
+ commands: process.platform === 'win32' ? ['python', 'py'] : ['python3', 'python'],
38
+ versionArgs: ['--version'],
39
+ installAction: 'Install Python 3 and ensure python3 or python is available on PATH.',
40
+ },
41
+ go: {
42
+ id: 'go',
43
+ name: 'Go',
44
+ commands: ['go'],
45
+ versionArgs: ['version'],
46
+ installAction: 'Install Go and ensure go is available on PATH.',
47
+ },
48
+ java: {
49
+ id: 'java',
50
+ name: 'Java',
51
+ commands: ['java'],
52
+ versionArgs: ['-version'],
53
+ installAction: 'Install a JDK and ensure java is available on PATH.',
54
+ },
55
+ rust: {
56
+ id: 'rust',
57
+ name: 'Rust',
58
+ commands: ['cargo', 'rustc'],
59
+ versionArgs: ['--version'],
60
+ installAction: 'Install Rust with rustup and ensure cargo is available on PATH.',
61
+ },
62
+ };
63
+
64
+ function runtimeDiagnostic(definition, status, detail) {
65
+ if (status === 'available') {
66
+ return {
67
+ message: `${definition.name} is available.`,
68
+ action: 'No action needed.',
69
+ };
70
+ }
71
+
72
+ if (status === 'too_old') {
73
+ return {
74
+ message: `${definition.name} is installed but does not meet the required version.`,
75
+ action: definition.installAction,
76
+ detail,
77
+ };
78
+ }
79
+
80
+ if (status === 'error') {
81
+ return {
82
+ message: `${definition.name} was found but could not be started.`,
83
+ action: definition.installAction,
84
+ detail,
85
+ };
86
+ }
87
+
88
+ return {
89
+ message: `${definition.name} is not available on this machine.`,
90
+ action: definition.installAction,
91
+ detail,
92
+ };
93
+ }
94
+
95
+ function normalizeVersion(output) {
96
+ const match = String(output || '').match(/(?:v|version\s*)?(\d+\.\d+\.\d+)/i);
97
+ return match?.[1];
98
+ }
99
+
100
+ function runVersion(command, args, env) {
101
+ return new Promise((resolve) => {
102
+ let output = '';
103
+ let settled = false;
104
+ const child = spawn(command, args, {
105
+ env,
106
+ stdio: ['ignore', 'pipe', 'pipe'],
107
+ windowsHide: true,
108
+ });
109
+
110
+ const finish = (result) => {
111
+ if (settled) return;
112
+ settled = true;
113
+ clearTimeout(timer);
114
+ resolve(result);
115
+ };
116
+ const timer = setTimeout(() => {
117
+ try {
118
+ child.kill();
119
+ } catch {
120
+ // Ignore a raced process exit.
121
+ }
122
+ finish({ ok: false, error: `${command} version check timed out.` });
123
+ }, RUNTIME_CHECK_TIMEOUT_MS);
124
+
125
+ child.stdout.on('data', (chunk) => {
126
+ output += chunk.toString();
127
+ });
128
+ child.stderr.on('data', (chunk) => {
129
+ output += chunk.toString();
130
+ });
131
+ child.on('error', (error) => {
132
+ finish({ ok: false, error: error.message });
133
+ });
134
+ child.on('exit', (code) => {
135
+ finish({
136
+ ok: code === 0,
137
+ output,
138
+ error: code === 0 ? undefined : `${command} exited with code ${code}.`,
139
+ });
140
+ });
141
+ });
142
+ }
143
+
144
+ function findExecutableOnDeclaredPath(name, env = process.env) {
145
+ const separator = process.platform === 'win32' ? ';' : ':';
146
+ const extensions = process.platform === 'win32'
147
+ ? ['.cmd', '.exe', '.bat', '.ps1', '']
148
+ : [''];
149
+ const pathDirs = (env.PATH || env.Path || '').split(separator).filter(Boolean);
150
+ for (const dir of pathDirs) {
151
+ for (const extension of extensions) {
152
+ const candidate = path.join(dir, `${name}${extension}`);
153
+ try {
154
+ if (candidate && fs.existsSync(candidate)) return candidate;
155
+ } catch {
156
+ // Ignore invalid PATH entries.
157
+ }
158
+ }
159
+ }
160
+ return null;
161
+ }
162
+
163
+ function commandPathForRuntime(id, definition, env, strictPath = false) {
164
+ const spawnEnv = buildCliSpawnEnv(env);
165
+ if (id === 'node') return process.execPath;
166
+ if (id === 'npm') return resolveNpmCommand(spawnEnv);
167
+
168
+ for (const command of definition.commands) {
169
+ const executable = strictPath
170
+ ? findExecutableOnDeclaredPath(command, env)
171
+ : findExecutableOnPath(command, spawnEnv);
172
+ if (executable) return executable;
173
+ }
174
+ return null;
175
+ }
176
+
177
+ async function discoverSystemRuntime(id, definition, env, options = {}) {
178
+ const executable = commandPathForRuntime(id, definition, env, Boolean(options.strictPath));
179
+ if (!executable) {
180
+ return {
181
+ id,
182
+ name: definition.name,
183
+ status: 'missing',
184
+ source: 'none',
185
+ installStatus: 'missing',
186
+ diagnostic: runtimeDiagnostic(definition, 'missing'),
187
+ };
188
+ }
189
+
190
+ const versionResult = await runVersion(executable, definition.versionArgs, env);
191
+ if (!versionResult.ok) {
192
+ return {
193
+ id,
194
+ name: definition.name,
195
+ status: 'error',
196
+ source: 'system',
197
+ path: executable,
198
+ installStatus: 'installed',
199
+ diagnostic: runtimeDiagnostic(definition, 'error', versionResult.error),
200
+ };
201
+ }
202
+
203
+ return {
204
+ id,
205
+ name: definition.name,
206
+ status: 'available',
207
+ source: 'system',
208
+ path: executable,
209
+ version: normalizeVersion(versionResult.output),
210
+ installStatus: 'installed',
211
+ diagnostic: runtimeDiagnostic(definition, 'available'),
212
+ };
213
+ }
214
+
215
+ async function discoverManagedRuntime(id, definition, env, preferManaged) {
216
+ if (!definition.managedRuntimeId || !preferManaged) return null;
217
+
218
+ const managedRuntime = await getManagedRuntimeStatus(definition.managedRuntimeId, {
219
+ env,
220
+ preferManaged: true,
221
+ });
222
+ const available = managedRuntime.status === 'installed' || managedRuntime.status === 'system';
223
+ return {
224
+ id,
225
+ name: definition.name,
226
+ status: available ? 'available' : 'missing',
227
+ source: 'managed',
228
+ path: managedRuntime.executablePath,
229
+ version: managedRuntime.version,
230
+ installStatus: managedRuntime.status,
231
+ installable: managedRuntime.installable,
232
+ managedRuntime,
233
+ diagnostic: runtimeDiagnostic(definition, available ? 'available' : 'missing', managedRuntime.reason),
234
+ };
235
+ }
236
+
237
+ export async function discoverRuntime(id, options = {}) {
238
+ const definition = RUNTIME_DEFINITIONS[id];
239
+ if (!definition) {
240
+ return {
241
+ id,
242
+ name: id,
243
+ status: 'unsupported',
244
+ source: 'none',
245
+ installStatus: 'unsupported',
246
+ diagnostic: {
247
+ message: `${id} is not registered in the Pixcode runtime manager.`,
248
+ action: 'Add a runtime definition before using this stack.',
249
+ },
250
+ };
251
+ }
252
+
253
+ const env = options.env || process.env;
254
+ const managed = await discoverManagedRuntime(id, definition, env, Boolean(options.preferManaged));
255
+ if (managed) return managed;
256
+ return discoverSystemRuntime(id, definition, env, options);
257
+ }
258
+
259
+ function isPackageManagerCommand(command) {
260
+ return command === 'npm' || command === 'pnpm' || command === 'yarn' || command === 'bun';
261
+ }
262
+
263
+ function runtimeIdForLiveViewCommand(command) {
264
+ if (!command) return null;
265
+ if (isPackageManagerCommand(command.command) || command.packageManager) return 'node';
266
+ if (command.framework === 'PHP' || command.command === 'php') return 'php';
267
+ if (command.framework === 'Django' || command.framework === 'FastAPI' || command.framework === 'Flask') return 'python';
268
+ if (command.framework === 'Go' || command.command === 'go') return 'go';
269
+ if (command.framework === 'Rust' || command.command === 'cargo') return 'rust';
270
+ if (command.framework === 'Java' || command.command === 'java') return 'java';
271
+ return null;
272
+ }
273
+
274
+ export async function resolveLiveViewRuntime(command, options = {}) {
275
+ const env = options.env || process.env;
276
+ const runtimeId = runtimeIdForLiveViewCommand(command);
277
+ if (!runtimeId) {
278
+ return {
279
+ available: true,
280
+ reason: 'No dedicated runtime check is required for this Live View command.',
281
+ };
282
+ }
283
+
284
+ if (runtimeId === 'node') {
285
+ const runtime = await discoverRuntime('node', { env });
286
+ const managedRuntime = await getManagedRuntimeStatus('npm', {
287
+ env,
288
+ preferManaged: Boolean(options.preferManaged),
289
+ });
290
+ return {
291
+ available: true,
292
+ runtime,
293
+ managedRuntime,
294
+ reason: managedRuntime.reason || 'Pixcode will run this project with its managed Node package runner.',
295
+ };
296
+ }
297
+
298
+ if (runtimeId === 'php') {
299
+ const runtime = await discoverRuntime('php', {
300
+ env,
301
+ preferManaged: Boolean(options.preferManaged),
302
+ });
303
+ return {
304
+ available: Boolean(runtime.managedRuntime?.installable) || runtime.status === 'available',
305
+ runtime,
306
+ managedRuntime: runtime.managedRuntime,
307
+ reason: runtime.managedRuntime?.reason || runtime.diagnostic.message,
308
+ };
309
+ }
310
+
311
+ const runtime = await discoverRuntime(runtimeId, { env });
312
+ return {
313
+ available: runtime.status === 'available',
314
+ runtime,
315
+ reason: runtime.status === 'available' ? runtime.diagnostic.message : runtime.diagnostic.action,
316
+ };
317
+ }
318
+
319
+ export const runtimeManager = {
320
+ definitions: RUNTIME_DEFINITIONS,
321
+ discover: discoverRuntime,
322
+ resolveLiveViewRuntime,
323
+ };