@akanjs/devkit 3.0.0-beta.24 → 3.0.0-beta.25

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 (57) hide show
  1. package/DEV_RUNTIME_KNOBS.md +37 -22
  2. package/akanApp/akanApp.host.ts +34 -1
  3. package/akanApp/devBootLatch.test.ts +39 -0
  4. package/akanApp/devBootLatch.ts +39 -0
  5. package/akanApp/devHostPolicy.test.ts +25 -0
  6. package/akanApp/devHostPolicy.ts +27 -2
  7. package/cloud/globalConfig.ts +3 -17
  8. package/csrE2e/csrE2eHarness.fixture.ts +66 -11
  9. package/csrE2e/csrPatchFirst.e2e.test.ts +87 -0
  10. package/csrE2e/devHmrLatency.e2e.test.ts +391 -0
  11. package/csrE2e/ssrRegistry.e2e.test.ts +644 -0
  12. package/fileSys.ts +16 -1
  13. package/frontendBuild/buildRouteClient.test.ts +61 -0
  14. package/frontendBuild/bundleDefine.test.ts +12 -0
  15. package/frontendBuild/bundleDefine.ts +5 -1
  16. package/frontendBuild/clientEntryDiscovery.test.ts +37 -0
  17. package/frontendBuild/clientEntryDiscovery.ts +10 -5
  18. package/frontendBuild/csrArtifactBuilder.ts +3 -6
  19. package/frontendBuild/csrDevArtifactWriter.test.ts +143 -0
  20. package/frontendBuild/csrDevArtifactWriter.ts +158 -24
  21. package/frontendBuild/csrDevBundler.ts +44 -205
  22. package/frontendBuild/csrDevModuleCompiler.ts +51 -9
  23. package/frontendBuild/csrDevPatcher.test.ts +340 -0
  24. package/frontendBuild/csrDevPatcher.ts +374 -0
  25. package/frontendBuild/csrDevPaths.ts +60 -0
  26. package/frontendBuild/csrDevResolver.test.ts +165 -0
  27. package/frontendBuild/csrDevResolver.ts +70 -18
  28. package/frontendBuild/csrDevTypes.ts +14 -1
  29. package/frontendBuild/csrEntryFiles.ts +24 -0
  30. package/frontendBuild/cssCompiler.ts +2 -4
  31. package/frontendBuild/devRegistryBundler.ts +177 -0
  32. package/frontendBuild/hmrWatcher.test.ts +51 -0
  33. package/frontendBuild/hmrWatcher.ts +17 -3
  34. package/frontendBuild/index.ts +3 -0
  35. package/frontendBuild/pagesBundleBuilder.ts +24 -1
  36. package/frontendBuild/pagesEntrySourceGenerator.ts +9 -2
  37. package/frontendBuild/registryMetadataFingerprint.test.ts +63 -0
  38. package/frontendBuild/registryMetadataFingerprint.ts +28 -0
  39. package/frontendBuild/routeClientBuilder.ts +79 -18
  40. package/frontendBuild/serverGraphFile.test.ts +77 -0
  41. package/frontendBuild/serverGraphFile.ts +93 -0
  42. package/frontendBuild/ssrDevBundler.ts +91 -0
  43. package/incrementalBuilder/buildBatch.proc.ts +94 -16
  44. package/incrementalBuilder/buildBatchProtocol.ts +6 -3
  45. package/incrementalBuilder/buildBatchRunner.test.ts +31 -0
  46. package/incrementalBuilder/buildBatchRunner.ts +17 -8
  47. package/incrementalBuilder/builderWorkQueue.test.ts +94 -0
  48. package/incrementalBuilder/builderWorkQueue.ts +126 -0
  49. package/incrementalBuilder/incrementalBuilder.host.test.ts +1 -1
  50. package/incrementalBuilder/incrementalBuilder.host.ts +32 -3
  51. package/incrementalBuilder/incrementalBuilder.proc.test.ts +129 -2
  52. package/incrementalBuilder/incrementalBuilder.proc.ts +518 -52
  53. package/integration/devStability.integration.test.ts +18 -7
  54. package/package.json +2 -2
  55. package/transforms/rscUseClientTransform.ts +7 -0
  56. package/transforms/transforms.test.ts +13 -0
  57. package/transforms/useClientBundlePlugin.ts +4 -1
@@ -16,7 +16,7 @@ const until = async (proc: Bun.Subprocess, what: string, condition: () => boolea
16
16
  };
17
17
 
18
18
  describe("incremental builder process", () => {
19
- test("says why and where a route's client bundle failed, in its answer, its build status and its log", async () => {
19
+ test("says why and where a route's client bundle failed, in its answer, its build status and its log, at the builder's generation", async () => {
20
20
  const { root } = track(await createTempApp("demo"));
21
21
  const appDir = path.join(root, "apps/demo");
22
22
  // The batch runner looks under the workspace root first: a stand-in for the boot build, which needs a whole app.
@@ -44,6 +44,7 @@ process.send({ type: "build-batch-result", data: { generation, errors: {}, artif
44
44
  AKAN_PUBLIC_SERVE_DOMAIN: "localhost",
45
45
  AKAN_PUBLIC_ENV: "local",
46
46
  AKAN_WATCH: "0",
47
+ AKAN_BUILDER_INITIAL_GENERATION: "5",
47
48
  },
48
49
  stdio: ["ignore", "ignore", "pipe"],
49
50
  serialization: "advanced",
@@ -64,6 +65,8 @@ process.send({ type: "build-batch-result", data: { generation, errors: {}, artif
64
65
  await until(proc, "build-route-res", () => messages.some((message) => message.type === "build-route-res"));
65
66
  } finally {
66
67
  proc.kill();
68
+ //? Windows refuses to remove a directory a live process has as its cwd.
69
+ await proc.exited;
67
70
  }
68
71
  const log = await new Response(proc.stderr).text();
69
72
 
@@ -74,8 +77,132 @@ process.send({ type: "build-batch-result", data: { generation, errors: {}, artif
74
77
  error: expect.stringContaining(reason),
75
78
  });
76
79
  expect(messages.find((message) => message.type === "build-status")).toMatchObject({
77
- data: { generation: 3, phase: "route", ok: false, message: expect.stringContaining(reason) },
80
+ data: { generation: 6, phase: "route", ok: false, message: expect.stringContaining(reason) },
78
81
  });
79
82
  expect(log).toContain(reason);
80
83
  }, 30_000);
84
+
85
+ test("a killed boot build is tried again after the crash window, before boot-armed; a route build's check waits likewise", async () => {
86
+ // Inside the checkout so the route's client bundle resolves the framework from its source.
87
+ const { root } = track(await createTempApp("demo", path.join(import.meta.dir, "..", "local")));
88
+ const appDir = path.join(root, "apps/demo");
89
+ const runs = path.join(root, "ssr-runs.log");
90
+ // The first two registry builds are killed (the boot build and its retry), the third succeeds.
91
+ await writeText(
92
+ path.join(root, "pkgs/@akanjs/devkit/incrementalBuilder/buildBatch.proc.ts"),
93
+ `import fs from "node:fs";
94
+ const { generation, needs } = JSON.parse(process.argv[2]);
95
+ if (needs.includes("ssr")) {
96
+ fs.appendFileSync(${JSON.stringify(runs)}, Date.now() + "\\n");
97
+ if (fs.readFileSync(${JSON.stringify(runs)}, "utf8").trim().split("\\n").length <= 2) process.exit(1);
98
+ }
99
+ process.send({ type: "build-batch-result", data: { generation, errors: {}, artifact: {}, optimizedFonts: { css: "", files: [] } } });
100
+ `,
101
+ );
102
+ const page = path.join(appDir, "page/_index.tsx");
103
+ await writeText(page, 'import { Card } from "../ui/Card";\nexport default Card;\n');
104
+ await writeText(path.join(appDir, "ui/Card.tsx"), '"use client";\nexport const Card = () => null;\n');
105
+
106
+ const messages: BuilderMessage[] = [];
107
+ const proc = Bun.spawn(["bun", path.join(import.meta.dir, "incrementalBuilder.proc.ts")], {
108
+ cwd: appDir,
109
+ env: {
110
+ ...process.env,
111
+ AKAN_WORKSPACE_ROOT: root,
112
+ AKAN_PUBLIC_APP_NAME: "demo",
113
+ AKAN_PUBLIC_REPO_NAME: "repo",
114
+ AKAN_PUBLIC_SERVE_DOMAIN: "localhost",
115
+ AKAN_PUBLIC_ENV: "local",
116
+ AKAN_WATCH: "0",
117
+ },
118
+ stdio: ["ignore", "ignore", "ignore"],
119
+ serialization: "advanced",
120
+ ipc: (message: BuilderMessage) => {
121
+ messages.push(message);
122
+ },
123
+ });
124
+ const ssrRuns = async () =>
125
+ (await Bun.file(runs).exists()) ? (await Bun.file(runs).text()).trim().split("\n").map(Number) : [];
126
+ try {
127
+ await until(proc, "boot-armed", () => messages.some((message) => message.type === "boot-armed"));
128
+ const [first = 0, second = 0] = await ssrRuns();
129
+ expect(await ssrRuns()).toHaveLength(2);
130
+ expect(second - first).toBeGreaterThanOrEqual(9_500);
131
+ proc.send({
132
+ type: "build-route",
133
+ id: 1,
134
+ routeId: "page:/",
135
+ seeds: [page],
136
+ knownEntries: [],
137
+ generation: 3,
138
+ } satisfies BuilderReq);
139
+ await until(proc, "build-route-res", () => messages.some((message) => message.type === "build-route-res"));
140
+ expect(messages.find((message) => message.type === "build-route-res")).toMatchObject({ ok: true });
141
+ const deadline = Date.now() + 15_000;
142
+ while ((await ssrRuns()).length < 3 && Date.now() < deadline) await wait(100);
143
+ const [, retried = 0, third = 0] = await ssrRuns();
144
+ expect(third - retried).toBeGreaterThanOrEqual(9_500);
145
+ } finally {
146
+ proc.kill();
147
+ await proc.exited;
148
+ }
149
+ }, 60_000);
150
+
151
+ test("a route build's registry check that has nothing to add reports no ssr status, so it cannot hide a failure", async () => {
152
+ const { root } = track(await createTempApp("demo", path.join(import.meta.dir, "..", "local")));
153
+ const appDir = path.join(root, "apps/demo");
154
+ // The registry's own build is the real worker's; the rest is stood in for, as a boot build needs a whole app.
155
+ await writeText(
156
+ path.join(root, "pkgs/@akanjs/devkit/incrementalBuilder/buildBatch.proc.ts"),
157
+ `const { generation, needs } = JSON.parse(process.argv[2]);
158
+ if (needs.includes("ssr")) await import(${JSON.stringify(path.join(import.meta.dir, "buildBatch.proc.ts"))});
159
+ else process.send({ type: "build-batch-result", data: { generation, errors: {}, artifact: {}, optimizedFonts: { css: "", files: [] } } });
160
+ `,
161
+ );
162
+ await writeText(path.join(appDir, "env/env.client.ts"), "export const env = {} as const;\n");
163
+ const page = path.join(appDir, "page/_index.tsx");
164
+ await writeText(page, 'import { Card } from "../ui/Card";\nexport default Card;\n');
165
+ await writeText(path.join(appDir, "ui/Card.tsx"), '"use client";\nexport const Card = () => null;\n');
166
+
167
+ const messages: BuilderMessage[] = [];
168
+ const proc = Bun.spawn(["bun", path.join(import.meta.dir, "incrementalBuilder.proc.ts")], {
169
+ cwd: appDir,
170
+ env: {
171
+ ...process.env,
172
+ AKAN_WORKSPACE_ROOT: root,
173
+ AKAN_PUBLIC_APP_NAME: "demo",
174
+ AKAN_PUBLIC_REPO_NAME: "repo",
175
+ AKAN_PUBLIC_SERVE_DOMAIN: "localhost",
176
+ AKAN_PUBLIC_ENV: "local",
177
+ AKAN_WATCH: "0",
178
+ },
179
+ stdio: ["ignore", "ignore", "ignore"],
180
+ serialization: "advanced",
181
+ ipc: (message: BuilderMessage) => {
182
+ messages.push(message);
183
+ },
184
+ });
185
+ const ssrStatuses = () =>
186
+ messages.filter((message) => message.type === "build-status" && message.data.phase === "ssr");
187
+ try {
188
+ await until(proc, "boot-armed", () => messages.some((message) => message.type === "boot-armed"));
189
+ expect(messages.some((message) => message.type === "ssr-updated")).toBe(true);
190
+ const before = ssrStatuses().length;
191
+ proc.send({
192
+ type: "build-route",
193
+ id: 1,
194
+ routeId: "page:/",
195
+ seeds: [page],
196
+ knownEntries: [],
197
+ generation: 3,
198
+ } satisfies BuilderReq);
199
+ await until(proc, "build-route-res", () => messages.some((message) => message.type === "build-route-res"));
200
+ expect(messages.find((message) => message.type === "build-route-res")).toMatchObject({ ok: true });
201
+ await wait(200);
202
+ expect(ssrStatuses().length).toBe(before);
203
+ } finally {
204
+ proc.kill();
205
+ await proc.exited;
206
+ }
207
+ }, 60_000);
81
208
  });