Unified · Split
| @@ -0,0 +1,5 @@ | ||
| 1 | +--- | |
| 2 | +"executor": patch | |
| 3 | +--- | |
| 4 | + | |
| 5 | +Fix compiled web serving so embedded UI assets are bundled into release binaries, and extend the bootstrap smoke test to verify the root web page and docs routes respond successfully. | |
| @@ -26,6 +26,7 @@ · 25 unmodified lines | ||
| 26 | 26 | "@executor/ui": "1.4.0-beta.0" |
| 27 | 27 | }, |
| 28 | 28 | "changesets": [ |
| 29 | + "green-bobcats-shop", | |
| 29 | 30 | "kind-cycles-hang", |
| 30 | 31 | "ripe-yaks-sniff", |
| 31 | 32 | "tiny-melons-jump" |
| @@ -1,6 +1,6 @@ | ||
| 1 | 1 | { |
| 2 | 2 | "name": "executor", |
| 3 | − "version": "1.4.0-beta.4", | |
| 3 | + "version": "1.4.0-beta.5", | |
| 4 | 4 | "private": true, |
| 5 | 5 | "type": "module", |
| 6 | 6 | "bin": { |
| @@ -121,6 +121,8 @@ const createEmbeddedWebUISource = async () => { · 120 unmodified lines | ||
| 121 | 121 | // Build platform binaries |
| 122 | 122 | // --------------------------------------------------------------------------- |
| 123 | 123 | |
| 124 | +const EMBEDDED_WEB_UI_STUB = `const files: Record<string, string> | null = null;\n\nexport default files;\n`; | |
| 125 | + | |
| 124 | 126 | const buildBinaries = async (targets: Target[]) => { |
| 125 | 127 | const meta = await readMetadata(); |
| 126 | 128 | const binaries: Record<string, string> = {}; |
| @@ -143,7 +145,7 @@ const buildBinaries = async (targets: Target[]) => { · 16 unmodified lines | ||
| 143 | 145 | console.log(`Building ${name}...`); |
| 144 | 146 | |
| 145 | 147 | await Bun.build({ |
| 146 | − entrypoints: [join(cliRoot, "src/main.ts"), embeddedWebUIPath], | |
| 148 | + entrypoints: [join(cliRoot, "src/main.ts")], | |
| 147 | 149 | minify: true, |
| 148 | 150 | compile: { |
| 149 | 151 | target: bunTarget(target) as any, |
| @@ -183,7 +185,7 @@ const buildBinaries = async (targets: Target[]) => { · 33 unmodified lines | ||
| 183 | 185 | |
| 184 | 186 | return binaries; |
| 185 | 187 | } finally { |
| 186 | − await rm(embeddedWebUIPath, { force: true }); | |
| 188 | + await writeFile(embeddedWebUIPath, EMBEDDED_WEB_UI_STUB); | |
| 187 | 189 | } |
| 188 | 190 | }; |
| 189 | 191 | |
| @@ -1,2 +1,2 @@ | ||
| 1 | −declare const files: Record<string, string>; | |
| 1 | +declare const files: Record<string, string> | null; | |
| 2 | 2 | export default files; |
| @@ -0,0 +1,3 @@ | ||
| 1 | +const files: Record<string, string> | null = null; | |
| 2 | + | |
| 3 | +export default files; | |
| @@ -40,10 +40,7 @@ const DEFAULT_PORT = 8788; · 39 unmodified lines | ||
| 40 | 40 | const DEFAULT_BASE_URL = `http://localhost:${DEFAULT_PORT}`; |
| 41 | 41 | |
| 42 | 42 | // Embedded web UI — baked into compiled binaries via `with { type: "file" }` |
| 43 | −const embeddedWebUI: Record<string, string> | null = | |
| 44 | − await import("./embedded-web-ui.gen") | |
| 45 | − .then((m) => m.default as Record<string, string>) | |
| 46 | − .catch(() => null); | |
| 43 | +import embeddedWebUI from "./embedded-web-ui.gen"; | |
| 47 | 44 | |
| 48 | 45 | // --------------------------------------------------------------------------- |
| 49 | 46 | // Helpers |
| @@ -18,7 +18,7 @@ · 17 unmodified lines | ||
| 18 | 18 | }, |
| 19 | 19 | "apps/cli": { |
| 20 | 20 | "name": "executor", |
| 21 | − "version": "1.4.0-beta.4", | |
| 21 | + "version": "1.4.0-beta.5", | |
| 22 | 22 | "bin": { |
| 23 | 23 | "executor": "./bin/executor.ts", |
| 24 | 24 | }, |
| @@ -161,15 +161,75 @@ describe("release bootstrap smoke", () => { · 160 unmodified lines | ||
| 161 | 161 | const installedBinaryStat = await readFile(installedBinaryPath); |
| 162 | 162 | expect(installedBinaryStat.byteLength).toBeGreaterThan(0); |
| 163 | 163 | |
| 164 | − const secondRun = await runCommand( | |
| 164 | + const probeServer = createServer((_, response) => { | |
| 165 | + response.statusCode = 204; | |
| 166 | + response.end(); | |
| 167 | + }); | |
| 168 | + const webPort = await listen(probeServer); | |
| 169 | + await closeServer(probeServer); | |
| 170 | + | |
| 171 | + const webProcess = spawn( | |
| 165 | 172 | process.execPath, |
| 166 | − [join(installedPackageDir, "bin", "executor"), "--help"], | |
| 167 | − installedPackageDir, | |
| 173 | + [join(installedPackageDir, "bin", "executor"), "web", "--port", String(webPort)], | |
| 174 | + { | |
| 175 | + cwd: installedPackageDir, | |
| 176 | + stdio: ["ignore", "pipe", "pipe"], | |
| 177 | + }, | |
| 168 | 178 | ); |
| 169 | − const secondCombinedOutput = `${secondRun.stdout}\n${secondRun.stderr}`; | |
| 170 | 179 | |
| 171 | − expect(secondRun.exitCode, secondCombinedOutput).toBe(0); | |
| 172 | − expect(secondCombinedOutput).not.toContain("downloading release asset"); | |
| 180 | + let webStdout = ""; | |
| 181 | + let webStderr = ""; | |
| 182 | + webProcess.stdout.setEncoding("utf8"); | |
| 183 | + webProcess.stdout.on("data", (chunk) => { | |
| 184 | + webStdout += chunk; | |
| 185 | + }); | |
| 186 | + webProcess.stderr.setEncoding("utf8"); | |
| 187 | + webProcess.stderr.on("data", (chunk) => { | |
| 188 | + webStderr += chunk; | |
| 189 | + }); | |
| 190 | + | |
| 191 | + try { | |
| 192 | + const deadline = Date.now() + 30_000; | |
| 193 | + let rootResponse: Response | null = null; | |
| 194 | + while (Date.now() < deadline) { | |
| 195 | + await new Promise((resolveDelay) => setTimeout(resolveDelay, 250)); | |
| 196 | + try { | |
| 197 | + rootResponse = await fetch(`http://127.0.0.1:${webPort}/`); | |
| 198 | + if (rootResponse.ok) { | |
| 199 | + break; | |
| 200 | + } | |
| 201 | + } catch { | |
| 202 | + // keep polling until the server is ready | |
| 203 | + } | |
| 204 | + } | |
| 205 | + | |
| 206 | + expect(rootResponse, `${webStdout}\n${webStderr}`).not.toBeNull(); | |
| 207 | + expect(rootResponse!.status, `${webStdout}\n${webStderr}`).toBe(200); | |
| 208 | + const rootHtml = await rootResponse!.text(); | |
| 209 | + expect(rootHtml.toLowerCase()).toContain("<html"); | |
| 210 | + | |
| 211 | + const docsResponse = await fetch(`http://127.0.0.1:${webPort}/docs`); | |
| 212 | + expect(docsResponse.status, `${webStdout}\n${webStderr}`).toBe(200); | |
| 213 | + | |
| 214 | + const secondRun = await runCommand( | |
| 215 | + process.execPath, | |
| 216 | + [join(installedPackageDir, "bin", "executor"), "--help"], | |
| 217 | + installedPackageDir, | |
| 218 | + ); | |
| 219 | + const secondCombinedOutput = `${secondRun.stdout}\n${secondRun.stderr}`; | |
| 220 | + | |
| 221 | + expect(secondRun.exitCode, secondCombinedOutput).toBe(0); | |
| 222 | + expect(secondCombinedOutput).not.toContain("downloading release asset"); | |
| 223 | + } finally { | |
| 224 | + webProcess.kill("SIGTERM"); | |
| 225 | + await Promise.race([ | |
| 226 | + new Promise((resolveClose) => webProcess.once("close", () => resolveClose(undefined))), | |
| 227 | + new Promise((resolveClose) => setTimeout(resolveClose, 5_000)), | |
| 228 | + ]); | |
| 229 | + if (webProcess.exitCode === null) { | |
| 230 | + webProcess.kill("SIGKILL"); | |
| 231 | + } | |
| 232 | + } | |
| 173 | 233 | } finally { |
| 174 | 234 | await closeServer(server); |
| 175 | 235 | await rm(tempRoot, { recursive: true, force: true }); |