History

fix embedded web ui release smoke

Rhys Sullivan committed 7 months agov1.4.0-beta.5

Commit 2365bf51 parent fa61e979 files · +83 −15Browse files

Unified · Split

9 files
  1. .changeset/green-bobcats-shop.md +5 −0
  2. .changeset/pre.json +1 −0
  3. apps/cli/package.json +1 −1
  4. apps/cli/src/build.ts +4 −2
  5. apps/cli/src/embedded-web-ui.gen.d.ts +1 −1
  6. apps/cli/src/embedded-web-ui.gen.ts +3 −0
  7. apps/cli/src/main.ts +1 −4
  8. bun.lock +1 −1
  9. tests/release-bootstrap-smoke.test.ts +66 −6
.changeset/green-bobcats-shop.md+5−0
@@ -0,0 +1,5 @@
1---
2"executor": patch
3---
4
5Fix 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.
.changeset/pre.json+1−0
@@ -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"
apps/cli/package.json+1−1
@@ -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": {
apps/cli/src/build.ts+4−2
@@ -121,6 +121,8 @@ const createEmbeddedWebUISource = async () => { · 120 unmodified lines
121// Build platform binaries121// Build platform binaries
122// ---------------------------------------------------------------------------122// ---------------------------------------------------------------------------
123 123
124const EMBEDDED_WEB_UI_STUB = `const files: Record<string, string> | null = null;\n\nexport default files;\n`;
125
124const buildBinaries = async (targets: Target[]) => {126const 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
apps/cli/src/embedded-web-ui.gen.d.ts+1−1
@@ -1,2 +1,2 @@
1declare const files: Record<string, string>;1declare const files: Record<string, string> | null;
2export default files;2export default files;
apps/cli/src/embedded-web-ui.gen.ts+3−0
@@ -0,0 +1,3 @@
1const files: Record<string, string> | null = null;
2
3export default files;
apps/cli/src/main.ts+1−4
@@ -40,10 +40,7 @@ const DEFAULT_PORT = 8788; · 39 unmodified lines
40const DEFAULT_BASE_URL = `http://localhost:${DEFAULT_PORT}`;40const 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" }`
43const embeddedWebUI: Record<string, string> | null =43import 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// Helpers46// Helpers
bun.lock+1−1
@@ -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 },
tests/release-bootstrap-smoke.test.ts+66−6
@@ -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 });