@akanjs/devkit 2.3.5 → 2.3.6-rc.1
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.
- package/CHANGELOG.md +7 -0
- package/akanApp/akanApp.host.test.ts +211 -0
- package/akanApp/akanApp.host.ts +360 -27
- package/frontendBuild/devChangePlanner.ts +179 -0
- package/frontendBuild/devGeneratedIndexSync.ts +157 -0
- package/frontendBuild/frontendBuild.test.ts +110 -1
- package/frontendBuild/index.ts +2 -0
- package/incrementalBuilder/devWatchBatch.test.ts +59 -0
- package/incrementalBuilder/devWatchBatch.ts +48 -0
- package/incrementalBuilder/incrementalBuilder.host.ts +1 -0
- package/incrementalBuilder/incrementalBuilder.proc.ts +69 -13
- package/incrementalBuilder/index.ts +1 -0
- package/integration/devStability.integration.test.ts +248 -0
- package/integration/devStabilityHarness.ts +485 -0
- package/lint/no-deep-internal-import.grit +2 -2
- package/package.json +2 -2
package/CHANGELOG.md
CHANGED
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
import { describe, expect, test } from "bun:test";
|
|
2
|
+
import type { BuilderMessage, DevBuildStatus, DevChangeAction } from "akanjs/server";
|
|
3
|
+
import {
|
|
4
|
+
backendRestartReasonFromMessage,
|
|
5
|
+
buildStatusReplaySequence,
|
|
6
|
+
createBackendBuildStatus,
|
|
7
|
+
isLegacyBackendFallbackFile,
|
|
8
|
+
mergeBackendRestartReasons,
|
|
9
|
+
shouldMarkBuildPhaseRecovered,
|
|
10
|
+
shouldQueueBuildStatusReplay,
|
|
11
|
+
shouldReplaceLastGoodMessage,
|
|
12
|
+
shouldRestartBackendByDevPlan,
|
|
13
|
+
shouldRestartBuilderByDevPlan,
|
|
14
|
+
shouldRestartDevHostByDevPlan,
|
|
15
|
+
} from "./akanApp.host";
|
|
16
|
+
|
|
17
|
+
const invalidateWithActions = (actions: DevChangeAction[]): Extract<BuilderMessage, { type: "invalidate" }> => ({
|
|
18
|
+
type: "invalidate",
|
|
19
|
+
kinds: ["code"],
|
|
20
|
+
files: ["/repo/libs/shared/common/foo.ts"],
|
|
21
|
+
generation: 1,
|
|
22
|
+
devPlan: {
|
|
23
|
+
generation: 1,
|
|
24
|
+
files: ["/repo/libs/shared/common/foo.ts"],
|
|
25
|
+
generatedFiles: [],
|
|
26
|
+
roles: ["shared"],
|
|
27
|
+
actions,
|
|
28
|
+
reasonByFile: {},
|
|
29
|
+
},
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
describe("shouldRestartBackendByDevPlan", () => {
|
|
33
|
+
test("uses explicit restart-backend action", () => {
|
|
34
|
+
expect(shouldRestartBackendByDevPlan(invalidateWithActions(["restart-backend"]))).toBe(true);
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
test("blocks restart for error generations", () => {
|
|
38
|
+
expect(shouldRestartBackendByDevPlan(invalidateWithActions(["restart-backend", "report-error"]))).toBe(false);
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
test("does not use backend-only restart when builder recycle is required", () => {
|
|
42
|
+
expect(shouldRestartBackendByDevPlan(invalidateWithActions(["restart-backend", "restart-builder"]))).toBe(false);
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
test("falls back when no devPlan is present", () => {
|
|
46
|
+
expect(
|
|
47
|
+
shouldRestartBackendByDevPlan({
|
|
48
|
+
type: "invalidate",
|
|
49
|
+
kinds: ["code"],
|
|
50
|
+
files: ["/repo/apps/akan/page/_index.tsx"],
|
|
51
|
+
}),
|
|
52
|
+
).toBeNull();
|
|
53
|
+
});
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
describe("shouldRestartDevHostByDevPlan", () => {
|
|
57
|
+
test("detects explicit restart-builder actions", () => {
|
|
58
|
+
expect(shouldRestartBuilderByDevPlan(invalidateWithActions(["restart-builder"]))).toBe(true);
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
test("detects explicit restart-dev-host actions", () => {
|
|
62
|
+
expect(shouldRestartDevHostByDevPlan(invalidateWithActions(["restart-dev-host"]))).toBe(true);
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
test("detects legacy config invalidates", () => {
|
|
66
|
+
expect(
|
|
67
|
+
shouldRestartDevHostByDevPlan({
|
|
68
|
+
type: "invalidate",
|
|
69
|
+
kinds: ["config"],
|
|
70
|
+
files: ["/repo/apps/akan/akan.config.ts"],
|
|
71
|
+
}),
|
|
72
|
+
).toBe(true);
|
|
73
|
+
});
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
describe("backend restart reason helpers", () => {
|
|
77
|
+
test("extracts restart roles and generation from devPlan", () => {
|
|
78
|
+
const message = invalidateWithActions(["restart-backend"]);
|
|
79
|
+
const devPlan = message.devPlan;
|
|
80
|
+
if (!devPlan) throw new Error("devPlan expected");
|
|
81
|
+
message.devPlan = {
|
|
82
|
+
...devPlan,
|
|
83
|
+
roles: ["client", "shared", "barrel", "css"],
|
|
84
|
+
generation: 7,
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
expect(backendRestartReasonFromMessage(message)).toEqual({
|
|
88
|
+
generation: 7,
|
|
89
|
+
files: ["/repo/libs/shared/common/foo.ts"],
|
|
90
|
+
roles: ["shared", "barrel"],
|
|
91
|
+
});
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
test("merges debounced restart reasons without losing latest generation", () => {
|
|
95
|
+
expect(
|
|
96
|
+
mergeBackendRestartReasons(
|
|
97
|
+
{ generation: 12, files: ["/repo/b.ts", "/repo/a.ts"], roles: ["server"] },
|
|
98
|
+
{ generation: 13, files: ["/repo/b.ts", "/repo/c.ts"], roles: ["barrel", "shared"] },
|
|
99
|
+
),
|
|
100
|
+
).toEqual({
|
|
101
|
+
generation: 13,
|
|
102
|
+
files: ["/repo/a.ts", "/repo/b.ts", "/repo/c.ts"],
|
|
103
|
+
roles: ["server", "shared", "barrel"],
|
|
104
|
+
});
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
test("keeps newer pending generation when an older reason is merged later", () => {
|
|
108
|
+
expect(
|
|
109
|
+
mergeBackendRestartReasons(
|
|
110
|
+
{ generation: 14, files: ["/repo/newer.ts"], roles: ["shared"] },
|
|
111
|
+
{ generation: 13, files: ["/repo/older.ts"], roles: ["server"] },
|
|
112
|
+
).generation,
|
|
113
|
+
).toBe(14);
|
|
114
|
+
});
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
describe("last-good frontend helpers", () => {
|
|
118
|
+
const pagesUpdated = (
|
|
119
|
+
generation: number | undefined,
|
|
120
|
+
buildId: number,
|
|
121
|
+
): Extract<BuilderMessage, { type: "pages-updated" }> => ({
|
|
122
|
+
type: "pages-updated",
|
|
123
|
+
data: {
|
|
124
|
+
bundlePath: `/tmp/pages-${buildId}.js`,
|
|
125
|
+
buildId,
|
|
126
|
+
generation,
|
|
127
|
+
changedFiles: [],
|
|
128
|
+
},
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
test("accepts newer successful pages payloads", () => {
|
|
132
|
+
expect(shouldReplaceLastGoodMessage(pagesUpdated(10, 1), pagesUpdated(11, 2))).toBe(true);
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
test("rejects stale successful pages payloads", () => {
|
|
136
|
+
expect(shouldReplaceLastGoodMessage(pagesUpdated(11, 2), pagesUpdated(10, 1))).toBe(false);
|
|
137
|
+
});
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
describe("build status helpers", () => {
|
|
141
|
+
const status = (phase: DevBuildStatus["phase"], generation: number, ok: boolean): DevBuildStatus => ({
|
|
142
|
+
generation,
|
|
143
|
+
phase,
|
|
144
|
+
ok,
|
|
145
|
+
files: [],
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
test("tracks recovery by phase without unrelated phases masking failures", () => {
|
|
149
|
+
const previousByPhase = new Map<DevBuildStatus["phase"], DevBuildStatus>([
|
|
150
|
+
["pages", status("pages", 10, false)],
|
|
151
|
+
["css", status("css", 11, true)],
|
|
152
|
+
]);
|
|
153
|
+
|
|
154
|
+
expect(shouldMarkBuildPhaseRecovered(previousByPhase, status("css", 12, true))).toBe(false);
|
|
155
|
+
expect(shouldMarkBuildPhaseRecovered(previousByPhase, status("pages", 12, true))).toBe(true);
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
test("creates backend build-status payloads for lifecycle test hooks", () => {
|
|
159
|
+
expect(
|
|
160
|
+
createBackendBuildStatus({
|
|
161
|
+
generation: 14,
|
|
162
|
+
ok: false,
|
|
163
|
+
files: ["/repo/libs/shared/common/foo.ts"],
|
|
164
|
+
message: "Backend exited unexpectedly",
|
|
165
|
+
}),
|
|
166
|
+
).toEqual({
|
|
167
|
+
generation: 14,
|
|
168
|
+
phase: "backend",
|
|
169
|
+
ok: false,
|
|
170
|
+
files: ["/repo/libs/shared/common/foo.ts"],
|
|
171
|
+
message: "Backend exited unexpectedly",
|
|
172
|
+
});
|
|
173
|
+
});
|
|
174
|
+
|
|
175
|
+
test("marks backend phase recovered independently", () => {
|
|
176
|
+
const previousByPhase = new Map<DevBuildStatus["phase"], DevBuildStatus>([
|
|
177
|
+
["backend", createBackendBuildStatus({ generation: 15, ok: false, message: "Backend exited" })],
|
|
178
|
+
["pages", status("pages", 16, false)],
|
|
179
|
+
]);
|
|
180
|
+
|
|
181
|
+
expect(shouldMarkBuildPhaseRecovered(previousByPhase, createBackendBuildStatus({ generation: 15, ok: true }))).toBe(
|
|
182
|
+
true,
|
|
183
|
+
);
|
|
184
|
+
});
|
|
185
|
+
|
|
186
|
+
test("keeps backend failure and recovery statuses ordered for backend replay", () => {
|
|
187
|
+
const failed = createBackendBuildStatus({ generation: 15, ok: false, message: "Backend exited" });
|
|
188
|
+
const recovered = createBackendBuildStatus({ generation: 15, ok: true, message: "Backend ready" });
|
|
189
|
+
const latestByPhase = new Map<DevBuildStatus["phase"], DevBuildStatus>([["backend", recovered]]);
|
|
190
|
+
|
|
191
|
+
expect(shouldQueueBuildStatusReplay(false, 0)).toBe(true);
|
|
192
|
+
expect(shouldQueueBuildStatusReplay(true, 1)).toBe(true);
|
|
193
|
+
expect(shouldQueueBuildStatusReplay(true, 0)).toBe(false);
|
|
194
|
+
expect(buildStatusReplaySequence([failed, recovered], latestByPhase).slice(0, 2)).toEqual([failed, recovered]);
|
|
195
|
+
});
|
|
196
|
+
});
|
|
197
|
+
|
|
198
|
+
describe("legacy backend graph fallback", () => {
|
|
199
|
+
const root = "/repo";
|
|
200
|
+
|
|
201
|
+
test("treats server and shared path roles as backend restart candidates", () => {
|
|
202
|
+
expect(isLegacyBackendFallbackFile(`${root}/libs/shared/srvkit/foo.ts`, root)).toBe(true);
|
|
203
|
+
expect(isLegacyBackendFallbackFile(`${root}/libs/shared/common/foo.ts`, root)).toBe(true);
|
|
204
|
+
expect(isLegacyBackendFallbackFile(`${root}/libs/shared/lib/admin/admin.signal.ts`, root)).toBe(true);
|
|
205
|
+
});
|
|
206
|
+
|
|
207
|
+
test("does not restart backend for client-only path roles", () => {
|
|
208
|
+
expect(isLegacyBackendFallbackFile(`${root}/libs/shared/ui/Foo.tsx`, root)).toBe(false);
|
|
209
|
+
expect(isLegacyBackendFallbackFile(`${root}/apps/akan/page/_index.tsx`, root)).toBe(false);
|
|
210
|
+
});
|
|
211
|
+
});
|