@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,630 +0,0 @@
|
|
|
1
|
-
// scaffoldApp unit-tests (DX-1.0 + #352 deploy/schema scaffold).
|
|
2
|
-
|
|
3
|
-
import { afterAll, afterEach, beforeEach, describe, expect, test } from "bun:test";
|
|
4
|
-
import { existsSync, mkdirSync, mkdtempSync, readFileSync, rmSync } from "node:fs";
|
|
5
|
-
import { tmpdir } from "node:os";
|
|
6
|
-
import { join } from "node:path";
|
|
7
|
-
import { type ScaffoldTestSetup, scaffoldApp } from "../scaffold-app";
|
|
8
|
-
|
|
9
|
-
const SCAFFOLD_FILES = [
|
|
10
|
-
"package.json",
|
|
11
|
-
"tsconfig.json",
|
|
12
|
-
"biome.json",
|
|
13
|
-
"bunfig.toml",
|
|
14
|
-
"bunfig.ci.toml",
|
|
15
|
-
"src/run-config.ts",
|
|
16
|
-
"src/features/tasks/feature.ts",
|
|
17
|
-
"src/features/tasks/index.ts",
|
|
18
|
-
"src/seed.ts",
|
|
19
|
-
"kumiko/schema.ts",
|
|
20
|
-
"bin/main.ts",
|
|
21
|
-
"bin/dev.ts",
|
|
22
|
-
"bin/kumiko.ts",
|
|
23
|
-
"src/client.tsx",
|
|
24
|
-
"src/styles.css",
|
|
25
|
-
".env.example",
|
|
26
|
-
"docker-compose.yml",
|
|
27
|
-
"kumiko/migrations/0001_init.sql",
|
|
28
|
-
"kumiko/migrations/.snapshot.json",
|
|
29
|
-
"deploy/Dockerfile",
|
|
30
|
-
"deploy/Dockerfile.dockerignore",
|
|
31
|
-
"deploy/migrate-step.sh",
|
|
32
|
-
"README.md",
|
|
33
|
-
] as const;
|
|
34
|
-
|
|
35
|
-
const STUB_TEST_SETUP: ScaffoldTestSetup = {
|
|
36
|
-
files: {
|
|
37
|
-
"bunfig.toml": "# stub unit bunfig\n",
|
|
38
|
-
"e2e/smoke.spec.ts": "// stub spec\n",
|
|
39
|
-
"src/__tests__/stub.test.ts": "// stub test\n",
|
|
40
|
-
},
|
|
41
|
-
scripts: { test: "stub-unit", "test:integration": "stub-integration", e2e: "stub-e2e" },
|
|
42
|
-
devDependencies: { "@cosmicdrift/kumiko-testing": "stub-version", "@playwright/test": "^1.0.0" },
|
|
43
|
-
rulesMarkdown: "## Testing\n\n- stub rule\n",
|
|
44
|
-
};
|
|
45
|
-
|
|
46
|
-
describe("scaffoldApp", () => {
|
|
47
|
-
let tmp: string;
|
|
48
|
-
beforeEach(() => {
|
|
49
|
-
tmp = mkdtempSync(join(tmpdir(), "scaffold-app-"));
|
|
50
|
-
});
|
|
51
|
-
afterEach(() => {
|
|
52
|
-
rmSync(tmp, { recursive: true, force: true });
|
|
53
|
-
});
|
|
54
|
-
|
|
55
|
-
test("scaffolds the expected files into <cwd>/<name>", async () => {
|
|
56
|
-
const dest = join(tmp, "my-shop");
|
|
57
|
-
const result = await scaffoldApp({ name: "my-shop", destination: dest });
|
|
58
|
-
|
|
59
|
-
expect(result.appName).toBe("my-shop");
|
|
60
|
-
expect(result.destination).toBe(dest);
|
|
61
|
-
for (const f of SCAFFOLD_FILES) {
|
|
62
|
-
expect(result.files).toContain(f);
|
|
63
|
-
expect(existsSync(join(dest, f))).toBe(true);
|
|
64
|
-
}
|
|
65
|
-
});
|
|
66
|
-
|
|
67
|
-
test("package.json has @cosmicdrift/* deps with version pin", async () => {
|
|
68
|
-
const dest = join(tmp, "my-shop");
|
|
69
|
-
await scaffoldApp({ name: "my-shop", destination: dest, frameworkVersion: "^0.13.0" });
|
|
70
|
-
|
|
71
|
-
const pkg = JSON.parse(readFileSync(join(dest, "package.json"), "utf-8")) as {
|
|
72
|
-
name: string;
|
|
73
|
-
dependencies: Record<string, string>;
|
|
74
|
-
scripts: Record<string, string>;
|
|
75
|
-
};
|
|
76
|
-
expect(pkg.name).toBe("my-shop");
|
|
77
|
-
expect(pkg.dependencies["@cosmicdrift/kumiko-bundled-features"]).toBe("^0.13.0");
|
|
78
|
-
expect(pkg.dependencies["@cosmicdrift/kumiko-dev-server"]).toBe("^0.13.0");
|
|
79
|
-
expect(pkg.dependencies["@cosmicdrift/kumiko-framework"]).toBe("^0.13.0");
|
|
80
|
-
expect(pkg.dependencies["@cosmicdrift/kumiko-renderer-web"]).toBe("^0.13.0");
|
|
81
|
-
expect(pkg.scripts["boot"]).toContain("KUMIKO_DRY_RUN_ENV=boot");
|
|
82
|
-
expect(pkg.scripts["dev"]).toBe("bun --watch bin/dev.ts");
|
|
83
|
-
expect(pkg.scripts["build"]).toBe("bun kumiko-build");
|
|
84
|
-
expect(pkg.scripts["start"]).toBe("bun run bin/main.ts");
|
|
85
|
-
expect(pkg.scripts["lint"]).toBe("biome check .");
|
|
86
|
-
});
|
|
87
|
-
|
|
88
|
-
test("without testSetup the scaffold keeps the legacy bunfig pair and test script", async () => {
|
|
89
|
-
const dest = join(tmp, "my-shop");
|
|
90
|
-
const result = await scaffoldApp({ name: "my-shop", destination: dest });
|
|
91
|
-
|
|
92
|
-
const pkg = JSON.parse(readFileSync(join(dest, "package.json"), "utf-8")) as {
|
|
93
|
-
scripts: Record<string, string>;
|
|
94
|
-
devDependencies: Record<string, string>;
|
|
95
|
-
};
|
|
96
|
-
expect(pkg.scripts["test"]).toBe("bun --config=bunfig.ci.toml test --dots");
|
|
97
|
-
expect(Object.keys(pkg.scripts)).not.toContain("e2e");
|
|
98
|
-
expect(Object.keys(pkg.devDependencies)).not.toContain("@cosmicdrift/kumiko-testing");
|
|
99
|
-
expect(result.files).toContain("bunfig.ci.toml");
|
|
100
|
-
expect(existsSync(join(dest, "e2e"))).toBe(false);
|
|
101
|
-
expect(readFileSync(join(dest, "README.md"), "utf-8")).not.toContain("## Testing");
|
|
102
|
-
});
|
|
103
|
-
|
|
104
|
-
test("scaffolded bunfig.toml and bunfig.ci.toml have no [test].concurrency key", async () => {
|
|
105
|
-
const dest = join(tmp, "my-shop");
|
|
106
|
-
await scaffoldApp({ name: "my-shop", destination: dest });
|
|
107
|
-
|
|
108
|
-
for (const file of ["bunfig.toml", "bunfig.ci.toml"]) {
|
|
109
|
-
const parsed = Bun.TOML.parse(readFileSync(join(dest, file), "utf-8")) as {
|
|
110
|
-
test: Record<string, unknown>;
|
|
111
|
-
};
|
|
112
|
-
expect(parsed.test["concurrency"]).toBeUndefined();
|
|
113
|
-
}
|
|
114
|
-
});
|
|
115
|
-
|
|
116
|
-
test("testSetup: factory gets name + version, files land (nested dirs) and are reported", async () => {
|
|
117
|
-
const dest = join(tmp, "my-shop");
|
|
118
|
-
let received: { appName: string; frameworkVersion: string } | undefined;
|
|
119
|
-
const result = await scaffoldApp({
|
|
120
|
-
name: "my-shop",
|
|
121
|
-
destination: dest,
|
|
122
|
-
frameworkVersion: "^0.13.0",
|
|
123
|
-
testSetup: (input) => {
|
|
124
|
-
received = input;
|
|
125
|
-
return STUB_TEST_SETUP;
|
|
126
|
-
},
|
|
127
|
-
});
|
|
128
|
-
|
|
129
|
-
expect(received).toEqual({ appName: "my-shop", frameworkVersion: "^0.13.0" });
|
|
130
|
-
for (const rel of Object.keys(STUB_TEST_SETUP.files)) {
|
|
131
|
-
expect(result.files).toContain(rel);
|
|
132
|
-
expect(readFileSync(join(dest, rel), "utf-8")).toBe(STUB_TEST_SETUP.files[rel] as string);
|
|
133
|
-
}
|
|
134
|
-
expect(existsSync(join(dest, "bunfig.ci.toml"))).toBe(false);
|
|
135
|
-
expect(result.files.filter((f) => f === "bunfig.toml")).toHaveLength(1);
|
|
136
|
-
});
|
|
137
|
-
|
|
138
|
-
test("testSetup: scripts replace test in place, devDependencies merge sorted, rules land in README", async () => {
|
|
139
|
-
const dest = join(tmp, "my-shop");
|
|
140
|
-
await scaffoldApp({ name: "my-shop", destination: dest, testSetup: () => STUB_TEST_SETUP });
|
|
141
|
-
|
|
142
|
-
const pkg = JSON.parse(readFileSync(join(dest, "package.json"), "utf-8")) as {
|
|
143
|
-
scripts: Record<string, string>;
|
|
144
|
-
devDependencies: Record<string, string>;
|
|
145
|
-
};
|
|
146
|
-
expect(pkg.scripts["test"]).toBe("stub-unit");
|
|
147
|
-
expect(Object.keys(pkg.scripts)).toEqual([
|
|
148
|
-
"dev",
|
|
149
|
-
"build",
|
|
150
|
-
"start",
|
|
151
|
-
"boot",
|
|
152
|
-
"typecheck",
|
|
153
|
-
"lint",
|
|
154
|
-
"test",
|
|
155
|
-
"test:integration",
|
|
156
|
-
"e2e",
|
|
157
|
-
"schema:apply",
|
|
158
|
-
"schema:generate",
|
|
159
|
-
]);
|
|
160
|
-
expect(pkg.devDependencies["@cosmicdrift/kumiko-testing"]).toBe("stub-version");
|
|
161
|
-
const names = Object.keys(pkg.devDependencies);
|
|
162
|
-
expect(names).toEqual([...names].sort());
|
|
163
|
-
expect(names).toContain("@biomejs/biome");
|
|
164
|
-
|
|
165
|
-
const readme = readFileSync(join(dest, "README.md"), "utf-8");
|
|
166
|
-
expect(readme).toContain("## Architecture");
|
|
167
|
-
expect(readme.endsWith(STUB_TEST_SETUP.rulesMarkdown)).toBe(true);
|
|
168
|
-
});
|
|
169
|
-
|
|
170
|
-
test("tsconfig and biome cover the e2e sources", async () => {
|
|
171
|
-
const dest = join(tmp, "my-shop");
|
|
172
|
-
await scaffoldApp({ name: "my-shop", destination: dest, testSetup: () => STUB_TEST_SETUP });
|
|
173
|
-
|
|
174
|
-
const tsconfig = JSON.parse(readFileSync(join(dest, "tsconfig.json"), "utf-8")) as {
|
|
175
|
-
include: string[];
|
|
176
|
-
};
|
|
177
|
-
expect(tsconfig.include).toEqual(expect.arrayContaining(["e2e", "playwright.config.ts"]));
|
|
178
|
-
const biome = JSON.parse(readFileSync(join(dest, "biome.json"), "utf-8")) as {
|
|
179
|
-
files: { includes: string[] };
|
|
180
|
-
};
|
|
181
|
-
expect(biome.files.includes).toEqual(
|
|
182
|
-
expect.arrayContaining(["e2e/**", "playwright.config.ts"]),
|
|
183
|
-
);
|
|
184
|
-
});
|
|
185
|
-
|
|
186
|
-
test("src/seed.ts bare return has a skip comment directly above it (kumiko-guard-silent-skip)", async () => {
|
|
187
|
-
const dest = join(tmp, "my-shop");
|
|
188
|
-
await scaffoldApp({ name: "my-shop", destination: dest });
|
|
189
|
-
|
|
190
|
-
const lines = readFileSync(join(dest, "src/seed.ts"), "utf-8").split("\n");
|
|
191
|
-
const returnLineIndex = lines.findIndex((l) => /^\s*if\s*\(.*\)\s*return;\s*$/.test(l));
|
|
192
|
-
expect(returnLineIndex).toBeGreaterThan(-1);
|
|
193
|
-
|
|
194
|
-
const precedingLine = lines[returnLineIndex - 1];
|
|
195
|
-
expect(precedingLine).toMatch(/^\s*\/\/\s*skip:/i);
|
|
196
|
-
});
|
|
197
|
-
|
|
198
|
-
test("init migration includes auth-mode tables (read_users)", async () => {
|
|
199
|
-
const dest = join(tmp, "my-shop");
|
|
200
|
-
await scaffoldApp({ name: "my-shop", destination: dest });
|
|
201
|
-
|
|
202
|
-
const sql = readFileSync(join(dest, "kumiko/migrations/0001_init.sql"), "utf-8");
|
|
203
|
-
expect(sql).toContain('CREATE TABLE IF NOT EXISTS "read_users"');
|
|
204
|
-
});
|
|
205
|
-
|
|
206
|
-
test("kumiko/schema.ts + bin/kumiko.ts wire HAS_AUTH single-source", async () => {
|
|
207
|
-
const dest = join(tmp, "my-shop");
|
|
208
|
-
await scaffoldApp({ name: "my-shop", destination: dest });
|
|
209
|
-
|
|
210
|
-
const schema = readFileSync(join(dest, "kumiko/schema.ts"), "utf-8");
|
|
211
|
-
expect(schema).toContain("APP_FEATURES, HAS_AUTH");
|
|
212
|
-
expect(schema).toContain("collectTableMetas");
|
|
213
|
-
|
|
214
|
-
const kumikoBin = readFileSync(join(dest, "bin/kumiko.ts"), "utf-8");
|
|
215
|
-
expect(kumikoBin).toContain("runSchemaCli");
|
|
216
|
-
expect(kumikoBin).toContain("runConsumerCli");
|
|
217
|
-
expect(kumikoBin).toContain("includeBundled: HAS_AUTH");
|
|
218
|
-
});
|
|
219
|
-
|
|
220
|
-
test("bin/dev.ts contains runDevApp + welcomeBanner + admin login + clientEntry", async () => {
|
|
221
|
-
const dest = join(tmp, "my-shop");
|
|
222
|
-
await scaffoldApp({ name: "my-shop", destination: dest });
|
|
223
|
-
|
|
224
|
-
const dev = readFileSync(join(dest, "bin/dev.ts"), "utf-8");
|
|
225
|
-
expect(dev).toContain("runDevApp");
|
|
226
|
-
expect(dev).toContain("welcomeBanner: true");
|
|
227
|
-
expect(dev).toContain("admin@my-shop.local");
|
|
228
|
-
expect(dev).toContain(`password: "changeme"`);
|
|
229
|
-
expect(dev).toContain(`clientEntry: "./src/client.tsx"`);
|
|
230
|
-
});
|
|
231
|
-
|
|
232
|
-
test("src/client.tsx bundles createKumikoApp + emailPasswordClient + DefaultAppShell", async () => {
|
|
233
|
-
const dest = join(tmp, "my-shop");
|
|
234
|
-
await scaffoldApp({ name: "my-shop", destination: dest });
|
|
235
|
-
|
|
236
|
-
const client = readFileSync(join(dest, "src/client.tsx"), "utf-8");
|
|
237
|
-
expect(client).toContain("createKumikoApp");
|
|
238
|
-
expect(client).toContain("emailPasswordClient");
|
|
239
|
-
expect(client).toContain("DefaultAppShell");
|
|
240
|
-
expect(client).toContain("shell: AppShell");
|
|
241
|
-
expect(client).toContain("function AppShell(");
|
|
242
|
-
expect(client).toContain("brand={<span");
|
|
243
|
-
expect(client).toContain(">my-shop</span>");
|
|
244
|
-
expect(client).toContain('from "@cosmicdrift/kumiko-renderer-web"');
|
|
245
|
-
expect(client).toContain('from "@cosmicdrift/kumiko-bundled-features/auth-email-password/web"');
|
|
246
|
-
expect(client).toContain("tasksClient");
|
|
247
|
-
});
|
|
248
|
-
|
|
249
|
-
test(".env.example carries KUMIKO_DEV_DB_NAME default so reboots are persistent", async () => {
|
|
250
|
-
const dest = join(tmp, "my-shop");
|
|
251
|
-
await scaffoldApp({ name: "my-shop", destination: dest });
|
|
252
|
-
|
|
253
|
-
const env = readFileSync(join(dest, ".env.example"), "utf-8");
|
|
254
|
-
expect(env).toContain("KUMIKO_DEV_DB_NAME=my_shop_dev");
|
|
255
|
-
});
|
|
256
|
-
|
|
257
|
-
test(".env.example lists both TEST_DATABASE_URL (bun dev) + DATABASE_URL (prod)", async () => {
|
|
258
|
-
const dest = join(tmp, "my-shop");
|
|
259
|
-
await scaffoldApp({ name: "my-shop", destination: dest });
|
|
260
|
-
|
|
261
|
-
const env = readFileSync(join(dest, ".env.example"), "utf-8");
|
|
262
|
-
expect(env).toContain("TEST_DATABASE_URL=");
|
|
263
|
-
expect(env).toContain("DATABASE_URL=");
|
|
264
|
-
});
|
|
265
|
-
|
|
266
|
-
// kumiko-framework#2330: unset (not pre-filled) so the default stays on
|
|
267
|
-
// resolveKmsWiring's plaintext fallback — a half-filled trio would throw
|
|
268
|
-
// the all-or-none error instead of booting.
|
|
269
|
-
test(".env.example documents the PLATFORM_KEK / SUBJECT_KEYS_DATABASE_URL / KUMIKO_BLIND_INDEX_KEY trio, unset by default", async () => {
|
|
270
|
-
const dest = join(tmp, "my-shop");
|
|
271
|
-
await scaffoldApp({ name: "my-shop", destination: dest });
|
|
272
|
-
|
|
273
|
-
const env = readFileSync(join(dest, ".env.example"), "utf-8");
|
|
274
|
-
expect(env).toContain("PLATFORM_KEK=\n");
|
|
275
|
-
expect(env).toContain("SUBJECT_KEYS_DATABASE_URL=\n");
|
|
276
|
-
expect(env).toContain("KUMIKO_BLIND_INDEX_KEY=\n");
|
|
277
|
-
});
|
|
278
|
-
|
|
279
|
-
test("docker-compose.yml ports + credentials match the .env.example *_URL defaults", async () => {
|
|
280
|
-
const dest = join(tmp, "my-shop");
|
|
281
|
-
await scaffoldApp({ name: "my-shop", destination: dest });
|
|
282
|
-
|
|
283
|
-
const compose = readFileSync(join(dest, "docker-compose.yml"), "utf-8");
|
|
284
|
-
const env = readFileSync(join(dest, ".env.example"), "utf-8");
|
|
285
|
-
expect(env).toContain("127.0.0.1:5432");
|
|
286
|
-
expect(env).toContain("127.0.0.1:6379");
|
|
287
|
-
expect(compose).toContain('"127.0.0.1:5432:5432"');
|
|
288
|
-
expect(compose).toContain('"127.0.0.1:6379:6379"');
|
|
289
|
-
expect(compose).not.toContain('"5432:5432"');
|
|
290
|
-
expect(compose).not.toContain('"6379:6379"');
|
|
291
|
-
expect(compose).toContain("POSTGRES_PASSWORD: postgres");
|
|
292
|
-
expect(compose).toContain("image: postgres:");
|
|
293
|
-
expect(compose).toContain("image: redis:");
|
|
294
|
-
});
|
|
295
|
-
|
|
296
|
-
test("README lists the mounted features dynamically", async () => {
|
|
297
|
-
const dest = join(tmp, "my-shop");
|
|
298
|
-
await scaffoldApp({
|
|
299
|
-
name: "my-shop",
|
|
300
|
-
destination: dest,
|
|
301
|
-
features: [
|
|
302
|
-
{
|
|
303
|
-
name: "tenant",
|
|
304
|
-
importPath: "@cosmicdrift/kumiko-bundled-features/tenant",
|
|
305
|
-
exportName: "createTenantFeature",
|
|
306
|
-
callExpression: "createTenantFeature()",
|
|
307
|
-
},
|
|
308
|
-
{
|
|
309
|
-
name: "delivery",
|
|
310
|
-
importPath: "@cosmicdrift/kumiko-bundled-features/delivery",
|
|
311
|
-
exportName: "createDeliveryFeature",
|
|
312
|
-
callExpression: "createDeliveryFeature()",
|
|
313
|
-
},
|
|
314
|
-
],
|
|
315
|
-
});
|
|
316
|
-
|
|
317
|
-
const readme = readFileSync(join(dest, "README.md"), "utf-8");
|
|
318
|
-
expect(readme).toContain("## Mounted features");
|
|
319
|
-
expect(readme).toContain("- `tenant`");
|
|
320
|
-
expect(readme).toContain("- `delivery`");
|
|
321
|
-
});
|
|
322
|
-
|
|
323
|
-
test("README documents the plaintext-PII default until PLATFORM_KEK/SUBJECT_KEYS_DATABASE_URL/KUMIKO_BLIND_INDEX_KEY are set", async () => {
|
|
324
|
-
const dest = join(tmp, "my-shop");
|
|
325
|
-
await scaffoldApp({ name: "my-shop", destination: dest });
|
|
326
|
-
|
|
327
|
-
const readme = readFileSync(join(dest, "README.md"), "utf-8");
|
|
328
|
-
expect(readme).toContain("PLATFORM_KEK");
|
|
329
|
-
expect(readme).toContain("plaintext");
|
|
330
|
-
});
|
|
331
|
-
|
|
332
|
-
test("callExpression/export-callability mismatch throws instead of silently mis-instantiating", async () => {
|
|
333
|
-
const dest = join(tmp, "my-shop");
|
|
334
|
-
await expect(
|
|
335
|
-
scaffoldApp({
|
|
336
|
-
name: "my-shop",
|
|
337
|
-
destination: dest,
|
|
338
|
-
features: [
|
|
339
|
-
{
|
|
340
|
-
name: "delivery",
|
|
341
|
-
importPath: "@cosmicdrift/kumiko-bundled-features/delivery",
|
|
342
|
-
exportName: "createDeliveryFeature",
|
|
343
|
-
// createDeliveryFeature is a factory function, but this claims it's
|
|
344
|
-
// a plain object export (no trailing "()") — must not silently push
|
|
345
|
-
// the function itself as a FeatureDefinition.
|
|
346
|
-
callExpression: "createDeliveryFeature",
|
|
347
|
-
},
|
|
348
|
-
],
|
|
349
|
-
}),
|
|
350
|
-
).rejects.toThrow(/is.*callable but callExpression/);
|
|
351
|
-
});
|
|
352
|
-
|
|
353
|
-
test("bin/main.ts contains runProdApp + auth.admin stub + staticDir", async () => {
|
|
354
|
-
const dest = join(tmp, "my-shop");
|
|
355
|
-
await scaffoldApp({ name: "my-shop", destination: dest });
|
|
356
|
-
|
|
357
|
-
const main = readFileSync(join(dest, "bin/main.ts"), "utf-8");
|
|
358
|
-
expect(main).toContain("runProdApp");
|
|
359
|
-
expect(main).toContain('staticDir: "./dist"');
|
|
360
|
-
expect(main).toContain("auth: {");
|
|
361
|
-
expect(main).toContain("admin@my-shop.local");
|
|
362
|
-
expect(main).toContain('tenantKey: "my-shop"');
|
|
363
|
-
expect(main).toMatch(/[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}/);
|
|
364
|
-
});
|
|
365
|
-
|
|
366
|
-
test("bin/main.ts composes the auth-mode feature set into envSchema (JWT_SECRET boot-gate)", async () => {
|
|
367
|
-
const dest = join(tmp, "my-shop");
|
|
368
|
-
await scaffoldApp({ name: "my-shop", destination: dest });
|
|
369
|
-
|
|
370
|
-
const main = readFileSync(join(dest, "bin/main.ts"), "utf-8");
|
|
371
|
-
expect(main).toContain("composeFeatures(APP_FEATURES, { includeBundled: HAS_AUTH })");
|
|
372
|
-
expect(main).toContain(
|
|
373
|
-
"composeEnvSchema({ core: frameworkCoreEnvSchema, features: bootFeatures })",
|
|
374
|
-
);
|
|
375
|
-
});
|
|
376
|
-
|
|
377
|
-
// kumiko-framework#2330: the --yes recommended set mounts PII-annotated
|
|
378
|
-
// entities (user, tenant-invitation, fileRef via user-data-rights), so
|
|
379
|
-
// runProdApp's PII boot gate throws BOOT ABORTED unless kms/allowPlaintextPii
|
|
380
|
-
// is wired. resolveKmsWiring supplies a real KMS when the env trio is set,
|
|
381
|
-
// otherwise an explicit, logged plaintext fallback — never a silent one.
|
|
382
|
-
test("bin/main.ts wires resolveKmsWiring into runProdApp so the PII boot gate doesn't abort", async () => {
|
|
383
|
-
const dest = join(tmp, "my-shop");
|
|
384
|
-
await scaffoldApp({ name: "my-shop", destination: dest });
|
|
385
|
-
|
|
386
|
-
const main = readFileSync(join(dest, "bin/main.ts"), "utf-8");
|
|
387
|
-
expect(main).toContain('from "@cosmicdrift/kumiko-framework/crypto"');
|
|
388
|
-
expect(main).toContain("requireKmsWiring(process.env");
|
|
389
|
-
expect(main).toContain("resolveKmsWiring(process.env");
|
|
390
|
-
expect(main).toContain('process.env["NODE_ENV"] === "production"');
|
|
391
|
-
expect(main).not.toContain('if ("allowPlaintextPii" in kmsWiring)');
|
|
392
|
-
expect(main).toContain("...kmsWiring,");
|
|
393
|
-
});
|
|
394
|
-
|
|
395
|
-
test("src/run-config.ts mounts secrets + auth-foundation + sessions + tasks + HAS_AUTH", async () => {
|
|
396
|
-
const dest = join(tmp, "my-shop");
|
|
397
|
-
await scaffoldApp({ name: "my-shop", destination: dest });
|
|
398
|
-
|
|
399
|
-
const runConfig = readFileSync(join(dest, "src/run-config.ts"), "utf-8");
|
|
400
|
-
expect(runConfig).toContain("createSecretsFeature()");
|
|
401
|
-
expect(runConfig).toContain("authFoundationFeature");
|
|
402
|
-
expect(runConfig).toContain("createSessionsFeature()");
|
|
403
|
-
expect(runConfig).toContain("tasksFeature");
|
|
404
|
-
expect(runConfig).toContain('from "./features/tasks"');
|
|
405
|
-
expect(runConfig).toContain("export const APP_FEATURES");
|
|
406
|
-
expect(runConfig).toContain("export const HAS_AUTH");
|
|
407
|
-
});
|
|
408
|
-
|
|
409
|
-
test("rejects non-kebab-case names", async () => {
|
|
410
|
-
await expect(scaffoldApp({ name: "MyShop", destination: tmp })).rejects.toThrow(/kebab-case/);
|
|
411
|
-
await expect(scaffoldApp({ name: "my_shop", destination: tmp })).rejects.toThrow(/kebab-case/);
|
|
412
|
-
await expect(scaffoldApp({ name: "0shop", destination: tmp })).rejects.toThrow(/kebab-case/);
|
|
413
|
-
});
|
|
414
|
-
|
|
415
|
-
test("rejects trailing- and double-hyphen names (invalid package segment)", async () => {
|
|
416
|
-
await expect(scaffoldApp({ name: "my-shop-", destination: tmp })).rejects.toThrow(/kebab-case/);
|
|
417
|
-
await expect(scaffoldApp({ name: "my--shop", destination: tmp })).rejects.toThrow(/kebab-case/);
|
|
418
|
-
});
|
|
419
|
-
|
|
420
|
-
test("resolves a relative destination against the supplied cwd, not process.cwd()", async () => {
|
|
421
|
-
const result = await scaffoldApp({ name: "shop", destination: "apps/shop", cwd: tmp });
|
|
422
|
-
expect(result.destination).toBe(join(tmp, "apps/shop"));
|
|
423
|
-
expect(existsSync(join(tmp, "apps/shop", "package.json"))).toBe(true);
|
|
424
|
-
});
|
|
425
|
-
|
|
426
|
-
test("resolves the name-default destination against the supplied cwd", async () => {
|
|
427
|
-
const result = await scaffoldApp({ name: "shop", cwd: tmp });
|
|
428
|
-
expect(result.destination).toBe(join(tmp, "shop"));
|
|
429
|
-
expect(existsSync(join(tmp, "shop", "package.json"))).toBe(true);
|
|
430
|
-
});
|
|
431
|
-
|
|
432
|
-
test("refuses to overwrite existing destination", async () => {
|
|
433
|
-
const dest = join(tmp, "existing");
|
|
434
|
-
await scaffoldApp({ name: "existing", destination: dest });
|
|
435
|
-
await expect(scaffoldApp({ name: "existing", destination: dest })).rejects.toThrow(
|
|
436
|
-
/already exists/,
|
|
437
|
-
);
|
|
438
|
-
});
|
|
439
|
-
|
|
440
|
-
test("features-param: custom selection lands in run-config.ts imports + APP_FEATURES", async () => {
|
|
441
|
-
const dest = join(tmp, "custom-features");
|
|
442
|
-
await scaffoldApp({
|
|
443
|
-
name: "custom-features",
|
|
444
|
-
destination: dest,
|
|
445
|
-
features: [
|
|
446
|
-
{
|
|
447
|
-
name: "billing-foundation",
|
|
448
|
-
importPath: "@cosmicdrift/kumiko-bundled-features/billing-foundation",
|
|
449
|
-
exportName: "billingFoundationFeature",
|
|
450
|
-
callExpression: "billingFoundationFeature",
|
|
451
|
-
},
|
|
452
|
-
{
|
|
453
|
-
name: "delivery",
|
|
454
|
-
importPath: "@cosmicdrift/kumiko-bundled-features/delivery",
|
|
455
|
-
exportName: "createDeliveryFeature",
|
|
456
|
-
callExpression: "createDeliveryFeature()",
|
|
457
|
-
},
|
|
458
|
-
],
|
|
459
|
-
});
|
|
460
|
-
const cfg = readFileSync(join(dest, "src/run-config.ts"), "utf-8");
|
|
461
|
-
expect(cfg).toContain('from "@cosmicdrift/kumiko-bundled-features/billing-foundation"');
|
|
462
|
-
expect(cfg).toContain('from "@cosmicdrift/kumiko-bundled-features/delivery"');
|
|
463
|
-
expect(cfg).toContain("billingFoundationFeature");
|
|
464
|
-
expect(cfg).toContain("createDeliveryFeature()");
|
|
465
|
-
expect(cfg).not.toContain("createSecretsFeature");
|
|
466
|
-
expect(cfg).not.toContain("createSessionsFeature");
|
|
467
|
-
});
|
|
468
|
-
|
|
469
|
-
test("features-param: empty array falls back to foundation (backwards-compat)", async () => {
|
|
470
|
-
const dest = join(tmp, "empty-features");
|
|
471
|
-
await scaffoldApp({ name: "empty-features", destination: dest, features: [] });
|
|
472
|
-
const cfg = readFileSync(join(dest, "src/run-config.ts"), "utf-8");
|
|
473
|
-
expect(cfg).toContain("createSecretsFeature()");
|
|
474
|
-
expect(cfg).toContain("createSessionsFeature()");
|
|
475
|
-
});
|
|
476
|
-
|
|
477
|
-
test("features-param: composeFeatures auto-mounted names are filtered out", async () => {
|
|
478
|
-
const dest = join(tmp, "filtered");
|
|
479
|
-
await scaffoldApp({
|
|
480
|
-
name: "filtered",
|
|
481
|
-
destination: dest,
|
|
482
|
-
features: [
|
|
483
|
-
{
|
|
484
|
-
name: "config",
|
|
485
|
-
importPath: "@cosmicdrift/kumiko-bundled-features/config",
|
|
486
|
-
exportName: "createConfigFeature",
|
|
487
|
-
callExpression: "createConfigFeature()",
|
|
488
|
-
},
|
|
489
|
-
{
|
|
490
|
-
name: "user",
|
|
491
|
-
importPath: "@cosmicdrift/kumiko-bundled-features/user",
|
|
492
|
-
exportName: "createUserFeature",
|
|
493
|
-
callExpression: "createUserFeature()",
|
|
494
|
-
},
|
|
495
|
-
{
|
|
496
|
-
name: "tenant",
|
|
497
|
-
importPath: "@cosmicdrift/kumiko-bundled-features/tenant",
|
|
498
|
-
exportName: "createTenantFeature",
|
|
499
|
-
callExpression: "createTenantFeature()",
|
|
500
|
-
},
|
|
501
|
-
{
|
|
502
|
-
name: "auth-email-password",
|
|
503
|
-
importPath: "@cosmicdrift/kumiko-bundled-features/auth-email-password",
|
|
504
|
-
exportName: "createAuthEmailPasswordFeature",
|
|
505
|
-
callExpression: "createAuthEmailPasswordFeature()",
|
|
506
|
-
},
|
|
507
|
-
{
|
|
508
|
-
name: "delivery",
|
|
509
|
-
importPath: "@cosmicdrift/kumiko-bundled-features/delivery",
|
|
510
|
-
exportName: "createDeliveryFeature",
|
|
511
|
-
callExpression: "createDeliveryFeature()",
|
|
512
|
-
},
|
|
513
|
-
],
|
|
514
|
-
});
|
|
515
|
-
const cfg = readFileSync(join(dest, "src/run-config.ts"), "utf-8");
|
|
516
|
-
expect(cfg).not.toContain("createConfigFeature");
|
|
517
|
-
expect(cfg).not.toContain("createUserFeature");
|
|
518
|
-
expect(cfg).not.toContain("createTenantFeature");
|
|
519
|
-
expect(cfg).not.toContain("createAuthEmailPasswordFeature");
|
|
520
|
-
expect(cfg).toContain("createDeliveryFeature()");
|
|
521
|
-
});
|
|
522
|
-
|
|
523
|
-
test("features-param: ONLY auto-mounted names → empty effective set, no foundation fallback", async () => {
|
|
524
|
-
const dest = join(tmp, "only-auto-mounted");
|
|
525
|
-
await scaffoldApp({
|
|
526
|
-
name: "only-auto-mounted",
|
|
527
|
-
destination: dest,
|
|
528
|
-
features: [
|
|
529
|
-
{
|
|
530
|
-
name: "config",
|
|
531
|
-
importPath: "@cosmicdrift/kumiko-bundled-features/config",
|
|
532
|
-
exportName: "createConfigFeature",
|
|
533
|
-
callExpression: "createConfigFeature()",
|
|
534
|
-
},
|
|
535
|
-
{
|
|
536
|
-
name: "user",
|
|
537
|
-
importPath: "@cosmicdrift/kumiko-bundled-features/user",
|
|
538
|
-
exportName: "createUserFeature",
|
|
539
|
-
callExpression: "createUserFeature()",
|
|
540
|
-
},
|
|
541
|
-
{
|
|
542
|
-
name: "tenant",
|
|
543
|
-
importPath: "@cosmicdrift/kumiko-bundled-features/tenant",
|
|
544
|
-
exportName: "createTenantFeature",
|
|
545
|
-
callExpression: "createTenantFeature()",
|
|
546
|
-
},
|
|
547
|
-
{
|
|
548
|
-
name: "auth-email-password",
|
|
549
|
-
importPath: "@cosmicdrift/kumiko-bundled-features/auth-email-password",
|
|
550
|
-
exportName: "createAuthEmailPasswordFeature",
|
|
551
|
-
callExpression: "createAuthEmailPasswordFeature()",
|
|
552
|
-
},
|
|
553
|
-
],
|
|
554
|
-
});
|
|
555
|
-
const cfg = readFileSync(join(dest, "src/run-config.ts"), "utf-8");
|
|
556
|
-
expect(cfg).not.toContain("createSecretsFeature");
|
|
557
|
-
expect(cfg).not.toContain("createSessionsFeature");
|
|
558
|
-
expect(cfg).toContain("tasksFeature");
|
|
559
|
-
});
|
|
560
|
-
|
|
561
|
-
test("deterministic tenantId for same name (reproducible boots)", async () => {
|
|
562
|
-
const a = join(tmp, "a");
|
|
563
|
-
const b = join(tmp, "b");
|
|
564
|
-
await scaffoldApp({ name: "stable", destination: a });
|
|
565
|
-
await scaffoldApp({ name: "stable", destination: b });
|
|
566
|
-
const mainA = readFileSync(join(a, "bin/main.ts"), "utf-8");
|
|
567
|
-
const mainB = readFileSync(join(b, "bin/main.ts"), "utf-8");
|
|
568
|
-
const uuidA = mainA.match(
|
|
569
|
-
/[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}/,
|
|
570
|
-
)?.[0];
|
|
571
|
-
const uuidB = mainB.match(
|
|
572
|
-
/[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}/,
|
|
573
|
-
)?.[0];
|
|
574
|
-
expect(uuidA).toBeDefined();
|
|
575
|
-
expect(uuidA).toBe(uuidB);
|
|
576
|
-
});
|
|
577
|
-
});
|
|
578
|
-
|
|
579
|
-
// File-content assertions on run-config.ts can't catch a missing sessionStore
|
|
580
|
-
// provider — only actually booting the generated bin/main.ts can.
|
|
581
|
-
describe("generated default app actually boots (kumiko-framework#3120)", () => {
|
|
582
|
-
const FIXTURE_ROOT = join(import.meta.dir, ".tmp-fixtures");
|
|
583
|
-
const createdDirs: string[] = [];
|
|
584
|
-
|
|
585
|
-
afterAll(() => {
|
|
586
|
-
for (const d of createdDirs) {
|
|
587
|
-
try {
|
|
588
|
-
rmSync(d, { recursive: true, force: true });
|
|
589
|
-
} catch {
|
|
590
|
-
// ignore — best-effort
|
|
591
|
-
}
|
|
592
|
-
}
|
|
593
|
-
});
|
|
594
|
-
|
|
595
|
-
test("KUMIKO_DRY_RUN_ENV=boot bun bin/main.ts exits 0 against the default scaffold", async () => {
|
|
596
|
-
mkdirSync(FIXTURE_ROOT, { recursive: true });
|
|
597
|
-
const cwd = mkdtempSync(join(FIXTURE_ROOT, "boot-"));
|
|
598
|
-
createdDirs.push(cwd);
|
|
599
|
-
const dest = join(cwd, "boot-fixture");
|
|
600
|
-
|
|
601
|
-
await scaffoldApp({ name: "boot-fixture", destination: dest });
|
|
602
|
-
|
|
603
|
-
const proc = Bun.spawn({
|
|
604
|
-
cmd: ["bun", "bin/main.ts"],
|
|
605
|
-
cwd: dest,
|
|
606
|
-
env: {
|
|
607
|
-
...process.env,
|
|
608
|
-
KUMIKO_DRY_RUN_ENV: "boot",
|
|
609
|
-
JWT_SECRET: "a".repeat(32),
|
|
610
|
-
KUMIKO_SECRETS_MASTER_KEY_V1: Buffer.alloc(32, 7).toString("base64"),
|
|
611
|
-
DATABASE_URL: "postgres://dummy:dummy@127.0.0.1:1/dummy",
|
|
612
|
-
REDIS_URL: "redis://127.0.0.1:1",
|
|
613
|
-
PLATFORM_KEK: "",
|
|
614
|
-
SUBJECT_KEYS_DATABASE_URL: "",
|
|
615
|
-
KUMIKO_BLIND_INDEX_KEY: "",
|
|
616
|
-
},
|
|
617
|
-
stdout: "pipe",
|
|
618
|
-
stderr: "pipe",
|
|
619
|
-
});
|
|
620
|
-
const [stdout, stderr, code] = await Promise.all([
|
|
621
|
-
new Response(proc.stdout).text(),
|
|
622
|
-
new Response(proc.stderr).text(),
|
|
623
|
-
proc.exited,
|
|
624
|
-
]);
|
|
625
|
-
|
|
626
|
-
expect(code, `stdout:\n${stdout}\nstderr:\n${stderr}`).toBe(0);
|
|
627
|
-
expect(`${stdout}${stderr}`).not.toContain("BOOT ABORTED");
|
|
628
|
-
expect(stdout).toContain("boot validation OK");
|
|
629
|
-
});
|
|
630
|
-
});
|
|
@@ -1,47 +0,0 @@
|
|
|
1
|
-
// Regression for #2065: scaffolded `tasks` demo feature had hardcoded nav
|
|
2
|
-
// labels ("Tasks", "New task") instead of i18n keys, invisible to
|
|
3
|
-
// guard-i18n-ui-strings because it only scans src/features/**/*.ts, not
|
|
4
|
-
// demo/src/... or the dev-server's own template strings.
|
|
5
|
-
|
|
6
|
-
import { describe, expect, test } from "bun:test";
|
|
7
|
-
import { validateBoot } from "@cosmicdrift/kumiko-framework/engine";
|
|
8
|
-
import {
|
|
9
|
-
createDemoTasksFeature,
|
|
10
|
-
renderDemoTasksFeatureFile,
|
|
11
|
-
renderDemoTasksI18n,
|
|
12
|
-
} from "../scaffold-demo-tasks";
|
|
13
|
-
|
|
14
|
-
describe("demo tasks feature nav labels are i18n keys (#2065)", () => {
|
|
15
|
-
test("boot-validates with the tasks feature registered", () => {
|
|
16
|
-
expect(() => validateBoot([createDemoTasksFeature()])).not.toThrow();
|
|
17
|
-
});
|
|
18
|
-
|
|
19
|
-
test("nav labels use tasks: i18n keys, not raw text", () => {
|
|
20
|
-
const feature = createDemoTasksFeature();
|
|
21
|
-
expect(feature.navs["tasks"]?.label).toBe("tasks:nav.tasks");
|
|
22
|
-
expect(feature.navs["task-new"]?.label).toBe("tasks:nav.taskNew");
|
|
23
|
-
expect(feature.translations?.["tasks:nav.tasks"]).toEqual({ de: "Aufgaben", en: "Tasks" });
|
|
24
|
-
expect(feature.translations?.["tasks:nav.taskNew"]).toEqual({
|
|
25
|
-
de: "Neue Aufgabe",
|
|
26
|
-
en: "New task",
|
|
27
|
-
});
|
|
28
|
-
});
|
|
29
|
-
|
|
30
|
-
test("renderDemoTasksFeatureFile() emits i18n keys, not raw nav labels", () => {
|
|
31
|
-
const rendered = renderDemoTasksFeatureFile();
|
|
32
|
-
expect(rendered).not.toContain('label: "Tasks"');
|
|
33
|
-
expect(rendered).not.toContain('label: "New task"');
|
|
34
|
-
expect(rendered).toContain('label: "tasks:nav.tasks"');
|
|
35
|
-
expect(rendered).toContain('label: "tasks:nav.taskNew"');
|
|
36
|
-
});
|
|
37
|
-
|
|
38
|
-
test("renderDemoTasksI18n() bundles both nav-label keys for server + client", () => {
|
|
39
|
-
const rendered = renderDemoTasksI18n();
|
|
40
|
-
expect(rendered).toContain('"tasks:nav.tasks": { de: "Aufgaben", en: "Tasks" }');
|
|
41
|
-
expect(rendered).toContain('"tasks:nav.taskNew": { de: "Neue Aufgabe", en: "New task" }');
|
|
42
|
-
expect(rendered).toContain('"tasks:nav.tasks": "Aufgaben"');
|
|
43
|
-
expect(rendered).toContain('"tasks:nav.taskNew": "Neue Aufgabe"');
|
|
44
|
-
expect(rendered).toContain('"tasks:nav.tasks": "Tasks"');
|
|
45
|
-
expect(rendered).toContain('"tasks:nav.taskNew": "New task"');
|
|
46
|
-
});
|
|
47
|
-
});
|