@cosmicdrift/kumiko-dev-server 0.13.0 → 0.15.0
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/package.json +11 -14
- package/src/__tests__/build-prod-bundle.integration.ts +1 -1
- package/src/__tests__/build-prod-bundle.test.ts +1 -1
- package/src/__tests__/cache-headers.test.ts +1 -1
- package/src/__tests__/classify-change.test.ts +1 -1
- package/src/__tests__/compose-features-wiring.integration.ts +10 -9
- package/src/__tests__/compose-features.test.ts +1 -1
- package/src/__tests__/config-seed-boot.integration.ts +5 -4
- package/src/__tests__/crash-tracker.test.ts +1 -1
- package/src/__tests__/create-kumiko-server.integration.ts +5 -5
- package/src/__tests__/env-schema.integration.ts +1 -1
- package/src/__tests__/env-schema.test.ts +1 -1
- package/src/__tests__/few-shot-corpus.test.ts +1 -1
- package/src/__tests__/inject-schema.test.ts +1 -1
- package/src/__tests__/resolve-stylesheet.test.ts +11 -7
- package/src/__tests__/resolve-tailwind-cli.test.ts +1 -1
- package/src/__tests__/run-prod-app-spec.test.ts +1 -1
- package/src/__tests__/run-prod-app.integration.ts +6 -6
- package/src/__tests__/scaffold-app-feature.test.ts +1 -1
- package/src/__tests__/scaffold-app.test.ts +1 -1
- package/src/__tests__/scaffold-deploy.test.ts +1 -1
- package/src/__tests__/scaffold-feature.test.ts +1 -1
- package/src/__tests__/try-hono-first.test.ts +1 -1
- package/src/__tests__/walkthrough.integration.ts +118 -0
- package/src/codegen/__tests__/run-codegen.test.ts +1 -1
- package/src/codegen/__tests__/strict-mode-diagnostics.test.ts +1 -1
- package/src/codegen/__tests__/watch.test.ts +1 -1
- package/src/scaffold-app-feature.ts +2 -1
- package/src/scaffold-app.ts +139 -76
- package/src/{drizzle-tables-auth-mode.ts → schema-tables-auth-mode.ts} +1 -1
- package/templates/deploy/Dockerfile.template +15 -47
- package/CHANGELOG.md +0 -632
- package/src/drizzle-config.ts +0 -44
- /package/src/{drizzle-tables-minimal.ts → schema-tables-minimal.ts} +0 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cosmicdrift/kumiko-dev-server",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.15.0",
|
|
4
4
|
"description": "Development server bootstrap for Kumiko apps. Bundles the client, mints dev-JWTs, injects the resolved AppSchema, and seeds an admin. Not for production.",
|
|
5
5
|
"license": "BUSL-1.1",
|
|
6
6
|
"author": "Marc Frost <marc@cosmicdriftgamestudio.com>",
|
|
@@ -30,17 +30,13 @@
|
|
|
30
30
|
"types": "./src/compose-features.ts",
|
|
31
31
|
"default": "./src/compose-features.ts"
|
|
32
32
|
},
|
|
33
|
-
"./
|
|
34
|
-
"types": "./src/
|
|
35
|
-
"default": "./src/
|
|
33
|
+
"./schema-tables-auth-mode": {
|
|
34
|
+
"types": "./src/schema-tables-auth-mode.ts",
|
|
35
|
+
"default": "./src/schema-tables-auth-mode.ts"
|
|
36
36
|
},
|
|
37
|
-
"./
|
|
38
|
-
"types": "./src/
|
|
39
|
-
"default": "./src/
|
|
40
|
-
},
|
|
41
|
-
"./drizzle-tables-minimal": {
|
|
42
|
-
"types": "./src/drizzle-tables-minimal.ts",
|
|
43
|
-
"default": "./src/drizzle-tables-minimal.ts"
|
|
37
|
+
"./schema-tables-minimal": {
|
|
38
|
+
"types": "./src/schema-tables-minimal.ts",
|
|
39
|
+
"default": "./src/schema-tables-minimal.ts"
|
|
44
40
|
}
|
|
45
41
|
},
|
|
46
42
|
"bin": {
|
|
@@ -49,8 +45,9 @@
|
|
|
49
45
|
"kumiko-schema-check": "./bin/kumiko-schema-check.ts"
|
|
50
46
|
},
|
|
51
47
|
"dependencies": {
|
|
52
|
-
"@cosmicdrift/kumiko-bundled-features": "0.
|
|
53
|
-
"@cosmicdrift/kumiko-framework": "0.
|
|
48
|
+
"@cosmicdrift/kumiko-bundled-features": "0.14.0",
|
|
49
|
+
"@cosmicdrift/kumiko-framework": "0.14.0",
|
|
50
|
+
"ts-morph": "^28.0.0"
|
|
54
51
|
},
|
|
55
52
|
"publishConfig": {
|
|
56
53
|
"registry": "https://registry.npmjs.org",
|
|
@@ -62,4 +59,4 @@
|
|
|
62
59
|
"README.md",
|
|
63
60
|
"LICENSE"
|
|
64
61
|
]
|
|
65
|
-
}
|
|
62
|
+
}
|
|
@@ -12,13 +12,13 @@
|
|
|
12
12
|
// braucht Bun. Spawnen kumiko-build als subprocess via PATH.
|
|
13
13
|
// Skipped wenn `bun` nicht erreichbar — selten, aber sauber.
|
|
14
14
|
|
|
15
|
+
import { afterEach, beforeEach, describe, expect, test } from "bun:test";
|
|
15
16
|
import { execFileSync } from "node:child_process";
|
|
16
17
|
import { existsSync } from "node:fs";
|
|
17
18
|
import { mkdir, mkdtemp, readFile, rm, writeFile } from "node:fs/promises";
|
|
18
19
|
import { tmpdir } from "node:os";
|
|
19
20
|
import { dirname, join, resolve } from "node:path";
|
|
20
21
|
import { fileURLToPath } from "node:url";
|
|
21
|
-
import { afterEach, beforeEach, describe, expect, test } from "vitest";
|
|
22
22
|
import { buildProdBundle } from "../build-prod-bundle";
|
|
23
23
|
|
|
24
24
|
const __filename = fileURLToPath(import.meta.url);
|
|
@@ -6,11 +6,11 @@
|
|
|
6
6
|
// laufen im CI als `yarn build` auf der Showcase-App; das ist der
|
|
7
7
|
// ehrlichere Smoke-Test.
|
|
8
8
|
|
|
9
|
+
import { afterEach, beforeEach, describe, expect, test } from "bun:test";
|
|
9
10
|
import { existsSync } from "node:fs";
|
|
10
11
|
import { mkdir, mkdtemp, rm, writeFile } from "node:fs/promises";
|
|
11
12
|
import { tmpdir } from "node:os";
|
|
12
13
|
import { join } from "node:path";
|
|
13
|
-
import { afterEach, beforeEach, describe, expect, test } from "vitest";
|
|
14
14
|
import {
|
|
15
15
|
type ClientEntry,
|
|
16
16
|
discoverClientEntries,
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
// /manifest.json, /sw.js → no-cache
|
|
8
8
|
// alles andere → kein expliziter Header
|
|
9
9
|
|
|
10
|
-
import { describe, expect, test } from "
|
|
10
|
+
import { describe, expect, test } from "bun:test";
|
|
11
11
|
import { cacheHeadersFor } from "../run-prod-app";
|
|
12
12
|
|
|
13
13
|
describe("cacheHeadersFor", () => {
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
// Restarts (DX schlecht) oder echte Schema-Änderungen schlagen
|
|
6
6
|
// nicht durch (UX broken). Beide sind teuer — daher pinnen wir.
|
|
7
7
|
|
|
8
|
-
import { describe, expect, test } from "
|
|
8
|
+
import { describe, expect, test } from "bun:test";
|
|
9
9
|
import { classifyChange } from "../create-kumiko-server";
|
|
10
10
|
|
|
11
11
|
describe("classifyChange", () => {
|
|
@@ -21,6 +21,7 @@
|
|
|
21
21
|
// schreibt in ein lokales Array — gewollter Capture-Spy ohne vitest-
|
|
22
22
|
// Mock-API (CLAUDE.md "Kein Mock in *.integration.ts").
|
|
23
23
|
|
|
24
|
+
import { afterAll, afterEach, beforeAll, beforeEach, describe, expect, test } from "bun:test";
|
|
24
25
|
import { randomBytes } from "node:crypto";
|
|
25
26
|
import {
|
|
26
27
|
AuthErrors,
|
|
@@ -34,6 +35,7 @@ import {
|
|
|
34
35
|
import { tenantEntity, tenantMembershipsTable } from "@cosmicdrift/kumiko-bundled-features/tenant";
|
|
35
36
|
import { seedTenantMembership } from "@cosmicdrift/kumiko-bundled-features/tenant/testing";
|
|
36
37
|
import { UserHandlers, userEntity, userTable } from "@cosmicdrift/kumiko-bundled-features/user";
|
|
38
|
+
import { deleteMany } from "@cosmicdrift/kumiko-framework/bun-db";
|
|
37
39
|
import type { TenantId } from "@cosmicdrift/kumiko-framework/engine";
|
|
38
40
|
import {
|
|
39
41
|
setupTestStack,
|
|
@@ -42,7 +44,6 @@ import {
|
|
|
42
44
|
unsafeCreateEntityTable,
|
|
43
45
|
unsafePushTables,
|
|
44
46
|
} from "@cosmicdrift/kumiko-framework/stack";
|
|
45
|
-
import { afterAll, afterEach, beforeAll, beforeEach, describe, expect, test } from "vitest";
|
|
46
47
|
import { composeFeatures } from "../compose-features";
|
|
47
48
|
|
|
48
49
|
const RESET_HMAC = randomBytes(32).toString("base64");
|
|
@@ -151,8 +152,8 @@ describe("composeFeatures wiring — passwordReset", () => {
|
|
|
151
152
|
});
|
|
152
153
|
|
|
153
154
|
beforeEach(async () => {
|
|
154
|
-
await suite.stack.db
|
|
155
|
-
await suite.stack.db
|
|
155
|
+
await deleteMany(suite.stack.db, userTable, {});
|
|
156
|
+
await deleteMany(suite.stack.db, tenantMembershipsTable, {});
|
|
156
157
|
suite.resetEmails.length = 0;
|
|
157
158
|
suite.verifyEmails.length = 0;
|
|
158
159
|
});
|
|
@@ -225,8 +226,8 @@ describe("composeFeatures wiring — emailVerification", () => {
|
|
|
225
226
|
});
|
|
226
227
|
|
|
227
228
|
beforeEach(async () => {
|
|
228
|
-
await suite.stack.db
|
|
229
|
-
await suite.stack.db
|
|
229
|
+
await deleteMany(suite.stack.db, userTable, {});
|
|
230
|
+
await deleteMany(suite.stack.db, tenantMembershipsTable, {});
|
|
230
231
|
suite.resetEmails.length = 0;
|
|
231
232
|
suite.verifyEmails.length = 0;
|
|
232
233
|
});
|
|
@@ -271,8 +272,8 @@ describe("composeFeatures wiring — asymmetric activation", () => {
|
|
|
271
272
|
});
|
|
272
273
|
|
|
273
274
|
beforeEach(async () => {
|
|
274
|
-
await suite.stack.db
|
|
275
|
-
await suite.stack.db
|
|
275
|
+
await deleteMany(suite.stack.db, userTable, {});
|
|
276
|
+
await deleteMany(suite.stack.db, tenantMembershipsTable, {});
|
|
276
277
|
suite.resetEmails.length = 0;
|
|
277
278
|
suite.verifyEmails.length = 0;
|
|
278
279
|
});
|
|
@@ -325,8 +326,8 @@ describe("composeFeatures wiring — fail-closed ohne authOptions", () => {
|
|
|
325
326
|
});
|
|
326
327
|
|
|
327
328
|
afterEach(async () => {
|
|
328
|
-
await suite.stack.db
|
|
329
|
-
await suite.stack.db
|
|
329
|
+
await deleteMany(suite.stack.db, userTable, {});
|
|
330
|
+
await deleteMany(suite.stack.db, tenantMembershipsTable, {});
|
|
330
331
|
suite.resetEmails.length = 0;
|
|
331
332
|
});
|
|
332
333
|
|
|
@@ -7,8 +7,8 @@
|
|
|
7
7
|
// aber die Handler fehlen → POST /api/auth/request-password-reset
|
|
8
8
|
// dispatched ins Leere → 500.
|
|
9
9
|
|
|
10
|
+
import { describe, expect, test } from "bun:test";
|
|
10
11
|
import { defineFeature } from "@cosmicdrift/kumiko-framework/engine";
|
|
11
|
-
import { describe, expect, test } from "vitest";
|
|
12
12
|
import { composeFeatures } from "../compose-features";
|
|
13
13
|
|
|
14
14
|
const noopFeature = defineFeature("noop-app", () => {});
|
|
@@ -12,12 +12,14 @@
|
|
|
12
12
|
// 3. an admin set on top of a seed wins the resolver cascade; coexistence
|
|
13
13
|
// vs. override semantics depend on the admin user's tenantId.
|
|
14
14
|
|
|
15
|
+
import { afterAll, beforeAll, describe, expect, test } from "bun:test";
|
|
15
16
|
import {
|
|
16
17
|
configValuesTable,
|
|
17
18
|
createConfigAccessorFactory,
|
|
18
19
|
createConfigFeature,
|
|
19
20
|
createConfigResolver,
|
|
20
21
|
} from "@cosmicdrift/kumiko-bundled-features/config";
|
|
22
|
+
import { selectMany } from "@cosmicdrift/kumiko-framework/bun-db";
|
|
21
23
|
import {
|
|
22
24
|
access,
|
|
23
25
|
createSystemConfig,
|
|
@@ -32,7 +34,6 @@ import {
|
|
|
32
34
|
TestUsers,
|
|
33
35
|
unsafePushTables,
|
|
34
36
|
} from "@cosmicdrift/kumiko-framework/stack";
|
|
35
|
-
import { afterAll, beforeAll, describe, expect, test } from "vitest";
|
|
36
37
|
import { applyBootSeeds } from "../boot/apply-boot-seeds";
|
|
37
38
|
|
|
38
39
|
const bootSeedsFeature = defineFeature("boot-seeds-test", (r) => {
|
|
@@ -82,7 +83,7 @@ describe("config-seed boot wiring", () => {
|
|
|
82
83
|
test("first boot: applyBootSeeds writes one row per seed", async () => {
|
|
83
84
|
await applyBootSeeds({ registry: stack.registry, db: stack.db });
|
|
84
85
|
|
|
85
|
-
const rows = await stack.db
|
|
86
|
+
const rows = await selectMany(stack.db, configValuesTable);
|
|
86
87
|
expect(rows.length).toBe(2);
|
|
87
88
|
|
|
88
89
|
const siteKeyDef = stack.registry.getConfigKey(SITE_KEY);
|
|
@@ -111,7 +112,7 @@ describe("config-seed boot wiring", () => {
|
|
|
111
112
|
test("re-boot: idempotent — every seed already on disk → no extra rows", async () => {
|
|
112
113
|
await applyBootSeeds({ registry: stack.registry, db: stack.db });
|
|
113
114
|
|
|
114
|
-
const rows = await stack.db
|
|
115
|
+
const rows = await selectMany(stack.db, configValuesTable);
|
|
115
116
|
expect(rows.length).toBe(2);
|
|
116
117
|
});
|
|
117
118
|
|
|
@@ -151,7 +152,7 @@ describe("config-seed boot wiring", () => {
|
|
|
151
152
|
// - 3 rows = coexistence path (admin tenantId !== SYSTEM_TENANT_ID,
|
|
152
153
|
// new specific-tenant row sits next to the seed system-row).
|
|
153
154
|
// Either is correct as long as the resolver picks the admin value.
|
|
154
|
-
const rows = await stack.db
|
|
155
|
+
const rows = await selectMany(stack.db, configValuesTable);
|
|
155
156
|
expect([2, 3]).toContain(rows.length);
|
|
156
157
|
});
|
|
157
158
|
});
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
// Wrapper aufgibt oder noch eine Runde respawnt; off-by-one am Fenster-
|
|
4
4
|
// Rand würde im Bin-Skript schlecht auffallen.
|
|
5
5
|
|
|
6
|
-
import { describe, expect, test } from "
|
|
6
|
+
import { describe, expect, test } from "bun:test";
|
|
7
7
|
import { createCrashTracker } from "../crash-tracker";
|
|
8
8
|
|
|
9
9
|
describe("createCrashTracker", () => {
|
|
@@ -1,14 +1,14 @@
|
|
|
1
|
+
import { afterEach, describe, expect, test } from "bun:test";
|
|
1
2
|
import { mkdtempSync, rmSync, writeFileSync } from "node:fs";
|
|
2
3
|
import { tmpdir } from "node:os";
|
|
3
4
|
import { join } from "node:path";
|
|
5
|
+
import { asRawClient } from "@cosmicdrift/kumiko-framework/bun-db";
|
|
4
6
|
import {
|
|
5
7
|
createBooleanField,
|
|
6
8
|
createEntity,
|
|
7
9
|
createTextField,
|
|
8
10
|
defineFeature,
|
|
9
11
|
} from "@cosmicdrift/kumiko-framework/engine";
|
|
10
|
-
import { sql } from "drizzle-orm";
|
|
11
|
-
import { afterEach, describe, expect, test } from "vitest";
|
|
12
12
|
import { createKumikoServer, type KumikoServerHandle } from "../create-kumiko-server";
|
|
13
13
|
|
|
14
14
|
// Integration-Test: bootet createKumikoServer mit echtem Postgres,
|
|
@@ -50,10 +50,10 @@ async function boot(): Promise<KumikoServerHandle> {
|
|
|
50
50
|
describe("createKumikoServer", () => {
|
|
51
51
|
test("bootet den Kumiko-Stack + legt die Feature-Tables an", async () => {
|
|
52
52
|
const h = await boot();
|
|
53
|
-
const rows = await h.stack.db.
|
|
54
|
-
|
|
53
|
+
const rows = await asRawClient(h.stack.db).unsafe(
|
|
54
|
+
`SELECT to_regclass('public.kumiko_server_probe') IS NOT NULL AS "exists"`,
|
|
55
55
|
);
|
|
56
|
-
expect(rows[0]?.exists).toBe(true);
|
|
56
|
+
expect((rows as Array<Record<string, unknown>>)[0]?.["exists"]).toBe(true);
|
|
57
57
|
});
|
|
58
58
|
|
|
59
59
|
test("GET / → HTML + kumiko_auth/kumiko_csrf Set-Cookie", async () => {
|
|
@@ -4,9 +4,9 @@
|
|
|
4
4
|
// throws (via bootErrorReporter override) and KUMIKO_DRY_RUN_ENV
|
|
5
5
|
// returns the dry-run handle without booting.
|
|
6
6
|
|
|
7
|
+
import { describe, expect, it } from "bun:test";
|
|
7
8
|
import { defineFeature } from "@cosmicdrift/kumiko-framework/engine";
|
|
8
9
|
import { composeEnvSchema, KumikoBootError } from "@cosmicdrift/kumiko-framework/env";
|
|
9
|
-
import { describe, expect, it } from "vitest";
|
|
10
10
|
import { z } from "zod";
|
|
11
11
|
import { frameworkCoreEnvSchema } from "../env-schema";
|
|
12
12
|
import * as devServerPublicApi from "../index";
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
+
import { describe, expect, it } from "bun:test";
|
|
1
2
|
import { composeEnvSchema, KumikoBootError, parseEnv } from "@cosmicdrift/kumiko-framework/env";
|
|
2
|
-
import { describe, expect, it } from "vitest";
|
|
3
3
|
import { z } from "zod";
|
|
4
4
|
import { type FrameworkCoreEnv, frameworkCoreEnvSchema } from "../env-schema";
|
|
5
5
|
|
|
@@ -13,11 +13,11 @@
|
|
|
13
13
|
// because the corpus JSON now lives there — framework is public and must
|
|
14
14
|
// not carry the eval baseline.
|
|
15
15
|
|
|
16
|
+
import { afterEach, beforeEach, describe, expect, test } from "bun:test";
|
|
16
17
|
import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from "node:fs";
|
|
17
18
|
import { tmpdir } from "node:os";
|
|
18
19
|
import { join } from "node:path";
|
|
19
20
|
import { buildFewShotCorpus, pathToId } from "@cosmicdrift/kumiko-dev-server";
|
|
20
|
-
import { afterEach, beforeEach, describe, expect, test } from "vitest";
|
|
21
21
|
|
|
22
22
|
let workdir: string;
|
|
23
23
|
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
// nicht und mountet leer. Tests pinnen die Idempotenz + die zwei
|
|
5
5
|
// Insertion-Punkte (vor /client.js-Tag oder vor </body>).
|
|
6
6
|
|
|
7
|
-
import { describe, expect, test } from "
|
|
7
|
+
import { describe, expect, test } from "bun:test";
|
|
8
8
|
import { injectSchema } from "../inject-schema";
|
|
9
9
|
|
|
10
10
|
const SCHEMA = '{"features":[]}';
|
|
@@ -7,10 +7,10 @@
|
|
|
7
7
|
// die anderen Tests dort booten ein TestStack (DB + Redis), das brauchen
|
|
8
8
|
// wir für reine Resolver-Logik nicht.
|
|
9
9
|
|
|
10
|
+
import { describe, expect, test } from "bun:test";
|
|
10
11
|
import { mkdirSync, mkdtempSync, realpathSync, rmSync, writeFileSync } from "node:fs";
|
|
11
12
|
import { tmpdir } from "node:os";
|
|
12
13
|
import { join } from "node:path";
|
|
13
|
-
import { describe, expect, test } from "vitest";
|
|
14
14
|
import { resolveStylesheet } from "../create-kumiko-server";
|
|
15
15
|
|
|
16
16
|
describe("resolveStylesheet", () => {
|
|
@@ -48,16 +48,20 @@ describe("resolveStylesheet", () => {
|
|
|
48
48
|
expect(out).toBeUndefined();
|
|
49
49
|
});
|
|
50
50
|
|
|
51
|
-
test("undefined + clientEntry
|
|
52
|
-
//
|
|
53
|
-
//
|
|
54
|
-
//
|
|
55
|
-
//
|
|
51
|
+
test("undefined + clientEntry: undefined (silent skip)", () => {
|
|
52
|
+
// Bun resolveSync findet packages/renderer-web/src/styles.css im
|
|
53
|
+
// Repo. Das ist der prod-Pfad — unter Bun (real oder CI) ist der
|
|
54
|
+
// Wert ein absoluter path; unter Node (von wo der Test portiert
|
|
55
|
+
// wurde) wäre es undefined. Wir akzeptieren beides.
|
|
56
56
|
const out = resolveStylesheet({
|
|
57
57
|
features: [],
|
|
58
58
|
clientEntry: "./entry.tsx",
|
|
59
59
|
});
|
|
60
|
-
|
|
60
|
+
if (typeof Bun === "undefined") {
|
|
61
|
+
expect(out).toBeUndefined();
|
|
62
|
+
} else {
|
|
63
|
+
expect(out).toMatch(/renderer-web\/src\/styles\.css$/);
|
|
64
|
+
}
|
|
61
65
|
});
|
|
62
66
|
|
|
63
67
|
test("undefined + clientEntry + src/styles.css existiert → returns App-Theme-Override", () => {
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
// genau das, was den dev-server-Crash bei flakigem Netz verhindert:
|
|
3
3
|
// kein Bun → undefined, Package nicht installiert → undefined.
|
|
4
4
|
|
|
5
|
-
import { describe, expect, test } from "
|
|
5
|
+
import { describe, expect, test } from "bun:test";
|
|
6
6
|
import { resolveTailwindCli } from "../resolve-tailwind-cli";
|
|
7
7
|
|
|
8
8
|
describe("resolveTailwindCli", () => {
|
|
@@ -4,8 +4,8 @@
|
|
|
4
4
|
// "looks like a leak"-Reverts oder "bisschen rauf, sollte reichen"-
|
|
5
5
|
// Tweaks.
|
|
6
6
|
|
|
7
|
+
import { describe, expect, test } from "bun:test";
|
|
7
8
|
import { SSE_HEARTBEAT_INTERVAL_MS } from "@cosmicdrift/kumiko-framework/api";
|
|
8
|
-
import { describe, expect, test } from "vitest";
|
|
9
9
|
import { buildBunServeOptions } from "../run-prod-app";
|
|
10
10
|
|
|
11
11
|
describe("Bun.serve options for production", () => {
|
|
@@ -9,9 +9,11 @@
|
|
|
9
9
|
// fetch direkt. Bun.serve-Wiring ist in Production-Coolify selbst
|
|
10
10
|
// getestet wenn der Container hochfährt.
|
|
11
11
|
|
|
12
|
+
import { afterEach, beforeAll, describe, expect, test } from "bun:test";
|
|
12
13
|
import { mkdir, mkdtemp, rm, writeFile } from "node:fs/promises";
|
|
13
14
|
import { tmpdir } from "node:os";
|
|
14
15
|
import { dirname, join } from "node:path";
|
|
16
|
+
import { asRawClient } from "@cosmicdrift/kumiko-framework/bun-db";
|
|
15
17
|
import { createDbConnection } from "@cosmicdrift/kumiko-framework/db";
|
|
16
18
|
import {
|
|
17
19
|
createBooleanField,
|
|
@@ -28,9 +30,7 @@ import {
|
|
|
28
30
|
createProjectionStateTable,
|
|
29
31
|
} from "@cosmicdrift/kumiko-framework/pipeline";
|
|
30
32
|
import { unsafeEnsureEntityTable } from "@cosmicdrift/kumiko-framework/stack";
|
|
31
|
-
import { sql } from "drizzle-orm";
|
|
32
33
|
import postgres from "postgres";
|
|
33
|
-
import { afterEach, beforeAll, describe, expect, test } from "vitest";
|
|
34
34
|
import { z } from "zod";
|
|
35
35
|
import { type ProdAppHandle, runProdApp } from "../run-prod-app";
|
|
36
36
|
|
|
@@ -448,7 +448,7 @@ describe("runProdApp", () => {
|
|
|
448
448
|
});
|
|
449
449
|
|
|
450
450
|
expect(invocations).toBe(1);
|
|
451
|
-
expect(factoryDeps).toEqual({ db: true, redis: true, registry: true });
|
|
451
|
+
expect(factoryDeps!).toEqual({ db: true, redis: true, registry: true });
|
|
452
452
|
// Smoke: handle is functional (boot completed without error).
|
|
453
453
|
expect(handle.entrypoint).toBeDefined();
|
|
454
454
|
});
|
|
@@ -465,9 +465,9 @@ describe("runProdApp", () => {
|
|
|
465
465
|
seedInvocations++;
|
|
466
466
|
// Seed-side idempotence: check before inserting. runProdApp doesn't
|
|
467
467
|
// gate seeds — the seed itself is responsible.
|
|
468
|
-
const existing = await db.
|
|
469
|
-
if (existing.length > 0) return;
|
|
470
|
-
await db.
|
|
468
|
+
const existing = await asRawClient(db).unsafe(`SELECT 1 FROM prod_widgets LIMIT 1`);
|
|
469
|
+
if ((existing as Array<Record<string, unknown>>).length > 0) return;
|
|
470
|
+
await asRawClient(db).unsafe(`INSERT INTO prod_widgets (id, tenant_id, name) VALUES
|
|
471
471
|
(gen_random_uuid(), '00000000-0000-4000-8000-000000000001', 'seeded')`);
|
|
472
472
|
inserted = true;
|
|
473
473
|
};
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
// scaffoldAppFeature unit-tests (DX-2).
|
|
2
2
|
|
|
3
|
+
import { afterEach, beforeEach, describe, expect, test } from "bun:test";
|
|
3
4
|
import { existsSync, mkdtempSync, readFileSync, rmSync } from "node:fs";
|
|
4
5
|
import { tmpdir } from "node:os";
|
|
5
6
|
import { join } from "node:path";
|
|
6
|
-
import { afterEach, beforeEach, describe, expect, test } from "vitest";
|
|
7
7
|
import { scaffoldApp } from "../scaffold-app";
|
|
8
8
|
import { scaffoldAppFeature } from "../scaffold-app-feature";
|
|
9
9
|
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
// scaffoldApp unit-tests (DX-1.0).
|
|
2
2
|
|
|
3
|
+
import { afterEach, beforeEach, describe, expect, test } from "bun:test";
|
|
3
4
|
import { existsSync, mkdtempSync, readFileSync, rmSync } from "node:fs";
|
|
4
5
|
import { tmpdir } from "node:os";
|
|
5
6
|
import { join } from "node:path";
|
|
6
|
-
import { afterEach, beforeEach, describe, expect, test } from "vitest";
|
|
7
7
|
import { scaffoldApp } from "../scaffold-app";
|
|
8
8
|
|
|
9
9
|
describe("scaffoldApp", () => {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
+
import { afterEach, beforeEach, describe, expect, it } from "bun:test";
|
|
1
2
|
import { existsSync, mkdirSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from "node:fs";
|
|
2
3
|
import { tmpdir } from "node:os";
|
|
3
4
|
import { join } from "node:path";
|
|
4
|
-
import { afterEach, beforeEach, describe, expect, it } from "vitest";
|
|
5
5
|
import { scaffoldDeploy } from "../scaffold-deploy";
|
|
6
6
|
|
|
7
7
|
describe("scaffoldDeploy", () => {
|
|
@@ -8,12 +8,12 @@
|
|
|
8
8
|
// 4. Validation: bad names fail loudly, existing destination refuses
|
|
9
9
|
// to overwrite
|
|
10
10
|
|
|
11
|
+
import { afterEach, beforeEach, describe, expect, test } from "bun:test";
|
|
11
12
|
import { existsSync, mkdtempSync, readFileSync, rmSync } from "node:fs";
|
|
12
13
|
import { tmpdir } from "node:os";
|
|
13
14
|
import { join } from "node:path";
|
|
14
15
|
import { parseSourceFile, VERSION_HEADER } from "@cosmicdrift/kumiko-framework/engine";
|
|
15
16
|
import { Project } from "ts-morph";
|
|
16
|
-
import { afterEach, beforeEach, describe, expect, test } from "vitest";
|
|
17
17
|
import { scaffoldFeature } from "../scaffold-feature";
|
|
18
18
|
|
|
19
19
|
let workdir: string;
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
// sich ändert (z.B. "matched" auch für 4xx anders als 404), MÜSSEN
|
|
6
6
|
// beide Pfade synchron updaten.
|
|
7
7
|
|
|
8
|
-
import { describe, expect, test } from "
|
|
8
|
+
import { describe, expect, test } from "bun:test";
|
|
9
9
|
import { type HonoLikeApp, tryHonoFirst } from "../try-hono-first";
|
|
10
10
|
|
|
11
11
|
function makeApp(response: Response): HonoLikeApp {
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
// DX-3.1 — walkthrough-snapshot-test. Reproduces the 3-command path from
|
|
2
|
+
// docs.kumiko.so/en/walkthrough/ in-process and asserts what the walkthrough
|
|
3
|
+
// claims. Catches drift in scaffoldApp + scaffoldAppFeature against the
|
|
4
|
+
// docs without an actual `bunx … && yarn install && bun run boot` CI run.
|
|
5
|
+
//
|
|
6
|
+
// What this test pins:
|
|
7
|
+
// - scaffoldApp produces the 6 files the walkthrough lists
|
|
8
|
+
// - scaffoldAppFeature scaffolds + auto-mounts (the diff-block shown)
|
|
9
|
+
// - composeFeatures(includeBundled:true) yields the exact feature-count
|
|
10
|
+
// the walkthrough advertises in "Expected output"
|
|
11
|
+
|
|
12
|
+
import { afterEach, beforeEach, describe, expect, test } from "bun:test";
|
|
13
|
+
import { mkdtempSync, readFileSync, rmSync } from "node:fs";
|
|
14
|
+
import { tmpdir } from "node:os";
|
|
15
|
+
import { join } from "node:path";
|
|
16
|
+
import { createSecretsFeature } from "@cosmicdrift/kumiko-bundled-features/secrets";
|
|
17
|
+
import { createSessionsFeature } from "@cosmicdrift/kumiko-bundled-features/sessions";
|
|
18
|
+
import { createRegistry, defineFeature, validateBoot } from "@cosmicdrift/kumiko-framework/engine";
|
|
19
|
+
import { composeFeatures } from "../compose-features";
|
|
20
|
+
import { scaffoldApp } from "../scaffold-app";
|
|
21
|
+
import { scaffoldAppFeature } from "../scaffold-app-feature";
|
|
22
|
+
|
|
23
|
+
describe("walkthrough — DX-3.1 snapshot", () => {
|
|
24
|
+
let tmp: string;
|
|
25
|
+
let appRoot: string;
|
|
26
|
+
|
|
27
|
+
beforeEach(() => {
|
|
28
|
+
tmp = mkdtempSync(join(tmpdir(), "walkthrough-"));
|
|
29
|
+
appRoot = join(tmp, "my-notes");
|
|
30
|
+
});
|
|
31
|
+
afterEach(() => {
|
|
32
|
+
rmSync(tmp, { recursive: true, force: true });
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
test("Step 1 (kumiko new app) — produces walkthrough's 6 files", () => {
|
|
36
|
+
const result = scaffoldApp({ name: "my-notes", destination: appRoot });
|
|
37
|
+
expect(result.files).toEqual([
|
|
38
|
+
"package.json",
|
|
39
|
+
"tsconfig.json",
|
|
40
|
+
"src/run-config.ts",
|
|
41
|
+
"bin/main.ts",
|
|
42
|
+
".env.example",
|
|
43
|
+
"README.md",
|
|
44
|
+
]);
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
test("Step 2 (kumiko add feature) — auto-mounts + walkthrough diff matches", () => {
|
|
48
|
+
scaffoldApp({ name: "my-notes", destination: appRoot });
|
|
49
|
+
const result = scaffoldAppFeature({ name: "notes", appRoot });
|
|
50
|
+
expect(result.autoMounted).toBe(true);
|
|
51
|
+
|
|
52
|
+
const runConfig = readFileSync(join(appRoot, "src/run-config.ts"), "utf-8");
|
|
53
|
+
// Walkthrough's diff-block claims:
|
|
54
|
+
// + import { notesFeature } from "./features/notes";
|
|
55
|
+
// + notesFeature (in APP_FEATURES)
|
|
56
|
+
expect(runConfig).toContain(`import { notesFeature } from "./features/notes";`);
|
|
57
|
+
expect(runConfig).toContain("notesFeature");
|
|
58
|
+
// Foundation still mounted (createSecretsFeature + createSessionsFeature).
|
|
59
|
+
expect(runConfig).toContain("createSecretsFeature()");
|
|
60
|
+
expect(runConfig).toContain("createSessionsFeature()");
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
test("Step 3 (boot validation) — scaffolded run-config matches walkthrough's APP_FEATURES claim", () => {
|
|
64
|
+
scaffoldApp({ name: "my-notes", destination: appRoot });
|
|
65
|
+
scaffoldAppFeature({ name: "notes", appRoot });
|
|
66
|
+
|
|
67
|
+
// Text-assert: scaffolded run-config.ts contains exactly the 3 features
|
|
68
|
+
// the walkthrough's diff-block shows (secrets + sessions + notesFeature).
|
|
69
|
+
// Dynamic-import would fail because /tmp can't resolve @cosmicdrift/*
|
|
70
|
+
// workspace symlinks — instead we reproduce the equivalent APP_FEATURES
|
|
71
|
+
// array in-process below.
|
|
72
|
+
const runConfig = readFileSync(join(appRoot, "src/run-config.ts"), "utf-8");
|
|
73
|
+
expect(runConfig).toContain("createSecretsFeature()");
|
|
74
|
+
expect(runConfig).toContain("createSessionsFeature()");
|
|
75
|
+
expect(runConfig).toContain("notesFeature");
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
test("Step 3 (composeFeatures) — 3 explicit + 4 auto-mounted = 7 features", () => {
|
|
79
|
+
// Reproduces the scaffolded APP_FEATURES in-process. notesFeature gets
|
|
80
|
+
// a dummy defineFeature here — the scaffold-side of "notesFeature"
|
|
81
|
+
// (file-content) is pinned in test 2; this test pins the runtime-side
|
|
82
|
+
// (composeFeatures auto-prepend behaviour the walkthrough claims).
|
|
83
|
+
const notesFeature = defineFeature("notes", () => {});
|
|
84
|
+
const APP_FEATURES = [createSecretsFeature(), createSessionsFeature(), notesFeature];
|
|
85
|
+
|
|
86
|
+
const composed = composeFeatures(APP_FEATURES, { includeBundled: true });
|
|
87
|
+
// 3 explicit + 4 auto-mounted bundled = 7 total features.
|
|
88
|
+
expect(composed.length).toBe(7);
|
|
89
|
+
|
|
90
|
+
const composedNames = composed.map((f) => f.name).sort();
|
|
91
|
+
expect(composedNames).toEqual([
|
|
92
|
+
"auth-email-password",
|
|
93
|
+
"config",
|
|
94
|
+
"notes",
|
|
95
|
+
"secrets",
|
|
96
|
+
"sessions",
|
|
97
|
+
"tenant",
|
|
98
|
+
"user",
|
|
99
|
+
]);
|
|
100
|
+
|
|
101
|
+
// validateBoot must pass (no missing-requires, no schema-errors).
|
|
102
|
+
expect(() => validateBoot(composed)).not.toThrow();
|
|
103
|
+
// Registry must contain all 7 features.
|
|
104
|
+
const registry = createRegistry(composed);
|
|
105
|
+
expect(registry.features.size).toBe(7);
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
test("bin/main.ts contains the auth.admin stub the walkthrough relies on", () => {
|
|
109
|
+
scaffoldApp({ name: "my-notes", destination: appRoot });
|
|
110
|
+
const main = readFileSync(join(appRoot, "bin/main.ts"), "utf-8");
|
|
111
|
+
// composeFeatures(includeBundled:true)-trigger is `auth: { admin: { … } }`.
|
|
112
|
+
// Walkthrough explicitly says this is what auto-mounts the 4 bundled features.
|
|
113
|
+
expect(main).toContain("auth: {");
|
|
114
|
+
expect(main).toContain("admin: {");
|
|
115
|
+
expect(main).toContain("memberships:");
|
|
116
|
+
expect(main).toContain("runProdApp");
|
|
117
|
+
});
|
|
118
|
+
});
|
|
@@ -16,10 +16,10 @@
|
|
|
16
16
|
// - skipped-Flag wenn 0 Events UND kein .kumiko/ schon existiert
|
|
17
17
|
// - schemas.generated.ts wird erzeugt wenn inline-Schemas, sonst nicht
|
|
18
18
|
|
|
19
|
+
import { describe, expect, test } from "bun:test";
|
|
19
20
|
import { existsSync, mkdirSync, mkdtempSync, readFileSync, writeFileSync } from "node:fs";
|
|
20
21
|
import { tmpdir } from "node:os";
|
|
21
22
|
import { join } from "node:path";
|
|
22
|
-
import { describe, expect, test } from "vitest";
|
|
23
23
|
import { runCodegen } from "../run-codegen";
|
|
24
24
|
|
|
25
25
|
function makeAppDir(): string {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
+
import { afterAll, beforeAll, describe, expect, test } from "bun:test";
|
|
1
2
|
import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from "node:fs";
|
|
2
3
|
import { dirname, join } from "node:path";
|
|
3
4
|
import * as ts from "typescript";
|
|
4
|
-
import { afterAll, beforeAll, describe, expect, test } from "vitest";
|
|
5
5
|
import { runCodegen } from "../run-codegen";
|
|
6
6
|
|
|
7
7
|
const REPO_ROOT = join(__dirname, "../../../../..");
|
|
@@ -8,9 +8,9 @@
|
|
|
8
8
|
// 'zod' nicht direct nutzen, runCodegen scant feature-files die
|
|
9
9
|
// `import { z } from "zod"` haben.
|
|
10
10
|
|
|
11
|
+
import { afterAll, describe, expect, test } from "bun:test";
|
|
11
12
|
import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from "node:fs";
|
|
12
13
|
import { dirname, join } from "node:path";
|
|
13
|
-
import { afterAll, describe, expect, test } from "vitest";
|
|
14
14
|
import type { CodegenResult } from "../run-codegen";
|
|
15
15
|
import { watchAndRegenerate } from "../watch";
|
|
16
16
|
|
|
@@ -114,7 +114,8 @@ function mountInRunConfig(runConfigPath: string, name: string): boolean {
|
|
|
114
114
|
|
|
115
115
|
// 1. Prepend import after the last existing import.
|
|
116
116
|
const imports = sf.getImportDeclarations();
|
|
117
|
-
const insertIndex =
|
|
117
|
+
const insertIndex =
|
|
118
|
+
imports.length > 0 ? (imports[imports.length - 1]?.getChildIndex() ?? 0) + 1 : 0;
|
|
118
119
|
sf.insertImportDeclaration(insertIndex, {
|
|
119
120
|
moduleSpecifier: `./features/${name}`,
|
|
120
121
|
namedImports: [`${camel}Feature`],
|