@getpaseo/server 0.1.88 → 0.1.89
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/server/server/agent/agent-manager.js +4 -1
- package/dist/server/server/agent/agent-storage.d.ts +22 -22
- package/dist/server/server/agent/create-agent/create.d.ts +2 -0
- package/dist/server/server/agent/create-agent/create.js +16 -5
- package/dist/server/server/agent/create-agent-lifecycle-dispatch.d.ts +1 -0
- package/dist/server/server/agent/create-agent-lifecycle-dispatch.js +4 -0
- package/dist/server/server/agent/mcp-server.d.ts +1 -0
- package/dist/server/server/agent/mcp-server.js +113 -70
- package/dist/server/server/agent/providers/pi/agent.js +13 -0
- package/dist/server/server/agent/providers/pi/rpc-types.d.ts +3 -0
- package/dist/server/server/auto-archive-on-merge/archive-if-safe.d.ts +1 -0
- package/dist/server/server/auto-archive-on-merge/archive-if-safe.js +6 -1
- package/dist/server/server/bootstrap.d.ts +7 -2
- package/dist/server/server/bootstrap.js +152 -115
- package/dist/server/server/config.js +41 -0
- package/dist/server/server/loop-service.d.ts +22 -22
- package/dist/server/server/package-version.d.ts +2 -2
- package/dist/server/server/paseo-worktree-archive-service.d.ts +2 -0
- package/dist/server/server/paseo-worktree-archive-service.js +28 -9
- package/dist/server/server/persisted-config.d.ts +84 -28
- package/dist/server/server/persisted-config.js +17 -0
- package/dist/server/server/pid-lock.d.ts +2 -2
- package/dist/server/server/script-health-monitor.d.ts +4 -4
- package/dist/server/server/script-health-monitor.js +6 -6
- package/dist/server/server/script-proxy.d.ts +2 -39
- package/dist/server/server/script-proxy.js +1 -244
- package/dist/server/server/script-route-branch-handler.d.ts +2 -2
- package/dist/server/server/script-route-branch-handler.js +3 -37
- package/dist/server/server/script-status-projection.d.ts +6 -4
- package/dist/server/server/script-status-projection.js +85 -37
- package/dist/server/server/service-proxy.d.ts +237 -0
- package/dist/server/server/service-proxy.js +714 -0
- package/dist/server/server/session.d.ts +7 -3
- package/dist/server/server/session.js +22 -10
- package/dist/server/server/websocket-server.d.ts +7 -4
- package/dist/server/server/websocket-server.js +9 -4
- package/dist/server/server/workspace-directory.js +4 -0
- package/dist/server/server/workspace-git-service.d.ts +3 -0
- package/dist/server/server/workspace-git-service.js +53 -12
- package/dist/server/server/workspace-registry.d.ts +2 -2
- package/dist/server/server/workspace-service-env.d.ts +1 -0
- package/dist/server/server/workspace-service-env.js +23 -18
- package/dist/server/server/worktree/commands.d.ts +2 -0
- package/dist/server/server/worktree/commands.js +4 -1
- package/dist/server/server/worktree-bootstrap.d.ts +4 -3
- package/dist/server/server/worktree-bootstrap.js +14 -13
- package/dist/server/server/worktree-core.d.ts +1 -0
- package/dist/server/server/worktree-core.js +2 -0
- package/dist/server/server/worktree-session.d.ts +6 -2
- package/dist/server/server/worktree-session.js +3 -0
- package/dist/server/services/github-service.d.ts +1 -0
- package/dist/server/services/github-service.js +7 -1
- package/dist/server/utils/checkout-git.d.ts +6 -2
- package/dist/server/utils/checkout-git.js +17 -7
- package/dist/server/utils/worktree.d.ts +17 -12
- package/dist/server/utils/worktree.js +39 -22
- package/dist/src/server/persisted-config.js +17 -0
- package/package.json +5 -5
- package/dist/server/utils/script-hostname.d.ts +0 -8
- package/dist/server/utils/script-hostname.js +0 -14
|
@@ -1,245 +1,2 @@
|
|
|
1
|
-
|
|
2
|
-
import net from "node:net";
|
|
3
|
-
// ---------------------------------------------------------------------------
|
|
4
|
-
// Hop-by-hop headers that must not be forwarded
|
|
5
|
-
// ---------------------------------------------------------------------------
|
|
6
|
-
const HOP_BY_HOP_HEADERS = new Set([
|
|
7
|
-
"connection",
|
|
8
|
-
"transfer-encoding",
|
|
9
|
-
"keep-alive",
|
|
10
|
-
"upgrade",
|
|
11
|
-
"proxy-connection",
|
|
12
|
-
"proxy-authenticate",
|
|
13
|
-
"proxy-authorization",
|
|
14
|
-
"te",
|
|
15
|
-
"trailer",
|
|
16
|
-
]);
|
|
17
|
-
export class ScriptRouteStore {
|
|
18
|
-
constructor() {
|
|
19
|
-
this.routes = new Map();
|
|
20
|
-
this.workspaceHostnames = new Map();
|
|
21
|
-
}
|
|
22
|
-
registerRoute(entry) {
|
|
23
|
-
const previous = this.routes.get(entry.hostname);
|
|
24
|
-
if (previous) {
|
|
25
|
-
this.removeHostnameFromWorkspaceIndex(previous.workspaceId, previous.hostname);
|
|
26
|
-
}
|
|
27
|
-
const storedEntry = { ...entry };
|
|
28
|
-
this.routes.set(storedEntry.hostname, storedEntry);
|
|
29
|
-
this.addHostnameToWorkspaceIndex(storedEntry.workspaceId, storedEntry.hostname);
|
|
30
|
-
}
|
|
31
|
-
removeRoute(hostname) {
|
|
32
|
-
const entry = this.routes.get(hostname);
|
|
33
|
-
if (!entry) {
|
|
34
|
-
return;
|
|
35
|
-
}
|
|
36
|
-
this.routes.delete(hostname);
|
|
37
|
-
this.removeHostnameFromWorkspaceIndex(entry.workspaceId, hostname);
|
|
38
|
-
}
|
|
39
|
-
removeRouteForWorkspaceScript(params) {
|
|
40
|
-
const routes = this.listRoutesForWorkspace(params.workspaceId);
|
|
41
|
-
const route = routes.find((entry) => entry.scriptName === params.scriptName);
|
|
42
|
-
if (!route) {
|
|
43
|
-
return;
|
|
44
|
-
}
|
|
45
|
-
this.removeRoute(route.hostname);
|
|
46
|
-
}
|
|
47
|
-
removeRoutesForPort(port) {
|
|
48
|
-
for (const [hostname, entry] of this.routes) {
|
|
49
|
-
if (entry.port === port) {
|
|
50
|
-
this.routes.delete(hostname);
|
|
51
|
-
this.removeHostnameFromWorkspaceIndex(entry.workspaceId, hostname);
|
|
52
|
-
}
|
|
53
|
-
}
|
|
54
|
-
}
|
|
55
|
-
findRoute(host) {
|
|
56
|
-
// Strip port suffix from the Host header value
|
|
57
|
-
const hostname = host.replace(/:\d+$/, "");
|
|
58
|
-
// 1. Exact match
|
|
59
|
-
const exactRoute = this.routes.get(hostname);
|
|
60
|
-
if (exactRoute !== undefined) {
|
|
61
|
-
return { hostname: exactRoute.hostname, port: exactRoute.port };
|
|
62
|
-
}
|
|
63
|
-
// 2. Subdomain match — walk up the labels looking for a registered parent
|
|
64
|
-
const parts = hostname.split(".");
|
|
65
|
-
for (let i = 1; i < parts.length; i++) {
|
|
66
|
-
const candidate = parts.slice(i).join(".");
|
|
67
|
-
const candidateRoute = this.routes.get(candidate);
|
|
68
|
-
if (candidateRoute !== undefined) {
|
|
69
|
-
return { hostname: candidateRoute.hostname, port: candidateRoute.port };
|
|
70
|
-
}
|
|
71
|
-
}
|
|
72
|
-
return null;
|
|
73
|
-
}
|
|
74
|
-
getRouteEntry(hostname) {
|
|
75
|
-
const entry = this.routes.get(hostname);
|
|
76
|
-
return entry ? { ...entry } : null;
|
|
77
|
-
}
|
|
78
|
-
listRoutes() {
|
|
79
|
-
return Array.from(this.routes.values()).map((entry) => Object.assign({}, entry));
|
|
80
|
-
}
|
|
81
|
-
listRoutesForWorkspace(workspaceId) {
|
|
82
|
-
const hostnames = this.workspaceHostnames.get(workspaceId);
|
|
83
|
-
if (!hostnames) {
|
|
84
|
-
return [];
|
|
85
|
-
}
|
|
86
|
-
const routes = [];
|
|
87
|
-
for (const hostname of hostnames) {
|
|
88
|
-
const entry = this.routes.get(hostname);
|
|
89
|
-
if (entry) {
|
|
90
|
-
routes.push({ ...entry });
|
|
91
|
-
}
|
|
92
|
-
}
|
|
93
|
-
return routes;
|
|
94
|
-
}
|
|
95
|
-
addHostnameToWorkspaceIndex(workspaceId, hostname) {
|
|
96
|
-
const hostnames = this.workspaceHostnames.get(workspaceId) ?? new Set();
|
|
97
|
-
hostnames.add(hostname);
|
|
98
|
-
this.workspaceHostnames.set(workspaceId, hostnames);
|
|
99
|
-
}
|
|
100
|
-
removeHostnameFromWorkspaceIndex(workspaceId, hostname) {
|
|
101
|
-
const hostnames = this.workspaceHostnames.get(workspaceId);
|
|
102
|
-
if (!hostnames) {
|
|
103
|
-
return;
|
|
104
|
-
}
|
|
105
|
-
hostnames.delete(hostname);
|
|
106
|
-
if (hostnames.size === 0) {
|
|
107
|
-
this.workspaceHostnames.delete(workspaceId);
|
|
108
|
-
}
|
|
109
|
-
}
|
|
110
|
-
}
|
|
111
|
-
// ---------------------------------------------------------------------------
|
|
112
|
-
// Helpers
|
|
113
|
-
// ---------------------------------------------------------------------------
|
|
114
|
-
function stripHopByHopHeaders(rawHeaders) {
|
|
115
|
-
const out = {};
|
|
116
|
-
for (const [key, value] of Object.entries(rawHeaders)) {
|
|
117
|
-
if (value === undefined)
|
|
118
|
-
continue;
|
|
119
|
-
if (HOP_BY_HOP_HEADERS.has(key.toLowerCase()))
|
|
120
|
-
continue;
|
|
121
|
-
out[key] = value;
|
|
122
|
-
}
|
|
123
|
-
return out;
|
|
124
|
-
}
|
|
125
|
-
// ---------------------------------------------------------------------------
|
|
126
|
-
// createScriptProxyMiddleware
|
|
127
|
-
// ---------------------------------------------------------------------------
|
|
128
|
-
export function createScriptProxyMiddleware({ routeStore, logger, }) {
|
|
129
|
-
return (req, res, next) => {
|
|
130
|
-
const hostHeader = req.headers.host;
|
|
131
|
-
if (!hostHeader) {
|
|
132
|
-
next();
|
|
133
|
-
return;
|
|
134
|
-
}
|
|
135
|
-
const route = routeStore.findRoute(hostHeader);
|
|
136
|
-
if (!route) {
|
|
137
|
-
next();
|
|
138
|
-
return;
|
|
139
|
-
}
|
|
140
|
-
const forwardedHeaders = stripHopByHopHeaders(req.headers);
|
|
141
|
-
forwardedHeaders["x-forwarded-for"] = req.socket.remoteAddress ?? "127.0.0.1";
|
|
142
|
-
forwardedHeaders["x-forwarded-host"] = hostHeader.replace(/:\d+$/, "");
|
|
143
|
-
forwardedHeaders["x-forwarded-proto"] = req.protocol;
|
|
144
|
-
const proxyReq = http.request({
|
|
145
|
-
hostname: "127.0.0.1",
|
|
146
|
-
port: route.port,
|
|
147
|
-
path: req.originalUrl,
|
|
148
|
-
method: req.method,
|
|
149
|
-
headers: forwardedHeaders,
|
|
150
|
-
}, (proxyRes) => {
|
|
151
|
-
const responseHeaders = stripHopByHopHeaders(proxyRes.headers);
|
|
152
|
-
res.writeHead(proxyRes.statusCode ?? 502, responseHeaders);
|
|
153
|
-
proxyRes.pipe(res, { end: true });
|
|
154
|
-
});
|
|
155
|
-
proxyReq.on("error", (err) => {
|
|
156
|
-
logger.warn({ err, hostname: route.hostname, port: route.port }, "Script proxy: upstream unreachable");
|
|
157
|
-
if (!res.headersSent) {
|
|
158
|
-
res.writeHead(502, { "content-type": "text/plain" });
|
|
159
|
-
res.end("502 Bad Gateway");
|
|
160
|
-
}
|
|
161
|
-
});
|
|
162
|
-
req.pipe(proxyReq, { end: true });
|
|
163
|
-
};
|
|
164
|
-
}
|
|
165
|
-
// ---------------------------------------------------------------------------
|
|
166
|
-
// createScriptProxyUpgradeHandler
|
|
167
|
-
// ---------------------------------------------------------------------------
|
|
168
|
-
export function createScriptProxyUpgradeHandler({ routeStore, logger, }) {
|
|
169
|
-
return (req, socket, head) => {
|
|
170
|
-
const hostHeader = req.headers.host;
|
|
171
|
-
if (!hostHeader) {
|
|
172
|
-
return;
|
|
173
|
-
}
|
|
174
|
-
const route = routeStore.findRoute(hostHeader);
|
|
175
|
-
if (!route) {
|
|
176
|
-
return;
|
|
177
|
-
}
|
|
178
|
-
const targetSocket = net.connect({ host: "127.0.0.1", port: route.port }, () => {
|
|
179
|
-
// Reconstruct the raw HTTP upgrade request to send to the target
|
|
180
|
-
const forwardedHeaders = stripHopByHopHeaders(req.headers);
|
|
181
|
-
forwardedHeaders["x-forwarded-for"] = req.socket.remoteAddress ?? "127.0.0.1";
|
|
182
|
-
forwardedHeaders["x-forwarded-host"] = hostHeader.replace(/:\d+$/, "");
|
|
183
|
-
forwardedHeaders["x-forwarded-proto"] = "http";
|
|
184
|
-
// Re-include upgrade and connection headers — they are required for
|
|
185
|
-
// WebSocket handshake even though they are hop-by-hop.
|
|
186
|
-
forwardedHeaders["connection"] = "Upgrade";
|
|
187
|
-
forwardedHeaders["upgrade"] = req.headers.upgrade ?? "websocket";
|
|
188
|
-
const headerLines = [];
|
|
189
|
-
headerLines.push(`${req.method ?? "GET"} ${req.url ?? "/"} HTTP/${req.httpVersion}`);
|
|
190
|
-
for (const [key, value] of Object.entries(forwardedHeaders)) {
|
|
191
|
-
if (Array.isArray(value)) {
|
|
192
|
-
for (const v of value) {
|
|
193
|
-
headerLines.push(`${key}: ${v}`);
|
|
194
|
-
}
|
|
195
|
-
}
|
|
196
|
-
else {
|
|
197
|
-
headerLines.push(`${key}: ${value}`);
|
|
198
|
-
}
|
|
199
|
-
}
|
|
200
|
-
headerLines.push("\r\n");
|
|
201
|
-
targetSocket.write(headerLines.join("\r\n"));
|
|
202
|
-
if (head.length > 0) {
|
|
203
|
-
targetSocket.write(head);
|
|
204
|
-
}
|
|
205
|
-
// Pipe in both directions
|
|
206
|
-
targetSocket.pipe(socket);
|
|
207
|
-
socket.pipe(targetSocket);
|
|
208
|
-
});
|
|
209
|
-
targetSocket.on("error", (err) => {
|
|
210
|
-
logger.warn({ err, hostname: route.hostname, port: route.port }, "Script proxy: WebSocket upstream unreachable");
|
|
211
|
-
socket.end();
|
|
212
|
-
});
|
|
213
|
-
socket.on("error", () => {
|
|
214
|
-
targetSocket.destroy();
|
|
215
|
-
});
|
|
216
|
-
};
|
|
217
|
-
}
|
|
218
|
-
// ---------------------------------------------------------------------------
|
|
219
|
-
// findFreePort
|
|
220
|
-
// ---------------------------------------------------------------------------
|
|
221
|
-
export function findFreePort() {
|
|
222
|
-
return new Promise((resolve, reject) => {
|
|
223
|
-
const server = net.createServer();
|
|
224
|
-
server.unref();
|
|
225
|
-
server.listen(0, "127.0.0.1", () => {
|
|
226
|
-
const address = server.address();
|
|
227
|
-
if (!address || typeof address === "string") {
|
|
228
|
-
server.close();
|
|
229
|
-
reject(new Error("Failed to get assigned port"));
|
|
230
|
-
return;
|
|
231
|
-
}
|
|
232
|
-
const { port } = address;
|
|
233
|
-
server.close((err) => {
|
|
234
|
-
if (err) {
|
|
235
|
-
reject(err);
|
|
236
|
-
}
|
|
237
|
-
else {
|
|
238
|
-
resolve(port);
|
|
239
|
-
}
|
|
240
|
-
});
|
|
241
|
-
});
|
|
242
|
-
server.on("error", reject);
|
|
243
|
-
});
|
|
244
|
-
}
|
|
1
|
+
export { createScriptProxyMiddleware, createScriptProxyUpgradeHandler, findFreePort, ScriptRouteStore, } from "./service-proxy.js";
|
|
245
2
|
//# sourceMappingURL=script-proxy.js.map
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { Logger } from "pino";
|
|
2
|
-
import type {
|
|
2
|
+
import type { ServiceProxySubsystem } from "./service-proxy.js";
|
|
3
3
|
interface BranchChangeRouteHandlerOptions {
|
|
4
|
-
|
|
4
|
+
serviceProxy: ServiceProxySubsystem;
|
|
5
5
|
onRoutesChanged: (workspaceId: string) => void;
|
|
6
6
|
logger?: Logger;
|
|
7
7
|
}
|
|
@@ -1,44 +1,10 @@
|
|
|
1
|
-
import { buildScriptHostname } from "../utils/script-hostname.js";
|
|
2
1
|
export function createBranchChangeRouteHandler(options) {
|
|
3
2
|
return (workspaceId, _oldBranch, newBranch) => {
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
if (routes.length === 0) {
|
|
3
|
+
const changed = options.serviceProxy.replaceWorkspaceBranchRoutes({ workspaceId, newBranch });
|
|
4
|
+
if (!changed) {
|
|
7
5
|
return;
|
|
8
6
|
}
|
|
9
|
-
|
|
10
|
-
for (const route of routes) {
|
|
11
|
-
const newHostname = buildScriptHostname({
|
|
12
|
-
projectSlug: route.projectSlug,
|
|
13
|
-
branchName: newBranch,
|
|
14
|
-
scriptName: route.scriptName,
|
|
15
|
-
});
|
|
16
|
-
if (newHostname !== route.hostname) {
|
|
17
|
-
updates.push({
|
|
18
|
-
oldHostname: route.hostname,
|
|
19
|
-
newHostname,
|
|
20
|
-
route,
|
|
21
|
-
});
|
|
22
|
-
}
|
|
23
|
-
}
|
|
24
|
-
if (updates.length === 0) {
|
|
25
|
-
return;
|
|
26
|
-
}
|
|
27
|
-
for (const { oldHostname, newHostname, route } of updates) {
|
|
28
|
-
options.routeStore.removeRoute(oldHostname);
|
|
29
|
-
options.routeStore.registerRoute({
|
|
30
|
-
hostname: newHostname,
|
|
31
|
-
port: route.port,
|
|
32
|
-
workspaceId: route.workspaceId,
|
|
33
|
-
projectSlug: route.projectSlug,
|
|
34
|
-
scriptName: route.scriptName,
|
|
35
|
-
});
|
|
36
|
-
options.logger?.info({
|
|
37
|
-
oldHostname,
|
|
38
|
-
newHostname,
|
|
39
|
-
scriptName: route.scriptName,
|
|
40
|
-
}, "Updated script route for branch rename");
|
|
41
|
-
}
|
|
7
|
+
options.logger?.info({ workspaceId, newBranch }, "Updated service proxy routes for branch rename");
|
|
42
8
|
options.onRoutesChanged(workspaceId);
|
|
43
9
|
};
|
|
44
10
|
}
|
|
@@ -2,7 +2,7 @@ import type { Logger } from "pino";
|
|
|
2
2
|
import type { SessionOutboundMessage, WorkspaceScriptPayload } from "@getpaseo/protocol/messages";
|
|
3
3
|
import type { PaseoConfig } from "@getpaseo/protocol/paseo-config-schema";
|
|
4
4
|
import type { ScriptHealthEntry, ScriptHealthState } from "./script-health-monitor.js";
|
|
5
|
-
import type {
|
|
5
|
+
import type { ServiceProxySubsystem } from "./service-proxy.js";
|
|
6
6
|
import type { WorkspaceScriptRuntimeStore } from "./workspace-script-runtime-store.js";
|
|
7
7
|
interface SessionEmitter {
|
|
8
8
|
emit(message: SessionOutboundMessage): void;
|
|
@@ -11,9 +11,10 @@ interface BuildWorkspaceScriptPayloadsOptions {
|
|
|
11
11
|
workspaceId: string;
|
|
12
12
|
workspaceDirectory: string;
|
|
13
13
|
paseoConfig: PaseoConfig | null;
|
|
14
|
-
|
|
14
|
+
serviceProxy: ServiceProxySubsystem;
|
|
15
15
|
runtimeStore: WorkspaceScriptRuntimeStore;
|
|
16
16
|
daemonPort: number | null;
|
|
17
|
+
serviceProxyPublicBaseUrl?: string | null;
|
|
17
18
|
gitMetadata?: {
|
|
18
19
|
projectSlug: string;
|
|
19
20
|
currentBranch: string | null;
|
|
@@ -22,11 +23,12 @@ interface BuildWorkspaceScriptPayloadsOptions {
|
|
|
22
23
|
}
|
|
23
24
|
export declare function readPaseoConfigForProjection(workspaceDirectory: string, logger: Logger): PaseoConfig | null;
|
|
24
25
|
export declare function buildWorkspaceScriptPayloads(options: BuildWorkspaceScriptPayloadsOptions): WorkspaceScriptPayload[];
|
|
25
|
-
export declare function createScriptStatusEmitter({ sessions,
|
|
26
|
+
export declare function createScriptStatusEmitter({ sessions, serviceProxy, runtimeStore, daemonPort, serviceProxyPublicBaseUrl, resolveWorkspaceDirectory, logger, }: {
|
|
26
27
|
sessions: () => SessionEmitter[];
|
|
27
|
-
|
|
28
|
+
serviceProxy: ServiceProxySubsystem;
|
|
28
29
|
runtimeStore: WorkspaceScriptRuntimeStore;
|
|
29
30
|
daemonPort: number | null | (() => number | null);
|
|
31
|
+
serviceProxyPublicBaseUrl?: string | null;
|
|
30
32
|
resolveWorkspaceDirectory: (workspaceId: string) => string | null | Promise<string | null>;
|
|
31
33
|
logger: Logger;
|
|
32
34
|
}): (workspaceId: string, scripts: ScriptHealthEntry[]) => void;
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { buildScriptHostname } from "../utils/script-hostname.js";
|
|
2
1
|
import { getScriptConfigs, isServiceScript, readPaseoConfig } from "../utils/worktree.js";
|
|
3
2
|
import { deriveProjectSlug } from "./workspace-git-metadata.js";
|
|
4
3
|
export function readPaseoConfigForProjection(workspaceDirectory, logger) {
|
|
@@ -15,12 +14,6 @@ function resolveDaemonPort(daemonPort) {
|
|
|
15
14
|
}
|
|
16
15
|
return daemonPort;
|
|
17
16
|
}
|
|
18
|
-
function toServiceProxyUrl(hostname, daemonPort) {
|
|
19
|
-
if (daemonPort === null) {
|
|
20
|
-
return null;
|
|
21
|
-
}
|
|
22
|
-
return `http://${hostname}:${daemonPort}`;
|
|
23
|
-
}
|
|
24
17
|
function toWireHealth(health) {
|
|
25
18
|
if (health === "pending" || health === null) {
|
|
26
19
|
return null;
|
|
@@ -33,48 +26,99 @@ function sortPayloads(payloads) {
|
|
|
33
26
|
sensitivity: "base",
|
|
34
27
|
}));
|
|
35
28
|
}
|
|
36
|
-
function
|
|
29
|
+
function projectWorkspaceServiceState(params) {
|
|
30
|
+
return params.ctx.serviceProxy.projectWorkspaceServiceState({
|
|
31
|
+
workspaceId: params.workspaceId,
|
|
32
|
+
projectSlug: params.ctx.projectSlug,
|
|
33
|
+
branchName: params.ctx.branchName,
|
|
34
|
+
scriptName: params.scriptName,
|
|
35
|
+
daemonPort: params.ctx.daemonPort,
|
|
36
|
+
publicBaseUrl: params.ctx.serviceProxyPublicBaseUrl,
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
function buildConfiguredPlainScriptPayload(scriptName, runtimeEntry) {
|
|
40
|
+
return {
|
|
41
|
+
scriptName,
|
|
42
|
+
type: "script",
|
|
43
|
+
hostname: scriptName,
|
|
44
|
+
port: null,
|
|
45
|
+
proxyUrl: null,
|
|
46
|
+
lifecycle: runtimeEntry?.lifecycle ?? "stopped",
|
|
47
|
+
health: null,
|
|
48
|
+
exitCode: runtimeEntry?.exitCode ?? null,
|
|
49
|
+
terminalId: runtimeEntry?.terminalId ?? null,
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
function buildConfiguredScriptPayload(scriptName, config, runtimeEntry, serviceState, ctx) {
|
|
37
53
|
const configIsService = isServiceScript(config);
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
54
|
+
if (!configIsService) {
|
|
55
|
+
return buildConfiguredPlainScriptPayload(scriptName, runtimeEntry);
|
|
56
|
+
}
|
|
57
|
+
const type = "service";
|
|
58
|
+
const configuredPort = config.port ?? null;
|
|
59
|
+
const hostname = (serviceState ??
|
|
60
|
+
ctx.serviceProxy.projectWorkspaceService({
|
|
61
|
+
projectSlug: ctx.projectSlug,
|
|
62
|
+
branchName: ctx.branchName,
|
|
63
|
+
scriptName,
|
|
64
|
+
daemonPort: ctx.daemonPort,
|
|
65
|
+
publicBaseUrl: ctx.serviceProxyPublicBaseUrl,
|
|
66
|
+
})).hostname;
|
|
67
|
+
const urls = serviceState ??
|
|
68
|
+
ctx.serviceProxy.projectUrls({
|
|
69
|
+
projectSlug: ctx.projectSlug,
|
|
70
|
+
branchName: ctx.branchName,
|
|
71
|
+
scriptName,
|
|
72
|
+
daemonPort: ctx.daemonPort,
|
|
73
|
+
publicBaseUrl: ctx.serviceProxyPublicBaseUrl,
|
|
74
|
+
});
|
|
48
75
|
return {
|
|
49
76
|
scriptName,
|
|
50
77
|
type,
|
|
51
78
|
hostname,
|
|
52
|
-
port:
|
|
53
|
-
|
|
79
|
+
port: serviceState?.port ?? configuredPort,
|
|
80
|
+
localProxyUrl: urls.localProxyUrl,
|
|
81
|
+
publicProxyUrl: urls.publicProxyUrl,
|
|
82
|
+
proxyUrl: urls.proxyUrl,
|
|
54
83
|
lifecycle: runtimeEntry?.lifecycle ?? "stopped",
|
|
55
|
-
health:
|
|
84
|
+
health: toWireHealth(ctx.resolveHealth?.(hostname) ?? null),
|
|
56
85
|
exitCode: runtimeEntry?.exitCode ?? null,
|
|
57
86
|
terminalId: runtimeEntry?.terminalId ?? null,
|
|
58
87
|
};
|
|
59
88
|
}
|
|
60
|
-
function buildOrphanRuntimePayload(runtimeEntry,
|
|
89
|
+
function buildOrphanRuntimePayload(runtimeEntry, serviceState, ctx) {
|
|
61
90
|
const type = runtimeEntry.type;
|
|
62
91
|
const hostname = type === "service"
|
|
63
|
-
? (
|
|
64
|
-
|
|
92
|
+
? (serviceState ??
|
|
93
|
+
ctx.serviceProxy.projectWorkspaceService({
|
|
65
94
|
projectSlug: ctx.projectSlug,
|
|
66
95
|
branchName: ctx.branchName,
|
|
67
96
|
scriptName: runtimeEntry.scriptName,
|
|
68
|
-
|
|
97
|
+
daemonPort: ctx.daemonPort,
|
|
98
|
+
publicBaseUrl: ctx.serviceProxyPublicBaseUrl,
|
|
99
|
+
})).hostname
|
|
69
100
|
: runtimeEntry.scriptName;
|
|
101
|
+
const urls = serviceState ??
|
|
102
|
+
ctx.serviceProxy.projectUrls({
|
|
103
|
+
projectSlug: ctx.projectSlug,
|
|
104
|
+
branchName: ctx.branchName,
|
|
105
|
+
scriptName: runtimeEntry.scriptName,
|
|
106
|
+
daemonPort: ctx.daemonPort,
|
|
107
|
+
publicBaseUrl: ctx.serviceProxyPublicBaseUrl,
|
|
108
|
+
});
|
|
70
109
|
return {
|
|
71
110
|
scriptName: runtimeEntry.scriptName,
|
|
72
111
|
type,
|
|
73
112
|
hostname,
|
|
74
|
-
port: type === "service" ? (
|
|
75
|
-
|
|
113
|
+
port: type === "service" ? (serviceState?.port ?? null) : null,
|
|
114
|
+
...(type === "service"
|
|
115
|
+
? { localProxyUrl: urls.localProxyUrl, publicProxyUrl: urls.publicProxyUrl }
|
|
116
|
+
: {}),
|
|
117
|
+
proxyUrl: type === "service" ? urls.proxyUrl : null,
|
|
76
118
|
lifecycle: runtimeEntry.lifecycle,
|
|
77
|
-
health: type === "service" &&
|
|
119
|
+
health: type === "service" && serviceState?.port !== null
|
|
120
|
+
? toWireHealth(ctx.resolveHealth?.(hostname) ?? null)
|
|
121
|
+
: null,
|
|
78
122
|
exitCode: runtimeEntry.exitCode,
|
|
79
123
|
terminalId: runtimeEntry.terminalId,
|
|
80
124
|
};
|
|
@@ -88,27 +132,30 @@ export function buildWorkspaceScriptPayloads(options) {
|
|
|
88
132
|
const runtimeEntries = new Map(options.runtimeStore
|
|
89
133
|
.listForWorkspace(workspaceId)
|
|
90
134
|
.map((entry) => [entry.scriptName, entry]));
|
|
91
|
-
const routesByScriptName = new Map(options.routeStore
|
|
92
|
-
.listRoutesForWorkspace(workspaceId)
|
|
93
|
-
.map((entry) => [entry.scriptName, entry]));
|
|
94
135
|
const ctx = {
|
|
95
136
|
projectSlug,
|
|
96
137
|
branchName,
|
|
97
138
|
daemonPort: options.daemonPort,
|
|
139
|
+
serviceProxyPublicBaseUrl: options.serviceProxyPublicBaseUrl,
|
|
140
|
+
serviceProxy: options.serviceProxy,
|
|
98
141
|
resolveHealth: options.resolveHealth,
|
|
99
142
|
};
|
|
100
143
|
const payloads = [];
|
|
101
144
|
for (const [scriptName, config] of scriptConfigs.entries()) {
|
|
102
145
|
const runtimeEntry = runtimeEntries.get(scriptName) ?? null;
|
|
103
|
-
const
|
|
104
|
-
|
|
146
|
+
const serviceState = isServiceScript(config)
|
|
147
|
+
? projectWorkspaceServiceState({ workspaceId, scriptName, ctx })
|
|
148
|
+
: null;
|
|
149
|
+
payloads.push(buildConfiguredScriptPayload(scriptName, config, runtimeEntry, serviceState, ctx));
|
|
105
150
|
}
|
|
106
151
|
for (const runtimeEntry of runtimeEntries.values()) {
|
|
107
152
|
if (scriptConfigs.has(runtimeEntry.scriptName) || runtimeEntry.lifecycle !== "running") {
|
|
108
153
|
continue;
|
|
109
154
|
}
|
|
110
|
-
const
|
|
111
|
-
|
|
155
|
+
const serviceState = runtimeEntry.type === "service"
|
|
156
|
+
? projectWorkspaceServiceState({ workspaceId, scriptName: runtimeEntry.scriptName, ctx })
|
|
157
|
+
: null;
|
|
158
|
+
payloads.push(buildOrphanRuntimePayload(runtimeEntry, serviceState, ctx));
|
|
112
159
|
}
|
|
113
160
|
return sortPayloads(payloads);
|
|
114
161
|
}
|
|
@@ -121,7 +168,7 @@ function buildScriptStatusUpdateMessage(params) {
|
|
|
121
168
|
},
|
|
122
169
|
};
|
|
123
170
|
}
|
|
124
|
-
export function createScriptStatusEmitter({ sessions,
|
|
171
|
+
export function createScriptStatusEmitter({ sessions, serviceProxy, runtimeStore, daemonPort, serviceProxyPublicBaseUrl, resolveWorkspaceDirectory, logger, }) {
|
|
125
172
|
return (workspaceId, scripts) => {
|
|
126
173
|
void (async () => {
|
|
127
174
|
const workspaceDirectory = await resolveWorkspaceDirectory(workspaceId);
|
|
@@ -134,9 +181,10 @@ export function createScriptStatusEmitter({ sessions, routeStore, runtimeStore,
|
|
|
134
181
|
workspaceId,
|
|
135
182
|
workspaceDirectory,
|
|
136
183
|
paseoConfig: readPaseoConfigForProjection(workspaceDirectory, logger),
|
|
137
|
-
|
|
184
|
+
serviceProxy,
|
|
138
185
|
runtimeStore,
|
|
139
186
|
daemonPort: resolvedDaemonPort,
|
|
187
|
+
serviceProxyPublicBaseUrl,
|
|
140
188
|
resolveHealth: (hostname) => scriptHealthByHostname.get(hostname) ?? null,
|
|
141
189
|
});
|
|
142
190
|
const message = buildScriptStatusUpdateMessage({
|