@pasko70/pibo 3.3.0 → 3.4.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/agent-runtime/capabilities.js +5 -0
- package/dist/agent-runtime/contract.js +2 -0
- package/dist/agent-runtime/routed-session.js +68 -11
- package/dist/agent-runtimes/codex-native/adapter.js +3 -0
- package/dist/agent-runtimes/codex-native/thread.js +26 -4
- package/dist/agent-runtimes/omp/adapter.js +1 -0
- package/dist/agent-runtimes/pi/adapter.js +4 -0
- package/dist/agent-runtimes/pi/routed-session.js +53 -5
- package/dist/apps/chat/chat-api-routes.js +11 -37
- package/dist/apps/chat/chat-request-normalizers.js +8 -48
- package/dist/apps/chat/data/legacy-project-migration.js +492 -0
- package/dist/apps/chat/data/room-service.js +2 -2
- package/dist/apps/chat/data/workflow-session-model.js +1 -0
- package/dist/apps/chat/data/workflow-session-service.js +352 -0
- package/dist/apps/chat/rooms.js +1 -1
- package/dist/apps/chat/types/rooms.js +30 -0
- package/dist/apps/chat/web-app.js +264 -647
- package/dist/apps/chat/workflow-catalog.js +18 -18
- package/dist/apps/chat/{project-workflow-human-actions.js → workflow-human-actions.js} +14 -14
- package/dist/apps/chat/workflow-manual-trigger-runtime.js +5 -1
- package/dist/apps/chat/workflow-persistence.js +20 -28
- package/dist/apps/chat/{project-workflow-sessions.js → workflow-sessions.js} +18 -17
- package/dist/apps/chat-ui/assets/{dist-Kc3zbvQi.js → dist-AhtdAETR.js} +1 -1
- package/dist/apps/chat-ui/assets/{dist-CcAkJ8iv.js → dist-Bk_L1qbJ.js} +1 -1
- package/dist/apps/chat-ui/assets/{dist-BbCjOLiw.js → dist-Dyp6sLQn.js} +1 -1
- package/dist/apps/chat-ui/assets/{dist-Bd9gWCbk.js → dist-jVaiGXFD.js} +1 -1
- package/dist/apps/chat-ui/assets/{dist-C9a0_446.js → dist-t25moyJS.js} +1 -1
- package/dist/apps/chat-ui/assets/index-CZjNw-aK.js +228 -0
- package/dist/apps/chat-ui/assets/index-CcEjFITM.css +1 -0
- package/dist/apps/chat-ui/index.html +2 -2
- package/dist/apps/chat-vscode-web/assets/index-Spj6M0tn.js +43 -0
- package/dist/apps/chat-vscode-web/index.html +1 -1
- package/dist/compute/pool/cli.js +1 -1
- package/dist/compute/pool/seeds.js +2 -3
- package/dist/config/config.js +1 -1
- package/dist/core/session-router.js +7 -4
- package/dist/previews/cli.js +154 -79
- package/dist/previews/compute-worker.js +37 -0
- package/dist/previews/config.js +2 -1
- package/dist/previews/migrations/session-ownership.js +7 -0
- package/dist/previews/proxy.js +4 -2
- package/dist/previews/public-setup.js +108 -0
- package/dist/previews/store.js +20 -13
- package/dist/previews/web-app.js +12 -2
- package/dist/sessions/workflow-session-kind.js +0 -3
- package/npm-shrinkwrap.json +7 -6
- package/package.json +6 -2
- package/packages/workflows/README.md +21 -0
- package/packages/workflows/dist/api/index.d.ts +48 -0
- package/packages/workflows/dist/api/index.d.ts.map +1 -0
- package/packages/workflows/dist/api/index.js +96 -0
- package/packages/workflows/dist/api/index.js.map +1 -0
- package/packages/workflows/dist/definition-hash.d.ts +4 -0
- package/packages/workflows/dist/definition-hash.d.ts.map +1 -0
- package/packages/workflows/dist/definition-hash.js +27 -0
- package/packages/workflows/dist/definition-hash.js.map +1 -0
- package/packages/workflows/dist/fixtures/index.d.ts +39 -0
- package/packages/workflows/dist/fixtures/index.d.ts.map +1 -0
- package/packages/workflows/dist/fixtures/index.js +608 -0
- package/packages/workflows/dist/fixtures/index.js.map +1 -0
- package/packages/workflows/dist/index.d.ts +16 -0
- package/packages/workflows/dist/index.d.ts.map +1 -0
- package/packages/workflows/dist/index.js +10 -0
- package/packages/workflows/dist/index.js.map +1 -0
- package/packages/workflows/dist/inspection/index.d.ts +47 -0
- package/packages/workflows/dist/inspection/index.d.ts.map +1 -0
- package/packages/workflows/dist/inspection/index.js +131 -0
- package/packages/workflows/dist/inspection/index.js.map +1 -0
- package/packages/workflows/dist/registry/index.d.ts +30 -0
- package/packages/workflows/dist/registry/index.d.ts.map +1 -0
- package/packages/workflows/dist/registry/index.js +161 -0
- package/packages/workflows/dist/registry/index.js.map +1 -0
- package/packages/workflows/dist/runtime/adapter-node.d.ts +28 -0
- package/packages/workflows/dist/runtime/adapter-node.d.ts.map +1 -0
- package/packages/workflows/dist/runtime/adapter-node.js +172 -0
- package/packages/workflows/dist/runtime/adapter-node.js.map +1 -0
- package/packages/workflows/dist/runtime/agent-node.d.ts +32 -0
- package/packages/workflows/dist/runtime/agent-node.d.ts.map +1 -0
- package/packages/workflows/dist/runtime/agent-node.js +231 -0
- package/packages/workflows/dist/runtime/agent-node.js.map +1 -0
- package/packages/workflows/dist/runtime/agent-runtime.d.ts +25 -0
- package/packages/workflows/dist/runtime/agent-runtime.d.ts.map +1 -0
- package/packages/workflows/dist/runtime/agent-runtime.js +111 -0
- package/packages/workflows/dist/runtime/agent-runtime.js.map +1 -0
- package/packages/workflows/dist/runtime/code-node.d.ts +31 -0
- package/packages/workflows/dist/runtime/code-node.d.ts.map +1 -0
- package/packages/workflows/dist/runtime/code-node.js +223 -0
- package/packages/workflows/dist/runtime/code-node.js.map +1 -0
- package/packages/workflows/dist/runtime/dispatch-failures.d.ts +36 -0
- package/packages/workflows/dist/runtime/dispatch-failures.d.ts.map +1 -0
- package/packages/workflows/dist/runtime/dispatch-failures.js +80 -0
- package/packages/workflows/dist/runtime/dispatch-failures.js.map +1 -0
- package/packages/workflows/dist/runtime/edge-payloads.d.ts +3 -0
- package/packages/workflows/dist/runtime/edge-payloads.d.ts.map +1 -0
- package/packages/workflows/dist/runtime/edge-payloads.js +11 -0
- package/packages/workflows/dist/runtime/edge-payloads.js.map +1 -0
- package/packages/workflows/dist/runtime/edge-transfer.d.ts +39 -0
- package/packages/workflows/dist/runtime/edge-transfer.d.ts.map +1 -0
- package/packages/workflows/dist/runtime/edge-transfer.js +356 -0
- package/packages/workflows/dist/runtime/edge-transfer.js.map +1 -0
- package/packages/workflows/dist/runtime/human-action.d.ts +43 -0
- package/packages/workflows/dist/runtime/human-action.d.ts.map +1 -0
- package/packages/workflows/dist/runtime/human-action.js +231 -0
- package/packages/workflows/dist/runtime/human-action.js.map +1 -0
- package/packages/workflows/dist/runtime/human-node.d.ts +27 -0
- package/packages/workflows/dist/runtime/human-node.d.ts.map +1 -0
- package/packages/workflows/dist/runtime/human-node.js +126 -0
- package/packages/workflows/dist/runtime/human-node.js.map +1 -0
- package/packages/workflows/dist/runtime/ids.d.ts +2 -0
- package/packages/workflows/dist/runtime/ids.d.ts.map +1 -0
- package/packages/workflows/dist/runtime/ids.js +4 -0
- package/packages/workflows/dist/runtime/ids.js.map +1 -0
- package/packages/workflows/dist/runtime/index.d.ts +23 -0
- package/packages/workflows/dist/runtime/index.d.ts.map +1 -0
- package/packages/workflows/dist/runtime/index.js +12 -0
- package/packages/workflows/dist/runtime/index.js.map +1 -0
- package/packages/workflows/dist/runtime/manual-trigger.d.ts +51 -0
- package/packages/workflows/dist/runtime/manual-trigger.d.ts.map +1 -0
- package/packages/workflows/dist/runtime/manual-trigger.js +403 -0
- package/packages/workflows/dist/runtime/manual-trigger.js.map +1 -0
- package/packages/workflows/dist/runtime/nested-workflow-node.d.ts +48 -0
- package/packages/workflows/dist/runtime/nested-workflow-node.d.ts.map +1 -0
- package/packages/workflows/dist/runtime/nested-workflow-node.js +246 -0
- package/packages/workflows/dist/runtime/nested-workflow-node.js.map +1 -0
- package/packages/workflows/dist/runtime/one-node-agent.d.ts +34 -0
- package/packages/workflows/dist/runtime/one-node-agent.d.ts.map +1 -0
- package/packages/workflows/dist/runtime/one-node-agent.js +334 -0
- package/packages/workflows/dist/runtime/one-node-agent.js.map +1 -0
- package/packages/workflows/dist/runtime/persistence.d.ts +9 -0
- package/packages/workflows/dist/runtime/persistence.d.ts.map +1 -0
- package/packages/workflows/dist/runtime/persistence.js +67 -0
- package/packages/workflows/dist/runtime/persistence.js.map +1 -0
- package/packages/workflows/dist/runtime/pibo-routing.d.ts +126 -0
- package/packages/workflows/dist/runtime/pibo-routing.d.ts.map +1 -0
- package/packages/workflows/dist/runtime/pibo-routing.js +96 -0
- package/packages/workflows/dist/runtime/pibo-routing.js.map +1 -0
- package/packages/workflows/dist/runtime/prompts.d.ts +20 -0
- package/packages/workflows/dist/runtime/prompts.d.ts.map +1 -0
- package/packages/workflows/dist/runtime/prompts.js +235 -0
- package/packages/workflows/dist/runtime/prompts.js.map +1 -0
- package/packages/workflows/dist/runtime/retry.d.ts +35 -0
- package/packages/workflows/dist/runtime/retry.d.ts.map +1 -0
- package/packages/workflows/dist/runtime/retry.js +80 -0
- package/packages/workflows/dist/runtime/retry.js.map +1 -0
- package/packages/workflows/dist/runtime/state.d.ts +14 -0
- package/packages/workflows/dist/runtime/state.d.ts.map +1 -0
- package/packages/workflows/dist/runtime/state.js +139 -0
- package/packages/workflows/dist/runtime/state.js.map +1 -0
- package/packages/workflows/dist/runtime/time.d.ts +6 -0
- package/packages/workflows/dist/runtime/time.d.ts.map +1 -0
- package/packages/workflows/dist/runtime/time.js +42 -0
- package/packages/workflows/dist/runtime/time.js.map +1 -0
- package/packages/workflows/dist/store/catalog-records.d.ts +18 -0
- package/packages/workflows/dist/store/catalog-records.d.ts.map +1 -0
- package/packages/workflows/dist/store/catalog-records.js +59 -0
- package/packages/workflows/dist/store/catalog-records.js.map +1 -0
- package/packages/workflows/dist/store/contracts.d.ts +149 -0
- package/packages/workflows/dist/store/contracts.d.ts.map +1 -0
- package/packages/workflows/dist/store/contracts.js +2 -0
- package/packages/workflows/dist/store/contracts.js.map +1 -0
- package/packages/workflows/dist/store/draft-writes.d.ts +7 -0
- package/packages/workflows/dist/store/draft-writes.d.ts.map +1 -0
- package/packages/workflows/dist/store/draft-writes.js +18 -0
- package/packages/workflows/dist/store/draft-writes.js.map +1 -0
- package/packages/workflows/dist/store/index.d.ts +55 -0
- package/packages/workflows/dist/store/index.d.ts.map +1 -0
- package/packages/workflows/dist/store/index.js +650 -0
- package/packages/workflows/dist/store/index.js.map +1 -0
- package/packages/workflows/dist/store/list-query.d.ts +13 -0
- package/packages/workflows/dist/store/list-query.d.ts.map +1 -0
- package/packages/workflows/dist/store/list-query.js +20 -0
- package/packages/workflows/dist/store/list-query.js.map +1 -0
- package/packages/workflows/dist/store/row-mappers.d.ts +206 -0
- package/packages/workflows/dist/store/row-mappers.d.ts.map +1 -0
- package/packages/workflows/dist/store/row-mappers.js +262 -0
- package/packages/workflows/dist/store/row-mappers.js.map +1 -0
- package/packages/workflows/dist/store/schema.d.ts +13 -0
- package/packages/workflows/dist/store/schema.d.ts.map +1 -0
- package/packages/workflows/dist/store/schema.js +458 -0
- package/packages/workflows/dist/store/schema.js.map +1 -0
- package/packages/workflows/dist/store/write-values.d.ts +18 -0
- package/packages/workflows/dist/store/write-values.d.ts.map +1 -0
- package/packages/workflows/dist/store/write-values.js +227 -0
- package/packages/workflows/dist/store/write-values.js.map +1 -0
- package/packages/workflows/dist/types/index.d.ts +1202 -0
- package/packages/workflows/dist/types/index.d.ts.map +1 -0
- package/packages/workflows/dist/types/index.js +8 -0
- package/packages/workflows/dist/types/index.js.map +1 -0
- package/packages/workflows/dist/validation/diagnostics.d.ts +27 -0
- package/packages/workflows/dist/validation/diagnostics.d.ts.map +1 -0
- package/packages/workflows/dist/validation/diagnostics.js +104 -0
- package/packages/workflows/dist/validation/diagnostics.js.map +1 -0
- package/packages/workflows/dist/validation/graph-cycles.d.ts +4 -0
- package/packages/workflows/dist/validation/graph-cycles.d.ts.map +1 -0
- package/packages/workflows/dist/validation/graph-cycles.js +130 -0
- package/packages/workflows/dist/validation/graph-cycles.js.map +1 -0
- package/packages/workflows/dist/validation/graph-edges.d.ts +5 -0
- package/packages/workflows/dist/validation/graph-edges.d.ts.map +1 -0
- package/packages/workflows/dist/validation/graph-edges.js +48 -0
- package/packages/workflows/dist/validation/graph-edges.js.map +1 -0
- package/packages/workflows/dist/validation/graph-ports.d.ts +5 -0
- package/packages/workflows/dist/validation/graph-ports.d.ts.map +1 -0
- package/packages/workflows/dist/validation/graph-ports.js +55 -0
- package/packages/workflows/dist/validation/graph-ports.js.map +1 -0
- package/packages/workflows/dist/validation/index.d.ts +18 -0
- package/packages/workflows/dist/validation/index.d.ts.map +1 -0
- package/packages/workflows/dist/validation/index.js +175 -0
- package/packages/workflows/dist/validation/index.js.map +1 -0
- package/packages/workflows/dist/validation/json-schema.d.ts +12 -0
- package/packages/workflows/dist/validation/json-schema.d.ts.map +1 -0
- package/packages/workflows/dist/validation/json-schema.js +556 -0
- package/packages/workflows/dist/validation/json-schema.js.map +1 -0
- package/packages/workflows/dist/validation/registry-refs.d.ts +21 -0
- package/packages/workflows/dist/validation/registry-refs.d.ts.map +1 -0
- package/packages/workflows/dist/validation/registry-refs.js +283 -0
- package/packages/workflows/dist/validation/registry-refs.js.map +1 -0
- package/packages/workflows/dist/validation/retry-policy.d.ts +3 -0
- package/packages/workflows/dist/validation/retry-policy.d.ts.map +1 -0
- package/packages/workflows/dist/validation/retry-policy.js +114 -0
- package/packages/workflows/dist/validation/retry-policy.js.map +1 -0
- package/packages/workflows/dist/validation/schema-declarations.d.ts +5 -0
- package/packages/workflows/dist/validation/schema-declarations.d.ts.map +1 -0
- package/packages/workflows/dist/validation/schema-declarations.js +36 -0
- package/packages/workflows/dist/validation/schema-declarations.js.map +1 -0
- package/packages/workflows/dist/validation/state-access.d.ts +4 -0
- package/packages/workflows/dist/validation/state-access.d.ts.map +1 -0
- package/packages/workflows/dist/validation/state-access.js +121 -0
- package/packages/workflows/dist/validation/state-access.js.map +1 -0
- package/packages/workflows/dist/xstate/index.d.ts +73 -0
- package/packages/workflows/dist/xstate/index.d.ts.map +1 -0
- package/packages/workflows/dist/xstate/index.js +728 -0
- package/packages/workflows/dist/xstate/index.js.map +1 -0
- package/packages/workflows/package.json +23 -0
- package/dist/apps/chat/data/project-service.js +0 -1029
- package/dist/apps/chat-ui/assets/index-DWJnUye8.css +0 -1
- package/dist/apps/chat-ui/assets/index-exohEOUE.js +0 -228
- package/dist/apps/chat-vscode-web/assets/index-D1bpAUkR.js +0 -43
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
6
6
|
<meta name="theme-color" content="#101d22" />
|
|
7
7
|
<title>Pibo</title>
|
|
8
|
-
<script type="module" crossorigin src="/apps/chat-vscode/assets/index-
|
|
8
|
+
<script type="module" crossorigin src="/apps/chat-vscode/assets/index-Spj6M0tn.js"></script>
|
|
9
9
|
<link rel="stylesheet" crossorigin href="/apps/chat-vscode/assets/index-b18ZkEo0.css">
|
|
10
10
|
</head>
|
|
11
11
|
<body>
|
package/dist/compute/pool/cli.js
CHANGED
|
@@ -149,7 +149,7 @@ Provide exactly one source:
|
|
|
149
149
|
program.command("seed")
|
|
150
150
|
.action(() => console.log(`Deployment seed modes:
|
|
151
151
|
full Nearly complete Pibo home plus configured workspace; excludes active runtime, lock, browser, debug, and pool state.
|
|
152
|
-
medium Operational config, selected product databases,
|
|
152
|
+
medium Operational config, selected product and workflow databases, contexts, agents, and user skills; excludes heavy payload/tool/browser/debug state.
|
|
153
153
|
fresh Operational config, Google/Machine auth configuration, model defaults, contexts, and user skills; no existing product databases.
|
|
154
154
|
`));
|
|
155
155
|
await program.parseAsync(argv);
|
|
@@ -35,11 +35,10 @@ const MEDIUM_ALLOWED_TOP_LEVEL = new Set([
|
|
|
35
35
|
"chat-agents.sqlite",
|
|
36
36
|
"pibo.sqlite",
|
|
37
37
|
"pibo-events.sqlite",
|
|
38
|
-
"
|
|
38
|
+
"pibo-workflows.sqlite",
|
|
39
39
|
"web-annotations.sqlite",
|
|
40
40
|
"context-files",
|
|
41
41
|
"user-skills",
|
|
42
|
-
"projects",
|
|
43
42
|
]);
|
|
44
43
|
const FRESH_ALLOWED_TOP_LEVEL = new Set([
|
|
45
44
|
"config.json",
|
|
@@ -137,7 +136,7 @@ async function backupSeedDatabases(sourceHome, destinationHome, mode) {
|
|
|
137
136
|
if (mode === "fresh")
|
|
138
137
|
return [];
|
|
139
138
|
const allowed = mode === "medium"
|
|
140
|
-
? new Set(["chat-agents.sqlite", "pibo.sqlite", "pibo-events.sqlite", "
|
|
139
|
+
? new Set(["chat-agents.sqlite", "pibo.sqlite", "pibo-events.sqlite", "pibo-workflows.sqlite", "web-annotations.sqlite"])
|
|
141
140
|
: undefined;
|
|
142
141
|
const copied = [];
|
|
143
142
|
for (const entry of await readdir(sourceHome, { withFileTypes: true })) {
|
package/dist/config/config.js
CHANGED
|
@@ -59,7 +59,7 @@ export const PIBO_CONFIG_KEYS = [
|
|
|
59
59
|
{
|
|
60
60
|
key: "preview.baseURL",
|
|
61
61
|
type: "string",
|
|
62
|
-
description: "Public
|
|
62
|
+
description: "Public Preview base URL. Run `pibo preview setup --base-url https://preview.example.com` before enabling it.",
|
|
63
63
|
},
|
|
64
64
|
{
|
|
65
65
|
key: "preview.databasePath",
|
|
@@ -231,7 +231,6 @@ function derivedSessionMetadata(value) {
|
|
|
231
231
|
for (const key of [
|
|
232
232
|
DERIVED_SESSION_RECONCILIATION_METADATA_KEY,
|
|
233
233
|
"workflowSessionKind",
|
|
234
|
-
"projectSessionKind",
|
|
235
234
|
"subagentName",
|
|
236
235
|
"subagentToolName",
|
|
237
236
|
"agentStatus",
|
|
@@ -1692,9 +1691,13 @@ export class PiboSessionRouter {
|
|
|
1692
1691
|
catch (error) {
|
|
1693
1692
|
transitionError = error;
|
|
1694
1693
|
}
|
|
1695
|
-
|
|
1696
|
-
|
|
1697
|
-
|
|
1694
|
+
if (result.sourceSessionUnchanged) {
|
|
1695
|
+
if (transitionError)
|
|
1696
|
+
throw transitionError;
|
|
1697
|
+
return;
|
|
1698
|
+
}
|
|
1699
|
+
// Identity-moving forks and clones transfer the live native handle to the
|
|
1700
|
+
// derived session, so the source must be reopened from its persisted binding.
|
|
1698
1701
|
try {
|
|
1699
1702
|
await this.resetCachedSession(event.piboSessionId);
|
|
1700
1703
|
}
|
package/dist/previews/cli.js
CHANGED
|
@@ -2,11 +2,13 @@ import { randomBytes } from "node:crypto";
|
|
|
2
2
|
import { resolve } from "node:path";
|
|
3
3
|
import { Command } from "commander";
|
|
4
4
|
import { createDefaultPiboDataSessionStore } from "../sessions/pibo-data-store.js";
|
|
5
|
+
import { resolvePreviewComputeWorkerTarget } from "./compute-worker.js";
|
|
5
6
|
import { DEFAULT_PREVIEW_TTL_MINUTES, loadEffectivePreviewServerSettings, loadPreviewConfig, previewPublicURL, requirePreviewBaseURL } from "./config.js";
|
|
6
7
|
import { reconcileManagedPreviews, startManagedPreview, stopManagedPreview, validatePreviewStartCommand, } from "./manager.js";
|
|
7
8
|
import { findPreviewTargetProcess, isPiboControlProcess, isPiboYieldedProcess, isPreviewTargetProcessCurrent, probePreviewTarget, validatePreviewPort, } from "./network.js";
|
|
8
9
|
import { createDefaultPreviewStore, previewExposureState } from "./store.js";
|
|
9
10
|
import { DEFAULT_MAX_PREVIEW_PROXY_CONNECTIONS, DEFAULT_MAX_PREVIEW_PROXY_CONNECTIONS_PER_PREVIEW, } from "./proxy.js";
|
|
11
|
+
import { createPreviewProductionSetupPlan, inspectPreviewPublicRoute } from "./public-setup.js";
|
|
10
12
|
function printJson(value) {
|
|
11
13
|
console.log(JSON.stringify(value, null, 2));
|
|
12
14
|
}
|
|
@@ -19,6 +21,12 @@ function parsePositiveInteger(value) {
|
|
|
19
21
|
throw new Error("Value must be a positive integer");
|
|
20
22
|
return parsed;
|
|
21
23
|
}
|
|
24
|
+
function parseGatewayPort(value) {
|
|
25
|
+
const parsed = Number(value);
|
|
26
|
+
if (!Number.isInteger(parsed) || parsed < 1 || parsed > 65535)
|
|
27
|
+
throw new Error("Gateway port must be an integer between 1 and 65535");
|
|
28
|
+
return parsed;
|
|
29
|
+
}
|
|
22
30
|
function parsePreviewTtlMinutes(value) {
|
|
23
31
|
const parsed = parsePositiveInteger(value);
|
|
24
32
|
if (parsed > 7 * 24 * 60)
|
|
@@ -28,12 +36,6 @@ function parsePreviewTtlMinutes(value) {
|
|
|
28
36
|
function createPreviewId() {
|
|
29
37
|
return `pv-${randomBytes(9).toString("hex")}`;
|
|
30
38
|
}
|
|
31
|
-
function inferredProjectId(metadata) {
|
|
32
|
-
if (!metadata || typeof metadata !== "object" || Array.isArray(metadata))
|
|
33
|
-
return undefined;
|
|
34
|
-
const projectId = metadata.projectId;
|
|
35
|
-
return typeof projectId === "string" && projectId.trim() ? projectId.trim() : undefined;
|
|
36
|
-
}
|
|
37
39
|
async function previewHealth(exposure) {
|
|
38
40
|
const state = previewExposureState(exposure);
|
|
39
41
|
if (state !== "active")
|
|
@@ -64,23 +66,117 @@ async function exposureView(exposure) {
|
|
|
64
66
|
...(target ? { latencyMs: target.latencyMs } : {}),
|
|
65
67
|
};
|
|
66
68
|
}
|
|
69
|
+
async function createPreviewExposure(port, options, defaults = {}) {
|
|
70
|
+
const baseURL = requirePreviewBaseURL();
|
|
71
|
+
const piboSessionId = options.session.trim();
|
|
72
|
+
if (!piboSessionId)
|
|
73
|
+
throw new Error("--session must contain a Pibo Session ID");
|
|
74
|
+
const sessionStore = createDefaultPiboDataSessionStore();
|
|
75
|
+
let session;
|
|
76
|
+
try {
|
|
77
|
+
session = sessionStore.get(piboSessionId);
|
|
78
|
+
}
|
|
79
|
+
finally {
|
|
80
|
+
sessionStore.close();
|
|
81
|
+
}
|
|
82
|
+
if (!session)
|
|
83
|
+
throw new Error(`Pibo Session "${piboSessionId}" was not found`);
|
|
84
|
+
const command = options.command === undefined ? undefined : validatePreviewStartCommand(options.command);
|
|
85
|
+
const target = command ? undefined : await probePreviewTarget(port);
|
|
86
|
+
if (!command && !target)
|
|
87
|
+
throw new Error(`No development server is reachable on loopback port ${port}`);
|
|
88
|
+
if (command && await probePreviewTarget(port, { timeoutMs: 150 }))
|
|
89
|
+
throw new Error(`Preview port ${port} is already occupied`);
|
|
90
|
+
const now = new Date();
|
|
91
|
+
const id = createPreviewId();
|
|
92
|
+
const targetProcess = target ? findPreviewTargetProcess(target.host, port) : undefined;
|
|
93
|
+
if (!command && process.platform === "linux" && !targetProcess) {
|
|
94
|
+
throw new Error(`Preview port ${port} is not bound exclusively to the selected loopback address`);
|
|
95
|
+
}
|
|
96
|
+
if (!command && targetProcess && isPiboControlProcess(targetProcess.pid)) {
|
|
97
|
+
throw new Error(`Preview port ${port} belongs to a Pibo control process and cannot be exposed`);
|
|
98
|
+
}
|
|
99
|
+
if (!command && process.env.NODE_ENV !== "test" && targetProcess && isPiboYieldedProcess(targetProcess.pid)) {
|
|
100
|
+
throw new Error("Preview servers cannot run as yielded agent resources; stop that process and use --command so Preview owns its lifecycle");
|
|
101
|
+
}
|
|
102
|
+
const store = createDefaultPreviewStore();
|
|
103
|
+
let exposure;
|
|
104
|
+
try {
|
|
105
|
+
exposure = store.createExposure({
|
|
106
|
+
id,
|
|
107
|
+
piboSessionId,
|
|
108
|
+
label: options.name?.trim() || defaults.label || `Preview ${port}`,
|
|
109
|
+
targetHost: target?.host ?? "127.0.0.1",
|
|
110
|
+
targetPort: port,
|
|
111
|
+
targetProcessId: targetProcess?.pid,
|
|
112
|
+
targetProcessStartTicks: targetProcess?.startTicks,
|
|
113
|
+
workspace: resolve(options.workspace ?? defaults.workspace ?? session.workspace ?? process.cwd()),
|
|
114
|
+
managementMode: command ? "managed" : "external",
|
|
115
|
+
proxyMode: defaults.proxyMode,
|
|
116
|
+
startCommand: command,
|
|
117
|
+
serverState: command ? "stopped" : undefined,
|
|
118
|
+
createdAt: now.toISOString(),
|
|
119
|
+
expiresAt: new Date(now.getTime() + options.ttlMinutes * 60_000).toISOString(),
|
|
120
|
+
});
|
|
121
|
+
if (command)
|
|
122
|
+
exposure = await startManagedPreview(store, id);
|
|
123
|
+
}
|
|
124
|
+
finally {
|
|
125
|
+
store.close();
|
|
126
|
+
}
|
|
127
|
+
return { ...await exposureView(exposure), publicUrl: previewPublicURL(id, baseURL).toString() };
|
|
128
|
+
}
|
|
129
|
+
function printCreatedPreview(result, json = false) {
|
|
130
|
+
if (json) {
|
|
131
|
+
printJson(result);
|
|
132
|
+
return;
|
|
133
|
+
}
|
|
134
|
+
console.log(`${result.id}\t${result.health}\t${result.label}`);
|
|
135
|
+
console.log(`url\t${result.publicUrl}`);
|
|
136
|
+
console.log(`session\t${result.piboSessionId}`);
|
|
137
|
+
console.log(`managed\t${result.managed}`);
|
|
138
|
+
console.log(`proxyMode\t${result.proxyMode}`);
|
|
139
|
+
if (result.serverStopAt)
|
|
140
|
+
console.log(`autoStop\t${result.serverStopAt}`);
|
|
141
|
+
console.log(`expires\t${result.expiresAt}`);
|
|
142
|
+
}
|
|
67
143
|
function printPreviewDiscovery() {
|
|
68
144
|
console.log(`pibo preview - Session-linked live development previews
|
|
69
145
|
|
|
70
146
|
Commands:
|
|
147
|
+
setup Print production DNS, TLS, proxy, and restart instructions
|
|
71
148
|
expose <port> Register an external port or start a managed server with --command
|
|
149
|
+
expose-worker <id> Register a running Pibo compute worker with local dev auth
|
|
72
150
|
list List preview registrations
|
|
73
151
|
show <preview-id> Inspect one preview
|
|
74
152
|
start <preview-id> Start a saved managed Preview server
|
|
75
153
|
stop <preview-id> Stop a managed Preview server without removing it
|
|
76
|
-
doctor [preview-id] Check
|
|
154
|
+
doctor [preview-id] Check local state; add --public to verify DNS, TLS, and routing
|
|
77
155
|
remove <preview-id> Stop and remove a preview definition
|
|
78
156
|
close <preview-id> Alias for remove
|
|
79
157
|
|
|
80
158
|
Next:
|
|
159
|
+
pibo preview setup --help
|
|
81
160
|
pibo preview expose --help
|
|
161
|
+
pibo preview expose-worker --help
|
|
82
162
|
`);
|
|
83
163
|
}
|
|
164
|
+
function printProductionSetupPlan(plan) {
|
|
165
|
+
console.log("Preview production setup");
|
|
166
|
+
console.log(`baseURL\t${plan.baseURL}`);
|
|
167
|
+
console.log(`DNS\t${plan.dnsRecord.type} ${plan.dnsRecord.name} -> ${plan.dnsRecord.value}`);
|
|
168
|
+
console.log("\nCaddy global options (merge into an existing global block):");
|
|
169
|
+
console.log(plan.caddy.globalOptions);
|
|
170
|
+
console.log("\nCaddy site block:");
|
|
171
|
+
console.log(plan.caddy.siteBlock);
|
|
172
|
+
console.log("\nCommands:");
|
|
173
|
+
console.log(`configure\t${plan.commands.configure}`);
|
|
174
|
+
console.log(`validate proxy\t${plan.commands.validateCaddy}`);
|
|
175
|
+
console.log(`restart gateway\t${plan.commands.restartGateway}`);
|
|
176
|
+
console.log(`verify\t${plan.commands.verify}`);
|
|
177
|
+
for (const warning of plan.warnings)
|
|
178
|
+
console.log(`warning\t${warning}`);
|
|
179
|
+
}
|
|
84
180
|
export async function runPreviewCli(argv = process.argv) {
|
|
85
181
|
if (argv.length <= 2 || argv[2] === "--help" || argv[2] === "-h") {
|
|
86
182
|
printPreviewDiscovery();
|
|
@@ -88,86 +184,51 @@ export async function runPreviewCli(argv = process.argv) {
|
|
|
88
184
|
}
|
|
89
185
|
const program = new Command();
|
|
90
186
|
program.name("pibo preview").description("Manage session-linked live development previews");
|
|
187
|
+
program
|
|
188
|
+
.command("setup")
|
|
189
|
+
.requiredOption("--base-url <url>", "Dedicated HTTPS base URL whose subdomains host Previews")
|
|
190
|
+
.option("--gateway-port <port>", "Loopback Pibo Web Gateway port", parseGatewayPort, 4788)
|
|
191
|
+
.option("--public-ip <address>", "Public IPv4 or IPv6 address for the exact DNS record")
|
|
192
|
+
.option("--json", "Print JSON")
|
|
193
|
+
.action((options) => {
|
|
194
|
+
const plan = createPreviewProductionSetupPlan({
|
|
195
|
+
baseURL: options.baseUrl,
|
|
196
|
+
gatewayPort: options.gatewayPort,
|
|
197
|
+
publicIp: options.publicIp,
|
|
198
|
+
});
|
|
199
|
+
if (options.json)
|
|
200
|
+
printJson(plan);
|
|
201
|
+
else
|
|
202
|
+
printProductionSetupPlan(plan);
|
|
203
|
+
});
|
|
91
204
|
program
|
|
92
205
|
.command("expose")
|
|
93
206
|
.argument("<port>", "Reachable loopback development port", parsePort)
|
|
94
207
|
.requiredOption("--session <pibo-session-id>", "Pibo Session that owns the preview")
|
|
95
|
-
.option("--project <project-id>", "Optional Project association")
|
|
96
208
|
.option("--name <label>", "Preview label")
|
|
97
209
|
.option("--workspace <path>", "Workspace used by the managed command and recorded for diagnostics")
|
|
98
210
|
.option("--command <shell-command>", "Save and start this command as a Preview-managed server")
|
|
99
211
|
.option("--ttl-minutes <minutes>", "Automatic preview-definition lifetime", parsePreviewTtlMinutes, DEFAULT_PREVIEW_TTL_MINUTES)
|
|
100
212
|
.option("--json", "Print JSON")
|
|
101
213
|
.action(async (port, options) => {
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
if (command && await probePreviewTarget(port, { timeoutMs: 150 }))
|
|
121
|
-
throw new Error(`Preview port ${port} is already occupied`);
|
|
122
|
-
const now = new Date();
|
|
123
|
-
const id = createPreviewId();
|
|
124
|
-
const targetProcess = target ? findPreviewTargetProcess(target.host, port) : undefined;
|
|
125
|
-
if (!command && process.platform === "linux" && !targetProcess) {
|
|
126
|
-
throw new Error(`Preview port ${port} is not bound exclusively to the selected loopback address`);
|
|
127
|
-
}
|
|
128
|
-
if (!command && targetProcess && isPiboControlProcess(targetProcess.pid)) {
|
|
129
|
-
throw new Error(`Preview port ${port} belongs to a Pibo control process and cannot be exposed`);
|
|
130
|
-
}
|
|
131
|
-
if (!command && process.env.NODE_ENV !== "test" && targetProcess && isPiboYieldedProcess(targetProcess.pid)) {
|
|
132
|
-
throw new Error("Preview servers cannot run as yielded agent resources; stop that process and use --command so Preview owns its lifecycle");
|
|
133
|
-
}
|
|
134
|
-
const store = createDefaultPreviewStore();
|
|
135
|
-
let exposure;
|
|
136
|
-
try {
|
|
137
|
-
exposure = store.createExposure({
|
|
138
|
-
id,
|
|
139
|
-
piboSessionId,
|
|
140
|
-
projectId: options.project?.trim() || inferredProjectId(session.metadata),
|
|
141
|
-
label: options.name?.trim() || `Preview ${port}`,
|
|
142
|
-
targetHost: target?.host ?? "127.0.0.1",
|
|
143
|
-
targetPort: port,
|
|
144
|
-
targetProcessId: targetProcess?.pid,
|
|
145
|
-
targetProcessStartTicks: targetProcess?.startTicks,
|
|
146
|
-
workspace: resolve(options.workspace ?? session.workspace ?? process.cwd()),
|
|
147
|
-
managementMode: command ? "managed" : "external",
|
|
148
|
-
startCommand: command,
|
|
149
|
-
serverState: command ? "stopped" : undefined,
|
|
150
|
-
createdAt: now.toISOString(),
|
|
151
|
-
expiresAt: new Date(now.getTime() + options.ttlMinutes * 60_000).toISOString(),
|
|
152
|
-
});
|
|
153
|
-
if (command)
|
|
154
|
-
exposure = await startManagedPreview(store, id);
|
|
155
|
-
}
|
|
156
|
-
finally {
|
|
157
|
-
store.close();
|
|
158
|
-
}
|
|
159
|
-
const result = { ...await exposureView(exposure), publicUrl: previewPublicURL(id, baseURL).toString() };
|
|
160
|
-
if (options.json)
|
|
161
|
-
printJson(result);
|
|
162
|
-
else {
|
|
163
|
-
console.log(`${result.id}\t${result.health}\t${result.label}`);
|
|
164
|
-
console.log(`url\t${result.publicUrl}`);
|
|
165
|
-
console.log(`session\t${result.piboSessionId}`);
|
|
166
|
-
console.log(`managed\t${result.managed}`);
|
|
167
|
-
if (result.serverStopAt)
|
|
168
|
-
console.log(`autoStop\t${result.serverStopAt}`);
|
|
169
|
-
console.log(`expires\t${result.expiresAt}`);
|
|
170
|
-
}
|
|
214
|
+
printCreatedPreview(await createPreviewExposure(port, options), options.json);
|
|
215
|
+
});
|
|
216
|
+
program
|
|
217
|
+
.command("expose-worker")
|
|
218
|
+
.argument("<worker>", "Running Pibo compute worker name or id")
|
|
219
|
+
.requiredOption("--session <pibo-session-id>", "Pibo Session that owns the preview")
|
|
220
|
+
.option("--name <label>", "Preview label")
|
|
221
|
+
.option("--workspace <path>", "Workspace recorded for diagnostics")
|
|
222
|
+
.option("--ttl-minutes <minutes>", "Automatic preview-definition lifetime", parsePreviewTtlMinutes, DEFAULT_PREVIEW_TTL_MINUTES)
|
|
223
|
+
.option("--json", "Print JSON")
|
|
224
|
+
.action(async (workerSelector, options) => {
|
|
225
|
+
const worker = await resolvePreviewComputeWorkerTarget(workerSelector);
|
|
226
|
+
const result = await createPreviewExposure(worker.webPort, options, {
|
|
227
|
+
label: worker.name,
|
|
228
|
+
workspace: worker.worktreePath,
|
|
229
|
+
proxyMode: "pibo-compute-dev-auth",
|
|
230
|
+
});
|
|
231
|
+
printCreatedPreview(result, options.json);
|
|
171
232
|
});
|
|
172
233
|
program
|
|
173
234
|
.command("list")
|
|
@@ -258,9 +319,12 @@ export async function runPreviewCli(argv = process.argv) {
|
|
|
258
319
|
program
|
|
259
320
|
.command("doctor")
|
|
260
321
|
.argument("[preview-id]")
|
|
322
|
+
.option("--public", "Verify public DNS, trusted TLS, and Preview-host routing")
|
|
261
323
|
.option("--json", "Print JSON")
|
|
262
324
|
.action(async (id, options) => {
|
|
263
325
|
const baseURL = requirePreviewBaseURL();
|
|
326
|
+
if (options.public && !id)
|
|
327
|
+
throw new Error("--public requires a preview id. Create an active Preview, then run `pibo preview doctor <preview-id> --public`.");
|
|
264
328
|
if (!id) {
|
|
265
329
|
const config = loadPreviewConfig();
|
|
266
330
|
const serverLimits = loadEffectivePreviewServerSettings();
|
|
@@ -277,6 +341,7 @@ export async function runPreviewCli(argv = process.argv) {
|
|
|
277
341
|
const result = {
|
|
278
342
|
configured: true,
|
|
279
343
|
baseURL: baseURL.toString(),
|
|
344
|
+
productionSetup: createPreviewProductionSetupPlan({ baseURL: baseURL.toString() }),
|
|
280
345
|
limits: {
|
|
281
346
|
...serverLimits,
|
|
282
347
|
maxProxyConnections: config.maxProxyConnections ?? DEFAULT_MAX_PREVIEW_PROXY_CONNECTIONS,
|
|
@@ -292,6 +357,8 @@ export async function runPreviewCli(argv = process.argv) {
|
|
|
292
357
|
console.log(`baseURL\t${result.baseURL}`);
|
|
293
358
|
console.log(`active\t${diagnostics.activeExposures}`);
|
|
294
359
|
console.log(`managed\t${diagnostics.managedStartingOrRunning}`);
|
|
360
|
+
console.log(`DNS required\t${result.productionSetup.dnsRecord.name}`);
|
|
361
|
+
console.log(`production guide\tpibo preview setup --base-url ${baseURL.origin}`);
|
|
295
362
|
}
|
|
296
363
|
return;
|
|
297
364
|
}
|
|
@@ -304,15 +371,23 @@ export async function runPreviewCli(argv = process.argv) {
|
|
|
304
371
|
finally {
|
|
305
372
|
store.close();
|
|
306
373
|
}
|
|
307
|
-
const
|
|
374
|
+
const publicChecks = options.public ? await inspectPreviewPublicRoute(baseURL, id) : undefined;
|
|
375
|
+
const result = {
|
|
376
|
+
...await exposureView(exposure),
|
|
377
|
+
...(publicChecks ? { publicChecks } : {}),
|
|
378
|
+
};
|
|
308
379
|
if (options.json)
|
|
309
380
|
printJson(result);
|
|
310
381
|
else {
|
|
311
382
|
console.log(`${result.id}\t${result.health}`);
|
|
312
383
|
console.log(`target\t${result.targetHost}:${result.targetPort}`);
|
|
313
384
|
console.log(`url\t${result.publicUrl}`);
|
|
385
|
+
if (publicChecks) {
|
|
386
|
+
console.log(`DNS\t${publicChecks.dns.status}\t${publicChecks.dns.detail}`);
|
|
387
|
+
console.log(`TLS and routing\t${publicChecks.tlsAndRouting.status}\t${publicChecks.tlsAndRouting.detail}`);
|
|
388
|
+
}
|
|
314
389
|
}
|
|
315
|
-
if (result.health !== "online")
|
|
390
|
+
if (result.health !== "online" || publicChecks && (publicChecks.dns.status !== "ok" || publicChecks.tlsAndRouting.status !== "ok"))
|
|
316
391
|
process.exitCode = 1;
|
|
317
392
|
});
|
|
318
393
|
const removeAction = async (id, options) => {
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { listWorkers } from "../compute/docker.js";
|
|
2
|
+
import { validatePreviewPort } from "./network.js";
|
|
3
|
+
export function selectPreviewComputeWorkerTarget(workers, selector) {
|
|
4
|
+
const normalized = selector.trim();
|
|
5
|
+
if (!normalized)
|
|
6
|
+
throw new Error("Compute worker name or id is required");
|
|
7
|
+
const worker = workers.find((candidate) => candidate.name === normalized || candidate.id === normalized);
|
|
8
|
+
if (!worker)
|
|
9
|
+
throw new Error(`Pibo compute worker "${normalized}" was not found`);
|
|
10
|
+
if (worker.role !== "worker" && worker.role !== "dev")
|
|
11
|
+
throw new Error(`Pibo compute target "${worker.name}" is not a managed worker`);
|
|
12
|
+
if (worker.state !== "running")
|
|
13
|
+
throw new Error(`Pibo compute worker "${worker.name}" is not running`);
|
|
14
|
+
const webPort = Number(worker.portMap.web);
|
|
15
|
+
if (!Number.isInteger(webPort))
|
|
16
|
+
throw new Error(`Pibo compute worker "${worker.name}" does not publish a labeled Web port`);
|
|
17
|
+
const bindings = worker.portMap["4788/tcp"]?.split(",") ?? [];
|
|
18
|
+
if (!bindings.includes(`127.0.0.1:${webPort}`)) {
|
|
19
|
+
throw new Error(`Pibo compute worker "${worker.name}" Web port is not published on host loopback`);
|
|
20
|
+
}
|
|
21
|
+
return {
|
|
22
|
+
id: worker.id,
|
|
23
|
+
name: worker.name,
|
|
24
|
+
webPort: validatePreviewPort(webPort),
|
|
25
|
+
worktreePath: worker.worktreePath,
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
export async function resolvePreviewComputeWorkerTarget(selector) {
|
|
29
|
+
let workers;
|
|
30
|
+
try {
|
|
31
|
+
workers = await listWorkers({ all: true });
|
|
32
|
+
}
|
|
33
|
+
catch (error) {
|
|
34
|
+
throw new Error(`Unable to inspect Pibo compute workers: ${error instanceof Error ? error.message : String(error)}`);
|
|
35
|
+
}
|
|
36
|
+
return selectPreviewComputeWorkerTarget(workers, selector);
|
|
37
|
+
}
|
package/dist/previews/config.js
CHANGED
|
@@ -5,6 +5,7 @@ import { parsePreviewBaseURL } from "./base-url.js";
|
|
|
5
5
|
export const DEFAULT_PREVIEW_TTL_MINUTES = 8 * 60;
|
|
6
6
|
export const DEFAULT_PREVIEW_TICKET_TTL_SECONDS = 60;
|
|
7
7
|
export const DEFAULT_PREVIEW_SESSION_TTL_MINUTES = 8 * 60;
|
|
8
|
+
export const PREVIEW_TLS_AUTHORIZATION_PATH = "/api/previews/tls-authorize";
|
|
8
9
|
export function loadPreviewConfig() {
|
|
9
10
|
return loadPiboConfig().preview ?? {};
|
|
10
11
|
}
|
|
@@ -18,7 +19,7 @@ export function loadEffectivePreviewServerSettings() {
|
|
|
18
19
|
}
|
|
19
20
|
export function requirePreviewBaseURL(value = loadPreviewConfig().baseURL) {
|
|
20
21
|
if (!value)
|
|
21
|
-
throw new Error("preview.baseURL is required.
|
|
22
|
+
throw new Error("preview.baseURL is required. Run `pibo preview setup --base-url https://preview.example.com` for DNS, TLS, proxy, and restart instructions.");
|
|
22
23
|
return parsePreviewBaseURL(value);
|
|
23
24
|
}
|
|
24
25
|
export function previewPublicURL(previewId, baseURL = requirePreviewBaseURL()) {
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
/** One-time v4 upgrade: session identity already owns every exposure and authorization. */
|
|
2
|
+
export function migratePreviewSessionOwnership(db) {
|
|
3
|
+
const columns = db.prepare("PRAGMA table_info(preview_exposures)").all();
|
|
4
|
+
if (columns.some((column) => column.name === "project_id")) {
|
|
5
|
+
db.exec("ALTER TABLE preview_exposures DROP COLUMN project_id");
|
|
6
|
+
}
|
|
7
|
+
}
|
package/dist/previews/proxy.js
CHANGED
|
@@ -154,8 +154,10 @@ function requestHeaders(incoming, exposure, requestURL, websocket) {
|
|
|
154
154
|
}
|
|
155
155
|
const targetHost = exposure.targetHost === "::1" ? `[::1]` : exposure.targetHost;
|
|
156
156
|
headers.host = `${targetHost}:${exposure.targetPort}`;
|
|
157
|
-
|
|
158
|
-
|
|
157
|
+
if (exposure.proxyMode !== "pibo-compute-dev-auth") {
|
|
158
|
+
headers["x-forwarded-host"] = requestURL.host;
|
|
159
|
+
headers["x-forwarded-proto"] = requestURL.protocol.slice(0, -1);
|
|
160
|
+
}
|
|
159
161
|
if (websocket) {
|
|
160
162
|
headers.connection = "Upgrade";
|
|
161
163
|
headers.upgrade = incoming.upgrade ?? "websocket";
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
import { lookup } from "node:dns/promises";
|
|
2
|
+
import { isIP } from "node:net";
|
|
3
|
+
import { PREVIEW_TLS_AUTHORIZATION_PATH, previewPublicURL, requirePreviewBaseURL } from "./config.js";
|
|
4
|
+
function shellQuote(value) {
|
|
5
|
+
return `'${value.replace(/'/g, `'\\''`)}'`;
|
|
6
|
+
}
|
|
7
|
+
function validateGatewayPort(port) {
|
|
8
|
+
if (!Number.isInteger(port) || port < 1 || port > 65535)
|
|
9
|
+
throw new Error("Gateway port must be an integer between 1 and 65535");
|
|
10
|
+
return port;
|
|
11
|
+
}
|
|
12
|
+
function validatePublicIp(value) {
|
|
13
|
+
if (!value)
|
|
14
|
+
return { type: "A or AAAA", value: "<public-gateway-ip>" };
|
|
15
|
+
const family = isIP(value);
|
|
16
|
+
if (family === 0)
|
|
17
|
+
throw new Error("Public IP must be a valid IPv4 or IPv6 address");
|
|
18
|
+
return { type: family === 4 ? "A" : "AAAA", value };
|
|
19
|
+
}
|
|
20
|
+
export function createPreviewProductionSetupPlan(input) {
|
|
21
|
+
const baseURL = requirePreviewBaseURL(input.baseURL);
|
|
22
|
+
const gatewayPort = validateGatewayPort(input.gatewayPort ?? 4788);
|
|
23
|
+
const publicIp = validatePublicIp(input.publicIp);
|
|
24
|
+
const wildcardHostname = `*.${baseURL.hostname}`;
|
|
25
|
+
const caddySiteAddress = baseURL.port ? `${wildcardHostname}:${baseURL.port}` : wildcardHostname;
|
|
26
|
+
const tlsAuthorizationURL = `http://127.0.0.1:${gatewayPort}${PREVIEW_TLS_AUTHORIZATION_PATH}`;
|
|
27
|
+
return {
|
|
28
|
+
baseURL: baseURL.toString(),
|
|
29
|
+
wildcardHostname,
|
|
30
|
+
dnsRecord: {
|
|
31
|
+
type: publicIp.type,
|
|
32
|
+
name: wildcardHostname,
|
|
33
|
+
value: publicIp.value,
|
|
34
|
+
},
|
|
35
|
+
gateway: {
|
|
36
|
+
host: "127.0.0.1",
|
|
37
|
+
port: gatewayPort,
|
|
38
|
+
tlsAuthorizationURL,
|
|
39
|
+
},
|
|
40
|
+
caddy: {
|
|
41
|
+
globalOptions: `on_demand_tls {\n\task ${tlsAuthorizationURL}\n}`,
|
|
42
|
+
siteBlock: `${caddySiteAddress} {\n\ttls {\n\t\ton_demand\n\t}\n\treverse_proxy 127.0.0.1:${gatewayPort}\n}`,
|
|
43
|
+
},
|
|
44
|
+
commands: {
|
|
45
|
+
configure: `pibo config set preview.baseURL ${shellQuote(baseURL.origin)}`,
|
|
46
|
+
restartGateway: "pibo gateway web restart",
|
|
47
|
+
validateCaddy: "caddy validate --config /etc/caddy/Caddyfile && systemctl reload caddy",
|
|
48
|
+
verify: "pibo preview doctor <preview-id> --public",
|
|
49
|
+
},
|
|
50
|
+
warnings: [
|
|
51
|
+
"Merge the on_demand_tls stanza into an existing Caddy global options block; a Caddyfile may contain only one global options block.",
|
|
52
|
+
"Create the wildcard DNS record before Caddy requests the first Preview certificate.",
|
|
53
|
+
"Restart the Pibo gateway after setting preview.baseURL; the safe restart may wait for active sessions.",
|
|
54
|
+
],
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
async function resolveHostname(hostname) {
|
|
58
|
+
return (await lookup(hostname, { all: true })).map((entry) => entry.address);
|
|
59
|
+
}
|
|
60
|
+
async function requestPreview(url) {
|
|
61
|
+
const response = await fetch(url, {
|
|
62
|
+
method: "GET",
|
|
63
|
+
redirect: "manual",
|
|
64
|
+
signal: AbortSignal.timeout(10_000),
|
|
65
|
+
});
|
|
66
|
+
return { status: response.status, location: response.headers.get("location") ?? undefined };
|
|
67
|
+
}
|
|
68
|
+
function errorDetail(error) {
|
|
69
|
+
return error instanceof Error ? error.message : String(error);
|
|
70
|
+
}
|
|
71
|
+
export async function inspectPreviewPublicRoute(baseURLValue, previewId, options = {}) {
|
|
72
|
+
const baseURL = requirePreviewBaseURL(baseURLValue instanceof URL ? baseURLValue.toString() : baseURLValue);
|
|
73
|
+
const publicURL = previewPublicURL(previewId, baseURL);
|
|
74
|
+
let addresses = [];
|
|
75
|
+
let dns;
|
|
76
|
+
try {
|
|
77
|
+
addresses = await (options.resolveHostname ?? resolveHostname)(publicURL.hostname);
|
|
78
|
+
dns = addresses.length > 0
|
|
79
|
+
? { status: "ok", detail: `Resolved ${publicURL.hostname}`, addresses }
|
|
80
|
+
: { status: "fail", detail: `${publicURL.hostname} returned no addresses`, addresses };
|
|
81
|
+
}
|
|
82
|
+
catch (error) {
|
|
83
|
+
dns = { status: "fail", detail: `DNS lookup failed: ${errorDetail(error)}`, addresses };
|
|
84
|
+
}
|
|
85
|
+
let tlsAndRouting;
|
|
86
|
+
try {
|
|
87
|
+
const response = await (options.request ?? requestPreview)(publicURL);
|
|
88
|
+
if (response.status === 401) {
|
|
89
|
+
tlsAndRouting = {
|
|
90
|
+
status: "ok",
|
|
91
|
+
detail: "HTTPS reached the Preview gateway and anonymous access was rejected",
|
|
92
|
+
httpStatus: response.status,
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
else {
|
|
96
|
+
tlsAndRouting = {
|
|
97
|
+
status: "fail",
|
|
98
|
+
detail: `Expected HTTP 401 from the Preview gateway, received HTTP ${response.status}`,
|
|
99
|
+
httpStatus: response.status,
|
|
100
|
+
...(response.location ? { location: response.location } : {}),
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
catch (error) {
|
|
105
|
+
tlsAndRouting = { status: "fail", detail: `HTTPS request failed: ${errorDetail(error)}` };
|
|
106
|
+
}
|
|
107
|
+
return { publicURL: publicURL.toString(), dns, tlsAndRouting };
|
|
108
|
+
}
|