@procedures/peer 0.2.3 → 0.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.
Files changed (69) hide show
  1. package/CHANGELOG.md +75 -0
  2. package/README.md +3 -3
  3. package/dist/.tsbuildinfo +1 -1
  4. package/dist/connection/index.d.ts +3 -2
  5. package/dist/connection/index.d.ts.map +1 -1
  6. package/dist/connection/index.js +6 -2
  7. package/dist/connection/index.js.map +1 -1
  8. package/dist/discovery/index.d.ts +4 -2
  9. package/dist/discovery/index.d.ts.map +1 -1
  10. package/dist/discovery/index.js +22 -34
  11. package/dist/discovery/index.js.map +1 -1
  12. package/dist/input-schemas.d.ts +5 -7
  13. package/dist/input-schemas.d.ts.map +1 -1
  14. package/dist/input-schemas.js +4 -15
  15. package/dist/input-schemas.js.map +1 -1
  16. package/dist/module.d.ts +2 -1
  17. package/dist/module.d.ts.map +1 -1
  18. package/dist/module.js +2 -7
  19. package/dist/module.js.map +1 -1
  20. package/dist/output-schemas.d.ts +1 -4
  21. package/dist/output-schemas.d.ts.map +1 -1
  22. package/dist/output-schemas.js +0 -23
  23. package/dist/output-schemas.js.map +1 -1
  24. package/dist/peer/index.d.ts +16 -3
  25. package/dist/peer/index.d.ts.map +1 -1
  26. package/dist/peer/index.js +45 -6
  27. package/dist/peer/index.js.map +1 -1
  28. package/dist/procedures/index.d.ts +0 -6
  29. package/dist/procedures/index.d.ts.map +1 -1
  30. package/dist/procedures/index.js +1 -3
  31. package/dist/procedures/index.js.map +1 -1
  32. package/dist/procedures/manifest.generate.d.ts.map +1 -1
  33. package/dist/procedures/manifest.generate.js +10 -33
  34. package/dist/procedures/manifest.generate.js.map +1 -1
  35. package/dist/procedures/server.call.d.ts.map +1 -1
  36. package/dist/procedures/server.call.js +8 -3
  37. package/dist/procedures/server.call.js.map +1 -1
  38. package/dist/procedures/server.connect.d.ts.map +1 -1
  39. package/dist/procedures/server.connect.js +3 -1
  40. package/dist/procedures/server.connect.js.map +1 -1
  41. package/dist/types.d.ts +2 -58
  42. package/dist/types.d.ts.map +1 -1
  43. package/package.json +3 -3
  44. package/src/connection/index.ts +8 -5
  45. package/src/discovery/index.ts +26 -35
  46. package/src/input-schemas.ts +4 -27
  47. package/src/module.ts +2 -7
  48. package/src/output-schemas.ts +0 -29
  49. package/src/peer/index.ts +70 -7
  50. package/src/procedures/index.ts +1 -8
  51. package/src/procedures/manifest.generate.ts +10 -33
  52. package/src/procedures/server.call.ts +8 -3
  53. package/src/procedures/server.connect.ts +3 -1
  54. package/src/types.ts +2 -76
  55. package/dist/procedures/server.start.d.ts +0 -14
  56. package/dist/procedures/server.start.d.ts.map +0 -1
  57. package/dist/procedures/server.start.js +0 -182
  58. package/dist/procedures/server.start.js.map +0 -1
  59. package/dist/procedures/server.status.d.ts +0 -13
  60. package/dist/procedures/server.status.d.ts.map +0 -1
  61. package/dist/procedures/server.status.js +0 -118
  62. package/dist/procedures/server.status.js.map +0 -1
  63. package/dist/procedures/server.stop.d.ts +0 -12
  64. package/dist/procedures/server.stop.d.ts.map +0 -1
  65. package/dist/procedures/server.stop.js +0 -98
  66. package/dist/procedures/server.stop.js.map +0 -1
  67. package/src/procedures/server.start.ts +0 -212
  68. package/src/procedures/server.status.ts +0 -141
  69. package/src/procedures/server.stop.ts +0 -123
package/src/peer/index.ts CHANGED
@@ -14,12 +14,15 @@ import {
14
14
  defaultServerUrlStrategy,
15
15
  defaultUrlPattern,
16
16
  procedureServerOptions,
17
+ hostProcedures,
18
+ metricsInterceptor,
19
+ Client,
17
20
  type BrowserGuardOptions,
18
21
  type HostPolicy,
19
22
  type Method,
20
23
  } from "@procedures/client";
21
24
  import type { Request } from "express";
22
- import type { ProcedurePath } from "@procedures/client";
25
+ import type { ProcedurePath, StopOptions } from "@procedures/client";
23
26
  import type { HttpTransportConfig, TransportConfig, TransportType, WebSocketTransportConfig } from "../types.js";
24
27
  import { bearerToken, hostAllowed, originAllowed, tokenMatches } from "./security.js";
25
28
 
@@ -57,6 +60,11 @@ export interface PeerOptions {
57
60
  registry?: ProcedureRegistry;
58
61
  /** The procedures that the peer serves (see `exposeRule`). Without it, the peer serves every procedure. */
59
62
  expose?: ((path: ProcedurePath) => boolean) | undefined;
63
+ /**
64
+ * The peer counts the calls of each procedure, and `host.metrics` gives the counts (roadmap
65
+ * R19). The default is true.
66
+ */
67
+ metrics?: boolean | undefined;
60
68
  /**
61
69
  * The policy of the host (`hostPolicy()` of the core, roadmap 2.3): the expose rule, the call
62
70
  * rule, the interceptors (a time limit), the token of each network transport that has none of
@@ -76,11 +84,19 @@ export interface Peer {
76
84
  /** Start all transports */
77
85
  start(): Promise<void>;
78
86
  /** Stop all transports */
79
- stop(): Promise<void>;
87
+ /** Stop the peer. With `grace`, the calls in flight have that time to end (roadmap R14). */
88
+ stop(options?: StopOptions): Promise<void>;
80
89
  /** Get active endpoints */
81
90
  getEndpoints(): PeerEndpoint[];
82
91
  /** Get underlying server */
83
92
  getServer(): ProcedureServer;
93
+ /** The ids of the WebSocket connections of the clients (roadmap R24). */
94
+ connections(): string[];
95
+ /**
96
+ * A client of one connected client (roadmap R24): its calls go to the procedures that the client
97
+ * serves (the `serve` option of its WebSocketTransport), over the same WebSocket.
98
+ */
99
+ clientFor(connectionId: string): Client;
84
100
  }
85
101
 
86
102
  // =============================================================================
@@ -93,6 +109,10 @@ class PeerImpl implements Peer {
93
109
  private transports: TransportConfig[];
94
110
  private endpoints: PeerEndpoint[] = [];
95
111
  private started = false;
112
+ /** The WebSocket transports of the peer: the calls to its clients go over them (R24) */
113
+ private wsTransports: WebSocketServerTransport[] = [];
114
+ /** The HTTP servers that the peer made for its WebSocket transports: `stop()` closes them */
115
+ private ownedServers: Array<import("node:http").Server> = [];
96
116
  private registry: ProcedureRegistry;
97
117
  private startTime: number = Date.now();
98
118
 
@@ -111,13 +131,27 @@ class PeerImpl implements Peer {
111
131
  this.registry = options.registry ?? PROCEDURE_REGISTRY;
112
132
  this.browser = policy?.browser ?? {};
113
133
 
114
- // Create procedure server
134
+ // Create procedure server. The metrics interceptor counts each call for host.metrics (R19).
135
+ const metrics = options.metrics === false ? undefined : metricsInterceptor();
136
+ const serverOptions = policy ? procedureServerOptions(policy, this.registry) : {};
137
+ const interceptors = [...(serverOptions.interceptors ?? []), ...(metrics ? [metrics.interceptor] : [])];
115
138
  this.server = new ProcedureServer({
116
- ...(policy ? procedureServerOptions(policy, this.registry) : {}),
139
+ ...serverOptions,
140
+ ...(interceptors.length > 0 ? { interceptors } : {}),
117
141
  autoRegister: options.autoRegister ?? true,
118
142
  registry: this.registry,
119
143
  ...(options.expose ? { expose: options.expose } : {}),
120
144
  });
145
+ // host.health, host.info and host.metrics: pinned on the server, not in the registry (R19)
146
+ this.server.registerProcedures(
147
+ hostProcedures({
148
+ id: this.id,
149
+ startedAt: this.startTime,
150
+ server: this.server,
151
+ endpoints: () => this.getEndpoints(),
152
+ ...(metrics ? { metrics } : {}),
153
+ })
154
+ );
121
155
  }
122
156
 
123
157
  async start(): Promise<void> {
@@ -160,7 +194,9 @@ class PeerImpl implements Peer {
160
194
  const express = (await import("express")).default;
161
195
 
162
196
  const app = express();
163
- app.use(express.json());
197
+ // No body parser here: the transport reads the body itself, with bodyLimit, and accepts each
198
+ // JSON value (roadmap R5). Before, the strict parser of express refused a bare value (a
199
+ // number, a string, null) with 400, and its limit of 100 kB replaced bodyLimit.
164
200
 
165
201
  const port = config.port ?? 3000;
166
202
  // Loopback by default so the full procedure registry (incl. shell.run / fs.write) is not
@@ -227,6 +263,7 @@ class PeerImpl implements Peer {
227
263
  allowedOrigins: this.browser.allowedOrigins ?? ((origin) => originAllowed(origin, config.corsOrigins)),
228
264
  ...(this.browser.allowedHosts ? { allowedHosts: this.browser.allowedHosts } : {}),
229
265
  allowGet: this.browser.allowGet ?? config.urlStrategy === "rest",
266
+ ...(config.bodyLimit !== undefined ? { bodyLimit: config.bodyLimit } : {}),
230
267
  });
231
268
 
232
269
  this.server.addTransport(httpTransport);
@@ -290,6 +327,8 @@ class PeerImpl implements Peer {
290
327
  httpServer.close();
291
328
  throw error;
292
329
  }
330
+ this.ownedServers.push(httpServer);
331
+ this.wsTransports.push(wsTransport);
293
332
 
294
333
  this.endpoints.push({
295
334
  type: "websocket",
@@ -297,11 +336,25 @@ class PeerImpl implements Peer {
297
336
  });
298
337
  }
299
338
 
300
- async stop(): Promise<void> {
339
+ async stop(options: StopOptions = {}): Promise<void> {
301
340
  if (!this.started) return;
302
- await this.server.stop();
341
+ await this.server.stop(options);
342
+ // The HTTP servers of the WebSocket transports: before, they stayed open, so the port stayed
343
+ // bound after stop() (roadmap R5)
344
+ const owned = this.ownedServers;
345
+ this.ownedServers = [];
346
+ await Promise.all(
347
+ owned.map(
348
+ (httpServer) =>
349
+ new Promise<void>((resolve) => {
350
+ httpServer.close(() => resolve());
351
+ httpServer.closeAllConnections();
352
+ }),
353
+ ),
354
+ );
303
355
  this.started = false;
304
356
  this.endpoints = [];
357
+ this.wsTransports = [];
305
358
  }
306
359
 
307
360
  getEndpoints(): PeerEndpoint[] {
@@ -311,6 +364,16 @@ class PeerImpl implements Peer {
311
364
  getServer(): ProcedureServer {
312
365
  return this.server;
313
366
  }
367
+
368
+ connections(): string[] {
369
+ return this.wsTransports.flatMap((transport) => transport.getTrackedConnections().map((connection) => connection.id));
370
+ }
371
+
372
+ clientFor(connectionId: string): Client {
373
+ const transport = this.wsTransports.find((ws) => ws.getTrackedConnection(connectionId) !== undefined);
374
+ if (!transport) throw new Error(`Connection not found: ${connectionId}`);
375
+ return new Client({ transport: transport.transportFor(connectionId), throwOnError: true });
376
+ }
314
377
  }
315
378
 
316
379
  // =============================================================================
@@ -16,14 +16,7 @@ export type { ServerDisconnectInput, ServerDisconnectOutput } from "./server.dis
16
16
  export { manifestGenerateProcedure } from "./manifest.generate.js";
17
17
  export type { ManifestGenerateInput, ManifestGenerateOutput } from "./manifest.generate.js";
18
18
 
19
- export { serverStartProcedure } from "./server.start.js";
20
- export type { ServerStartInput, ServerStartOutput } from "./server.start.js";
21
-
22
- export { serverStopProcedure } from "./server.stop.js";
23
- export type { ServerStopInput, ServerStopOutput } from "./server.stop.js";
24
-
25
- export { serverStatusProcedure } from "./server.status.js";
26
- export type { ServerStatusInput, ServerStatusOutput } from "./server.status.js";
19
+ // server.start, server.stop and server.status are in @procedures/server (the package of the host)
27
20
 
28
21
  export { serverCallProcedure } from "./server.call.js";
29
22
  export type { ServerCallInput, ServerCallOutput } from "./server.call.js";
@@ -3,7 +3,8 @@
3
3
  * Generates procedure manifests in various formats
4
4
  */
5
5
 
6
- import { createProcedure, jsonSchemaOf, type Effects, type Procedure, type ProcedureContext, PROCEDURE_REGISTRY } from "@procedures/client";
6
+ import { createProcedure, describeRegistry, type AnyProcedure, type Effects, type Procedure, type ProcedureContext, PROCEDURE_REGISTRY } from "@procedures/client";
7
+ import { infoOf } from "../discovery/index.js";
7
8
  import { wrapSchema } from "../schema.js";
8
9
  import { ManifestGenerateOutputSchema } from "../output-schemas.js";
9
10
  import { manifestGenerateInputSchema } from "../input-schemas.js";
@@ -30,39 +31,15 @@ export const manifestGenerateProcedure: Procedure<
30
31
  effects: { reads: ["registry"], writes: ["fs"], idempotent: true },
31
32
  })
32
33
  .handler(async (input: ManifestGenerateInput, ctx?: ProcedureContext) => {
33
- // The procedures of the caller's registry
34
- const allProcedures = (ctx?.registry ?? PROCEDURE_REGISTRY).getAll();
35
-
36
- // Filter by namespace if specified
37
- let procedures = allProcedures;
38
- if (input.namespace && input.namespace.length > 0) {
39
- procedures = allProcedures.filter((proc) => {
40
- // Check if procedure path starts with namespace
41
- for (let i = 0; i < input.namespace!.length; i++) {
42
- if (proc.path[i] !== input.namespace![i]) {
43
- return false;
44
- }
45
- }
46
- return true;
47
- });
48
- }
49
-
50
- // Convert to ProcedureInfo format
51
- const procedureInfos: ProcedureInfo[] = procedures.map((proc) => {
52
- const meta = proc.metadata as { description?: string; internal?: boolean } | undefined;
53
- const info: ProcedureInfo = {
54
- path: proc.path,
55
- };
56
- if (meta?.description !== undefined) {
57
- info.description = meta.description;
58
- }
59
- // The JSON Schemas of the input and the output, when the schemas can describe themselves
60
- const inputSchema = jsonSchemaOf(proc.input, { io: "input" });
61
- const outputSchema = jsonSchemaOf(proc.output, { io: "output" });
62
- if (inputSchema) info.inputSchema = inputSchema;
63
- if (outputSchema) info.outputSchema = outputSchema;
64
- return info;
34
+ // The one manifest of the core (roadmap R17): the procedures that the host exposes, with
35
+ // their JSON Schemas, under the namespace when the input gives one
36
+ const namespace = input.namespace ?? [];
37
+ const manifest = describeRegistry(ctx?.registry ?? PROCEDURE_REGISTRY, {
38
+ ...(ctx?.expose ? { expose: ctx.expose } : {}),
39
+ withoutHandler: true,
40
+ include: (proc: AnyProcedure) => namespace.every((segment, index) => proc.path[index] === segment),
65
41
  });
42
+ const procedureInfos: ProcedureInfo[] = manifest.procedures.map(infoOf);
66
43
 
67
44
  // Determine formats to generate
68
45
  const formats = input.formats ?? ["json"];
@@ -3,7 +3,7 @@
3
3
  * Call a procedure on a remote peer through an outbound connection
4
4
  */
5
5
 
6
- import { createProcedure, pathToMethod, type Effects, type Procedure } from "@procedures/client";
6
+ import { TIMEOUT_METADATA_KEY, createProcedure, pathToMethod, type Effects, type Procedure, type ProcedureContext } from "@procedures/client";
7
7
  import { wrapSchema } from "../schema.js";
8
8
  import { ServerCallOutputSchema } from "../output-schemas.js";
9
9
  import { serverCallInputSchema } from "../input-schemas.js";
@@ -55,7 +55,7 @@ export const serverCallProcedure: Procedure<
55
55
  shorts: { connectionId: "c", address: "a" },
56
56
  output: "json",
57
57
  })
58
- .handler(async (input: ServerCallInput): Promise<ServerCallOutput> => {
58
+ .handler(async (input: ServerCallInput, ctx?: ProcedureContext): Promise<ServerCallOutput> => {
59
59
  // Normalize path: accept dot-separated string or array
60
60
  const pathArray = typeof input.path === "string"
61
61
  ? input.path.split(".")
@@ -87,7 +87,12 @@ export const serverCallProcedure: Procedure<
87
87
  // (deep dive architecture review 3.3)
88
88
  const method = pathToMethod(pathArray);
89
89
 
90
- const result = await client.call(method, input.input ?? {});
90
+ // The signal and the remaining time of this call go with the relayed call (roadmap R14)
91
+ const deadline = ctx?.capability?.deadline;
92
+ const result = await client.call(method, input.input ?? {}, {
93
+ ...(ctx?.signal ? { signal: ctx.signal } : {}),
94
+ ...(deadline !== undefined ? { context: { [TIMEOUT_METADATA_KEY]: Math.max(0, deadline - Date.now()) } } : {}),
95
+ });
91
96
 
92
97
  return {
93
98
  result,
@@ -9,6 +9,7 @@ import { ServerConnectOutputSchema } from "../output-schemas.js";
9
9
  import { serverConnectInputSchema } from "../input-schemas.js";
10
10
  import type { ServerConnectInput, ServerConnectOutput } from "../types.js";
11
11
  import { connect } from "../connection/index.js";
12
+ import { ms } from "@procedures/client";
12
13
 
13
14
  // Use pass-through schemas
14
15
  const serverConnectOutputSchema = wrapSchema<ServerConnectOutput>(ServerConnectOutputSchema);
@@ -35,7 +36,8 @@ export const serverConnectProcedure: Procedure<
35
36
  connectOptions.transport = input.transport;
36
37
  }
37
38
  if (input.timeout !== undefined) {
38
- connectOptions.timeout = input.timeout;
39
+ // The input is data: a number of milliseconds
40
+ connectOptions.timeout = ms(input.timeout);
39
41
  }
40
42
  const connection = await connect(connectOptions);
41
43
 
package/src/types.ts CHANGED
@@ -21,6 +21,8 @@ export interface HttpTransportConfig {
21
21
  urlStrategy?: "rest" | "rpc" | undefined;
22
22
  /** A secret that every request must send as `Authorization: Bearer <token>` */
23
23
  token?: string | undefined;
24
+ /** The largest request body in bytes. The default is 1 MiB. */
25
+ bodyLimit?: number | undefined;
24
26
  }
25
27
 
26
28
  export interface WebSocketTransportConfig {
@@ -179,79 +181,3 @@ export interface ProcedureUpdateMessage {
179
181
  added?: ProcedureInfo[];
180
182
  removed?: string[]; // Procedure paths as strings
181
183
  }
182
-
183
- // =============================================================================
184
- // server.start Types
185
- // =============================================================================
186
-
187
- export interface ServerStartInput {
188
- /** Port to listen on */
189
- port?: number | undefined;
190
- /** Host to bind to */
191
- host?: string | undefined;
192
- /** Transport type */
193
- transport?: "http" | "websocket" | "both" | undefined;
194
- }
195
-
196
- export interface ServerStartOutput {
197
- /** Whether start succeeded */
198
- success: boolean;
199
- /** Process ID of server */
200
- pid?: number | undefined;
201
- /** Port server is running on */
202
- port?: number | undefined;
203
- /** Endpoint URL */
204
- endpoint?: string | undefined;
205
- /** Log file path */
206
- logFile?: string | undefined;
207
- /** Status message */
208
- message: string;
209
- }
210
-
211
- // =============================================================================
212
- // server.stop Types
213
- // =============================================================================
214
-
215
- export interface ServerStopInput {
216
- /** Port of server to stop (stops all if omitted) */
217
- port?: number | undefined;
218
- /** Force kill with SIGKILL instead of SIGTERM */
219
- force?: boolean | undefined;
220
- }
221
-
222
- export interface ServerStopOutput {
223
- /** Whether stop succeeded */
224
- success: boolean;
225
- /** Status message */
226
- message: string;
227
- }
228
-
229
- // =============================================================================
230
- // server.status Types
231
- // =============================================================================
232
-
233
- export interface ServerStatusInput {
234
- /** Port to check (shows all if omitted) */
235
- port?: number | undefined;
236
- }
237
-
238
- export interface ServerStatusOutput {
239
- /** Whether server is running */
240
- running: boolean;
241
- /** Process ID */
242
- pid?: number | undefined;
243
- /** Port */
244
- port?: number | undefined;
245
- /** Endpoint URL */
246
- endpoint?: string | undefined;
247
- /** Transport type */
248
- transport?: string | undefined;
249
- /** When server started (ISO string) */
250
- startedAt?: string | undefined;
251
- /** Human-readable uptime */
252
- uptime?: string | undefined;
253
- /** Log file path */
254
- logFile?: string | undefined;
255
- /** Status message */
256
- message: string;
257
- }
@@ -1,14 +0,0 @@
1
- /**
2
- * Procedure: server.start
3
- * Starts the CLI server as a background daemon
4
- */
5
- import { type Effects, type Procedure } from "@procedures/client";
6
- import type { ServerStartInput, ServerStartOutput } from "../types.js";
7
- export declare const serverStartProcedure: Procedure<ServerStartInput, ServerStartOutput, {
8
- description: string;
9
- effects: Effects;
10
- args: string[];
11
- shorts: Record<string, string>;
12
- }>;
13
- export type { ServerStartInput, ServerStartOutput };
14
- //# sourceMappingURL=server.start.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"server.start.d.ts","sourceRoot":"","sources":["../../src/procedures/server.start.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAMH,OAAO,EAAmB,KAAK,OAAO,EAAE,KAAK,SAAS,EAAE,MAAM,oBAAoB,CAAC;AAKnF,OAAO,KAAK,EAAE,gBAAgB,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAC;AAyEvE,eAAO,MAAM,oBAAoB,EAAE,SAAS,CAC1C,gBAAgB,EAChB,iBAAiB,EACjB;IAAE,WAAW,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,OAAO,CAAC;IAAC,IAAI,EAAE,MAAM,EAAE,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;CAAE,CAuHjF,CAAC;AAEX,YAAY,EAAE,gBAAgB,EAAE,iBAAiB,EAAE,CAAC"}
@@ -1,182 +0,0 @@
1
- /**
2
- * Procedure: server.start
3
- * Starts the CLI server as a background daemon
4
- */
5
- import { spawn } from "node:child_process";
6
- import * as fs from "node:fs";
7
- import * as path from "node:path";
8
- import * as os from "node:os";
9
- import { createProcedure } from "@procedures/client";
10
- import { wrapSchema } from "../schema.js";
11
- import { ServerStartOutputSchema } from "../output-schemas.js";
12
- import { serverStartInputSchema } from "../input-schemas.js";
13
- import { checkServer, readLockfileForPort } from "../lockfile.js";
14
- const MARK_DIR = path.join(os.homedir(), ".mark");
15
- const SERVERS_DIR = path.join(MARK_DIR, "servers");
16
- const LOG_PATH = path.join(MARK_DIR, "server.log");
17
- const LOG_PREV_PATH = path.join(MARK_DIR, "server.log.1");
18
- const serverStartOutputSchema = wrapSchema(ServerStartOutputSchema);
19
- /**
20
- * Check if process is alive
21
- */
22
- function isProcessAlive(pid) {
23
- try {
24
- process.kill(pid, 0);
25
- return true;
26
- }
27
- catch {
28
- return false;
29
- }
30
- }
31
- /**
32
- * Rotate log file
33
- */
34
- function rotateLog() {
35
- try {
36
- fs.mkdirSync(MARK_DIR, { recursive: true });
37
- if (fs.existsSync(LOG_PATH)) {
38
- fs.renameSync(LOG_PATH, LOG_PREV_PATH);
39
- }
40
- }
41
- catch {
42
- // Ignore rotation errors
43
- }
44
- }
45
- /**
46
- * Find the CLI entry point
47
- */
48
- function findCliPath() {
49
- // First, try to use the currently running script if it's the CLI
50
- const currentScript = process.argv[1];
51
- if (currentScript) {
52
- // Normalize path for cross-platform check
53
- const normalized = currentScript.replace(/\\/g, "/");
54
- // mark runs as dist/bin.js (its bin) or dist/cli.js
55
- if (normalized.endsWith("/dist/bin.js") || normalized.endsWith("cli.js") || normalized.includes("cli/dist")) {
56
- // Return absolute path
57
- return path.resolve(currentScript);
58
- }
59
- }
60
- // Try to find the mark CLI in node_modules or as a direct path
61
- const candidates = [
62
- path.join(process.cwd(), "node_modules", "@procedures", "mark", "dist", "cli.js"),
63
- path.join(process.cwd(), "node_modules", ".bin", "mark"),
64
- // Check parent directories for monorepo setups
65
- path.join(process.cwd(), "..", "cli", "dist", "cli.js"),
66
- // Direct check in current directory
67
- path.join(process.cwd(), "dist", "cli.js"),
68
- ];
69
- // Check which candidate exists
70
- for (const candidate of candidates) {
71
- if (fs.existsSync(candidate)) {
72
- return candidate;
73
- }
74
- }
75
- // Fallback: try mark in PATH (will fail if not installed globally)
76
- return "mark";
77
- }
78
- export const serverStartProcedure = createProcedure()
79
- .path(["server", "start"])
80
- .input(serverStartInputSchema)
81
- .output(serverStartOutputSchema)
82
- .meta({
83
- description: "Start CLI server as background daemon",
84
- // The daemon loads the procedure packages of the workspace. The procedure moves server.log
85
- // over server.log.1.
86
- effects: { reads: ["fs", "server"], writes: ["fs", "network", "process", "server"] },
87
- args: [],
88
- shorts: { port: "p", host: "h" },
89
- })
90
- .handler(async (input) => {
91
- const port = input.port ?? 3000;
92
- // Loopback by default; pass host: "0.0.0.0" to expose on the LAN deliberately.
93
- // See documentation/BUGS-2026-07.md (H19).
94
- const host = input.host ?? "127.0.0.1";
95
- const transport = input.transport ?? "http";
96
- // Check if server is already running on this port
97
- const existing = readLockfileForPort(port);
98
- if (existing && (await checkServer(existing)) === "alive") {
99
- return {
100
- success: false,
101
- message: `Server already running on port ${port} (PID ${existing.pid})`,
102
- };
103
- }
104
- // Rotate log file
105
- rotateLog();
106
- // Ensure directories exist
107
- fs.mkdirSync(SERVERS_DIR, { recursive: true });
108
- // Open log file for writing
109
- const logFd = fs.openSync(LOG_PATH, "a");
110
- // Find CLI path
111
- const cliPath = findCliPath();
112
- // Build arguments
113
- const args = ["--server", "--port", String(port), "--host", host];
114
- if (transport !== "http") {
115
- args.push("--transport", transport);
116
- }
117
- // Determine how to spawn
118
- let spawnCmd;
119
- let spawnArgs;
120
- if (cliPath === "mark" || cliPath.endsWith(".bin/mark") || cliPath.endsWith(".bin\\mark")) {
121
- // Use mark command directly
122
- spawnCmd = cliPath;
123
- spawnArgs = args;
124
- }
125
- else {
126
- // Use node to run the JS file
127
- spawnCmd = process.execPath;
128
- spawnArgs = [cliPath, ...args];
129
- }
130
- // Spawn daemon process
131
- const child = spawn(spawnCmd, spawnArgs, {
132
- detached: true,
133
- stdio: ["ignore", logFd, logFd],
134
- env: { ...process.env },
135
- cwd: process.cwd(),
136
- // No console window on Windows (deep dive CLI-16)
137
- windowsHide: true,
138
- });
139
- // Unref so parent can exit
140
- child.unref();
141
- fs.closeSync(logFd);
142
- // Poll for lockfile (server takes ~4-8s to load ecosystem)
143
- const maxWait = 15000;
144
- const pollInterval = 500;
145
- const startTime = Date.now();
146
- while (Date.now() - startTime < maxWait) {
147
- await new Promise((resolve) => setTimeout(resolve, pollInterval));
148
- // Check if child died
149
- if (child.pid && !isProcessAlive(child.pid)) {
150
- break;
151
- }
152
- // Check for lockfile
153
- const lockfile = readLockfileForPort(port);
154
- if (lockfile && (await checkServer(lockfile)) === "alive") {
155
- return {
156
- success: true,
157
- pid: lockfile.pid,
158
- port: lockfile.port,
159
- endpoint: lockfile.endpoint,
160
- logFile: LOG_PATH,
161
- message: `Server started (PID ${lockfile.pid})`,
162
- };
163
- }
164
- }
165
- // Timed out waiting for lockfile, but child may still be alive
166
- if (child.pid && isProcessAlive(child.pid)) {
167
- return {
168
- success: true,
169
- pid: child.pid,
170
- port,
171
- endpoint: `http://${host === "0.0.0.0" ? "localhost" : host}:${port}/api`,
172
- logFile: LOG_PATH,
173
- message: `Server starting (PID ${child.pid}, still loading...)`,
174
- };
175
- }
176
- return {
177
- success: false,
178
- message: `Failed to start server. Check ${LOG_PATH} for details.`,
179
- };
180
- })
181
- .build();
182
- //# sourceMappingURL=server.start.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"server.start.js","sourceRoot":"","sources":["../../src/procedures/server.start.ts"],"names":[],"mappings":"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"}
@@ -1,13 +0,0 @@
1
- /**
2
- * Procedure: server.status
3
- * Gets the current CLI server status
4
- */
5
- import { type Effects, type Procedure } from "@procedures/client";
6
- import type { ServerStatusInput, ServerStatusOutput } from "../types.js";
7
- export declare const serverStatusProcedure: Procedure<ServerStatusInput, ServerStatusOutput, {
8
- description: string;
9
- effects: Effects;
10
- output: string;
11
- }>;
12
- export type { ServerStatusInput, ServerStatusOutput };
13
- //# sourceMappingURL=server.status.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"server.status.d.ts","sourceRoot":"","sources":["../../src/procedures/server.status.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAIH,OAAO,EAAmB,KAAK,OAAO,EAAE,KAAK,SAAS,EAAE,MAAM,oBAAoB,CAAC;AAKnF,OAAO,KAAK,EAAE,iBAAiB,EAAE,kBAAkB,EAAE,MAAM,aAAa,CAAC;AAqCzE,eAAO,MAAM,qBAAqB,EAAE,SAAS,CAC3C,iBAAiB,EACjB,kBAAkB,EAClB;IAAE,WAAW,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,OAAO,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAsFjD,CAAC;AAEX,YAAY,EAAE,iBAAiB,EAAE,kBAAkB,EAAE,CAAC"}