@mh-gg/cli 0.1.1-alpha.20260613T085325975Z

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 (71) hide show
  1. package/README.md +5 -0
  2. package/bin/matterhorn.cjs +57 -0
  3. package/package.json +49 -0
  4. package/runtime/bin/appFrontend/artifacts.cjs +25 -0
  5. package/runtime/bin/appFrontend/buildServers.cjs +176 -0
  6. package/runtime/bin/appFrontend/commandEnv.cjs +74 -0
  7. package/runtime/bin/appFrontend/commandPolicy.cjs +23 -0
  8. package/runtime/bin/appFrontend/devServers.cjs +150 -0
  9. package/runtime/bin/appFrontend/httpServers.cjs +221 -0
  10. package/runtime/bin/appFrontend/paths.cjs +103 -0
  11. package/runtime/bin/appFrontend/ports.cjs +36 -0
  12. package/runtime/bin/appFrontend/processes.cjs +127 -0
  13. package/runtime/bin/appFrontend.cjs +45 -0
  14. package/runtime/bin/appHostCommand.cjs +381 -0
  15. package/runtime/bin/matterhorn.cjs +501 -0
  16. package/runtime/bin/matterhornAppLoader.cjs +588 -0
  17. package/runtime/bin/matterhornApps.cjs +223 -0
  18. package/runtime/bin/matterhornDeploy.cjs +108 -0
  19. package/runtime/bin/matterhornEmitAppBundle.cjs +20 -0
  20. package/runtime/bin/matterhornInstall.cjs +609 -0
  21. package/runtime/host/callAuth.cjs +76 -0
  22. package/runtime/host/host.cjs +103 -0
  23. package/runtime/host/hostAnnouncement.cjs +70 -0
  24. package/runtime/host/hostClients/constants.cjs +7 -0
  25. package/runtime/host/hostClients/frontendBundleRefresh.cjs +158 -0
  26. package/runtime/host/hostClients/frontendRequests.cjs +166 -0
  27. package/runtime/host/hostClients/index.cjs +68 -0
  28. package/runtime/host/hostClients/rejections.cjs +37 -0
  29. package/runtime/host/hostSession.cjs +160 -0
  30. package/runtime/host/inlineProgressBar.cjs +128 -0
  31. package/runtime/host/localPeerServer.cjs +114 -0
  32. package/runtime/host/localRelayClient.cjs +151 -0
  33. package/runtime/host/matterhornrc.cjs +75 -0
  34. package/runtime/host/memberRootRegistry.cjs +132 -0
  35. package/runtime/host/nodePeer.cjs +127 -0
  36. package/runtime/host/nodePeerRacePatch.cjs +106 -0
  37. package/runtime/host/peerJsConfig.cjs +26 -0
  38. package/runtime/host/pushEgress.cjs +48 -0
  39. package/runtime/host/pushStorage.cjs +233 -0
  40. package/runtime/host/relay/config.cjs +179 -0
  41. package/runtime/host/relay/connectionCleanup.cjs +34 -0
  42. package/runtime/host/relay/connectionDispatcher.cjs +140 -0
  43. package/runtime/host/relay/matterhornOperationEvents.cjs +100 -0
  44. package/runtime/host/relay/matterhornRuntimeEventBridge.cjs +182 -0
  45. package/runtime/host/relay/nostrRelay.cjs +30 -0
  46. package/runtime/host/relay/peerStartup.cjs +81 -0
  47. package/runtime/host/relay.cjs +653 -0
  48. package/runtime/host/relayClientRouting.cjs +1054 -0
  49. package/runtime/host/relayConfig.cjs +156 -0
  50. package/runtime/host/relayHostAuth.cjs +39 -0
  51. package/runtime/host/relayHostMessages.cjs +367 -0
  52. package/runtime/host/relayHttp.cjs +48 -0
  53. package/runtime/host/relayIdentity.cjs +496 -0
  54. package/runtime/host/relayIncomingGate.cjs +153 -0
  55. package/runtime/host/relayMeshEnvelopes.cjs +522 -0
  56. package/runtime/host/relayPeerLifecycle.cjs +96 -0
  57. package/runtime/host/relayPeerSignals.cjs +175 -0
  58. package/runtime/host/relayRoomRuntimePersistence.cjs +129 -0
  59. package/runtime/host/relayStatus.cjs +160 -0
  60. package/runtime/host/sfuRelay.cjs +553 -0
  61. package/runtime/host/sqliteRelayStorage.cjs +352 -0
  62. package/runtime/host/wireValidation/client.cjs +213 -0
  63. package/runtime/host/wireValidation/host.cjs +33 -0
  64. package/runtime/host/wireValidation/index.cjs +13 -0
  65. package/runtime/host/wireValidation/peerSignal.cjs +35 -0
  66. package/runtime/host/wireValidation/presenceEvent.cjs +49 -0
  67. package/runtime/host/wireValidation/push.cjs +49 -0
  68. package/runtime/host/wireValidation/relay.cjs +131 -0
  69. package/runtime/host/wireValidation/shared.cjs +49 -0
  70. package/runtime/scripts/ensureWorkspaceSdkBuild.cjs +148 -0
  71. package/runtime/scripts/killChildTree.cjs +18 -0
@@ -0,0 +1,221 @@
1
+ const fs = require("node:fs");
2
+ const fsp = require("node:fs/promises");
3
+ const http = require("node:http");
4
+ const path = require("node:path");
5
+ const { appBundleArtifact } = require("./artifacts.cjs");
6
+ const {
7
+ bundleConfig,
8
+ bundleDistDir,
9
+ bundleMountPath,
10
+ bundleUrl,
11
+ ensureLeadingSlash,
12
+ ensureTrailingSlash,
13
+ frontendRoot,
14
+ frontendUrl,
15
+ launcherDist
16
+ } = require("./paths.cjs");
17
+
18
+ const DEFAULT_FRONTEND_START_TIMEOUT_MS = 30000;
19
+
20
+ function delay(ms) {
21
+ return new Promise((resolve) => setTimeout(resolve, ms));
22
+ }
23
+
24
+ async function waitForHttpOk(url, timeoutMs = DEFAULT_FRONTEND_START_TIMEOUT_MS) {
25
+ const deadline = Date.now() + timeoutMs;
26
+ let lastError;
27
+ while (Date.now() < deadline) {
28
+ try {
29
+ const response = await fetch(url);
30
+ if (response.ok) return response;
31
+ lastError = new Error(`HTTP ${response.status}`);
32
+ } catch (error) {
33
+ lastError = error;
34
+ }
35
+ await delay(100);
36
+ }
37
+ throw lastError || new Error(`Timed out waiting for ${url}`);
38
+ }
39
+
40
+ function contentType(filePath) {
41
+ const extension = path.extname(filePath).toLowerCase();
42
+ if (extension === ".html") return "text/html; charset=utf-8";
43
+ if (extension === ".js") return "text/javascript; charset=utf-8";
44
+ if (extension === ".css") return "text/css; charset=utf-8";
45
+ if (extension === ".json") return "application/json; charset=utf-8";
46
+ if (extension === ".svg") return "image/svg+xml";
47
+ if (extension === ".png") return "image/png";
48
+ if (extension === ".jpg" || extension === ".jpeg") return "image/jpeg";
49
+ if (extension === ".webp") return "image/webp";
50
+ return "application/octet-stream";
51
+ }
52
+
53
+ function requestPathname(requestUrl) {
54
+ try {
55
+ return decodeURIComponent(new URL(requestUrl, "http://127.0.0.1").pathname);
56
+ } catch {
57
+ return "/";
58
+ }
59
+ }
60
+
61
+ function staticPathForRequest(distDir, basePath, requestUrl) {
62
+ const root = path.resolve(distDir);
63
+ const normalizedBase = ensureTrailingSlash(ensureLeadingSlash(basePath));
64
+ let pathname = requestPathname(requestUrl);
65
+ if (normalizedBase !== "/" && pathname.startsWith(normalizedBase)) {
66
+ pathname = `/${pathname.slice(normalizedBase.length)}`;
67
+ }
68
+ if (pathname === "/" || pathname.endsWith("/")) pathname = `${pathname}index.html`;
69
+ const target = path.resolve(root, `.${pathname}`);
70
+ const relative = path.relative(root, target);
71
+ if (relative.startsWith("..") || path.isAbsolute(relative)) return path.join(root, "index.html");
72
+ return target;
73
+ }
74
+
75
+ async function serveFile(response, filePath) {
76
+ try {
77
+ const stat = await fsp.stat(filePath);
78
+ if (!stat.isFile()) throw new Error("not a file");
79
+ response.writeHead(200, { "content-type": contentType(filePath) });
80
+ fs.createReadStream(filePath).pipe(response);
81
+ } catch {
82
+ response.writeHead(404, { "content-type": "text/plain; charset=utf-8" });
83
+ response.end("Not found");
84
+ }
85
+ }
86
+
87
+ function closeServer(server) {
88
+ return new Promise((resolve) => server.close(() => resolve()));
89
+ }
90
+
91
+ function startStaticFrontend(frontend, options = {}) {
92
+ const distDir = path.resolve(frontendRoot(frontend), frontend.dist || "dist");
93
+ const basePath = frontend.builtBasePath || frontend.basePath || "/";
94
+ const indexPath = path.join(distDir, "index.html");
95
+ if (!fs.existsSync(indexPath)) throw new Error(`Built frontend is missing ${indexPath}`);
96
+
97
+ const server = http.createServer(async (request, response) => {
98
+ const requestedFile = staticPathForRequest(distDir, basePath, request.url || "/");
99
+ await serveFile(response, fs.existsSync(requestedFile) ? requestedFile : indexPath);
100
+ });
101
+
102
+ return new Promise((resolve, reject) => {
103
+ server.once("error", reject);
104
+ server.listen(options.port, "127.0.0.1", () => {
105
+ server.off("error", reject);
106
+ const address = server.address();
107
+ const port = typeof address === "object" && address ? address.port : options.port;
108
+ resolve({
109
+ appUrl: frontendUrl(port, basePath),
110
+ mode: "built",
111
+ port,
112
+ close: () => closeServer(server)
113
+ });
114
+ });
115
+ });
116
+ }
117
+
118
+ function startBundledStaticFrontend(frontend, options = {}) {
119
+ const bundle = bundleConfig(frontend);
120
+ const shellDir = launcherDist(frontend);
121
+ const bundleDir = bundleDistDir(frontend);
122
+ const basePath = frontend.launcher?.builtBasePath || frontend.builtBasePath || "/matterhorn/";
123
+ const mountPath = bundleMountPath(frontend);
124
+ const shellIndex = path.join(shellDir, "index.html");
125
+ const artifact = appBundleArtifact(frontend);
126
+ if (!fs.existsSync(shellIndex)) throw new Error(`Built launcher is missing ${shellIndex}`);
127
+ if (!fs.existsSync(artifact.file)) throw new Error(`Built app bundle is missing ${artifact.file}`);
128
+
129
+ const server = http.createServer(async (request, response) => {
130
+ const pathname = requestPathname(request.url || "/");
131
+ if (pathname.startsWith(mountPath)) {
132
+ const relativePath = pathname.slice(mountPath.length);
133
+ const target = path.resolve(bundleDir, relativePath || (bundle.builtEntry || "matterhorn-app.js"));
134
+ const relative = path.relative(bundleDir, target);
135
+ if (relative.startsWith("..") || path.isAbsolute(relative)) {
136
+ response.writeHead(404, { "content-type": "text/plain; charset=utf-8" });
137
+ response.end("Not found");
138
+ return;
139
+ }
140
+ await serveFile(response, target);
141
+ return;
142
+ }
143
+
144
+ const requestedFile = staticPathForRequest(shellDir, basePath, request.url || "/");
145
+ await serveFile(response, fs.existsSync(requestedFile) ? requestedFile : shellIndex);
146
+ });
147
+
148
+ return new Promise((resolve, reject) => {
149
+ server.once("error", reject);
150
+ server.listen(options.port, "127.0.0.1", () => {
151
+ server.off("error", reject);
152
+ const address = server.address();
153
+ const port = typeof address === "object" && address ? address.port : options.port;
154
+ const appUrl = frontendUrl(port, basePath);
155
+ resolve({
156
+ appUrl,
157
+ bundleUrl: bundleUrl(appUrl, mountPath, artifact.entry),
158
+ bundleFile: artifact.file,
159
+ bundleEntry: artifact.entry,
160
+ bundleIntegrity: artifact.integrity,
161
+ bundleByteLength: artifact.byteLength,
162
+ mode: "built",
163
+ port,
164
+ close: () => closeServer(server)
165
+ });
166
+ });
167
+ });
168
+ }
169
+
170
+ function startBundleStaticFrontend(frontend, options = {}) {
171
+ const bundle = bundleConfig(frontend);
172
+ const bundleDir = bundleDistDir(frontend);
173
+ const mountPath = bundleMountPath(frontend);
174
+ const artifact = appBundleArtifact(frontend);
175
+
176
+ const server = http.createServer(async (request, response) => {
177
+ const pathname = requestPathname(request.url || "/");
178
+ if (!pathname.startsWith(mountPath)) {
179
+ response.writeHead(404, { "content-type": "text/plain; charset=utf-8" });
180
+ response.end("Not found");
181
+ return;
182
+ }
183
+ const relativePath = pathname.slice(mountPath.length);
184
+ const target = path.resolve(bundleDir, relativePath || (bundle.builtEntry || "matterhorn-app.js"));
185
+ const relative = path.relative(bundleDir, target);
186
+ if (relative.startsWith("..") || path.isAbsolute(relative)) {
187
+ response.writeHead(404, { "content-type": "text/plain; charset=utf-8" });
188
+ response.end("Not found");
189
+ return;
190
+ }
191
+ await serveFile(response, target);
192
+ });
193
+
194
+ return new Promise((resolve, reject) => {
195
+ server.once("error", reject);
196
+ server.listen(options.port, "127.0.0.1", () => {
197
+ server.off("error", reject);
198
+ const address = server.address();
199
+ const port = typeof address === "object" && address ? address.port : options.port;
200
+ const rootUrl = frontendUrl(port, "/");
201
+ resolve({
202
+ appUrl: rootUrl,
203
+ bundleUrl: bundleUrl(rootUrl, mountPath, artifact.entry),
204
+ bundleFile: artifact.file,
205
+ bundleEntry: artifact.entry,
206
+ bundleIntegrity: artifact.integrity,
207
+ bundleByteLength: artifact.byteLength,
208
+ mode: "built",
209
+ port,
210
+ close: () => closeServer(server)
211
+ });
212
+ });
213
+ });
214
+ }
215
+
216
+ module.exports = {
217
+ startBundleStaticFrontend,
218
+ startBundledStaticFrontend,
219
+ startStaticFrontend,
220
+ waitForHttpOk
221
+ };
@@ -0,0 +1,103 @@
1
+ const path = require("node:path");
2
+
3
+ function parseServeMode(value) {
4
+ if (value === true || value === undefined || value === "") return "dev";
5
+ const mode = String(value).trim().toLowerCase();
6
+ if (mode === "dev" || mode === "development") return "dev";
7
+ if (mode === "built" || mode === "build" || mode === "static" || mode === "dist") return "built";
8
+ throw new Error(`Unsupported frontend serve mode "${value}". Use dev or built.`);
9
+ }
10
+
11
+ function frontendRoot(frontend) {
12
+ if (!frontend?.root) throw new Error("App frontend.root is required");
13
+ return path.resolve(frontend.root);
14
+ }
15
+
16
+ function ensureLeadingSlash(value) {
17
+ if (!value) return "/";
18
+ return value.startsWith("/") ? value : `/${value}`;
19
+ }
20
+
21
+ function ensureTrailingSlash(value) {
22
+ return value.endsWith("/") ? value : `${value}/`;
23
+ }
24
+
25
+ function frontendUrl(port, basePath = "/") {
26
+ return `http://127.0.0.1:${port}${ensureTrailingSlash(ensureLeadingSlash(basePath))}`;
27
+ }
28
+
29
+ function repoRoot() {
30
+ return path.resolve(__dirname, "..", "..");
31
+ }
32
+
33
+ function launcherDevCommand() {
34
+ return {
35
+ command: process.execPath,
36
+ args: [
37
+ path.join(repoRoot(), "node_modules", "vite", "bin", "vite.js"),
38
+ "--config",
39
+ path.join("client", "vite.config.ts"),
40
+ "--host",
41
+ "127.0.0.1",
42
+ "--port",
43
+ "${port}",
44
+ "--strictPort"
45
+ ]
46
+ };
47
+ }
48
+
49
+ function launcherBuildCommand() {
50
+ return {
51
+ command: process.platform === "win32" ? "pnpm.cmd" : "pnpm",
52
+ args: ["run", "build:launcher"]
53
+ };
54
+ }
55
+
56
+ function launcherDist(frontend = {}) {
57
+ return path.resolve(frontend.launcher?.dist || path.join(repoRoot(), "client", "dist"));
58
+ }
59
+
60
+ function bundleConfig(frontend) {
61
+ if (!frontend?.bundle) throw new Error("App frontend.bundle is required");
62
+ return frontend.bundle;
63
+ }
64
+
65
+ function bundleMountPath(frontend) {
66
+ const bundle = bundleConfig(frontend);
67
+ return ensureTrailingSlash(ensureLeadingSlash(bundle.mountPath || `/matterhorn/apps/${frontend.appId || "app"}/`));
68
+ }
69
+
70
+ function bundleUrl(appUrl, mountPath, entry) {
71
+ return new URL(`${mountPath}${entry}`, appUrl).toString();
72
+ }
73
+
74
+ function bundleDistDir(frontend) {
75
+ const bundle = bundleConfig(frontend);
76
+ return path.resolve(frontendRoot(frontend), bundle.dist || "frontend/dist");
77
+ }
78
+
79
+ function bundleEntryName(frontend) {
80
+ return bundleConfig(frontend).builtEntry || "matterhorn-app.js";
81
+ }
82
+
83
+ function bundleEntryFile(frontend) {
84
+ return path.join(bundleDistDir(frontend), bundleEntryName(frontend));
85
+ }
86
+
87
+ module.exports = {
88
+ bundleConfig,
89
+ bundleDistDir,
90
+ bundleEntryFile,
91
+ bundleEntryName,
92
+ bundleMountPath,
93
+ bundleUrl,
94
+ ensureLeadingSlash,
95
+ ensureTrailingSlash,
96
+ frontendRoot,
97
+ frontendUrl,
98
+ launcherBuildCommand,
99
+ launcherDevCommand,
100
+ launcherDist,
101
+ parseServeMode,
102
+ repoRoot
103
+ };
@@ -0,0 +1,36 @@
1
+ const net = require("node:net");
2
+
3
+ function canUsePort(port) {
4
+ return new Promise((resolve) => {
5
+ const server = net.createServer();
6
+ server.once("error", () => resolve(false));
7
+ server.listen(port, "127.0.0.1", () => {
8
+ server.close(() => resolve(true));
9
+ });
10
+ });
11
+ }
12
+
13
+ async function findAvailablePort(preferredPort) {
14
+ const preferred = Number(preferredPort);
15
+ if (Number.isInteger(preferred) && preferred > 0 && await canUsePort(preferred)) return preferred;
16
+ for (let attempt = 0; attempt < 60; attempt += 1) {
17
+ const port = 30000 + Math.floor(Math.random() * 20000);
18
+ if (await canUsePort(port)) return port;
19
+ }
20
+ throw new Error("Could not find an available frontend port");
21
+ }
22
+
23
+ async function selectFrontendPort(requestedPort, preferredPort) {
24
+ if (requestedPort === 0 || requestedPort === "0") return await findAvailablePort();
25
+ if (requestedPort !== undefined && requestedPort !== null && requestedPort !== "") {
26
+ const port = Number(requestedPort);
27
+ if (!Number.isInteger(port) || port <= 0) throw new Error(`Invalid frontend port "${requestedPort}"`);
28
+ return port;
29
+ }
30
+ return await findAvailablePort(preferredPort);
31
+ }
32
+
33
+ module.exports = {
34
+ findAvailablePort,
35
+ selectFrontendPort
36
+ };
@@ -0,0 +1,127 @@
1
+ const { spawn } = require("node:child_process");
2
+ const { killChildTree } = require("../../scripts/killChildTree.cjs");
3
+ const { buildMatterhornCommandEnv, redactCommandOutput } = require("./commandEnv.cjs");
4
+
5
+ function quoteCmdArg(value) {
6
+ const text = String(value);
7
+ if (/^[A-Za-z0-9_./:@=${}-]+$/.test(text)) return text;
8
+ return `"${text.replaceAll("\"", "\\\"")}"`;
9
+ }
10
+
11
+ function commandInvocation(command, args) {
12
+ if (process.platform === "win32" && ["npm", "npx", "npm.cmd", "npx.cmd", "pnpm", "pnpx", "pnpm.cmd", "pnpx.cmd"].includes(command)) {
13
+ const base = command.endsWith(".cmd") ? command : `${command}.cmd`;
14
+ return {
15
+ command: process.env.ComSpec || "cmd.exe",
16
+ args: ["/d", "/s", "/c", [base, ...args].map(quoteCmdArg).join(" ")]
17
+ };
18
+ }
19
+ return { command, args };
20
+ }
21
+
22
+ function replaceTokens(value, options) {
23
+ return String(value)
24
+ .replaceAll("${port}", String(options.port))
25
+ .replaceAll("${bundlePort}", String(options.bundlePort ?? options.port))
26
+ .replaceAll("${mountPath}", String(options.mountPath || "/"));
27
+ }
28
+
29
+ function commandSpec(command, name) {
30
+ if (!command || typeof command !== "object") throw new Error(`App frontend.${name} command is required`);
31
+ if (Array.isArray(command)) {
32
+ if (!command[0]) throw new Error(`App frontend.${name} command is empty`);
33
+ return { command: command[0], args: command.slice(1), env: {} };
34
+ }
35
+ for (const key of Object.keys(command)) {
36
+ if (key !== "command" && key !== "args" && key !== "env") {
37
+ throw new Error(`App frontend.${name}.${key} is not a supported command field`);
38
+ }
39
+ }
40
+ if (!command.command) throw new Error(`App frontend.${name}.command is required`);
41
+ if (command.args !== undefined && !Array.isArray(command.args)) throw new Error(`App frontend.${name}.args must be an array`);
42
+ if (command.env !== undefined && (!command.env || typeof command.env !== "object" || Array.isArray(command.env))) {
43
+ throw new Error(`App frontend.${name}.env must be an object`);
44
+ }
45
+ return {
46
+ command: command.command,
47
+ args: (command.args || []).map((arg) => String(arg)),
48
+ env: command.env || {}
49
+ };
50
+ }
51
+
52
+ function spawnCommand(spec, options) {
53
+ const rawArgs = spec.args.map((arg) => replaceTokens(arg, options));
54
+ const invocation = commandInvocation(spec.command, rawArgs);
55
+ const commandEnv = {
56
+ ...options.env,
57
+ ...Object.fromEntries(Object.entries(spec.env || {}).map(([key, value]) => [key, replaceTokens(value, options)]))
58
+ };
59
+ const child = spawn(invocation.command, invocation.args, {
60
+ cwd: options.cwd,
61
+ env: buildMatterhornCommandEnv({
62
+ trust: options.trust || "trusted-local-dev",
63
+ commandEnv,
64
+ port: options.port,
65
+ bundlePort: options.bundlePort ?? options.port,
66
+ mountPath: options.mountPath || "/",
67
+ unsafeInheritEnv: options.unsafeInheritEnv,
68
+ allowSensitiveCommandEnv: options.allowSensitiveCommandEnv
69
+ }),
70
+ stdio: ["ignore", "pipe", "pipe"],
71
+ shell: false,
72
+ windowsHide: true
73
+ });
74
+
75
+ const logger = options.logger;
76
+ child.stdout.on("data", (chunk) => logger?.log?.(`${options.label} stdout: ${redactCommandOutput(chunk.toString("utf8").trimEnd())}`));
77
+ child.stderr.on("data", (chunk) => logger?.error?.(`${options.label} stderr: ${redactCommandOutput(chunk.toString("utf8").trimEnd())}`));
78
+ return child;
79
+ }
80
+
81
+ function runCommand(spec, options) {
82
+ const child = spawnCommand(spec, options);
83
+ return new Promise((resolve, reject) => {
84
+ const timer = setTimeout(() => {
85
+ child.kill();
86
+ reject(new Error(`${options.label} timed out after ${options.timeoutMs}ms`));
87
+ }, options.timeoutMs);
88
+ child.on("exit", (code) => {
89
+ clearTimeout(timer);
90
+ if (code === 0) resolve();
91
+ else reject(new Error(`${options.label} exited ${code}`));
92
+ });
93
+ child.on("error", (error) => {
94
+ clearTimeout(timer);
95
+ reject(error);
96
+ });
97
+ });
98
+ }
99
+
100
+ function closeChildProcess(child) {
101
+ if (!child || child.exitCode !== null || child.signalCode !== null) return Promise.resolve();
102
+ return new Promise((resolve) => {
103
+ const timer = setTimeout(() => {
104
+ child.stdout?.destroy?.();
105
+ child.stderr?.destroy?.();
106
+ child.unref?.();
107
+ resolve();
108
+ }, 2000);
109
+ child.once("exit", () => {
110
+ clearTimeout(timer);
111
+ child.stdout?.destroy?.();
112
+ child.stderr?.destroy?.();
113
+ child.unref?.();
114
+ resolve();
115
+ });
116
+ killChildTree(child);
117
+ });
118
+ }
119
+
120
+ module.exports = {
121
+ closeChildProcess,
122
+ commandInvocation,
123
+ commandSpec,
124
+ replaceTokens,
125
+ runCommand,
126
+ spawnCommand
127
+ };
@@ -0,0 +1,45 @@
1
+ const { appBundleArtifact } = require("./appFrontend/artifacts.cjs");
2
+ const { buildAppBundleFrontend, startBundleBuiltFrontend, startBuiltFrontend, startBundledBuiltFrontend } = require("./appFrontend/buildServers.cjs");
3
+ const { startBundleDevFrontend, startBundledDevFrontend, startDevFrontend } = require("./appFrontend/devServers.cjs");
4
+ const { startBundleStaticFrontend, startBundledStaticFrontend, startStaticFrontend, waitForHttpOk } = require("./appFrontend/httpServers.cjs");
5
+ const { findAvailablePort } = require("./appFrontend/ports.cjs");
6
+ const { parseServeMode } = require("./appFrontend/paths.cjs");
7
+
8
+ async function startAppFrontend(appOrFrontend, options = {}) {
9
+ const frontend = appOrFrontend?.frontend ? { ...appOrFrontend.frontend, trust: appOrFrontend.trust } : appOrFrontend;
10
+ if (!frontend) throw new Error("App does not declare a frontend");
11
+ const mode = parseServeMode(options.mode);
12
+ if (frontend.bundle) {
13
+ if (mode === "built") return await startBundledBuiltFrontend(frontend, options);
14
+ return await startBundledDevFrontend(frontend, options);
15
+ }
16
+ if (mode === "built") return await startBuiltFrontend(frontend, options);
17
+ return await startDevFrontend(frontend, options);
18
+ }
19
+
20
+ async function startAppBundleFrontend(appOrFrontend, options = {}) {
21
+ const frontend = appOrFrontend?.frontend ? { ...appOrFrontend.frontend, trust: appOrFrontend.trust } : appOrFrontend;
22
+ if (!frontend?.bundle) throw new Error("App does not declare a bundled frontend");
23
+ const mode = parseServeMode(options.mode);
24
+ if (mode === "built") return await startBundleBuiltFrontend(frontend, options);
25
+ return await startBundleDevFrontend(frontend, options);
26
+ }
27
+
28
+ module.exports = {
29
+ appBundleArtifact,
30
+ buildAppBundleFrontend,
31
+ findAvailablePort,
32
+ parseServeMode,
33
+ startAppBundleFrontend,
34
+ startAppFrontend,
35
+ startBundleBuiltFrontend,
36
+ startBundleDevFrontend,
37
+ startBundleStaticFrontend,
38
+ startBuiltFrontend,
39
+ startBundledBuiltFrontend,
40
+ startBundledDevFrontend,
41
+ startBundledStaticFrontend,
42
+ startDevFrontend,
43
+ startStaticFrontend,
44
+ waitForHttpOk
45
+ };