@jmfederico/pi-web 1.202608.1 → 1.202609.0
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/cli.js +220 -108
- package/dist/cli.js.map +1 -1
- package/dist/client/apple-touch-icon-dev.png +0 -0
- package/dist/client/assets/{CodeViewer-mEiBZFA5.js → CodeViewer-CGAlg9S8.js} +1 -1
- package/dist/client/assets/{TerminalPanel-O9381Eme.js → TerminalPanel-xhJRhOas.js} +1 -1
- package/dist/client/assets/{index-uI2AGFUy.js → index-DXQKhn1P.js} +570 -335
- package/dist/client/assets/vendor-editor-core-CXO8gGab.js +12 -0
- package/dist/client/assets/vendor-editor-languages-CpW4sJsX.js +46 -0
- package/dist/client/favicon-dev.svg +8 -0
- package/dist/client/index.html +4 -4
- package/dist/client/pwa-icon-dev-192.png +0 -0
- package/dist/client/pwa-icon-dev-512.png +0 -0
- package/dist/config.js +21 -0
- package/dist/config.js.map +1 -1
- package/dist/environment.js +4 -0
- package/dist/environment.js.map +1 -0
- package/dist/nativeServices/installedServiceDefinitions.js +601 -0
- package/dist/nativeServices/installedServiceDefinitions.js.map +1 -0
- package/dist/nativeServices/serviceAction.js +174 -0
- package/dist/nativeServices/serviceAction.js.map +1 -0
- package/dist/nativeServices/serviceDoctor.js +374 -71
- package/dist/nativeServices/serviceDoctor.js.map +1 -1
- package/dist/{pi-web-plugins → pi-packages}/relays/markdownDocument.js +1 -1
- package/dist/pi-packages/relays/package.json +11 -0
- package/dist/{pi-web-plugins → pi-packages}/relays/pi-web-plugin.js +1 -1
- package/dist/pi-packages/relays/prompts/relay-worktree.md +28 -0
- package/dist/pi-packages/relays/prompts/relay.md +45 -0
- package/dist/{pi-web-plugins → pi-packages}/relays/relayDiscovery.js +3 -3
- package/dist/{pi-web-plugins → pi-packages}/relays/relaysPanelElement.js +2 -2
- package/dist/pi-packages/relays/skills/relay/SKILL.md +94 -0
- package/dist/pi-packages/relays/skills/relay-runner/SKILL.md +245 -0
- package/dist/pi-web-plugins/info/infoInternals.js +15 -0
- package/dist/pi-web-plugins/updates/pi-web-plugin.js +44 -34
- package/dist/piWebVersionReport.js +63 -10
- package/dist/piWebVersionReport.js.map +1 -1
- package/dist/server/app.js +19 -3
- package/dist/server/app.js.map +1 -1
- package/dist/server/configRoutes.js +6 -1
- package/dist/server/configRoutes.js.map +1 -1
- package/dist/server/deploymentIdentity.js +67 -0
- package/dist/server/deploymentIdentity.js.map +1 -0
- package/dist/server/deploymentIdentityRoutes.js +22 -0
- package/dist/server/deploymentIdentityRoutes.js.map +1 -0
- package/dist/server/knownAutoInstallPiPackages.js +30 -0
- package/dist/server/knownAutoInstallPiPackages.js.map +1 -0
- package/dist/server/machines/machineService.js +1 -0
- package/dist/server/machines/machineService.js.map +1 -1
- package/dist/server/notices/serverNoticeRoutes.js +34 -0
- package/dist/server/notices/serverNoticeRoutes.js.map +1 -0
- package/dist/server/notices/serverNoticeService.js +26 -0
- package/dist/server/notices/serverNoticeService.js.map +1 -0
- package/dist/server/notices/serverNoticeStore.js +78 -0
- package/dist/server/notices/serverNoticeStore.js.map +1 -0
- package/dist/server/piPackageIdentity.js +25 -0
- package/dist/server/piPackageIdentity.js.map +1 -0
- package/dist/server/piPackageService.js +69 -8
- package/dist/server/piPackageService.js.map +1 -1
- package/dist/server/piWebStatus.js +14 -3
- package/dist/server/piWebStatus.js.map +1 -1
- package/dist/server/projectTrustRoutes.js +80 -0
- package/dist/server/projectTrustRoutes.js.map +1 -0
- package/dist/server/projects/projectService.js +3 -1
- package/dist/server/projects/projectService.js.map +1 -1
- package/dist/server/sessiond/autoInstallPiPackages.js +63 -0
- package/dist/server/sessiond/autoInstallPiPackages.js.map +1 -0
- package/dist/server/sessiond/sessionProxyRoutes.js +2 -0
- package/dist/server/sessiond/sessionProxyRoutes.js.map +1 -1
- package/dist/server/sessiond/sessionServiceDependencies.js +1 -0
- package/dist/server/sessiond/sessionServiceDependencies.js.map +1 -1
- package/dist/server/sessiond.js +36 -5
- package/dist/server/sessiond.js.map +1 -1
- package/dist/server/sessions/attachmentService.js +1 -5
- package/dist/server/sessions/attachmentService.js.map +1 -1
- package/dist/server/sessions/piSessionManagerGateway.js +171 -1
- package/dist/server/sessions/piSessionManagerGateway.js.map +1 -1
- package/dist/server/sessions/piSessionService.js +535 -20
- package/dist/server/sessions/piSessionService.js.map +1 -1
- package/dist/server/sessions/sessionModelScope.js +160 -0
- package/dist/server/sessions/sessionModelScope.js.map +1 -0
- package/dist/server/sessions/sessionNameGenerator.js +6 -6
- package/dist/server/sessions/sessionNameGenerator.js.map +1 -1
- package/dist/server/sessions/sessionRoutes.js +41 -0
- package/dist/server/sessions/sessionRoutes.js.map +1 -1
- package/dist/server/sessions/transcriptBranchCache.js +81 -0
- package/dist/server/sessions/transcriptBranchCache.js.map +1 -0
- package/dist/server/storage/piPackageDismissalStore.js +78 -0
- package/dist/server/storage/piPackageDismissalStore.js.map +1 -0
- package/dist/server/terminals/terminalService.js +18 -1
- package/dist/server/terminals/terminalService.js.map +1 -1
- package/dist/server/workspaces/projectPiWebConfig.js +6 -1
- package/dist/server/workspaces/projectPiWebConfig.js.map +1 -1
- package/dist/server/workspaces/workspaceRemovalService.js +16 -5
- package/dist/server/workspaces/workspaceRemovalService.js.map +1 -1
- package/dist/shared/apiTypes.js.map +1 -1
- package/dist/shared/federatedRoutes.js +10 -0
- package/dist/shared/federatedRoutes.js.map +1 -1
- package/dist/shared/piWebStatusParsing.js +4 -0
- package/dist/shared/piWebStatusParsing.js.map +1 -1
- package/dist/shared/pluginApiTypes.d.ts +2 -0
- package/docs/config.md +45 -4
- package/docs/plugins.md +25 -11
- package/package.json +6 -6
- package/dist/client/assets/vendor-editor-core-vUi74DnD.js +0 -12
- package/dist/client/assets/vendor-editor-languages-DoaXUodB.js +0 -46
- package/dist/pi-web-plugins/relays/package.json +0 -9
- /package/dist/{pi-web-plugins → pi-packages}/relays/vendor/README.md +0 -0
- /package/dist/{pi-web-plugins → pi-packages}/relays/vendor/marked.esm.js +0 -0
package/dist/cli.js
CHANGED
|
@@ -7,13 +7,16 @@ import { basename, dirname, join, resolve } from "node:path";
|
|
|
7
7
|
import { fileURLToPath } from "node:url";
|
|
8
8
|
import { defaultPiWebConfigPath, defaultPiWebDataDir, examplePiWebConfig } from "./config.js";
|
|
9
9
|
import { piWebDockerCommand } from "./docker/piWebDockerCommandPlan.js";
|
|
10
|
+
import { ownEnvironmentValue } from "./environment.js";
|
|
10
11
|
import { runPluginRecoveryCli } from "./pluginRecoveryCli.js";
|
|
11
|
-
import { packageVersion, printPiWebVersionReport } from "./piWebVersionReport.js";
|
|
12
|
+
import { packageVersion, printPiWebVersionReport, probeRunningComponentReady, runningComponentsReady, } from "./piWebVersionReport.js";
|
|
12
13
|
import { checkNodePtyDarwinSpawnHelper, formatNodePtyDarwinSpawnHelperCheck } from "./server/diagnostics/nodePtySpawnHelper.js";
|
|
13
14
|
import { checkNodePtyNativeModule, formatNodePtyNativeModuleCheck } from "./server/diagnostics/nodePtyNativeModule.js";
|
|
14
15
|
import { installNativeServiceCandidate, nativeServiceInstallFailureNeedsPathAdvice, } from "./nativeServices/serviceInstall.js";
|
|
16
|
+
import { launchdBootoutArgs, launchdServiceTarget, orderServices, performServiceAction, serviceStartOrder, serviceStopOrder, settleLaunchdServiceUnload, startLaunchdService, } from "./nativeServices/serviceAction.js";
|
|
15
17
|
import { minimumSupportedNodeVersion, nativeServiceManagerRefs, productionNativeServiceIds, } from "./nativeServices/servicePlan.js";
|
|
16
|
-
import {
|
|
18
|
+
import { inspectInstalledNativeServiceDefinitions, } from "./nativeServices/installedServiceDefinitions.js";
|
|
19
|
+
import { formatNativeServiceDoctorResult, inferInstalledNativeServiceMode, inspectInstalledDevelopmentServiceInput, inspectInstalledProductionServiceContext, runNativeServiceDoctor, selectManagedNativeServiceConfig, } from "./nativeServices/serviceDoctor.js";
|
|
17
20
|
import { createNativeServiceAuthoritativeProbe, nativeServicePrerequisiteShellCheck, } from "./nativeServices/serviceProbe.js";
|
|
18
21
|
import { renderLaunchdPlist, renderSystemdUnit } from "./nativeServices/serviceRendering.js";
|
|
19
22
|
const PI_WEB_PACKAGE_NAME = "@jmfederico/pi-web";
|
|
@@ -26,12 +29,6 @@ const serviceRefs = {
|
|
|
26
29
|
uiDev: { id: "uiDev", ...nativeServiceManagerRefs.uiDev },
|
|
27
30
|
};
|
|
28
31
|
const productionServiceIds = [...productionNativeServiceIds];
|
|
29
|
-
const startServiceOrder = ["sessiond", "web", "uiDev"];
|
|
30
|
-
const stopServiceOrder = ["web", "uiDev", "sessiond"];
|
|
31
|
-
// Restart web/UI before sessiond: when `pi-web restart` runs in a pi-web
|
|
32
|
-
// terminal (owned by sessiond), restarting sessiond kills the command, so any
|
|
33
|
-
// services handled after it would never be restarted.
|
|
34
|
-
const restartServiceOrder = ["web", "uiDev", "sessiond"];
|
|
35
32
|
function platformLabel() {
|
|
36
33
|
if (process.platform === "darwin")
|
|
37
34
|
return "macOS";
|
|
@@ -57,9 +54,6 @@ function requireServiceBackend(command) {
|
|
|
57
54
|
return backend;
|
|
58
55
|
throw new Error(`\`${command}\` requires a supported per-user service manager (systemd user services or LaunchAgents) and is not supported on ${platformLabel()}.\n\n${manualRunAdvice()}`);
|
|
59
56
|
}
|
|
60
|
-
function supportsSystemdUserServices() {
|
|
61
|
-
return currentServiceBackend()?.kind === "systemd";
|
|
62
|
-
}
|
|
63
57
|
function manualRunAdvice() {
|
|
64
58
|
return [
|
|
65
59
|
"Run PI WEB manually from a checkout:",
|
|
@@ -91,6 +85,15 @@ function capture(command, args) {
|
|
|
91
85
|
const stderr = outputText(result.stderr);
|
|
92
86
|
return { status: result.status ?? 1, stdout: outputText(result.stdout), stderr: stderr === "" ? errorMessage : stderr };
|
|
93
87
|
}
|
|
88
|
+
function captureInstalledServiceDefinitionCommand(command, args) {
|
|
89
|
+
const result = spawnSync(command, args);
|
|
90
|
+
return {
|
|
91
|
+
status: result.status ?? 1,
|
|
92
|
+
stdout: result.stdout,
|
|
93
|
+
stderr: result.stderr,
|
|
94
|
+
spawnError: result.error instanceof Error ? result.error.message : "",
|
|
95
|
+
};
|
|
96
|
+
}
|
|
94
97
|
function runQuiet(command, args) {
|
|
95
98
|
return capture(command, args).status;
|
|
96
99
|
}
|
|
@@ -221,21 +224,11 @@ function allServiceRefs() {
|
|
|
221
224
|
function productionServiceRefs() {
|
|
222
225
|
return serviceRefList(productionServiceIds);
|
|
223
226
|
}
|
|
224
|
-
function orderServiceRefs(refs, order) {
|
|
225
|
-
const byId = new Map(refs.map((ref) => [ref.id, ref]));
|
|
226
|
-
return order.flatMap((id) => {
|
|
227
|
-
const ref = byId.get(id);
|
|
228
|
-
return ref === undefined ? [] : [ref];
|
|
229
|
-
});
|
|
230
|
-
}
|
|
231
227
|
function startOrder(refs) {
|
|
232
|
-
return
|
|
228
|
+
return orderServices(refs, serviceStartOrder);
|
|
233
229
|
}
|
|
234
230
|
function stopOrder(refs) {
|
|
235
|
-
return
|
|
236
|
-
}
|
|
237
|
-
function restartOrder(refs) {
|
|
238
|
-
return orderServiceRefs(refs, restartServiceOrder);
|
|
231
|
+
return orderServices(refs, serviceStopOrder);
|
|
239
232
|
}
|
|
240
233
|
function devRootPath() {
|
|
241
234
|
return resolve(process.cwd());
|
|
@@ -300,30 +293,30 @@ async function installSystemdServices(plan) {
|
|
|
300
293
|
function launchdDomain() {
|
|
301
294
|
return `gui/${String(userInfo().uid)}`;
|
|
302
295
|
}
|
|
303
|
-
function
|
|
304
|
-
return
|
|
305
|
-
}
|
|
306
|
-
function launchdIsLoaded(ref) {
|
|
307
|
-
return capture("launchctl", ["print", launchdServiceTarget(ref)]).status === 0;
|
|
308
|
-
}
|
|
309
|
-
function launchdBootout(ref) {
|
|
310
|
-
runQuiet("launchctl", ["bootout", launchdServiceTarget(ref)]);
|
|
296
|
+
function currentLaunchdServiceTarget(ref) {
|
|
297
|
+
return launchdServiceTarget(launchdDomain(), ref);
|
|
311
298
|
}
|
|
312
|
-
function
|
|
313
|
-
|
|
314
|
-
run("launchctl", ["enable", launchdServiceTarget(ref)], { check: true });
|
|
299
|
+
function launchdActionContext() {
|
|
300
|
+
return { domain: launchdDomain(), plistPath: launchdPlistPath };
|
|
315
301
|
}
|
|
316
|
-
function
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
302
|
+
function serviceActionDeps(backend, probeEnvironment = process.env) {
|
|
303
|
+
return {
|
|
304
|
+
run: (command, args) => run(command, args),
|
|
305
|
+
runQuiet,
|
|
306
|
+
sleep: (ms) => new Promise((resolve) => {
|
|
307
|
+
setTimeout(resolve, ms);
|
|
308
|
+
}),
|
|
309
|
+
isServiceRunning: (ref) => runtimeStatus(backend, ref).health === "running",
|
|
310
|
+
isComponentReady: (component) => probeRunningComponentReady(component, { configEnv: probeEnvironment }),
|
|
311
|
+
};
|
|
320
312
|
}
|
|
321
313
|
async function installLaunchdServices(plan) {
|
|
322
314
|
const selected = new Set(plan.services.map((service) => service.id));
|
|
323
315
|
await mkdir(launchdServiceDir, { recursive: true });
|
|
324
316
|
await mkdir(logDir, { recursive: true });
|
|
325
|
-
for (const ref of stopOrder(allServiceRefs()))
|
|
326
|
-
|
|
317
|
+
for (const ref of stopOrder(allServiceRefs())) {
|
|
318
|
+
runQuiet("launchctl", launchdBootoutArgs(currentLaunchdServiceTarget(ref)));
|
|
319
|
+
}
|
|
327
320
|
for (const ref of allServiceRefs().filter((candidate) => !selected.has(candidate.id))) {
|
|
328
321
|
await rm(launchdPlistPath(ref), { force: true });
|
|
329
322
|
}
|
|
@@ -331,8 +324,16 @@ async function installLaunchdServices(plan) {
|
|
|
331
324
|
const plistPath = join(launchdServiceDir, service.manager.launchdPlistName);
|
|
332
325
|
await writeFile(plistPath, renderLaunchdPlist(plan, service, logDir));
|
|
333
326
|
}
|
|
334
|
-
|
|
335
|
-
|
|
327
|
+
// Settle each bootout before deciding bootstrap vs kickstart: acting on a
|
|
328
|
+
// label launchd is still asynchronously unloading races the teardown and can
|
|
329
|
+
// lose the service entirely (the same defect the restart path had).
|
|
330
|
+
const context = launchdActionContext();
|
|
331
|
+
const deps = serviceActionDeps(plan.backend);
|
|
332
|
+
for (const service of plan.services) {
|
|
333
|
+
const ref = serviceRefFromPlan(service.id, service.manager);
|
|
334
|
+
await settleLaunchdServiceUnload(currentLaunchdServiceTarget(ref), deps);
|
|
335
|
+
startLaunchdService(ref, context, deps);
|
|
336
|
+
}
|
|
336
337
|
}
|
|
337
338
|
async function installNativeServices(plan) {
|
|
338
339
|
if (plan.backend.kind === "systemd")
|
|
@@ -352,7 +353,7 @@ async function uninstallSystemdServices() {
|
|
|
352
353
|
}
|
|
353
354
|
async function uninstallLaunchdServices() {
|
|
354
355
|
for (const ref of stopOrder(allServiceRefs())) {
|
|
355
|
-
|
|
356
|
+
runQuiet("launchctl", launchdBootoutArgs(currentLaunchdServiceTarget(ref)));
|
|
356
357
|
await rm(launchdPlistPath(ref), { force: true });
|
|
357
358
|
}
|
|
358
359
|
}
|
|
@@ -446,7 +447,7 @@ export function launchdRuntimeDetails(output) {
|
|
|
446
447
|
return { state, detail, pid };
|
|
447
448
|
}
|
|
448
449
|
function launchdRuntimeStatus(backend, ref) {
|
|
449
|
-
const target =
|
|
450
|
+
const target = currentLaunchdServiceTarget(ref);
|
|
450
451
|
const filePath = serviceFilePath(backend, ref);
|
|
451
452
|
if (!serviceFileExists(backend, ref))
|
|
452
453
|
return makeServiceRuntimeStatus(ref, "not-installed", "not installed", target, filePath);
|
|
@@ -612,40 +613,27 @@ async function uninstall() {
|
|
|
612
613
|
await uninstallNativeServices(backend);
|
|
613
614
|
console.log(`PI WEB ${backend.label} removed. Production and development service files were removed; config and data were left in place.`);
|
|
614
615
|
}
|
|
615
|
-
function
|
|
616
|
-
const orderedRefs = action === "stop" ? stopOrder(refs) : action === "restart" ? restartOrder(refs) : startOrder(refs);
|
|
617
|
-
run("systemctl", ["--user", action, ...orderedRefs.map((ref) => ref.systemdName)], { check: true });
|
|
618
|
-
}
|
|
619
|
-
function launchdServiceAction(action, refs) {
|
|
620
|
-
if (action === "stop") {
|
|
621
|
-
for (const ref of stopOrder(refs))
|
|
622
|
-
launchdBootout(ref);
|
|
623
|
-
return;
|
|
624
|
-
}
|
|
625
|
-
if (action === "restart") {
|
|
626
|
-
// Restart each service fully (bootout + start) before moving to the next,
|
|
627
|
-
// so the web/UI services are back up before sessiond is restarted.
|
|
628
|
-
for (const ref of restartOrder(refs)) {
|
|
629
|
-
launchdBootout(ref);
|
|
630
|
-
launchdStart(ref);
|
|
631
|
-
}
|
|
632
|
-
return;
|
|
633
|
-
}
|
|
634
|
-
for (const ref of startOrder(refs))
|
|
635
|
-
launchdStart(ref);
|
|
636
|
-
}
|
|
637
|
-
function serviceAction(action) {
|
|
616
|
+
async function serviceAction(action) {
|
|
638
617
|
const backend = requireServiceBackend(`pi-web ${action}`);
|
|
639
618
|
if (action === "status") {
|
|
640
619
|
if (!printServiceStatusReport(backend))
|
|
641
620
|
process.exitCode = 1;
|
|
642
621
|
return;
|
|
643
622
|
}
|
|
623
|
+
await performLifecycleServiceAction(backend, action, process.env);
|
|
624
|
+
}
|
|
625
|
+
async function performLifecycleServiceAction(backend, action, probeEnvironment) {
|
|
644
626
|
const refs = installedServiceRefs(backend);
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
627
|
+
const input = { backend, action, refs, launchdContext: launchdActionContext() };
|
|
628
|
+
const result = await performServiceAction(input, serviceActionDeps(backend, probeEnvironment));
|
|
629
|
+
if (result.unreadyServices.length > 0) {
|
|
630
|
+
for (const ref of result.unreadyServices) {
|
|
631
|
+
const target = backend.kind === "systemd" ? ref.systemdName : currentLaunchdServiceTarget(ref);
|
|
632
|
+
console.log(`✗ ${serviceDisplayName(ref)} did not become ready (${target}).`);
|
|
633
|
+
}
|
|
634
|
+
console.log(" Check `pi-web status` and `pi-web logs` for details.");
|
|
635
|
+
process.exitCode = 1;
|
|
636
|
+
}
|
|
649
637
|
}
|
|
650
638
|
/** Offline restart guidance used only after a recovery config mutation. */
|
|
651
639
|
export function sessionDaemonRestartPlan(options = {}) {
|
|
@@ -696,9 +684,9 @@ export function sessionDaemonRestartPlan(options = {}) {
|
|
|
696
684
|
};
|
|
697
685
|
}
|
|
698
686
|
function activeDockerMode(env) {
|
|
699
|
-
if (!truthyEnvValue(env
|
|
687
|
+
if (!truthyEnvValue(ownEnvironmentValue(env, "PI_WEB_DOCKER_RUNTIME")))
|
|
700
688
|
return undefined;
|
|
701
|
-
if (env
|
|
689
|
+
if (ownEnvironmentValue(env, "PI_WEB_DOCKER_MODE") === "dev")
|
|
702
690
|
return "dev";
|
|
703
691
|
return "runtime";
|
|
704
692
|
}
|
|
@@ -792,14 +780,115 @@ function printOptionalDoctorChecks() {
|
|
|
792
780
|
console.log(" PI WEB falls back to a bounded filesystem scan when rg is unavailable.");
|
|
793
781
|
}
|
|
794
782
|
}
|
|
795
|
-
function installedServiceDefinitions(backend, ids) {
|
|
796
|
-
|
|
783
|
+
function installedServiceDefinitions(backend, ids, purpose) {
|
|
784
|
+
const sources = ids.map((id) => ({
|
|
797
785
|
id,
|
|
798
|
-
|
|
786
|
+
path: serviceFilePath(backend, serviceRefs[id]),
|
|
787
|
+
systemdName: serviceRefs[id].systemdName,
|
|
788
|
+
launchdTarget: currentLaunchdServiceTarget(serviceRefs[id]),
|
|
799
789
|
}));
|
|
790
|
+
return inspectInstalledNativeServiceDefinitions(backend, sources, {
|
|
791
|
+
readFile: (path) => readFileSync(path),
|
|
792
|
+
realpath: realpathSync,
|
|
793
|
+
capture: captureInstalledServiceDefinitionCommand,
|
|
794
|
+
}, purpose);
|
|
795
|
+
}
|
|
796
|
+
/** Build an isolated probe environment without mutating the caller or process. */
|
|
797
|
+
export function managedServiceProbeEnvironment(backend, definitions, callerEnvironment) {
|
|
798
|
+
const selection = selectManagedNativeServiceConfig(backend, definitions, ownEnvironmentValue(callerEnvironment, "PI_WEB_CONFIG"));
|
|
799
|
+
if (!selection.ok)
|
|
800
|
+
return selection;
|
|
801
|
+
if (selection.value.source === "installed") {
|
|
802
|
+
return {
|
|
803
|
+
ok: true,
|
|
804
|
+
value: { ...callerEnvironment, PI_WEB_CONFIG: selection.value.configPath },
|
|
805
|
+
};
|
|
806
|
+
}
|
|
807
|
+
if (selection.value.source === "default" && definitions.length > 0) {
|
|
808
|
+
// An own empty value preserves default resolution while masking any
|
|
809
|
+
// prototype-inherited PI_WEB_CONFIG from downstream environment lookups.
|
|
810
|
+
return { ok: true, value: { ...callerEnvironment, PI_WEB_CONFIG: "" } };
|
|
811
|
+
}
|
|
812
|
+
return { ok: true, value: callerEnvironment };
|
|
813
|
+
}
|
|
814
|
+
function installedServiceProbeEnvironment(backend, ids, callerEnvironment, purpose, inspectDefinitions) {
|
|
815
|
+
// The explicit caller override wins without requiring installed definitions
|
|
816
|
+
// to be readable or canonical.
|
|
817
|
+
const callerConfigPath = ownEnvironmentValue(callerEnvironment, "PI_WEB_CONFIG");
|
|
818
|
+
if (callerConfigPath !== undefined && callerConfigPath !== "") {
|
|
819
|
+
return { ok: true, value: callerEnvironment };
|
|
820
|
+
}
|
|
821
|
+
if (ids.size === 0)
|
|
822
|
+
return { ok: true, value: callerEnvironment };
|
|
823
|
+
const definitions = inspectDefinitions(backend, [...ids], purpose);
|
|
824
|
+
if (!definitions.ok)
|
|
825
|
+
return definitions;
|
|
826
|
+
return managedServiceProbeEnvironment(backend, definitions.value, callerEnvironment);
|
|
800
827
|
}
|
|
801
|
-
function
|
|
802
|
-
|
|
828
|
+
function managedServiceProbeFailure(backend, message) {
|
|
829
|
+
return [
|
|
830
|
+
`Could not select managed PI_WEB_CONFIG from installed ${backend.label}: ${message}`,
|
|
831
|
+
"Repair the reported service-manager override or definition, then re-run `pi-web install` (or `pi-web install --dev`); a nonempty PI_WEB_CONFIG explicitly overrides managed config selection for this command.",
|
|
832
|
+
].join("\n");
|
|
833
|
+
}
|
|
834
|
+
/**
|
|
835
|
+
* Dispatch commands whose probes may need managed config. The service-manager,
|
|
836
|
+
* definition, lifecycle, doctor, and version boundaries are injected so this
|
|
837
|
+
* exact orchestration can be tested without mutating live services or process
|
|
838
|
+
* environment.
|
|
839
|
+
*/
|
|
840
|
+
export async function runReadinessCliCommand(command, dependencies) {
|
|
841
|
+
if (command === "version") {
|
|
842
|
+
await dependencies.printVersion();
|
|
843
|
+
return;
|
|
844
|
+
}
|
|
845
|
+
const environment = dependencies.environment;
|
|
846
|
+
if (command === "start" || command === "restart") {
|
|
847
|
+
const backend = dependencies.requireBackend(`pi-web ${command}`);
|
|
848
|
+
const installedIds = dependencies.installedServiceIds(backend);
|
|
849
|
+
const probeEnvironment = installedServiceProbeEnvironment(backend, installedIds, environment, command, dependencies.inspectDefinitions);
|
|
850
|
+
if (!probeEnvironment.ok)
|
|
851
|
+
throw new Error(managedServiceProbeFailure(backend, probeEnvironment.message));
|
|
852
|
+
await dependencies.runLifecycle(backend, command, probeEnvironment.value);
|
|
853
|
+
return;
|
|
854
|
+
}
|
|
855
|
+
const backend = dependencies.currentBackend();
|
|
856
|
+
const installedIds = backend === undefined
|
|
857
|
+
? new Set()
|
|
858
|
+
: dependencies.installedServiceIds(backend);
|
|
859
|
+
let versionReportOptions = { configEnv: environment };
|
|
860
|
+
let managedInspectionFailed = false;
|
|
861
|
+
if (backend !== undefined && activeDockerMode(environment) === undefined) {
|
|
862
|
+
const probeEnvironment = installedServiceProbeEnvironment(backend, installedIds, environment, "doctor", dependencies.inspectDefinitions);
|
|
863
|
+
if (probeEnvironment.ok) {
|
|
864
|
+
versionReportOptions = { configEnv: probeEnvironment.value };
|
|
865
|
+
}
|
|
866
|
+
else {
|
|
867
|
+
managedInspectionFailed = true;
|
|
868
|
+
versionReportOptions = { webEndpointError: managedServiceProbeFailure(backend, probeEnvironment.message) };
|
|
869
|
+
}
|
|
870
|
+
}
|
|
871
|
+
await dependencies.runDoctor({
|
|
872
|
+
backend,
|
|
873
|
+
installedIds,
|
|
874
|
+
environment,
|
|
875
|
+
versionReportOptions,
|
|
876
|
+
managedInspectionFailed,
|
|
877
|
+
});
|
|
878
|
+
}
|
|
879
|
+
function readinessCliCommandDependencies() {
|
|
880
|
+
return {
|
|
881
|
+
environment: process.env,
|
|
882
|
+
currentBackend: currentServiceBackend,
|
|
883
|
+
requireBackend: requireServiceBackend,
|
|
884
|
+
installedServiceIds,
|
|
885
|
+
inspectDefinitions: installedServiceDefinitions,
|
|
886
|
+
runLifecycle: performLifecycleServiceAction,
|
|
887
|
+
runDoctor: doctor,
|
|
888
|
+
printVersion: async () => { await printPiWebVersionReport(); },
|
|
889
|
+
};
|
|
890
|
+
}
|
|
891
|
+
function nativeServiceDoctorTarget(backend, ids) {
|
|
803
892
|
const mode = inferInstalledNativeServiceMode(ids);
|
|
804
893
|
if (mode === "ambiguous") {
|
|
805
894
|
return {
|
|
@@ -824,23 +913,16 @@ function nativeServiceDoctorTarget(backend) {
|
|
|
824
913
|
message: `installed ${mode} service set is incomplete; ${missingId} is missing.`,
|
|
825
914
|
};
|
|
826
915
|
}
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
}
|
|
831
|
-
catch (error) {
|
|
832
|
-
return {
|
|
833
|
-
kind: "inspection-failure",
|
|
834
|
-
message: error instanceof Error ? error.message : String(error),
|
|
835
|
-
};
|
|
836
|
-
}
|
|
916
|
+
const definitions = installedServiceDefinitions(backend, expectedIds, "doctor");
|
|
917
|
+
if (!definitions.ok)
|
|
918
|
+
return { kind: "inspection-failure", message: definitions.message };
|
|
837
919
|
if (mode === "development") {
|
|
838
|
-
const inspection = inspectInstalledDevelopmentServiceInput(backend, definitions);
|
|
920
|
+
const inspection = inspectInstalledDevelopmentServiceInput(backend, definitions.value);
|
|
839
921
|
return inspection.ok
|
|
840
922
|
? { kind: "installed-development", input: inspection.value }
|
|
841
923
|
: { kind: "inspection-failure", message: inspection.message };
|
|
842
924
|
}
|
|
843
|
-
const inspection = inspectInstalledProductionServiceContext(backend, definitions);
|
|
925
|
+
const inspection = inspectInstalledProductionServiceContext(backend, definitions.value);
|
|
844
926
|
return inspection.ok
|
|
845
927
|
? {
|
|
846
928
|
kind: "prospective-production",
|
|
@@ -849,8 +931,8 @@ function nativeServiceDoctorTarget(backend) {
|
|
|
849
931
|
}
|
|
850
932
|
: { kind: "inspection-failure", message: inspection.message };
|
|
851
933
|
}
|
|
852
|
-
async function printNativeServiceDoctorChecks(backend) {
|
|
853
|
-
const result = await runNativeServiceDoctor(nativeServiceDoctorTarget(backend), {
|
|
934
|
+
async function printNativeServiceDoctorChecks(backend, installedIds) {
|
|
935
|
+
const result = await runNativeServiceDoctor(nativeServiceDoctorTarget(backend, installedIds), {
|
|
854
936
|
probe: createNativeServiceAuthoritativeProbe(),
|
|
855
937
|
fileExists: regularFileExists,
|
|
856
938
|
});
|
|
@@ -899,11 +981,30 @@ function printPathSetupAdvice(shell = detectServiceShell()) {
|
|
|
899
981
|
console.log(" Avoid relying on prompt hooks; PI WEB services run non-interactive login shells.");
|
|
900
982
|
}
|
|
901
983
|
}
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
984
|
+
/**
|
|
985
|
+
* Running components an installed deployment must keep ready: the API-serving
|
|
986
|
+
* component whenever the web or UI dev service file is installed (the version
|
|
987
|
+
* endpoint probes the API port in both modes), and the session daemon whenever
|
|
988
|
+
* its service file is installed. Docker deployments have no native service
|
|
989
|
+
* files, so the Docker runtime marker expects both components. A machine with
|
|
990
|
+
* nothing installed expects nothing: doctor's manual-run guidance stays a
|
|
991
|
+
* passing outcome there.
|
|
992
|
+
*/
|
|
993
|
+
export function expectedRunningComponents(installedServices, env) {
|
|
994
|
+
if (activeDockerMode(env) !== undefined)
|
|
995
|
+
return ["web", "sessiond"];
|
|
996
|
+
const expected = [];
|
|
997
|
+
if (installedServices.has("web") || installedServices.has("uiDev"))
|
|
998
|
+
expected.push("web");
|
|
999
|
+
if (installedServices.has("sessiond"))
|
|
1000
|
+
expected.push("sessiond");
|
|
1001
|
+
return expected;
|
|
1002
|
+
}
|
|
1003
|
+
export function doctorExitCode(generalReadinessOk, nativeServicePlanOk, nodePtyRuntimeOk, runningComponentsOk) {
|
|
1004
|
+
return generalReadinessOk && nativeServicePlanOk && nodePtyRuntimeOk && runningComponentsOk ? 0 : 1;
|
|
1005
|
+
}
|
|
1006
|
+
async function doctor(context) {
|
|
1007
|
+
const { backend, installedIds, environment, versionReportOptions, managedInspectionFailed } = context;
|
|
907
1008
|
console.log(`Platform: ${platformLabel()}`);
|
|
908
1009
|
console.log(`Service backend: ${backend?.label ?? "manual run only"}`);
|
|
909
1010
|
console.log(`Service shell: ${describeServiceShell()}`);
|
|
@@ -911,7 +1012,13 @@ async function doctor() {
|
|
|
911
1012
|
console.log(`- Native user service plan checks skipped on ${platformLabel()}; no native-service drift is reported.`);
|
|
912
1013
|
}
|
|
913
1014
|
console.log("");
|
|
914
|
-
await printPiWebVersionReport();
|
|
1015
|
+
const runningVersionInfo = await printPiWebVersionReport(versionReportOptions);
|
|
1016
|
+
const expectedComponents = expectedRunningComponents(installedIds, environment);
|
|
1017
|
+
const runningComponentsOk = runningComponentsReady(runningVersionInfo, expectedComponents);
|
|
1018
|
+
if (!runningComponentsOk) {
|
|
1019
|
+
console.log("\n✗ Installed PI WEB services are unavailable or stale (see \"Running services\" above).");
|
|
1020
|
+
console.log(" Run `pi-web restart`, then check `pi-web status`.");
|
|
1021
|
+
}
|
|
915
1022
|
console.log("\nGeneral login-shell readiness (separate from native-service requirements):");
|
|
916
1023
|
const generalReadinessOk = runChecks(generalDoctorChecks());
|
|
917
1024
|
printOptionalDoctorChecks();
|
|
@@ -921,9 +1028,15 @@ async function doctor() {
|
|
|
921
1028
|
let nativeServiceReport = null;
|
|
922
1029
|
if (backend !== undefined) {
|
|
923
1030
|
console.log("\nNative service plan checks (service-manager context):");
|
|
924
|
-
|
|
1031
|
+
if (managedInspectionFailed) {
|
|
1032
|
+
console.log("✗ Installed native-service plan could not be inspected consistently with the readiness probe (see \"Running services\" above).");
|
|
1033
|
+
console.log(" Repair the reported service-manager definition before relying on doctor results.");
|
|
1034
|
+
}
|
|
1035
|
+
else {
|
|
1036
|
+
nativeServiceReport = await printNativeServiceDoctorChecks(backend, installedIds);
|
|
1037
|
+
}
|
|
925
1038
|
}
|
|
926
|
-
if (
|
|
1039
|
+
if (backend?.kind === "systemd") {
|
|
927
1040
|
const linger = isLingerEnabled();
|
|
928
1041
|
if (linger === true) {
|
|
929
1042
|
console.log("✓ systemd user lingering enabled");
|
|
@@ -943,7 +1056,7 @@ async function doctor() {
|
|
|
943
1056
|
else {
|
|
944
1057
|
console.log(`- systemd user lingering skipped on ${platformLabel()}`);
|
|
945
1058
|
}
|
|
946
|
-
const nativeServicePlanOk = nativeServiceReport?.ok ?? true;
|
|
1059
|
+
const nativeServicePlanOk = !managedInspectionFailed && (nativeServiceReport?.ok ?? true);
|
|
947
1060
|
const pathFailure = !generalReadinessOk || nativeServiceReport?.pathAdviceRecommended === true;
|
|
948
1061
|
if (pathFailure) {
|
|
949
1062
|
console.log("\nIf a command works in your terminal but fails in the service-manager check, compare the caller and manager contexts above.");
|
|
@@ -955,7 +1068,7 @@ async function doctor() {
|
|
|
955
1068
|
if (generalReadinessOk && backend === undefined) {
|
|
956
1069
|
console.log(`\n${manualRunAdvice()}`);
|
|
957
1070
|
}
|
|
958
|
-
if (doctorExitCode(generalReadinessOk, nativeServicePlanOk, nodePtyNativeModuleOk && nodePtySpawnHelperOk) !== 0)
|
|
1071
|
+
if (doctorExitCode(generalReadinessOk, nativeServicePlanOk, nodePtyNativeModuleOk && nodePtySpawnHelperOk, runningComponentsOk) !== 0)
|
|
959
1072
|
process.exitCode = 1;
|
|
960
1073
|
}
|
|
961
1074
|
function printNodePtyNativeModuleCheck() {
|
|
@@ -999,16 +1112,15 @@ async function main() {
|
|
|
999
1112
|
await install(args);
|
|
1000
1113
|
else if (command === "uninstall")
|
|
1001
1114
|
await uninstall();
|
|
1002
|
-
else if (command === "start" || command === "
|
|
1003
|
-
|
|
1115
|
+
else if (command === "start" || command === "restart" || command === "doctor" || command === "version") {
|
|
1116
|
+
await runReadinessCliCommand(command, readinessCliCommandDependencies());
|
|
1117
|
+
}
|
|
1118
|
+
else if (command === "stop" || command === "status")
|
|
1119
|
+
await serviceAction(command);
|
|
1004
1120
|
else if (command === "logs")
|
|
1005
1121
|
logs();
|
|
1006
1122
|
else if (command === "plugins")
|
|
1007
1123
|
runPluginRecoveryCli(args, { restartPlan: (context) => sessionDaemonRestartPlan(context) });
|
|
1008
|
-
else if (command === "doctor")
|
|
1009
|
-
await doctor();
|
|
1010
|
-
else if (command === "version")
|
|
1011
|
-
await printPiWebVersionReport();
|
|
1012
1124
|
else if (command === "--version" || command === "-v")
|
|
1013
1125
|
console.log(packageVersion());
|
|
1014
1126
|
else if (command === "help" || command === "--help" || command === "-h")
|