@cosmicdrift/kumiko-dev-server 0.329.0 → 0.330.2
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/README.md +17 -0
- package/bin/kumiko-build.ts +3 -2
- package/bin/kumiko-dev.ts +1 -1
- package/bin/kumiko-init-deploy.ts +1 -1
- package/bin/kumiko-schema-check.ts +4 -1
- package/dist/build-server-bundle.d.ts +31 -0
- package/dist/build-server-bundle.js +282 -0
- package/dist/build.d.ts +2 -0
- package/dist/build.js +5 -0
- package/dist/cli.d.ts +5 -0
- package/dist/cli.js +9 -0
- package/dist/codegen/index.d.ts +4 -0
- package/dist/codegen/index.js +4 -0
- package/dist/codegen/render.d.ts +55 -0
- package/dist/codegen/render.js +273 -0
- package/dist/codegen/run-codegen.d.ts +20 -0
- package/dist/codegen/run-codegen.js +219 -0
- package/dist/codegen/scan-events.d.ts +76 -0
- package/dist/codegen/scan-events.js +447 -0
- package/dist/codegen/watch.d.ts +33 -0
- package/dist/codegen/watch.js +112 -0
- package/dist/compose-stacks.d.ts +60 -0
- package/dist/compose-stacks.js +127 -0
- package/dist/crash-tracker.d.ts +17 -0
- package/dist/crash-tracker.js +37 -0
- package/dist/create-kumiko-server.d.ts +195 -0
- package/dist/create-kumiko-server.js +1040 -0
- package/dist/env-schema.d.ts +15 -0
- package/dist/env-schema.js +60 -0
- package/dist/few-shot-corpus.d.ts +72 -0
- package/dist/few-shot-corpus.js +262 -0
- package/dist/framework-version.d.ts +1 -0
- package/dist/framework-version.js +31 -0
- package/dist/index.d.ts +22 -0
- package/dist/index.js +33 -0
- package/dist/init-deploy-cli.d.ts +10 -0
- package/dist/init-deploy-cli.js +111 -0
- package/dist/kebab.d.ts +1 -0
- package/{src/kebab.ts → dist/kebab.js} +2 -3
- package/dist/run-dev-app.d.ts +174 -0
- package/dist/run-dev-app.js +403 -0
- package/dist/scaffold-app-feature.d.ts +16 -0
- package/dist/scaffold-app-feature.js +240 -0
- package/dist/scaffold-app.d.ts +45 -0
- package/dist/scaffold-app.js +852 -0
- package/dist/scaffold-demo-tasks.d.ts +8 -0
- package/{src/scaffold-demo-tasks.ts → dist/scaffold-demo-tasks.js} +69 -98
- package/dist/scaffold-deploy.d.ts +92 -0
- package/dist/scaffold-deploy.js +254 -0
- package/dist/scaffold-feature.d.ts +26 -0
- package/dist/scaffold-feature.js +163 -0
- package/dist/schema-apply.d.ts +11 -0
- package/dist/schema-apply.js +95 -0
- package/dist/schema-check-core.d.ts +2 -0
- package/{src/schema-check-core.ts → dist/schema-check-core.js} +8 -10
- package/dist/schema-tables-auth-mode.d.ts +2 -0
- package/{src/schema-tables-auth-mode.ts → dist/schema-tables-auth-mode.js} +2 -10
- package/dist/schema-tables-minimal.d.ts +2 -0
- package/{src/schema-tables-minimal.ts → dist/schema-tables-minimal.js} +2 -11
- package/dist/setup-test-stack-from-features.d.ts +11 -0
- package/dist/setup-test-stack-from-features.js +42 -0
- package/dist/welcome-banner.d.ts +15 -0
- package/dist/welcome-banner.js +27 -0
- package/package.json +71 -24
- package/src/changes.json +6 -0
- package/src/__tests__/build-prod-bundle.integration.test.ts +0 -282
- package/src/__tests__/build-server-bundle.test.ts +0 -228
- package/src/__tests__/classify-change.test.ts +0 -87
- package/src/__tests__/compose-stacks.test.ts +0 -273
- package/src/__tests__/config-resolver-default.integration.test.ts +0 -165
- package/src/__tests__/crash-tracker.test.ts +0 -89
- package/src/__tests__/create-kumiko-server-errors.test.ts +0 -54
- package/src/__tests__/create-kumiko-server-options.test.ts +0 -33
- package/src/__tests__/create-kumiko-server.integration.test.ts +0 -1007
- package/src/__tests__/discover-format.test.ts +0 -32
- package/src/__tests__/env-schema.integration.test.ts +0 -248
- package/src/__tests__/env-schema.test.ts +0 -132
- package/src/__tests__/few-shot-corpus.test.ts +0 -316
- package/src/__tests__/framework-version.test.ts +0 -45
- package/src/__tests__/init-deploy-cli.test.ts +0 -113
- package/src/__tests__/kumiko-schema-check.test.ts +0 -56
- package/src/__tests__/merge-extra-context.test.ts +0 -67
- package/src/__tests__/resolve-stylesheet.test.ts +0 -132
- package/src/__tests__/run-dev-app-boot-validation.test.ts +0 -38
- package/src/__tests__/run-dev-app.integration.test.ts +0 -290
- package/src/__tests__/runtime-classification.test.ts +0 -26
- package/src/__tests__/saas-identity-wire.integration.test.ts +0 -427
- package/src/__tests__/scaffold-app-feature.test.ts +0 -216
- package/src/__tests__/scaffold-app.test.ts +0 -630
- package/src/__tests__/scaffold-demo-tasks.test.ts +0 -47
- package/src/__tests__/scaffold-deploy.test.ts +0 -464
- package/src/__tests__/scaffold-dev-cycle.integration.test.ts +0 -109
- package/src/__tests__/scaffold-feature.test.ts +0 -143
- package/src/__tests__/schema-apply.integration.test.ts +0 -191
- package/src/__tests__/setup-test-stack-from-features.integration.test.ts +0 -30
- package/src/__tests__/setup-test-stack-job-run-logger.integration.test.ts +0 -72
- package/src/__tests__/walkthrough.integration.test.ts +0 -140
- package/src/__tests__/welcome-banner.test.ts +0 -42
- package/src/build-server-bundle.ts +0 -346
- package/src/build.ts +0 -28
- package/src/codegen/__tests__/render-codegen.test.ts +0 -100
- package/src/codegen/__tests__/run-codegen.test.ts +0 -665
- package/src/codegen/__tests__/strict-mode-diagnostics.test.ts +0 -347
- package/src/codegen/__tests__/watch.test.ts +0 -222
- package/src/codegen/index.ts +0 -19
- package/src/codegen/render.ts +0 -305
- package/src/codegen/run-codegen.ts +0 -278
- package/src/codegen/scan-events.ts +0 -565
- package/src/codegen/watch.ts +0 -140
- package/src/compose-stacks.ts +0 -193
- package/src/crash-tracker.ts +0 -56
- package/src/create-kumiko-server.ts +0 -1397
- package/src/env-schema.ts +0 -72
- package/src/few-shot-corpus.ts +0 -370
- package/src/framework-version.ts +0 -39
- package/src/index.ts +0 -105
- package/src/init-deploy-cli.ts +0 -148
- package/src/run-dev-app.ts +0 -694
- package/src/scaffold-app-feature.ts +0 -283
- package/src/scaffold-app.ts +0 -1033
- package/src/scaffold-deploy.ts +0 -393
- package/src/scaffold-feature.ts +0 -226
- package/src/schema-apply.ts +0 -123
- package/src/setup-test-stack-from-features.ts +0 -80
- package/src/welcome-banner.ts +0 -43
|
@@ -1,282 +0,0 @@
|
|
|
1
|
-
// Integration-Tests für buildProdBundle — End-to-End-Pfad gegen das
|
|
2
|
-
// echte Filesystem.
|
|
3
|
-
//
|
|
4
|
-
// Zwei Szenarien:
|
|
5
|
-
//
|
|
6
|
-
// 1. Vanilla-Pipeline (kein client.tsx, nur public/ + html-template):
|
|
7
|
-
// läuft unter Node-Vitest direkt — kein Bun.build, keine
|
|
8
|
-
// Subprocess-Akrobatik. Beweist Discovery + html-rendering +
|
|
9
|
-
// public-copy + manifest-format.
|
|
10
|
-
//
|
|
11
|
-
// 2. Volle Pipeline (client.tsx + Bun.build + Hash + Manifest):
|
|
12
|
-
// braucht Bun. Spawnen kumiko-build als subprocess via PATH.
|
|
13
|
-
// Skipped wenn `bun` nicht erreichbar — selten, aber sauber.
|
|
14
|
-
|
|
15
|
-
import { afterEach, beforeEach, describe, expect, test } from "bun:test";
|
|
16
|
-
import { execFileSync } from "node:child_process";
|
|
17
|
-
import { existsSync } from "node:fs";
|
|
18
|
-
import { mkdir, mkdtemp, readFile, rm, writeFile } from "node:fs/promises";
|
|
19
|
-
import { tmpdir } from "node:os";
|
|
20
|
-
import { dirname, join, resolve } from "node:path";
|
|
21
|
-
import { fileURLToPath } from "node:url";
|
|
22
|
-
import { buildProdBundle } from "@cosmicdrift/kumiko-server-runtime/build-prod-bundle";
|
|
23
|
-
|
|
24
|
-
const __filename = fileURLToPath(import.meta.url);
|
|
25
|
-
const __dirname = dirname(__filename);
|
|
26
|
-
const KUMIKO_BUILD_BIN = resolve(__dirname, "../../bin/kumiko-build.ts");
|
|
27
|
-
|
|
28
|
-
// Each of these tests spawns kumiko-build, which runs the real Bun.build +
|
|
29
|
-
// tailwind pipeline; bun's 5s default kills it mid-build on a loaded CI runner.
|
|
30
|
-
const FULL_PIPELINE_TIMEOUT_MS = 60_000;
|
|
31
|
-
|
|
32
|
-
function bunAvailable(): boolean {
|
|
33
|
-
try {
|
|
34
|
-
execFileSync("bun", ["--version"], { stdio: "ignore" });
|
|
35
|
-
return true;
|
|
36
|
-
} catch {
|
|
37
|
-
return false;
|
|
38
|
-
}
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
describe("buildProdBundle (vanilla pipeline)", () => {
|
|
42
|
-
let tmp = "";
|
|
43
|
-
|
|
44
|
-
beforeEach(async () => {
|
|
45
|
-
tmp = await mkdtemp(join(tmpdir(), "kumiko-build-it-"));
|
|
46
|
-
});
|
|
47
|
-
|
|
48
|
-
afterEach(async () => {
|
|
49
|
-
await rm(tmp, { recursive: true, force: true });
|
|
50
|
-
});
|
|
51
|
-
|
|
52
|
-
test("kopiert public/ 1:1 nach dist/", async () => {
|
|
53
|
-
await mkdir(join(tmp, "public"), { recursive: true });
|
|
54
|
-
await writeFile(join(tmp, "public/favicon.txt"), "fav-content");
|
|
55
|
-
await writeFile(join(tmp, "public/robots.txt"), "User-agent: *\nDisallow:\n");
|
|
56
|
-
await writeFile(
|
|
57
|
-
join(tmp, "public/index.html"),
|
|
58
|
-
`<!doctype html><html><body>Hello</body></html>`,
|
|
59
|
-
);
|
|
60
|
-
|
|
61
|
-
const result = await buildProdBundle({ cwd: tmp });
|
|
62
|
-
|
|
63
|
-
expect(result.manifest).toEqual({});
|
|
64
|
-
expect(await readFile(join(tmp, "dist/favicon.txt"), "utf8")).toBe("fav-content");
|
|
65
|
-
expect(await readFile(join(tmp, "dist/robots.txt"), "utf8")).toContain("User-agent");
|
|
66
|
-
});
|
|
67
|
-
|
|
68
|
-
test("rendert public/index.html als Template (Inhalt erhalten)", async () => {
|
|
69
|
-
await mkdir(join(tmp, "public"), { recursive: true });
|
|
70
|
-
await writeFile(
|
|
71
|
-
join(tmp, "public/index.html"),
|
|
72
|
-
`<!doctype html><html><head><title>Custom Title</title></head><body>Custom Body</body></html>`,
|
|
73
|
-
);
|
|
74
|
-
|
|
75
|
-
await buildProdBundle({ cwd: tmp });
|
|
76
|
-
|
|
77
|
-
const html = await readFile(join(tmp, "dist/index.html"), "utf8");
|
|
78
|
-
expect(html).toContain("Custom Title");
|
|
79
|
-
expect(html).toContain("Custom Body");
|
|
80
|
-
});
|
|
81
|
-
|
|
82
|
-
test("schreibt manifest.json auch wenn leer", async () => {
|
|
83
|
-
await mkdir(join(tmp, "public"), { recursive: true });
|
|
84
|
-
await writeFile(join(tmp, "public/index.html"), `<html></html>`);
|
|
85
|
-
|
|
86
|
-
await buildProdBundle({ cwd: tmp });
|
|
87
|
-
|
|
88
|
-
const manifest = JSON.parse(await readFile(join(tmp, "dist/manifest.json"), "utf8"));
|
|
89
|
-
expect(manifest).toEqual({});
|
|
90
|
-
});
|
|
91
|
-
|
|
92
|
-
test("public/index.html landet NICHT als rohes File in dist/ (wird als Template behandelt)", async () => {
|
|
93
|
-
await mkdir(join(tmp, "public"), { recursive: true });
|
|
94
|
-
const original = `<!doctype html><html><body>SOURCE</body></html>`;
|
|
95
|
-
await writeFile(join(tmp, "public/index.html"), original);
|
|
96
|
-
await writeFile(join(tmp, "public/other.txt"), "ABC");
|
|
97
|
-
|
|
98
|
-
await buildProdBundle({ cwd: tmp });
|
|
99
|
-
|
|
100
|
-
// dist/index.html existiert (gerendert), public/other.txt wurde kopiert
|
|
101
|
-
expect(existsSync(join(tmp, "dist/index.html"))).toBe(true);
|
|
102
|
-
expect(existsSync(join(tmp, "dist/other.txt"))).toBe(true);
|
|
103
|
-
// Inhalt: was im Template stand, kommt im Output an (kein Doppel-Pfad
|
|
104
|
-
// wo public/index.html und dist/index.html sich gegenseitig überschreiben).
|
|
105
|
-
expect(await readFile(join(tmp, "dist/index.html"), "utf8")).toContain("SOURCE");
|
|
106
|
-
});
|
|
107
|
-
|
|
108
|
-
test("clean wipe: vorhandenes dist/-Junk wird entfernt", async () => {
|
|
109
|
-
await mkdir(join(tmp, "public"), { recursive: true });
|
|
110
|
-
await writeFile(join(tmp, "public/index.html"), `<html></html>`);
|
|
111
|
-
// Junk-File aus altem Build
|
|
112
|
-
await mkdir(join(tmp, "dist/old"), { recursive: true });
|
|
113
|
-
await writeFile(join(tmp, "dist/old/stale.js"), "old content");
|
|
114
|
-
|
|
115
|
-
await buildProdBundle({ cwd: tmp });
|
|
116
|
-
|
|
117
|
-
expect(existsSync(join(tmp, "dist/old/stale.js"))).toBe(false);
|
|
118
|
-
});
|
|
119
|
-
|
|
120
|
-
test("wirft mit klarer Message wenn weder public/ noch index.html noch client da", async () => {
|
|
121
|
-
// tmp ist leer
|
|
122
|
-
await expect(buildProdBundle({ cwd: tmp })).rejects.toThrow(/nothing to build/);
|
|
123
|
-
});
|
|
124
|
-
|
|
125
|
-
test("wirft mit Anweisung wenn HTML-Template ohne /client.js Placeholder vorliegt", async () => {
|
|
126
|
-
// Simuliert: User hat bereits manifest-entry (faken über option, in
|
|
127
|
-
// realer Pipeline kommt's aus Bun.build). Wir testen renderHtml /
|
|
128
|
-
// injectAssetTags-Verhalten via lower-level Pfad: HTML ohne Tag,
|
|
129
|
-
// public-folder simuliert dass Build was zu tun hat. Erwartete
|
|
130
|
-
// Error-Message zitiert das exakte Snippet.
|
|
131
|
-
//
|
|
132
|
-
// Dieser Pfad wird im realen Build durch buildClientBundle ausgelöst
|
|
133
|
-
// und im Vanilla-Test können wir ihn nicht direkt triggern (kein
|
|
134
|
-
// Bun.build). Der CLI-Subprocess-Test deckt das ab.
|
|
135
|
-
await mkdir(join(tmp, "public"), { recursive: true });
|
|
136
|
-
await writeFile(
|
|
137
|
-
join(tmp, "public/index.html"),
|
|
138
|
-
`<!doctype html><html><body>no script tag here</body></html>`,
|
|
139
|
-
);
|
|
140
|
-
// Ohne client.tsx läuft Bun.build nicht, manifest bleibt {} →
|
|
141
|
-
// injectAssetTags wirft NICHT. Das ist gewollt: wenn nichts zu
|
|
142
|
-
// injizieren da ist, ist's auch kein Fehler.
|
|
143
|
-
const result = await buildProdBundle({ cwd: tmp });
|
|
144
|
-
expect(result.manifest).toEqual({});
|
|
145
|
-
});
|
|
146
|
-
});
|
|
147
|
-
|
|
148
|
-
// Voller Pipeline-Test gegen das echte CLI-bin via subprocess. Bringt
|
|
149
|
-
// echtes Bun.build + Tailwind ins Spiel — der Pfad den die anderen
|
|
150
|
-
// Tests bewusst nicht laufen können (Vitest unter Node).
|
|
151
|
-
describe.skipIf(!bunAvailable())("kumiko-build CLI (full pipeline with bun)", () => {
|
|
152
|
-
let tmp = "";
|
|
153
|
-
|
|
154
|
-
beforeEach(async () => {
|
|
155
|
-
tmp = await mkdtemp(join(tmpdir(), "kumiko-build-cli-"));
|
|
156
|
-
});
|
|
157
|
-
|
|
158
|
-
afterEach(async () => {
|
|
159
|
-
await rm(tmp, { recursive: true, force: true });
|
|
160
|
-
});
|
|
161
|
-
|
|
162
|
-
test(
|
|
163
|
-
"client.ts → hashed bundle, manifest, html mit injected script-tag",
|
|
164
|
-
async () => {
|
|
165
|
-
// Minimal client ohne externe Deps — Bun.build resolvt nichts.
|
|
166
|
-
await mkdir(join(tmp, "src"), { recursive: true });
|
|
167
|
-
await writeFile(
|
|
168
|
-
join(tmp, "src/client.ts"),
|
|
169
|
-
`const root = document.getElementById("root"); if (root) root.textContent = "hi";`,
|
|
170
|
-
);
|
|
171
|
-
await mkdir(join(tmp, "public"), { recursive: true });
|
|
172
|
-
await writeFile(
|
|
173
|
-
join(tmp, "public/index.html"),
|
|
174
|
-
`<!doctype html><html><head></head><body><div id="root"></div><script type="module" src="/client.js"></script></body></html>`,
|
|
175
|
-
);
|
|
176
|
-
// package.json mit stylesheet:false äquivalent — wir setzen kein
|
|
177
|
-
// src/styles.css und sind außerhalb des monorepos, sodass der
|
|
178
|
-
// renderer-web-Fallback fehlschlägt und gracefully undefined liefert.
|
|
179
|
-
await writeFile(join(tmp, "package.json"), `{"name":"build-it-fixture","private":true}`);
|
|
180
|
-
|
|
181
|
-
execFileSync("bun", [KUMIKO_BUILD_BIN, tmp], { stdio: "pipe" });
|
|
182
|
-
|
|
183
|
-
const manifest = JSON.parse(
|
|
184
|
-
await readFile(join(tmp, "dist/manifest.json"), "utf8"),
|
|
185
|
-
) as Record<string, string>;
|
|
186
|
-
expect(manifest["client.js"]).toMatch(/^\/assets\/client-[a-z0-9]+\.js$/);
|
|
187
|
-
|
|
188
|
-
const html = await readFile(join(tmp, "dist/index.html"), "utf8");
|
|
189
|
-
expect(html).toContain(`src="${manifest["client.js"]}"`);
|
|
190
|
-
expect(html).toContain('id="root"');
|
|
191
|
-
|
|
192
|
-
// Hashed asset existiert im dist/
|
|
193
|
-
const assetPath = join(tmp, "dist", manifest["client.js"] ?? "");
|
|
194
|
-
expect(existsSync(assetPath)).toBe(true);
|
|
195
|
-
|
|
196
|
-
// Bundle enthält den User-Code (minified — also Identifier-Namen
|
|
197
|
-
// mangled, aber der String-Literal "hi" überlebt).
|
|
198
|
-
expect(await readFile(assetPath, "utf8")).toContain('"hi"');
|
|
199
|
-
},
|
|
200
|
-
FULL_PIPELINE_TIMEOUT_MS,
|
|
201
|
-
);
|
|
202
|
-
|
|
203
|
-
test(
|
|
204
|
-
"client.ts ohne index.html → klarer Error mit Template-Vorschlag",
|
|
205
|
-
async () => {
|
|
206
|
-
await mkdir(join(tmp, "src"), { recursive: true });
|
|
207
|
-
await writeFile(join(tmp, "src/client.ts"), `console.log("hi");`);
|
|
208
|
-
await writeFile(join(tmp, "package.json"), `{"name":"no-html","private":true}`);
|
|
209
|
-
|
|
210
|
-
let stderr = "";
|
|
211
|
-
expect(() => {
|
|
212
|
-
try {
|
|
213
|
-
execFileSync("bun", [KUMIKO_BUILD_BIN, tmp], { stdio: "pipe" });
|
|
214
|
-
} catch (err) {
|
|
215
|
-
const e = err as { stderr?: Buffer };
|
|
216
|
-
stderr = e.stderr?.toString() ?? "";
|
|
217
|
-
throw err;
|
|
218
|
-
}
|
|
219
|
-
}).toThrow();
|
|
220
|
-
|
|
221
|
-
expect(stderr).toContain("kein index.html gefunden");
|
|
222
|
-
expect(stderr).toContain(`<script type="module" src="/client.js"></script>`);
|
|
223
|
-
},
|
|
224
|
-
FULL_PIPELINE_TIMEOUT_MS,
|
|
225
|
-
);
|
|
226
|
-
|
|
227
|
-
test(
|
|
228
|
-
"client.ts + index.html ohne /client.js Placeholder → klarer Error",
|
|
229
|
-
async () => {
|
|
230
|
-
await mkdir(join(tmp, "src"), { recursive: true });
|
|
231
|
-
await writeFile(join(tmp, "src/client.ts"), `console.log("hi");`);
|
|
232
|
-
await mkdir(join(tmp, "public"), { recursive: true });
|
|
233
|
-
await writeFile(
|
|
234
|
-
join(tmp, "public/index.html"),
|
|
235
|
-
`<!doctype html><html><body>no script</body></html>`,
|
|
236
|
-
);
|
|
237
|
-
await writeFile(join(tmp, "package.json"), `{"name":"no-placeholder","private":true}`);
|
|
238
|
-
|
|
239
|
-
let stderr = "";
|
|
240
|
-
expect(() => {
|
|
241
|
-
try {
|
|
242
|
-
execFileSync("bun", [KUMIKO_BUILD_BIN, tmp], { stdio: "pipe" });
|
|
243
|
-
} catch (err) {
|
|
244
|
-
const e = err as { stderr?: Buffer };
|
|
245
|
-
stderr = e.stderr?.toString() ?? "";
|
|
246
|
-
throw err;
|
|
247
|
-
}
|
|
248
|
-
}).toThrow();
|
|
249
|
-
|
|
250
|
-
expect(stderr).toContain("keinen Entry-Tag für /client.js");
|
|
251
|
-
expect(stderr).toContain(`<script type="module" src="/client.js"></script>`);
|
|
252
|
-
},
|
|
253
|
-
FULL_PIPELINE_TIMEOUT_MS,
|
|
254
|
-
);
|
|
255
|
-
|
|
256
|
-
test(
|
|
257
|
-
"Re-Build mit unverändertem Source produziert identischen Hash (reproducibility)",
|
|
258
|
-
async () => {
|
|
259
|
-
await mkdir(join(tmp, "src"), { recursive: true });
|
|
260
|
-
await writeFile(join(tmp, "src/client.ts"), `console.log("stable");`);
|
|
261
|
-
await mkdir(join(tmp, "public"), { recursive: true });
|
|
262
|
-
await writeFile(
|
|
263
|
-
join(tmp, "public/index.html"),
|
|
264
|
-
`<!doctype html><html><body><script type="module" src="/client.js"></script></body></html>`,
|
|
265
|
-
);
|
|
266
|
-
await writeFile(join(tmp, "package.json"), `{"name":"hash-stability","private":true}`);
|
|
267
|
-
|
|
268
|
-
execFileSync("bun", [KUMIKO_BUILD_BIN, tmp], { stdio: "pipe" });
|
|
269
|
-
const manifest1 = JSON.parse(
|
|
270
|
-
await readFile(join(tmp, "dist/manifest.json"), "utf8"),
|
|
271
|
-
) as Record<string, string>;
|
|
272
|
-
|
|
273
|
-
execFileSync("bun", [KUMIKO_BUILD_BIN, tmp], { stdio: "pipe" });
|
|
274
|
-
const manifest2 = JSON.parse(
|
|
275
|
-
await readFile(join(tmp, "dist/manifest.json"), "utf8"),
|
|
276
|
-
) as Record<string, string>;
|
|
277
|
-
|
|
278
|
-
expect(manifest2["client.js"]).toBe(manifest1["client.js"]);
|
|
279
|
-
},
|
|
280
|
-
FULL_PIPELINE_TIMEOUT_MS,
|
|
281
|
-
);
|
|
282
|
-
});
|
|
@@ -1,228 +0,0 @@
|
|
|
1
|
-
import { describe, expect, test } from "bun:test";
|
|
2
|
-
import { existsSync, mkdirSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from "node:fs";
|
|
3
|
-
import { tmpdir } from "node:os";
|
|
4
|
-
import { join } from "node:path";
|
|
5
|
-
import { buildServerBundle, readExtraRuntimeExternals } from "../build-server-bundle";
|
|
6
|
-
|
|
7
|
-
// Baut ein Mini-App-Fixture (bin/main.ts + bin/kumiko.ts teilen ein Modul) und
|
|
8
|
-
// prüft das Variante-B-Verhalten: ein Bun.build-Call → server.js + kumiko.js als
|
|
9
|
-
// Entries + geteilter chunk statt zwei vollen Bundles.
|
|
10
|
-
function makeFixture(): string {
|
|
11
|
-
const dir = mkdtempSync(join(tmpdir(), "server-bundle-"));
|
|
12
|
-
mkdirSync(join(dir, "bin"));
|
|
13
|
-
mkdirSync(join(dir, "src"));
|
|
14
|
-
writeFileSync(join(dir, "package.json"), `${JSON.stringify({ name: "fixture-app" })}\n`);
|
|
15
|
-
writeFileSync(join(dir, "src/shared.ts"), "export function shared(): number { return 42; }\n");
|
|
16
|
-
// main.ts → wird zu server.js umbenannt; kumiko.ts macht findRepoRoot zum
|
|
17
|
-
// repoRoot dieses Fixtures, also wird kumiko.js gebaut.
|
|
18
|
-
writeFileSync(
|
|
19
|
-
join(dir, "bin/main.ts"),
|
|
20
|
-
`import { shared } from "../src/shared";\nconsole.log("server", shared());\n`,
|
|
21
|
-
);
|
|
22
|
-
writeFileSync(
|
|
23
|
-
join(dir, "bin/kumiko.ts"),
|
|
24
|
-
`import { shared } from "../src/shared";\nconsole.log("migrate", shared());\n`,
|
|
25
|
-
);
|
|
26
|
-
return dir;
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
describe("buildServerBundle (multi-entry + splitting)", () => {
|
|
30
|
-
test("produces server.js + kumiko.js as entries + a shared chunk", async () => {
|
|
31
|
-
const dir = makeFixture();
|
|
32
|
-
try {
|
|
33
|
-
const result = await buildServerBundle({ cwd: dir, outDir: join(dir, "dist-server") });
|
|
34
|
-
const outDir = result.outDir;
|
|
35
|
-
|
|
36
|
-
// main.ts wurde zu server.js umbenannt.
|
|
37
|
-
expect(existsSync(join(outDir, "server.js"))).toBe(true);
|
|
38
|
-
expect(existsSync(join(outDir, "main.js"))).toBe(false);
|
|
39
|
-
expect(existsSync(join(outDir, "kumiko.js"))).toBe(true);
|
|
40
|
-
|
|
41
|
-
const entryFiles = result.entries.map((e) => e.file).sort();
|
|
42
|
-
expect(entryFiles).toEqual(["kumiko.js", "server.js"]);
|
|
43
|
-
|
|
44
|
-
// Das geteilte Modul liegt in einem chunk, nicht in beiden Entries inlined.
|
|
45
|
-
expect(result.chunks.length).toBeGreaterThanOrEqual(1);
|
|
46
|
-
const serverSrc = readFileSync(join(outDir, "server.js"), "utf8");
|
|
47
|
-
expect(serverSrc).toContain("chunk-");
|
|
48
|
-
|
|
49
|
-
// Kein Legacy-Drizzle-Output mehr.
|
|
50
|
-
expect(existsSync(join(outDir, "migration-hooks.js"))).toBe(false);
|
|
51
|
-
expect(existsSync(join(outDir, "drizzle.config.ts"))).toBe(false);
|
|
52
|
-
|
|
53
|
-
// Runtime-package.json mit start-Script, ohne drizzle-deps.
|
|
54
|
-
const pkg = JSON.parse(readFileSync(join(outDir, "package.json"), "utf8"));
|
|
55
|
-
expect(pkg.scripts.start).toBe("bun run server.js");
|
|
56
|
-
expect(Object.keys(pkg.dependencies)).not.toContain("drizzle-kit");
|
|
57
|
-
expect(Object.keys(pkg.dependencies)).not.toContain("drizzle-orm");
|
|
58
|
-
// Positive counterpart to the negative checks above — a runtime
|
|
59
|
-
// external accidentally dropped from RUNTIME_EXTERNALS (into
|
|
60
|
-
// BUILD_ONLY_EXTERNALS instead) would slip through silently. This
|
|
61
|
-
// is the exact regression class behind the money-horse prod crash
|
|
62
|
-
// ("Cannot find package 'meilisearch'").
|
|
63
|
-
expect(Object.keys(pkg.dependencies)).toContain("meilisearch");
|
|
64
|
-
|
|
65
|
-
expect(result.totalBytes).toBeGreaterThan(0);
|
|
66
|
-
} finally {
|
|
67
|
-
rmSync(dir, { recursive: true, force: true });
|
|
68
|
-
}
|
|
69
|
-
});
|
|
70
|
-
|
|
71
|
-
test("pins runtime-deps from node_modules/@cosmicdrift/* for consumer apps without a packages/ dir", async () => {
|
|
72
|
-
// Reproduziert #1217: findRepoRoot liefert für eine Consumer-App die
|
|
73
|
-
// App-Wurzel selbst (kein packages/-Ordner) — die Version muss aus den
|
|
74
|
-
// installierten node_modules/@cosmicdrift/*-Packages kommen, nicht "*".
|
|
75
|
-
const dir = makeFixture();
|
|
76
|
-
try {
|
|
77
|
-
const frameworkPkgDir = join(dir, "node_modules/@cosmicdrift/kumiko-framework");
|
|
78
|
-
mkdirSync(frameworkPkgDir, { recursive: true });
|
|
79
|
-
writeFileSync(
|
|
80
|
-
join(frameworkPkgDir, "package.json"),
|
|
81
|
-
`${JSON.stringify({ name: "@cosmicdrift/kumiko-framework", dependencies: { meilisearch: "^0.58.0" } })}\n`,
|
|
82
|
-
);
|
|
83
|
-
const bundledFeaturesPkgDir = join(dir, "node_modules/@cosmicdrift/kumiko-bundled-features");
|
|
84
|
-
mkdirSync(bundledFeaturesPkgDir, { recursive: true });
|
|
85
|
-
writeFileSync(
|
|
86
|
-
join(bundledFeaturesPkgDir, "package.json"),
|
|
87
|
-
`${JSON.stringify({ name: "@cosmicdrift/kumiko-bundled-features", dependencies: { ioredis: "^5.4.1" } })}\n`,
|
|
88
|
-
);
|
|
89
|
-
|
|
90
|
-
const result = await buildServerBundle({ cwd: dir, outDir: join(dir, "dist-server") });
|
|
91
|
-
|
|
92
|
-
expect(result.runtimeDeps["meilisearch"]).toBe("^0.58.0");
|
|
93
|
-
expect(result.runtimeDeps["ioredis"]).toBe("^5.4.1");
|
|
94
|
-
} finally {
|
|
95
|
-
rmSync(dir, { recursive: true, force: true });
|
|
96
|
-
}
|
|
97
|
-
});
|
|
98
|
-
|
|
99
|
-
test("prefers node_modules/@cosmicdrift/* over packages/* when both declare a version", async () => {
|
|
100
|
-
// Framework-monorepo checkout: both the installed node_modules package
|
|
101
|
-
// (symlinked in real life, diverging here) and packages/framework exist.
|
|
102
|
-
// node_modules must win — it reflects what actually gets shipped/installed
|
|
103
|
-
// for the consumer, packages/framework is only a repo-root fallback.
|
|
104
|
-
const dir = makeFixture();
|
|
105
|
-
try {
|
|
106
|
-
const frameworkPkgDir = join(dir, "node_modules/@cosmicdrift/kumiko-framework");
|
|
107
|
-
mkdirSync(frameworkPkgDir, { recursive: true });
|
|
108
|
-
writeFileSync(
|
|
109
|
-
join(frameworkPkgDir, "package.json"),
|
|
110
|
-
`${JSON.stringify({ name: "@cosmicdrift/kumiko-framework", dependencies: { meilisearch: "^0.58.0" } })}\n`,
|
|
111
|
-
);
|
|
112
|
-
|
|
113
|
-
mkdirSync(join(dir, "packages/framework"), { recursive: true });
|
|
114
|
-
writeFileSync(
|
|
115
|
-
join(dir, "packages/framework/package.json"),
|
|
116
|
-
`${JSON.stringify({ name: "@cosmicdrift/kumiko-framework", dependencies: { meilisearch: "^0.30.0" } })}\n`,
|
|
117
|
-
);
|
|
118
|
-
|
|
119
|
-
const result = await buildServerBundle({ cwd: dir, outDir: join(dir, "dist-server") });
|
|
120
|
-
|
|
121
|
-
expect(result.runtimeDeps["meilisearch"]).toBe("^0.58.0");
|
|
122
|
-
} finally {
|
|
123
|
-
rmSync(dir, { recursive: true, force: true });
|
|
124
|
-
}
|
|
125
|
-
});
|
|
126
|
-
});
|
|
127
|
-
|
|
128
|
-
describe("readExtraRuntimeExternals (package.json kumiko field, #1484)", () => {
|
|
129
|
-
test("reads string list from kumiko.extraRuntimeExternals", () => {
|
|
130
|
-
const dir = mkdtempSync(join(tmpdir(), "extra-ext-"));
|
|
131
|
-
try {
|
|
132
|
-
writeFileSync(
|
|
133
|
-
join(dir, "package.json"),
|
|
134
|
-
`${JSON.stringify({
|
|
135
|
-
name: "app",
|
|
136
|
-
kumiko: { extraRuntimeExternals: ["@napi-rs/canvas", "pdf-parse"] },
|
|
137
|
-
})}\n`,
|
|
138
|
-
);
|
|
139
|
-
expect(readExtraRuntimeExternals(dir)).toEqual(["@napi-rs/canvas", "pdf-parse"]);
|
|
140
|
-
} finally {
|
|
141
|
-
rmSync(dir, { recursive: true, force: true });
|
|
142
|
-
}
|
|
143
|
-
});
|
|
144
|
-
|
|
145
|
-
test("missing / invalid shapes yield empty list", () => {
|
|
146
|
-
const dir = mkdtempSync(join(tmpdir(), "extra-ext-bad-"));
|
|
147
|
-
try {
|
|
148
|
-
writeFileSync(join(dir, "package.json"), `${JSON.stringify({ name: "app" })}\n`);
|
|
149
|
-
expect(readExtraRuntimeExternals(dir)).toEqual([]);
|
|
150
|
-
writeFileSync(
|
|
151
|
-
join(dir, "package.json"),
|
|
152
|
-
`${JSON.stringify({ name: "app", kumiko: { extraRuntimeExternals: "nope" } })}\n`,
|
|
153
|
-
);
|
|
154
|
-
expect(readExtraRuntimeExternals(dir)).toEqual([]);
|
|
155
|
-
writeFileSync(
|
|
156
|
-
join(dir, "package.json"),
|
|
157
|
-
`${JSON.stringify({
|
|
158
|
-
name: "app",
|
|
159
|
-
kumiko: { extraRuntimeExternals: ["ok", 1, "", " "] },
|
|
160
|
-
})}\n`,
|
|
161
|
-
);
|
|
162
|
-
expect(readExtraRuntimeExternals(dir)).toEqual(["ok"]);
|
|
163
|
-
} finally {
|
|
164
|
-
rmSync(dir, { recursive: true, force: true });
|
|
165
|
-
}
|
|
166
|
-
});
|
|
167
|
-
});
|
|
168
|
-
|
|
169
|
-
describe("buildServerBundle + package.json extras (#1484)", () => {
|
|
170
|
-
test("extraRuntimeExternals from options land in runtimeDeps with app pin", async () => {
|
|
171
|
-
const dir = makeFixture();
|
|
172
|
-
try {
|
|
173
|
-
writeFileSync(
|
|
174
|
-
join(dir, "package.json"),
|
|
175
|
-
`${JSON.stringify({
|
|
176
|
-
name: "fixture-app",
|
|
177
|
-
dependencies: { "@napi-rs/canvas": "0.1.65" },
|
|
178
|
-
kumiko: { extraRuntimeExternals: ["@napi-rs/canvas"] },
|
|
179
|
-
})}\n`,
|
|
180
|
-
);
|
|
181
|
-
const extras = readExtraRuntimeExternals(dir);
|
|
182
|
-
const result = await buildServerBundle({
|
|
183
|
-
cwd: dir,
|
|
184
|
-
outDir: join(dir, "dist-server"),
|
|
185
|
-
extraRuntimeExternals: extras,
|
|
186
|
-
});
|
|
187
|
-
expect(result.runtimeDeps["@napi-rs/canvas"]).toBe("0.1.65");
|
|
188
|
-
const distPkg = JSON.parse(readFileSync(join(dir, "dist-server/package.json"), "utf-8")) as {
|
|
189
|
-
dependencies: Record<string, string>;
|
|
190
|
-
};
|
|
191
|
-
expect(distPkg.dependencies["@napi-rs/canvas"]).toBe("0.1.65");
|
|
192
|
-
} finally {
|
|
193
|
-
rmSync(dir, { recursive: true, force: true });
|
|
194
|
-
}
|
|
195
|
-
});
|
|
196
|
-
|
|
197
|
-
test("app package.json does not override framework runtime pins", async () => {
|
|
198
|
-
const dir = makeFixture();
|
|
199
|
-
try {
|
|
200
|
-
const frameworkPkgDir = join(dir, "node_modules/@cosmicdrift/kumiko-framework");
|
|
201
|
-
mkdirSync(frameworkPkgDir, { recursive: true });
|
|
202
|
-
writeFileSync(
|
|
203
|
-
join(frameworkPkgDir, "package.json"),
|
|
204
|
-
`${JSON.stringify({
|
|
205
|
-
name: "@cosmicdrift/kumiko-framework",
|
|
206
|
-
dependencies: { meilisearch: "^0.58.0" },
|
|
207
|
-
})}\n`,
|
|
208
|
-
);
|
|
209
|
-
writeFileSync(
|
|
210
|
-
join(dir, "package.json"),
|
|
211
|
-
`${JSON.stringify({
|
|
212
|
-
name: "fixture-app",
|
|
213
|
-
dependencies: { meilisearch: "^0.1.0", "@napi-rs/canvas": "0.1.65" },
|
|
214
|
-
kumiko: { extraRuntimeExternals: ["@napi-rs/canvas"] },
|
|
215
|
-
})}\n`,
|
|
216
|
-
);
|
|
217
|
-
const result = await buildServerBundle({
|
|
218
|
-
cwd: dir,
|
|
219
|
-
outDir: join(dir, "dist-server"),
|
|
220
|
-
extraRuntimeExternals: readExtraRuntimeExternals(dir),
|
|
221
|
-
});
|
|
222
|
-
expect(result.runtimeDeps["meilisearch"]).toBe("^0.58.0");
|
|
223
|
-
expect(result.runtimeDeps["@napi-rs/canvas"]).toBe("0.1.65");
|
|
224
|
-
} finally {
|
|
225
|
-
rmSync(dir, { recursive: true, force: true });
|
|
226
|
-
}
|
|
227
|
-
});
|
|
228
|
-
});
|
|
@@ -1,87 +0,0 @@
|
|
|
1
|
-
// Heuristik für die Watcher-Loop: server-side Änderungen brauchen
|
|
2
|
-
// Process-Restart (Bun cached Module-Imports), Client-Side reicht
|
|
3
|
-
// Hot-Reload, Tests/non-TS sollen den Watcher gar nicht triggern.
|
|
4
|
-
// Wenn die Klassifikation drift bekommt, kommen entweder unnötige
|
|
5
|
-
// Restarts (DX schlecht) oder echte Schema-Änderungen schlagen
|
|
6
|
-
// nicht durch (UX broken). Beide sind teuer — daher pinnen wir.
|
|
7
|
-
|
|
8
|
-
import { describe, expect, test } from "bun:test";
|
|
9
|
-
import { classifyChange } from "../create-kumiko-server";
|
|
10
|
-
|
|
11
|
-
describe("classifyChange", () => {
|
|
12
|
-
test("server-side feature.ts → restart", () => {
|
|
13
|
-
expect(classifyChange("/abs/samples/foo/src/features/items/feature.ts")).toBe("restart");
|
|
14
|
-
});
|
|
15
|
-
|
|
16
|
-
test("server-side schema-Datei → restart", () => {
|
|
17
|
-
expect(classifyChange("/abs/samples/foo/src/features/items/schema/item.ts")).toBe("restart");
|
|
18
|
-
});
|
|
19
|
-
|
|
20
|
-
test("server-side bin/server.ts → restart", () => {
|
|
21
|
-
expect(classifyChange("/abs/samples/foo/bin/server.ts")).toBe("restart");
|
|
22
|
-
});
|
|
23
|
-
|
|
24
|
-
test("client-side web/index.ts → hot-reload", () => {
|
|
25
|
-
expect(classifyChange("/abs/samples/foo/src/features/items/web/index.ts")).toBe("hot-reload");
|
|
26
|
-
});
|
|
27
|
-
|
|
28
|
-
test("client-side web/page.tsx → hot-reload", () => {
|
|
29
|
-
expect(classifyChange("/abs/samples/foo/src/features/items/web/page.tsx")).toBe("hot-reload");
|
|
30
|
-
});
|
|
31
|
-
|
|
32
|
-
test("client.tsx Entry → hot-reload", () => {
|
|
33
|
-
expect(classifyChange("/abs/samples/foo/src/app/client.tsx")).toBe("hot-reload");
|
|
34
|
-
});
|
|
35
|
-
|
|
36
|
-
test("client.ts Entry → hot-reload", () => {
|
|
37
|
-
expect(classifyChange("/abs/samples/foo/src/app/client.ts")).toBe("hot-reload");
|
|
38
|
-
});
|
|
39
|
-
|
|
40
|
-
test("Test-Datei *.test.ts → ignore", () => {
|
|
41
|
-
expect(classifyChange("/abs/samples/foo/src/feature.test.ts")).toBe("ignore");
|
|
42
|
-
});
|
|
43
|
-
|
|
44
|
-
test("Test-Datei *.test.tsx → ignore", () => {
|
|
45
|
-
expect(classifyChange("/abs/samples/foo/src/component.test.tsx")).toBe("ignore");
|
|
46
|
-
});
|
|
47
|
-
|
|
48
|
-
test("__tests__/ Subdir → ignore (auch wenn die Datei selbst nicht *.test.ts heißt)", () => {
|
|
49
|
-
expect(classifyChange("/abs/samples/foo/src/__tests__/test-utils.ts")).toBe("ignore");
|
|
50
|
-
});
|
|
51
|
-
|
|
52
|
-
test("Integration-Test → ignore (würde sonst Schema-Restart auslösen)", () => {
|
|
53
|
-
expect(classifyChange("/abs/samples/foo/src/feature.integration.test.ts")).toBe("ignore");
|
|
54
|
-
});
|
|
55
|
-
|
|
56
|
-
test("E2E-Test → ignore", () => {
|
|
57
|
-
expect(classifyChange("/abs/samples/foo/src/something.e2e.ts")).toBe("ignore");
|
|
58
|
-
});
|
|
59
|
-
|
|
60
|
-
test("CSS/JSON/sonstiges → ignore", () => {
|
|
61
|
-
expect(classifyChange("/abs/samples/foo/src/styles.css")).toBe("ignore");
|
|
62
|
-
expect(classifyChange("/abs/samples/foo/src/data.json")).toBe("ignore");
|
|
63
|
-
expect(classifyChange("/abs/samples/foo/public/index.html")).toBe("ignore");
|
|
64
|
-
});
|
|
65
|
-
|
|
66
|
-
test("Windows-Pfad-Trenner: web\\ → hot-reload", () => {
|
|
67
|
-
expect(classifyChange("C:\\abs\\samples\\foo\\src\\features\\items\\web\\page.tsx")).toBe(
|
|
68
|
-
"hot-reload",
|
|
69
|
-
);
|
|
70
|
-
});
|
|
71
|
-
|
|
72
|
-
// Sample-Conventions: publicstatus splittet `/admin/` (Admin-Bundle)
|
|
73
|
-
// und `/public/` (Public-Bundle). Beide sind client-side, müssen
|
|
74
|
-
// hot-reload sein — sonst kostet jeder Bridge-Edit einen Restart +
|
|
75
|
-
// DB-Reset. Andere Samples machen das eventuell auch so.
|
|
76
|
-
test("admin/-Subdir → hot-reload", () => {
|
|
77
|
-
expect(classifyChange("/abs/samples/foo/src/admin/branding-settings.tsx")).toBe("hot-reload");
|
|
78
|
-
});
|
|
79
|
-
|
|
80
|
-
test("public/-Subdir → hot-reload", () => {
|
|
81
|
-
expect(classifyChange("/abs/samples/foo/src/public/StatusPage.tsx")).toBe("hot-reload");
|
|
82
|
-
});
|
|
83
|
-
|
|
84
|
-
test("client/-Subdir → hot-reload", () => {
|
|
85
|
-
expect(classifyChange("/abs/samples/foo/src/client/widgets.tsx")).toBe("hot-reload");
|
|
86
|
-
});
|
|
87
|
-
});
|