overmux 0.0.3 → 0.0.5

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 (159) hide show
  1. package/CHANGELOG.md +44 -0
  2. package/README.md +2 -2
  3. package/dist/auth/.vite/manifest.json +2 -2
  4. package/dist/auth/assets/{index-Bx8JhwVi.css → index-BM9gETvx.css} +1 -1
  5. package/dist/auth/assets/{index-CHIE7qYj.js → index-DabAfcs1.js} +1 -1
  6. package/dist/auth/index.html +2 -2
  7. package/dist/bin.js +219 -126
  8. package/dist/bin.js.map +1 -1
  9. package/dist/docs/000-index.md +2 -4
  10. package/dist/docs/100-introduction/200-how-overmux-works.md +126 -2
  11. package/{docs/100-introduction/300-why-overmux.md → dist/docs/100-introduction/300-why-i-built-overmux.md} +1 -1
  12. package/dist/docs/200-getting-started/100-install-and-run-overmux.md +2 -2
  13. package/dist/docs/200-getting-started/400-secure-with-https/200-tailscale-serve.md +3 -3
  14. package/dist/docs/200-getting-started/400-secure-with-https/300-cloudflare-tunnel.md +2 -2
  15. package/dist/docs/200-getting-started/400-secure-with-https/400-self-hosted-reverse-proxy.md +2 -2
  16. package/dist/docs/400-reference/100-project-structure.md +83 -0
  17. package/dist/docs/400-reference/200-configuration.md +257 -0
  18. package/dist/docs/400-reference/300-storage-locations.md +46 -0
  19. package/dist/docs/400-reference/400-authentication-and-security.md +37 -0
  20. package/dist/docs/400-reference/500-server/000-index.md +13 -0
  21. package/dist/docs/400-reference/500-server/100-resources.md +191 -0
  22. package/dist/docs/400-reference/500-server/200-operations.md +111 -0
  23. package/dist/docs/400-reference/500-server/300-streams.md +140 -0
  24. package/dist/docs/400-reference/500-server/400-notifications.md +32 -0
  25. package/dist/docs/400-reference/500-server/500-api.md +118 -0
  26. package/dist/docs/400-reference/600-client/000-index.md +9 -0
  27. package/dist/docs/400-reference/600-client/100-api.md +370 -0
  28. package/dist/docs/400-reference/600-client/200-theming.md +94 -0
  29. package/dist/docs/400-reference/600-client/300-tech-stack-recommendations.md +12 -0
  30. package/{docs/400-reference/400-cli → dist/docs/400-reference/700-cli}/050-init.md +2 -4
  31. package/dist/docs/400-reference/{400-cli → 700-cli}/100-serve.md +9 -4
  32. package/{docs/400-reference/400-cli → dist/docs/400-reference/700-cli}/200-auth.md +1 -4
  33. package/{docs/400-reference/400-cli → dist/docs/400-reference/700-cli}/300-call.md +1 -4
  34. package/dist/docs/400-reference/700-cli/350-instance.md +21 -0
  35. package/dist/docs/400-reference/700-cli/600-docs.md +128 -0
  36. package/{docs/400-reference/400-cli → dist/docs/400-reference/700-cli}/700-desktop.md +1 -3
  37. package/dist/docs/500-hosted-pages.md +0 -1
  38. package/dist/exports/client.d.ts +19 -15
  39. package/dist/exports/client.d.ts.map +1 -1
  40. package/dist/exports/client.js +240 -58
  41. package/dist/exports/client.js.map +1 -1
  42. package/dist/exports/{index-D3_Dpy_T.d.ts → index-Cz3xkCa4.d.ts} +62 -36
  43. package/dist/exports/index-Cz3xkCa4.d.ts.map +1 -0
  44. package/dist/exports/index.d.ts +2 -2
  45. package/dist/exports/index.js +19 -5
  46. package/dist/exports/index.js.map +1 -1
  47. package/dist/exports/{notifications-kDp16bU_.js → notifications-BFAD3QQl.js} +34 -4
  48. package/dist/exports/notifications-BFAD3QQl.js.map +1 -0
  49. package/dist/exports/server.d.ts +1 -44
  50. package/dist/exports/server.d.ts.map +1 -1
  51. package/dist/exports/server.js +8 -1018
  52. package/dist/exports/server.js.map +1 -1
  53. package/dist/internal/server/coordinator/server-child.js +153 -85
  54. package/dist/internal/server/coordinator/server-child.js.map +1 -1
  55. package/docs/000-index.md +2 -4
  56. package/docs/100-introduction/200-how-overmux-works.md +126 -2
  57. package/{dist/docs/100-introduction/300-why-overmux.md → docs/100-introduction/300-why-i-built-overmux.md} +1 -1
  58. package/docs/200-getting-started/100-install-and-run-overmux.md +2 -2
  59. package/docs/200-getting-started/400-secure-with-https/200-tailscale-serve.md +3 -3
  60. package/docs/200-getting-started/400-secure-with-https/300-cloudflare-tunnel.md +2 -2
  61. package/docs/200-getting-started/400-secure-with-https/400-self-hosted-reverse-proxy.md +2 -2
  62. package/docs/400-reference/100-project-structure.md +83 -0
  63. package/docs/400-reference/200-configuration.md +257 -0
  64. package/docs/400-reference/300-storage-locations.md +46 -0
  65. package/docs/400-reference/400-authentication-and-security.md +37 -0
  66. package/docs/400-reference/500-server/000-index.md +13 -0
  67. package/docs/400-reference/500-server/100-resources.md +191 -0
  68. package/docs/400-reference/500-server/200-operations.md +111 -0
  69. package/docs/400-reference/500-server/300-streams.md +140 -0
  70. package/docs/400-reference/500-server/400-notifications.md +32 -0
  71. package/docs/400-reference/500-server/500-api.md +118 -0
  72. package/docs/400-reference/600-client/000-index.md +9 -0
  73. package/docs/400-reference/600-client/100-api.md +370 -0
  74. package/docs/400-reference/600-client/200-theming.md +94 -0
  75. package/docs/400-reference/600-client/300-tech-stack-recommendations.md +12 -0
  76. package/{dist/docs/400-reference/400-cli → docs/400-reference/700-cli}/050-init.md +2 -4
  77. package/docs/400-reference/{400-cli → 700-cli}/100-serve.md +9 -4
  78. package/{dist/docs/400-reference/400-cli → docs/400-reference/700-cli}/200-auth.md +1 -4
  79. package/{dist/docs/400-reference/400-cli → docs/400-reference/700-cli}/300-call.md +1 -4
  80. package/docs/400-reference/700-cli/350-instance.md +21 -0
  81. package/docs/400-reference/700-cli/600-docs.md +128 -0
  82. package/{dist/docs/400-reference/400-cli → docs/400-reference/700-cli}/700-desktop.md +1 -3
  83. package/docs/500-hosted-pages.md +0 -1
  84. package/package.json +3 -2
  85. package/src/internal/cli/app.ts +5 -5
  86. package/src/internal/cli/commands/docs-ai-context.ts +33 -0
  87. package/src/internal/cli/commands/docs.ts +19 -2
  88. package/src/internal/cli/commands/init-template.ts +1 -1
  89. package/src/internal/cli/commands/instance.ts +58 -0
  90. package/src/internal/cli/commands/serve.ts +3 -0
  91. package/src/internal/cli/login.ts +9 -9
  92. package/src/internal/cli/parse-port.ts +2 -1
  93. package/src/internal/client/auth/auth-shell.css +17 -1
  94. package/src/internal/client/auth/auth-shell.tsx +2 -11
  95. package/src/internal/client/auth/login-code-input.tsx +140 -0
  96. package/src/internal/client/browser-api.ts +17 -3
  97. package/src/internal/client/client-definition.ts +6 -8
  98. package/src/internal/client/host/deep-link-navigation.ts +75 -0
  99. package/src/internal/client/host/desktop-host.ts +3 -0
  100. package/src/internal/client/host/environment.ts +67 -0
  101. package/src/internal/client/host/instance-forwarding.ts +21 -0
  102. package/src/internal/client/host/overmux-host.tsx +17 -0
  103. package/src/internal/client/index.ts +0 -6
  104. package/src/internal/client/overmux-react.ts +95 -42
  105. package/src/internal/client/websocket.ts +32 -12
  106. package/src/internal/server/auth/auth-service.ts +2 -2
  107. package/src/internal/server/auth/instance-control.ts +89 -17
  108. package/src/internal/server/coordinator/ipc-protocol.ts +0 -1
  109. package/src/internal/server/runtime/create-runtime.ts +15 -2
  110. package/src/internal/server/runtime/runtime-instance.ts +47 -0
  111. package/src/internal/server/runtime/runtime-operations.ts +9 -3
  112. package/src/internal/server/runtime/runtime-resources.ts +15 -32
  113. package/src/internal/server/runtime/runtime-streams.ts +5 -6
  114. package/src/internal/server/server-logger.ts +5 -10
  115. package/src/internal/server/server-startup-options.ts +14 -14
  116. package/src/internal/server/start-application-server.ts +24 -19
  117. package/src/internal/server/websocket-server.ts +7 -0
  118. package/src/internal/shared/index.ts +2 -0
  119. package/src/internal/shared/protocol.ts +10 -1
  120. package/src/public/ai-context.ts +7 -29
  121. package/src/public/client.ts +1 -6
  122. package/src/public/config.ts +163 -31
  123. package/src/public/index.ts +2 -0
  124. package/src/public/server.ts +1 -16
  125. package/dist/docs/100-introduction/100-what-is-overmux.md +0 -7
  126. package/dist/docs/300-fundamentals/100-project-structure.md +0 -23
  127. package/dist/docs/300-fundamentals/200-configuration.md +0 -3
  128. package/dist/docs/300-fundamentals/300-theming.md +0 -3
  129. package/dist/docs/300-fundamentals/400-server.md +0 -3
  130. package/dist/docs/300-fundamentals/500-client.md +0 -3
  131. package/dist/docs/300-fundamentals/600-operations.md +0 -3
  132. package/dist/docs/300-fundamentals/700-resources.md +0 -3
  133. package/dist/docs/300-fundamentals/800-streams.md +0 -3
  134. package/dist/docs/300-fundamentals/900-authentication-and-security.md +0 -3
  135. package/dist/docs/400-reference/100-configuration.md +0 -3
  136. package/dist/docs/400-reference/200-server-api.md +0 -3
  137. package/dist/docs/400-reference/300-client-api.md +0 -20
  138. package/dist/docs/400-reference/400-cli/400-check.md +0 -20
  139. package/dist/docs/400-reference/400-cli/500-ai-context.md +0 -102
  140. package/dist/docs/400-reference/400-cli/600-docs.md +0 -23
  141. package/dist/exports/index-D3_Dpy_T.d.ts.map +0 -1
  142. package/dist/exports/notifications-kDp16bU_.js.map +0 -1
  143. package/docs/100-introduction/100-what-is-overmux.md +0 -7
  144. package/docs/300-fundamentals/100-project-structure.md +0 -23
  145. package/docs/300-fundamentals/200-configuration.md +0 -3
  146. package/docs/300-fundamentals/300-theming.md +0 -3
  147. package/docs/300-fundamentals/400-server.md +0 -3
  148. package/docs/300-fundamentals/500-client.md +0 -3
  149. package/docs/300-fundamentals/600-operations.md +0 -3
  150. package/docs/300-fundamentals/700-resources.md +0 -3
  151. package/docs/300-fundamentals/800-streams.md +0 -3
  152. package/docs/300-fundamentals/900-authentication-and-security.md +0 -3
  153. package/docs/400-reference/100-configuration.md +0 -3
  154. package/docs/400-reference/200-server-api.md +0 -3
  155. package/docs/400-reference/300-client-api.md +0 -20
  156. package/docs/400-reference/400-cli/400-check.md +0 -20
  157. package/docs/400-reference/400-cli/500-ai-context.md +0 -102
  158. package/docs/400-reference/400-cli/600-docs.md +0 -23
  159. package/src/internal/cli/commands/ai.ts +0 -44
@@ -1,14 +1,8 @@
1
- import { realpath } from "node:fs/promises";
2
1
  import { isIP } from "node:net";
3
- import { dirname, isAbsolute, join, resolve } from "node:path";
4
- import { createJiti } from "jiti";
5
- import { ZodError, z } from "zod";
2
+ import { isAbsolute, join } from "node:path";
3
+ import "jiti";
4
+ import { z } from "zod";
6
5
  import { homedir } from "node:os";
7
- import ts from "typescript";
8
- import { isDeepStrictEqual } from "node:util";
9
- import { fork } from "node:child_process";
10
- import { fileURLToPath } from "node:url";
11
- import { watch } from "node:fs";
12
6
  //#region ../shared/dist/node/index.js
13
7
  const durationPattern = /^([1-9]\d*)(ms|s|m|h|d)$/;
14
8
  const millisecondsByUnit = {
@@ -26,6 +20,7 @@ const durationToMilliseconds = (duration) => {
26
20
  return Number.isSafeInteger(milliseconds) ? milliseconds : void 0;
27
21
  };
28
22
  const aiContextSnippets = ["package-source", "tech-stack-recommendations"];
23
+ const instanceIdSchema = z.string().max(253).regex(/^[a-z0-9](?:[a-z0-9.-]*[a-z0-9])?(?![\s\S])/, "Expected a lowercase instance ID with only ASCII letters, digits, internal dots and hyphens");
29
24
  const zodTypeSchema = z.custom((value) => value instanceof z.ZodType, "Expected a Zod schema");
30
25
  const functionSchema = z.custom((value) => typeof value === "function", "Expected a function");
31
26
  const contractRuntimeSchema = z.object({
@@ -74,12 +69,11 @@ const loopbackPeerSchema = z.string().refine((value) => isIP(value) !== 0 && (va
74
69
  const authConfigRuntimeSchema = z.object({
75
70
  mode: z.literal("cli-login"),
76
71
  origins: z.array(exactOriginSchema).min(1).refine((origins) => new Set(origins).size === origins.length, "Expected unique origins").optional(),
77
- sessionLifetime: z.union([z.literal("never"), authDurationSchema]).default("never"),
72
+ sessionLifetime: z.union([z.literal("forever"), authDurationSchema]).default("forever"),
78
73
  trustedProxyPeer: loopbackPeerSchema.optional()
79
74
  }).strict();
80
75
  const serverConfigRuntimeSchema = z.object({
81
76
  host: z.string().min(1).optional(),
82
- logFile: z.string().min(1).optional(),
83
77
  port: z.number().int().min(1).max(65535).optional(),
84
78
  productionWebAssetsDir: z.string().min(1).optional(),
85
79
  watch: z.boolean().optional()
@@ -89,51 +83,14 @@ const serverDefinitionRuntimeSchema = z.object({
89
83
  resources: z.record(z.string(), resourceDefinitionRuntimeSchema),
90
84
  streams: z.record(z.string(), streamHandlerRuntimeSchema).optional()
91
85
  });
92
- const configDefinitionRuntimeSchema = serverConfigRuntimeSchema.extend({
86
+ serverConfigRuntimeSchema.extend({
93
87
  aiContextSnippets: z.array(z.enum(aiContextSnippets)).optional(),
94
88
  auth: authConfigRuntimeSchema,
95
89
  debug: z.boolean().optional(),
90
+ instanceId: z.union([instanceIdSchema, z.custom((value) => typeof value === "function", "Expected an instance ID or a function of the bound port")]).optional(),
96
91
  server: serverDefinitionRuntimeSchema,
97
92
  vite: z.string().min(1).optional()
98
93
  });
99
- const resolveLogicalPath = async (path) => {
100
- const logicalPath = resolve(path);
101
- return {
102
- logicalPath,
103
- realPath: await realpath(logicalPath)
104
- };
105
- };
106
- const resolveOvermuxConfigPaths = async (configPath) => {
107
- const { logicalPath, realPath } = await resolveLogicalPath(configPath);
108
- return {
109
- logicalApplicationRoot: dirname(logicalPath),
110
- logicalConfigPath: logicalPath,
111
- realApplicationRoot: dirname(realPath),
112
- realConfigPath: realPath
113
- };
114
- };
115
- const unwrapDefaultExport = (module) => {
116
- if (typeof module === "object" && module !== null && "default" in module) return module.default;
117
- return module;
118
- };
119
- const loadOvermuxConfig = async ({ aliases = {}, configPath }) => {
120
- const paths = await resolveOvermuxConfigPaths(configPath);
121
- const module = await createJiti(paths.logicalConfigPath, {
122
- alias: aliases,
123
- fsCache: true,
124
- jsx: {
125
- importSource: "react",
126
- runtime: "automatic"
127
- },
128
- moduleCache: false,
129
- sourceMaps: true,
130
- tsconfigPaths: true
131
- }).import(paths.logicalConfigPath);
132
- return {
133
- config: configDefinitionRuntimeSchema.parse(unwrapDefaultExport(module)),
134
- paths
135
- };
136
- };
137
94
  const xdgDirectory = (configured, fallback) => configured && isAbsolute(configured) ? configured : fallback;
138
95
  const overmuxDirectory = (root) => join(root, "overmux");
139
96
  const getOvermuxPaths = ({ environment = process.env, homeDir = homedir(), xdgCacheHome = environment.XDG_CACHE_HOME, xdgConfigHome = environment.XDG_CONFIG_HOME, xdgDataHome = environment.XDG_DATA_HOME, xdgRuntimeDir = environment.XDG_RUNTIME_DIR, xdgStateHome = environment.XDG_STATE_HOME } = {}) => {
@@ -148,973 +105,6 @@ const getOvermuxPaths = ({ environment = process.env, homeDir = homedir(), xdgCa
148
105
  };
149
106
  };
150
107
  //#endregion
151
- //#region src/internal/shared/background-notifications.ts
152
- const deliveryKeySchema = z.string().min(1).max(1024).regex(/^[\w-]+$/u);
153
- const backgroundNotificationSubscriptionSchema = z.object({
154
- endpoint: z.url().max(4096).refine((endpoint) => {
155
- const url = new URL(endpoint);
156
- return url.protocol === "https:" && !url.username && !url.password;
157
- }),
158
- expirationTime: z.number().int().nonnegative().nullable(),
159
- keys: z.object({
160
- auth: deliveryKeySchema,
161
- p256dh: deliveryKeySchema
162
- }).strict()
163
- }).strict();
164
- z.object({ publicKey: deliveryKeySchema }).strict();
165
- z.object({ endpoint: backgroundNotificationSubscriptionSchema.shape.endpoint }).strict();
166
- const safeRelativeNotificationLink = (link) => {
167
- let decoded;
168
- try {
169
- decoded = decodeURIComponent(link);
170
- } catch {
171
- return false;
172
- }
173
- return !decoded.startsWith("//") && !decoded.includes("\\") && ![...decoded].some((character) => {
174
- const codePoint = character.codePointAt(0) ?? 0;
175
- return codePoint < 32 || codePoint === 127;
176
- });
177
- };
178
- const notificationLinkSchema = z.union([z.string().max(4096).startsWith("/").refine(safeRelativeNotificationLink), z.string().max(4096).url().refine((link) => {
179
- if (!URL.canParse(link)) return false;
180
- const url = new URL(link);
181
- return ["http:", "https:"].includes(url.protocol) && !url.username && !url.password;
182
- })]);
183
- const notificationSchema = z.object({
184
- body: z.string().max(4096).optional(),
185
- open: z.object({ link: notificationLinkSchema }).strict().optional(),
186
- title: z.string().min(1).max(4096)
187
- }).strict();
188
- z.object({
189
- debug: z.boolean(),
190
- operations: z.array(z.string().min(1)),
191
- protocolVersion: z.literal(9),
192
- resources: z.array(z.string().min(1)),
193
- streams: z.array(z.string().min(1))
194
- });
195
- const updateAvailableEventSchema = z.object({ type: z.literal("update-available") });
196
- const restartingEventSchema = z.object({ type: z.literal("restarting") });
197
- z.discriminatedUnion("type", [updateAvailableEventSchema, restartingEventSchema]);
198
- const notificationEventSchema = z.object({
199
- notification: notificationSchema,
200
- type: z.literal("notification")
201
- }).strict();
202
- const operationIdSchema = z.string().min(1).max(256);
203
- const registrationNameSchema = z.string().min(1).max(256);
204
- const resourceReadMessageSchema = z.object({
205
- input: z.unknown().optional(),
206
- operationId: operationIdSchema,
207
- resourceName: registrationNameSchema,
208
- type: z.literal("resource-read")
209
- });
210
- const resourceSubscribeMessageSchema = z.object({
211
- input: z.unknown().optional(),
212
- operationId: operationIdSchema,
213
- resourceName: registrationNameSchema,
214
- subscriptionId: operationIdSchema,
215
- type: z.literal("resource-subscribe")
216
- });
217
- const resourceUnsubscribeMessageSchema = z.object({
218
- subscriptionId: operationIdSchema,
219
- type: z.literal("resource-unsubscribe")
220
- });
221
- const streamOpenMessageSchema = z.object({
222
- input: z.unknown().optional(),
223
- operationId: operationIdSchema,
224
- streamId: operationIdSchema,
225
- streamName: registrationNameSchema,
226
- type: z.literal("stream-open")
227
- });
228
- const streamMessageSchema = z.object({
229
- message: z.unknown(),
230
- streamId: operationIdSchema,
231
- type: z.literal("stream-message")
232
- });
233
- const streamCloseMessageSchema = z.object({
234
- streamId: operationIdSchema,
235
- type: z.literal("stream-close")
236
- });
237
- const clientDiagnosticEventSchema = z.object({
238
- arguments: z.string().max(16100),
239
- level: z.enum([
240
- "log",
241
- "info",
242
- "warn",
243
- "error",
244
- "debug"
245
- ]),
246
- message: z.string().max(4100),
247
- stack: z.string().max(8100).optional(),
248
- timestamp: z.iso.datetime(),
249
- type: z.literal("client-diagnostic"),
250
- url: z.string().max(2100).optional()
251
- });
252
- z.discriminatedUnion("type", [
253
- resourceReadMessageSchema,
254
- resourceSubscribeMessageSchema,
255
- resourceUnsubscribeMessageSchema,
256
- streamOpenMessageSchema,
257
- streamMessageSchema,
258
- streamCloseMessageSchema,
259
- clientDiagnosticEventSchema
260
- ]);
261
- const resourceResultMessageSchema = z.object({
262
- operationId: operationIdSchema,
263
- output: z.unknown(),
264
- type: z.literal("resource-result")
265
- });
266
- const resourceInvalidatedMessageSchema = z.object({
267
- subscriptionId: operationIdSchema,
268
- type: z.literal("resource-invalidated")
269
- });
270
- const streamOpenedMessageSchema = z.object({
271
- operationId: operationIdSchema,
272
- streamId: operationIdSchema,
273
- type: z.literal("stream-opened")
274
- });
275
- const streamOutputMessageSchema = z.object({
276
- message: z.unknown(),
277
- streamId: operationIdSchema,
278
- type: z.literal("stream-output")
279
- });
280
- const streamClosedMessageSchema = z.object({
281
- streamId: operationIdSchema,
282
- type: z.literal("stream-closed")
283
- });
284
- const protocolErrorMessageSchema = z.object({
285
- code: z.enum([
286
- "bad-request",
287
- "conflict",
288
- "internal",
289
- "not-found"
290
- ]),
291
- message: z.string(),
292
- operationId: operationIdSchema.optional(),
293
- streamId: operationIdSchema.optional(),
294
- subscriptionId: operationIdSchema.optional(),
295
- type: z.literal("error")
296
- });
297
- z.discriminatedUnion("type", [
298
- resourceResultMessageSchema,
299
- resourceInvalidatedMessageSchema,
300
- streamOpenedMessageSchema,
301
- streamOutputMessageSchema,
302
- streamClosedMessageSchema,
303
- protocolErrorMessageSchema,
304
- updateAvailableEventSchema,
305
- restartingEventSchema,
306
- notificationEventSchema
307
- ]);
308
- //#endregion
309
- //#region src/internal/server/runtime/runtime-lifecycle.ts
310
- const asError = (cause) => cause instanceof Error ? cause : new Error(String(cause));
311
- const disposeAll = async (disposers) => {
312
- const errors = (await Promise.allSettled([...disposers].map(async (dispose) => dispose()))).flatMap((result) => result.status === "rejected" ? [asError(result.reason)] : []);
313
- if (errors.length) throw new AggregateError(errors, "Runtime disposal failed");
314
- };
315
- const createRuntimeLifecycle = (preparationSignal) => {
316
- const controller = new AbortController();
317
- const activeDisposers = /* @__PURE__ */ new Set();
318
- let disposal;
319
- const abortPreparation = () => controller.abort(preparationSignal?.reason);
320
- preparationSignal?.addEventListener("abort", abortPreparation, { once: true });
321
- if (preparationSignal?.aborted) abortPreparation();
322
- const completePreparation = () => {
323
- preparationSignal?.removeEventListener("abort", abortPreparation);
324
- };
325
- const requestSignal = (signal) => signal ? AbortSignal.any([controller.signal, signal]) : controller.signal;
326
- const track = (dispose, signal) => {
327
- let pending;
328
- const disposeOnce = () => {
329
- if (pending) return pending;
330
- controller.signal.removeEventListener("abort", onAbort);
331
- signal?.removeEventListener("abort", onAbort);
332
- activeDisposers.delete(disposeOnce);
333
- pending = Promise.resolve().then(dispose);
334
- return pending;
335
- };
336
- const onAbort = () => {
337
- disposeOnce().catch(() => void 0);
338
- };
339
- activeDisposers.add(disposeOnce);
340
- controller.signal.addEventListener("abort", onAbort, { once: true });
341
- signal?.addEventListener("abort", onAbort, { once: true });
342
- if (controller.signal.aborted || signal?.aborted) onAbort();
343
- return disposeOnce;
344
- };
345
- const dispose = (reason = /* @__PURE__ */ new Error("Runtime disposed")) => {
346
- if (disposal) return disposal;
347
- completePreparation();
348
- const pending = [...activeDisposers];
349
- activeDisposers.clear();
350
- controller.abort(reason);
351
- disposal = disposeAll(pending);
352
- return disposal;
353
- };
354
- return {
355
- completePreparation,
356
- dispose,
357
- disposeAll,
358
- requestSignal,
359
- signal: controller.signal,
360
- track
361
- };
362
- };
363
- //#endregion
364
- //#region src/internal/server/runtime/runtime-operations.ts
365
- var OperationValidationError = class extends Error {
366
- phase;
367
- constructor(phase, cause) {
368
- super(cause.message, { cause });
369
- this.phase = phase;
370
- }
371
- };
372
- const createRuntimeOperations = ({ context, definitions, notifications }) => {
373
- const operations = /* @__PURE__ */ new Map();
374
- Object.entries(definitions).forEach(([name, definition]) => {
375
- if (!name) throw new Error("Operation names must not be empty");
376
- operations.set(name, {
377
- execute: async (rawInput, signal) => {
378
- const operationContext = context(signal);
379
- operationContext.signal.throwIfAborted();
380
- const input = definition.input.safeParse(rawInput);
381
- if (!input.success) throw new OperationValidationError("input", input.error);
382
- const rawOutput = await definition.handle(input.data, {
383
- ...operationContext,
384
- notifications
385
- });
386
- operationContext.signal.throwIfAborted();
387
- const output = definition.output.safeParse(rawOutput);
388
- if (!output.success) throw new OperationValidationError("output", output.error);
389
- return output.data;
390
- },
391
- returnsVoid: definition.output instanceof z.ZodVoid
392
- });
393
- });
394
- return {
395
- get: (name) => operations.get(name),
396
- names: [...operations.keys()]
397
- };
398
- };
399
- z.void();
400
- const jsonValueSchema = z.lazy(() => z.union([
401
- z.string(),
402
- z.number().finite(),
403
- z.boolean(),
404
- z.null(),
405
- z.array(jsonValueSchema),
406
- z.record(z.string(), jsonValueSchema)
407
- ]));
408
- //#endregion
409
- //#region src/internal/server/runtime/resource-dependency-graph.ts
410
- const emptyGraphFor = (resourceIds) => new Map(resourceIds.map((resourceId) => [resourceId, /* @__PURE__ */ new Set()]));
411
- const visitDependencies = ({ dependencies, path, resourceId, visited, visiting }) => {
412
- if (visiting.has(resourceId)) throw new Error(`Resource dependency cycle: ${[...path, resourceId].join(" -> ")}`);
413
- if (visited.has(resourceId)) return;
414
- visiting.add(resourceId);
415
- dependencies.get(resourceId)?.forEach((dependencyId) => {
416
- visitDependencies({
417
- dependencies,
418
- path: [...path, resourceId],
419
- resourceId: dependencyId,
420
- visited,
421
- visiting
422
- });
423
- });
424
- visiting.delete(resourceId);
425
- visited.add(resourceId);
426
- };
427
- const assertAcyclic = (dependencies) => {
428
- const visited = /* @__PURE__ */ new Set();
429
- const visiting = /* @__PURE__ */ new Set();
430
- dependencies.forEach((_, resourceId) => {
431
- visitDependencies({
432
- dependencies,
433
- path: [],
434
- resourceId,
435
- visited,
436
- visiting
437
- });
438
- });
439
- };
440
- const createResourceDependencyGraph = (resources) => {
441
- const resourceIds = Object.keys(resources);
442
- if (resourceIds.some((resourceId) => !resourceId)) throw new Error("Resource IDs must not be empty");
443
- const dependencies = emptyGraphFor(resourceIds);
444
- const dependants = emptyGraphFor(resourceIds);
445
- Object.entries(resources).forEach(([resourceId, resource]) => {
446
- if (resource.kind !== "derived") return;
447
- Object.entries(resource.dependencies).forEach(([dependencyName, dependencyId]) => {
448
- if (!dependencyName) throw new Error(`Resource ${resourceId} has an empty dependency name`);
449
- if (!dependencyId) throw new Error(`Resource ${resourceId} has an empty dependency ID`);
450
- if (!dependencies.has(dependencyId)) throw new Error(`Unknown resource dependency: ${dependencyId}`);
451
- dependencies.get(resourceId)?.add(dependencyId);
452
- dependants.get(dependencyId)?.add(resourceId);
453
- });
454
- });
455
- assertAcyclic(dependencies);
456
- return Object.freeze({
457
- dependencies,
458
- dependants
459
- });
460
- };
461
- const collectInvalidatedResourceIds = (graph, resourceId, invalidated) => {
462
- if (invalidated.has(resourceId)) return;
463
- invalidated.add(resourceId);
464
- graph.dependants.get(resourceId)?.forEach((dependantId) => {
465
- collectInvalidatedResourceIds(graph, dependantId, invalidated);
466
- });
467
- };
468
- const getInvalidatedResourceIds = (graph, resourceId) => {
469
- const invalidated = /* @__PURE__ */ new Set();
470
- collectInvalidatedResourceIds(graph, resourceId, invalidated);
471
- return invalidated;
472
- };
473
- //#endregion
474
- //#region src/internal/server/runtime/runtime-resources.ts
475
- const createRuntimeResources = ({ definitions, lifecycle }) => {
476
- const graph = createResourceDependencyGraph(definitions);
477
- const invalidationListeners = /* @__PURE__ */ new Set();
478
- const contracts = /* @__PURE__ */ new Map();
479
- const resources = /* @__PURE__ */ new Map();
480
- Object.entries(definitions).forEach(([id, definition]) => {
481
- contracts.set(definition.contract, [...contracts.get(definition.contract) ?? [], id]);
482
- });
483
- const invalidateResource = (id, input, allInputs = false) => {
484
- if (lifecycle.signal.aborted) return;
485
- const ids = getInvalidatedResourceIds(graph, id);
486
- invalidationListeners.forEach((entry) => {
487
- if (ids.has(entry.resourceId) && (allInputs || isDeepStrictEqual(entry.input, input))) entry.listener();
488
- });
489
- };
490
- const invalidate = (contract, input) => {
491
- const ids = contracts.get(contract) ?? Object.entries(definitions).filter(([, definition]) => definition.contract.input === contract.input && definition.contract.output === contract.output).map(([id]) => id);
492
- if (!ids.length) throw new Error("Cannot invalidate a resource outside this runtime");
493
- ids.forEach((id) => {
494
- const definition = definitions[id];
495
- const parsedInput = input === void 0 ? void 0 : definition.contract.input.parse(input);
496
- invalidateResource(id, parsedInput, input === void 0);
497
- });
498
- };
499
- const context = (signal) => ({
500
- invalidate,
501
- signal: lifecycle.requestSignal(signal)
502
- });
503
- const read = async (id, rawInput, signal) => {
504
- const definition = definitions[id];
505
- const operationSignal = lifecycle.requestSignal(signal);
506
- operationSignal.throwIfAborted();
507
- const input = definition.contract.input.parse(rawInput);
508
- const rawOutput = definition.kind === "derived" ? definition.combine(Object.fromEntries(await Promise.all(Object.entries(definition.dependencies).map(async ([name, dependencyId]) => [name, await read(dependencyId, rawInput, signal)])))) : await definition.read(input, context(signal));
509
- operationSignal.throwIfAborted();
510
- return definition.contract.output.parse(rawOutput);
511
- };
512
- const activateWatchers = (id, rawInput, signal, activated, disposers) => {
513
- if (activated.has(id)) return;
514
- activated.add(id);
515
- const definition = definitions[id];
516
- const input = definition.contract.input.parse(rawInput);
517
- if (definition.kind === "subscription") {
518
- const dispose = definition.subscribe(input, () => invalidateResource(id, input), context(signal));
519
- if (typeof dispose !== "function") throw new Error(`Resource ${id} subscribe must return a disposer`);
520
- disposers.push(dispose);
521
- return;
522
- }
523
- if (definition.kind === "derived") graph.dependencies.get(id)?.forEach((dependencyId) => {
524
- activateWatchers(dependencyId, rawInput, signal, activated, disposers);
525
- });
526
- };
527
- Object.keys(definitions).forEach((name) => {
528
- resources.set(name, {
529
- read: (input, signal) => read(name, input, signal),
530
- subscribe: (rawInput, listener, signal) => {
531
- lifecycle.requestSignal(signal).throwIfAborted();
532
- const entry = {
533
- input: definitions[name].contract.input.parse(rawInput),
534
- listener,
535
- resourceId: name
536
- };
537
- const watcherDisposers = [];
538
- invalidationListeners.add(entry);
539
- try {
540
- activateWatchers(name, rawInput, signal, /* @__PURE__ */ new Set(), watcherDisposers);
541
- } catch (cause) {
542
- invalidationListeners.delete(entry);
543
- lifecycle.disposeAll(watcherDisposers).catch(() => void 0);
544
- throw cause;
545
- }
546
- return lifecycle.track(async () => {
547
- invalidationListeners.delete(entry);
548
- await lifecycle.disposeAll(watcherDisposers);
549
- }, signal);
550
- }
551
- });
552
- });
553
- return {
554
- context,
555
- get: (name) => resources.get(name),
556
- invalidate,
557
- names: [...resources.keys()]
558
- };
559
- };
560
- //#endregion
561
- //#region src/internal/server/runtime/runtime-streams.ts
562
- const createRuntimeStreams = ({ context, definitions, lifecycle }) => {
563
- const streams = /* @__PURE__ */ new Map();
564
- Object.entries(definitions).forEach(([name, definition]) => {
565
- if (!name) throw new Error("Stream names must not be empty");
566
- streams.set(name, { open: async (rawInput, emit, requestSignal, onError) => {
567
- const sessionController = new AbortController();
568
- const sessionSignal = requestSignal ? AbortSignal.any([requestSignal, sessionController.signal]) : sessionController.signal;
569
- const operationSignal = lifecycle.requestSignal(sessionSignal);
570
- operationSignal.throwIfAborted();
571
- const input = definition.contract.input.parse(rawInput);
572
- let acceptingMessages = true;
573
- let failed = false;
574
- let failure;
575
- let opened = false;
576
- const fail = (cause) => {
577
- if (failed || operationSignal.aborted) return;
578
- failed = true;
579
- failure = cause;
580
- acceptingMessages = false;
581
- sessionController.abort(cause);
582
- if (opened) onError?.(cause);
583
- };
584
- const session = await definition.open(input, {
585
- ...context(sessionSignal),
586
- emit: (message) => {
587
- if (operationSignal.aborted) return;
588
- try {
589
- emit(definition.contract.serverMessage.parse(message));
590
- } catch (cause) {
591
- fail(cause);
592
- }
593
- },
594
- fail,
595
- signal: operationSignal
596
- });
597
- opened = true;
598
- if (failed || operationSignal.aborted) {
599
- await session.dispose?.();
600
- operationSignal.throwIfAborted();
601
- throw failure;
602
- }
603
- let messages = Promise.resolve();
604
- const dispose = lifecycle.track(async () => {
605
- acceptingMessages = false;
606
- sessionController.abort(/* @__PURE__ */ new Error("Stream session disposed"));
607
- await messages.catch(() => void 0);
608
- await session.dispose?.();
609
- }, requestSignal);
610
- return {
611
- dispose: async () => dispose(),
612
- send: async (rawMessage) => {
613
- if (!acceptingMessages) throw new Error("Stream session is closed");
614
- const message = definition.contract.clientMessage.parse(rawMessage);
615
- messages = messages.then(async () => {
616
- operationSignal.throwIfAborted();
617
- await session.onMessage?.(message);
618
- operationSignal.throwIfAborted();
619
- });
620
- return messages;
621
- }
622
- };
623
- } });
624
- });
625
- return {
626
- get: (name) => streams.get(name),
627
- names: [...streams.keys()]
628
- };
629
- };
630
- //#endregion
631
- //#region src/internal/server/runtime/create-runtime.ts
632
- const createRuntime = async ({ config, debug = false, notifications = { send: async () => void 0 }, signal }) => {
633
- const lifecycle = createRuntimeLifecycle(signal);
634
- try {
635
- const resources = createRuntimeResources({
636
- definitions: config.server.resources ?? {},
637
- lifecycle
638
- });
639
- const context = resources.context;
640
- const operations = createRuntimeOperations({
641
- context,
642
- definitions: config.server.operations ?? {},
643
- notifications
644
- });
645
- const streams = createRuntimeStreams({
646
- context,
647
- definitions: config.server.streams ?? {},
648
- lifecycle
649
- });
650
- lifecycle.signal.throwIfAborted();
651
- lifecycle.completePreparation();
652
- return {
653
- manifest: {
654
- debug,
655
- operations: [...operations.names],
656
- protocolVersion: 9,
657
- resources: [...resources.names],
658
- streams: [...streams.names]
659
- },
660
- dispose: lifecycle.dispose,
661
- getOperation: operations.get,
662
- getResource: resources.get,
663
- getStream: streams.get
664
- };
665
- } catch (cause) {
666
- await lifecycle.dispose(cause).catch(() => void 0);
667
- throw cause;
668
- }
669
- };
670
- //#endregion
671
- //#region src/internal/server/check-overmux.ts
672
- const loadCompilerOptions = (configPath) => {
673
- const tsconfig = ts.findConfigFile(dirname(configPath), ts.sys.fileExists);
674
- if (!tsconfig) return {
675
- allowImportingTsExtensions: true,
676
- allowJs: false,
677
- esModuleInterop: true,
678
- jsx: ts.JsxEmit.ReactJSX,
679
- module: ts.ModuleKind.ESNext,
680
- moduleResolution: ts.ModuleResolutionKind.Bundler,
681
- noEmit: true,
682
- skipLibCheck: true,
683
- strict: true,
684
- target: ts.ScriptTarget.ES2022
685
- };
686
- const loaded = ts.readConfigFile(tsconfig, ts.sys.readFile);
687
- if (loaded.error) throw new Error(ts.flattenDiagnosticMessageText(loaded.error.messageText, "\n"));
688
- return {
689
- ...ts.parseJsonConfigFileContent(loaded.config, ts.sys, dirname(tsconfig)).options,
690
- allowImportingTsExtensions: true,
691
- noEmit: true
692
- };
693
- };
694
- const diagnosticOf = (diagnostic) => {
695
- const position = diagnostic.file && diagnostic.start !== void 0 ? diagnostic.file.getLineAndCharacterOfPosition(diagnostic.start) : void 0;
696
- return {
697
- code: diagnostic.code,
698
- column: position ? position.character + 1 : void 0,
699
- file: diagnostic.file?.fileName,
700
- line: position ? position.line + 1 : void 0,
701
- message: ts.flattenDiagnosticMessageText(diagnostic.messageText, "\n"),
702
- phase: "types"
703
- };
704
- };
705
- const kindOf = (fileName, configPath) => {
706
- if (resolve(fileName) === resolve(configPath) || fileName.match(/\.server\.[cm]?[jt]sx?$/)) return "server";
707
- if (fileName.match(/\.ui\.[cm]?[jt]sx?$/)) return "client";
708
- if (fileName.match(/\.shared\.[cm]?[jt]sx?$/)) return "shared";
709
- };
710
- const kindOfSpecifier = (specifier) => {
711
- if (specifier === "overmux/client" || specifier.endsWith("/react")) return "client";
712
- if (specifier === "overmux/server" || specifier.endsWith("/server") || specifier.startsWith("node:")) return "server";
713
- if (specifier === "overmux" || specifier.endsWith("/shared")) return "shared";
714
- };
715
- const isTypeOnlyReference = (node) => {
716
- if (ts.isExportDeclaration(node)) return Boolean(node.isTypeOnly || node.exportClause && ts.isNamedExports(node.exportClause) && node.exportClause.elements.every((element) => element.isTypeOnly));
717
- const clause = node.importClause;
718
- if (!clause || clause.name) return Boolean(clause?.isTypeOnly);
719
- return Boolean(clause.isTypeOnly || clause.namedBindings && ts.isNamedImports(clause.namedBindings) && clause.namedBindings.elements.every((element) => element.isTypeOnly));
720
- };
721
- const resolvedImports = (program, source) => {
722
- const imports = [];
723
- source.forEachChild((node) => {
724
- if (!ts.isImportDeclaration(node) && !ts.isExportDeclaration(node) || !node.moduleSpecifier || !ts.isStringLiteral(node.moduleSpecifier) || isTypeOnlyReference(node)) return;
725
- const specifier = node.moduleSpecifier.text;
726
- const resolved = ts.resolveModuleName(specifier, source.fileName, program.getCompilerOptions(), ts.sys).resolvedModule?.resolvedFileName;
727
- const target = resolved ? program.getSourceFile(resolved) : void 0;
728
- const kind = kindOfSpecifier(specifier);
729
- if (kind || target && !target.isDeclarationFile) imports.push({
730
- kind,
731
- node,
732
- source: target?.isDeclarationFile ? void 0 : target
733
- });
734
- });
735
- return imports;
736
- };
737
- const invalidImport = (source, target) => source === "server" && target === "client" || source === "client" && target === "server" || source === "shared" && target !== void 0 && target !== "shared";
738
- const visitImportGraph = ({ configPath, diagnostics, kind, program, seen, source }) => {
739
- const visitKey = `${kind}:${source.fileName}`;
740
- if (seen.has(visitKey)) return;
741
- seen.add(visitKey);
742
- resolvedImports(program, source).forEach(({ kind: importKind, node, source: target }) => {
743
- const declaredKind = importKind ?? (target ? kindOf(target.fileName, configPath) : void 0);
744
- if (invalidImport(kind, declaredKind)) {
745
- const position = source.getLineAndCharacterOfPosition(node.getStart());
746
- diagnostics.push({
747
- code: "OVERMUX_IMPORT_BOUNDARY",
748
- column: position.character + 1,
749
- file: source.fileName,
750
- line: position.line + 1,
751
- message: `${kind} modules may not import ${declaredKind} modules`,
752
- phase: "imports"
753
- });
754
- return;
755
- }
756
- if (target) visitImportGraph({
757
- configPath,
758
- diagnostics,
759
- kind: declaredKind === "shared" ? "shared" : kind,
760
- program,
761
- seen,
762
- source: target
763
- });
764
- });
765
- };
766
- const importBoundaryDiagnostics = (program, configPath) => {
767
- const diagnostics = [];
768
- const seen = /* @__PURE__ */ new Set();
769
- program.getSourceFiles().forEach((source) => {
770
- const kind = kindOf(source.fileName, configPath);
771
- if (kind && !source.isDeclarationFile) visitImportGraph({
772
- configPath,
773
- diagnostics,
774
- kind,
775
- program,
776
- seen,
777
- source
778
- });
779
- });
780
- return diagnostics;
781
- };
782
- const runtimeDiagnostics = (cause) => {
783
- if (cause instanceof ZodError) return cause.issues.map((issue) => ({
784
- message: issue.message,
785
- path: issue.path.length ? issue.path.join(".") : void 0,
786
- phase: "runtime"
787
- }));
788
- return [{
789
- message: cause instanceof Error ? cause.message : String(cause),
790
- phase: "runtime"
791
- }];
792
- };
793
- const typecheck = async ({ aliases = {}, configPath, typeAliases }) => {
794
- const options = loadCompilerOptions(configPath);
795
- const aliasesForTypes = typeAliases ?? Object.fromEntries(Object.entries(aliases).filter(([key]) => key === "overmux" || key.startsWith("overmux/")));
796
- const absoluteAliases = Object.fromEntries(Object.entries(aliasesForTypes).map(([key, value]) => [key, [isAbsolute(value) ? value : resolve(value)]]));
797
- const program = ts.createProgram({
798
- options: {
799
- ...options,
800
- baseUrl: options.baseUrl ?? "/",
801
- paths: {
802
- ...options.paths,
803
- ...absoluteAliases
804
- }
805
- },
806
- rootNames: [configPath]
807
- });
808
- return {
809
- diagnostics: [...ts.getPreEmitDiagnostics(program).map(diagnosticOf), ...importBoundaryDiagnostics(program, configPath)],
810
- files: program.getSourceFiles().filter((file) => !file.isDeclarationFile && !file.fileName.includes("node_modules")).length
811
- };
812
- };
813
- const checkOvermux = async (options) => {
814
- const configPath = resolve(options.configPath);
815
- let checked;
816
- try {
817
- checked = await typecheck({
818
- ...options,
819
- configPath
820
- });
821
- } catch (cause) {
822
- return {
823
- config: configPath,
824
- diagnostics: [{
825
- message: cause instanceof Error ? cause.message : String(cause),
826
- phase: "config"
827
- }],
828
- files: 0,
829
- ok: false
830
- };
831
- }
832
- if (checked.diagnostics.length || options.typesOnly) return {
833
- config: configPath,
834
- diagnostics: checked.diagnostics,
835
- files: checked.files,
836
- ok: checked.diagnostics.length === 0
837
- };
838
- let runtime;
839
- try {
840
- const { config } = await loadOvermuxConfig({
841
- aliases: options.aliases,
842
- configPath
843
- });
844
- runtime = await createRuntime({ config });
845
- return {
846
- operations: runtime.manifest.operations.length,
847
- config: configPath,
848
- diagnostics: [],
849
- files: checked.files,
850
- ok: true
851
- };
852
- } catch (cause) {
853
- return {
854
- config: configPath,
855
- diagnostics: runtimeDiagnostics(cause),
856
- files: checked.files,
857
- ok: false
858
- };
859
- } finally {
860
- await runtime?.dispose();
861
- }
862
- };
863
- //#endregion
864
- //#region src/internal/server/server-logger.ts
865
- const errorMessage = (cause) => cause instanceof Error ? cause.message : String(cause);
866
- //#endregion
867
- //#region src/internal/server/coordinator/ipc-protocol.ts
868
- const childOptionsSchema = z.object({
869
- configAliases: z.record(z.string(), z.string()).optional(),
870
- configPath: z.string(),
871
- debug: z.boolean().optional(),
872
- developmentWebTarget: z.url().optional(),
873
- host: z.string().optional(),
874
- logFile: z.string().optional(),
875
- port: z.number().int().min(0).max(65535).optional(),
876
- productionWebAssetsDir: z.string().optional(),
877
- watch: z.boolean().optional()
878
- });
879
- z.discriminatedUnion("type", [
880
- z.object({
881
- options: childOptionsSchema,
882
- type: z.literal("start")
883
- }),
884
- z.object({ type: z.literal("update-available") }),
885
- z.object({ type: z.literal("stop") })
886
- ]);
887
- const childMessageSchema = z.discriminatedUnion("type", [
888
- z.object({
889
- host: z.string(),
890
- port: z.number().int().min(1).max(65535),
891
- type: z.literal("ready"),
892
- url: z.string(),
893
- watch: z.boolean()
894
- }),
895
- z.object({ type: z.literal("restart-requested") }),
896
- z.object({
897
- message: z.string(),
898
- type: z.literal("startup-failed")
899
- })
900
- ]);
901
- const parseChildMessage = (value) => childMessageSchema.parse(value);
902
- //#endregion
903
- //#region src/internal/server/coordinator/update-watcher.ts
904
- const isRelevantUpdatePath = (path) => {
905
- if (path.split(/[\\/]/).some((segment) => segment.toLowerCase() === "node_modules")) return false;
906
- return /\.(?:js|jsx|mjs|cjs|ts|tsx|mts|cts|json|css)$/i.test(path);
907
- };
908
- const startUpdateWatcher = ({ configPath, onUpdate }) => {
909
- let closed = false;
910
- let timeout;
911
- const watcher = watch(dirname(configPath), { recursive: true }, (_event, fileName) => {
912
- if (closed || !fileName || !isRelevantUpdatePath(fileName.toString())) return;
913
- if (timeout) clearTimeout(timeout);
914
- timeout = setTimeout(onUpdate, 100);
915
- });
916
- return { close: () => {
917
- if (closed) return;
918
- closed = true;
919
- if (timeout) clearTimeout(timeout);
920
- watcher.close();
921
- } };
922
- };
923
- //#endregion
924
- //#region src/internal/server/coordinator/server-coordinator.ts
925
- const send = (child, message, onError) => {
926
- if (!child.connected) {
927
- onError?.(/* @__PURE__ */ new Error("Server child IPC channel is closed"));
928
- return;
929
- }
930
- try {
931
- child.send(message, (cause) => {
932
- if (cause) onError?.(cause);
933
- });
934
- } catch (cause) {
935
- onError?.(cause instanceof Error ? cause : new Error(String(cause)));
936
- }
937
- };
938
- const stopChild = (child) => new Promise((resolve) => {
939
- let timeout;
940
- const finish = () => {
941
- child.off("exit", finish);
942
- clearTimeout(timeout);
943
- resolve();
944
- };
945
- child.once("exit", finish);
946
- if (child.exitCode !== null || child.signalCode !== null) {
947
- finish();
948
- return;
949
- }
950
- timeout = setTimeout(() => {
951
- if (!child.kill("SIGKILL")) finish();
952
- }, 5e3);
953
- send(child, { type: "stop" });
954
- });
955
- const serverChildPath = () => fileURLToPath(import.meta.resolve("#server-child"));
956
- const startChild = (options, onRestartRequested) => new Promise((resolve, reject) => {
957
- const child = fork(serverChildPath(), [], {
958
- serialization: "json",
959
- stdio: [
960
- "inherit",
961
- "inherit",
962
- "inherit",
963
- "ipc"
964
- ]
965
- });
966
- let settled = false;
967
- const fail = (cause) => {
968
- if (settled) return;
969
- settled = true;
970
- if (child.exitCode === null && child.signalCode === null) child.kill();
971
- reject(cause instanceof Error ? cause : new Error(String(cause)));
972
- };
973
- child.on("error", fail);
974
- child.on("exit", (code, signal) => {
975
- fail(/* @__PURE__ */ new Error(`Server child exited before readiness (${signal ?? code ?? "unknown"})`));
976
- });
977
- child.on("message", (raw) => {
978
- let message;
979
- try {
980
- message = parseChildMessage(raw);
981
- } catch (cause) {
982
- fail(cause);
983
- return;
984
- }
985
- if (message.type === "restart-requested") {
986
- if (settled) onRestartRequested();
987
- return;
988
- }
989
- if (message.type === "startup-failed") {
990
- fail(new Error(message.message));
991
- return;
992
- }
993
- if (!settled) {
994
- settled = true;
995
- resolve({
996
- host: message.host,
997
- port: message.port,
998
- process: child,
999
- url: message.url,
1000
- watch: message.watch
1001
- });
1002
- }
1003
- });
1004
- send(child, {
1005
- options,
1006
- type: "start"
1007
- }, fail);
1008
- });
1009
- const startServerCoordinator = async (initialOptions) => {
1010
- let child;
1011
- let closed = false;
1012
- let closePromise;
1013
- let restartPromise;
1014
- let updateDuringRestart = false;
1015
- let startupAddress;
1016
- let watcher;
1017
- const adoptChild = (next) => {
1018
- child = next;
1019
- next.process.once("exit", () => {
1020
- if (child?.process === next.process) child = void 0;
1021
- });
1022
- };
1023
- const restart = () => {
1024
- if (closed || restartPromise) return restartPromise ?? Promise.resolve();
1025
- restartPromise = (async () => {
1026
- const previous = child;
1027
- child = void 0;
1028
- if (previous) await stopChild(previous.process);
1029
- if (closed || !startupAddress) return;
1030
- try {
1031
- const replacement = await startChild({
1032
- ...initialOptions,
1033
- host: startupAddress.host,
1034
- port: startupAddress.port
1035
- }, () => void restart());
1036
- if (closed) {
1037
- await stopChild(replacement.process);
1038
- return;
1039
- }
1040
- adoptChild(replacement);
1041
- if (updateDuringRestart) {
1042
- updateDuringRestart = false;
1043
- send(replacement.process, { type: "update-available" });
1044
- }
1045
- } catch (cause) {
1046
- process.stderr.write(`[overmux] replacement child failed: ${errorMessage(cause)}\n`);
1047
- }
1048
- })().finally(() => {
1049
- const shouldRetry = updateDuringRestart && !child && !closed;
1050
- updateDuringRestart = false;
1051
- restartPromise = void 0;
1052
- if (shouldRetry) restart();
1053
- });
1054
- return restartPromise;
1055
- };
1056
- const onUpdate = () => {
1057
- if (closed) return;
1058
- if (restartPromise) {
1059
- updateDuringRestart = true;
1060
- return;
1061
- }
1062
- if (child) {
1063
- send(child.process, { type: "update-available" });
1064
- return;
1065
- }
1066
- restart();
1067
- };
1068
- const initial = await startChild(initialOptions, () => void restart());
1069
- startupAddress = {
1070
- host: initial.host,
1071
- port: initial.port
1072
- };
1073
- adoptChild(initial);
1074
- try {
1075
- if (initial.watch) watcher = startUpdateWatcher({
1076
- configPath: initialOptions.configPath,
1077
- onUpdate
1078
- });
1079
- } catch (cause) {
1080
- await stopChild(initial.process);
1081
- throw cause;
1082
- }
1083
- const close = () => {
1084
- if (closePromise) return closePromise;
1085
- closed = true;
1086
- closePromise = (async () => {
1087
- process.off("SIGINT", onSignal);
1088
- process.off("SIGTERM", onSignal);
1089
- watcher?.close();
1090
- const active = child;
1091
- child = void 0;
1092
- if (active) await stopChild(active.process);
1093
- await restartPromise;
1094
- })();
1095
- return closePromise;
1096
- };
1097
- const onSignal = () => {
1098
- close().catch((cause) => {
1099
- process.stderr.write(`[overmux] coordinator shutdown failed: ${errorMessage(cause)}\n`);
1100
- });
1101
- };
1102
- process.once("SIGINT", onSignal);
1103
- process.once("SIGTERM", onSignal);
1104
- return {
1105
- announceUpdateAvailable: onUpdate,
1106
- close,
1107
- port: initial.port,
1108
- url: initial.url
1109
- };
1110
- };
1111
- //#endregion
1112
- //#region src/internal/server/start-overmux-server.ts
1113
- const startOvermuxServer = async (options) => startServerCoordinator({
1114
- ...options,
1115
- configPath: resolve(options.configPath)
1116
- });
1117
- //#endregion
1118
- export { checkOvermux, getOvermuxPaths, startOvermuxServer };
108
+ export { getOvermuxPaths };
1119
109
 
1120
110
  //# sourceMappingURL=server.js.map