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 | "@executor/ui": "1.4.0-beta.0" | 26 | "@executor/ui": "1.4.0-beta.0" |
| 27 | }, | 27 | }, |
| 28 | "changesets": [ | 28 | "changesets": [ |
| 29 | "green-bobcats-shop", | ||
| 29 | "kind-cycles-hang", | 30 | "kind-cycles-hang", |
| 30 | "ripe-yaks-sniff", | 31 | "ripe-yaks-sniff", |
| 31 | "tiny-melons-jump" | 32 | "tiny-melons-jump" |
| @@ -1,6 +1,6 @@ | |||
| 1 | { | 1 | { |
| 2 | "name": "executor", | 2 | "name": "executor", |
| 3 | "version": "1.4.0-beta.4", | 3 | "version": "1.4.0-beta.5", |
| 4 | "private": true, | 4 | "private": true, |
| 5 | "type": "module", | 5 | "type": "module", |
| 6 | "bin": { | 6 | "bin": { |
| @@ -121,6 +121,8 @@ const createEmbeddedWebUISource = async () => { · 120 unmodified lines | |||
| 121 | // Build platform binaries | 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 | const buildBinaries = async (targets: Target[]) => { | 126 | const buildBinaries = async (targets: Target[]) => { |
| 125 | const meta = await readMetadata(); | 127 | const meta = await readMetadata(); |
| 126 | const binaries: Record<string, string> = {}; | 128 | const binaries: Record<string, string> = {}; |
| @@ -143,7 +145,7 @@ const buildBinaries = async (targets: Target[]) => { · 16 unmodified lines | |||
| 143 | console.log(`Building ${name}...`); | 145 | console.log(`Building ${name}...`); |
| 144 | 146 | ||
| 145 | await Bun.build({ | 147 | await Bun.build({ |
| 146 | entrypoints: [join(cliRoot, "src/main.ts"), embeddedWebUIPath], | 148 | entrypoints: [join(cliRoot, "src/main.ts")], |
| 147 | minify: true, | 149 | minify: true, |
| 148 | compile: { | 150 | compile: { |
| 149 | target: bunTarget(target) as any, | 151 | target: bunTarget(target) as any, |
| @@ -183,7 +185,7 @@ const buildBinaries = async (targets: Target[]) => { · 33 unmodified lines | |||
| 183 | 185 | ||
| 184 | return binaries; | 186 | return binaries; |
| 185 | } finally { | 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 | export default files; | 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 | const DEFAULT_BASE_URL = `http://localhost:${DEFAULT_PORT}`; | 40 | const DEFAULT_BASE_URL = `http://localhost:${DEFAULT_PORT}`; |
| 41 | 41 | ||
| 42 | // Embedded web UI — baked into compiled binaries via `with { type: "file" }` | 42 | // Embedded web UI — baked into compiled binaries via `with { type: "file" }` |
| 43 | const embeddedWebUI: Record<string, string> | null = | 43 | import embeddedWebUI from "./embedded-web-ui.gen"; |
| 44 | await import("./embedded-web-ui.gen") | ||
| 45 | .then((m) => m.default as Record<string, string>) | ||
| 46 | .catch(() => null); | ||
| 47 | 44 | ||
| 48 | // --------------------------------------------------------------------------- | 45 | // --------------------------------------------------------------------------- |
| 49 | // Helpers | 46 | // Helpers |
| @@ -18,7 +18,7 @@ · 17 unmodified lines | |||
| 18 | }, | 18 | }, |
| 19 | "apps/cli": { | 19 | "apps/cli": { |
| 20 | "name": "executor", | 20 | "name": "executor", |
| 21 | "version": "1.4.0-beta.4", | 21 | "version": "1.4.0-beta.5", |
| 22 | "bin": { | 22 | "bin": { |
| 23 | "executor": "./bin/executor.ts", | 23 | "executor": "./bin/executor.ts", |
| 24 | }, | 24 | }, |
| @@ -161,15 +161,75 @@ describe("release bootstrap smoke", () => { · 160 unmodified lines | |||
| 161 | const installedBinaryStat = await readFile(installedBinaryPath); | 161 | const installedBinaryStat = await readFile(installedBinaryPath); |
| 162 | expect(installedBinaryStat.byteLength).toBeGreaterThan(0); | 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 | process.execPath, | 172 | process.execPath, |
| 166 | [join(installedPackageDir, "bin", "executor"), "--help"], | 173 | [join(installedPackageDir, "bin", "executor"), "web", "--port", String(webPort)], |
| 167 | installedPackageDir, | 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); | 180 | let webStdout = ""; |
| 172 | expect(secondCombinedOutput).not.toContain("downloading release asset"); | 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 | } finally { | 233 | } finally { |
| 174 | await closeServer(server); | 234 | await closeServer(server); |
| 175 | await rm(tempRoot, { recursive: true, force: true }); | 235 | await rm(tempRoot, { recursive: true, force: true }); |