@cosmicdrift/kumiko-dev-server 0.329.0 → 0.330.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.
Files changed (125) hide show
  1. package/README.md +17 -0
  2. package/bin/kumiko-build.ts +3 -2
  3. package/bin/kumiko-dev.ts +1 -1
  4. package/bin/kumiko-init-deploy.ts +1 -1
  5. package/bin/kumiko-schema-check.ts +4 -1
  6. package/dist/build-server-bundle.d.ts +31 -0
  7. package/dist/build-server-bundle.js +282 -0
  8. package/dist/build.d.ts +2 -0
  9. package/dist/build.js +5 -0
  10. package/dist/cli.d.ts +5 -0
  11. package/dist/cli.js +9 -0
  12. package/dist/codegen/index.d.ts +4 -0
  13. package/dist/codegen/index.js +4 -0
  14. package/dist/codegen/render.d.ts +55 -0
  15. package/dist/codegen/render.js +273 -0
  16. package/dist/codegen/run-codegen.d.ts +20 -0
  17. package/dist/codegen/run-codegen.js +219 -0
  18. package/dist/codegen/scan-events.d.ts +76 -0
  19. package/dist/codegen/scan-events.js +447 -0
  20. package/dist/codegen/watch.d.ts +33 -0
  21. package/dist/codegen/watch.js +112 -0
  22. package/dist/compose-stacks.d.ts +60 -0
  23. package/dist/compose-stacks.js +127 -0
  24. package/dist/crash-tracker.d.ts +17 -0
  25. package/dist/crash-tracker.js +37 -0
  26. package/dist/create-kumiko-server.d.ts +195 -0
  27. package/dist/create-kumiko-server.js +1040 -0
  28. package/dist/env-schema.d.ts +15 -0
  29. package/dist/env-schema.js +60 -0
  30. package/dist/few-shot-corpus.d.ts +72 -0
  31. package/dist/few-shot-corpus.js +262 -0
  32. package/dist/framework-version.d.ts +1 -0
  33. package/dist/framework-version.js +31 -0
  34. package/dist/index.d.ts +22 -0
  35. package/dist/index.js +33 -0
  36. package/dist/init-deploy-cli.d.ts +10 -0
  37. package/dist/init-deploy-cli.js +111 -0
  38. package/dist/kebab.d.ts +1 -0
  39. package/{src/kebab.ts → dist/kebab.js} +2 -3
  40. package/dist/run-dev-app.d.ts +174 -0
  41. package/dist/run-dev-app.js +403 -0
  42. package/dist/scaffold-app-feature.d.ts +16 -0
  43. package/dist/scaffold-app-feature.js +240 -0
  44. package/dist/scaffold-app.d.ts +45 -0
  45. package/dist/scaffold-app.js +852 -0
  46. package/dist/scaffold-demo-tasks.d.ts +8 -0
  47. package/{src/scaffold-demo-tasks.ts → dist/scaffold-demo-tasks.js} +69 -98
  48. package/dist/scaffold-deploy.d.ts +92 -0
  49. package/dist/scaffold-deploy.js +254 -0
  50. package/dist/scaffold-feature.d.ts +26 -0
  51. package/dist/scaffold-feature.js +163 -0
  52. package/dist/schema-apply.d.ts +11 -0
  53. package/dist/schema-apply.js +95 -0
  54. package/dist/schema-check-core.d.ts +2 -0
  55. package/{src/schema-check-core.ts → dist/schema-check-core.js} +8 -10
  56. package/dist/schema-tables-auth-mode.d.ts +2 -0
  57. package/{src/schema-tables-auth-mode.ts → dist/schema-tables-auth-mode.js} +2 -10
  58. package/dist/schema-tables-minimal.d.ts +2 -0
  59. package/{src/schema-tables-minimal.ts → dist/schema-tables-minimal.js} +2 -11
  60. package/dist/setup-test-stack-from-features.d.ts +11 -0
  61. package/dist/setup-test-stack-from-features.js +42 -0
  62. package/dist/welcome-banner.d.ts +15 -0
  63. package/dist/welcome-banner.js +27 -0
  64. package/package.json +71 -24
  65. package/src/changes.json +6 -0
  66. package/src/__tests__/build-prod-bundle.integration.test.ts +0 -282
  67. package/src/__tests__/build-server-bundle.test.ts +0 -228
  68. package/src/__tests__/classify-change.test.ts +0 -87
  69. package/src/__tests__/compose-stacks.test.ts +0 -273
  70. package/src/__tests__/config-resolver-default.integration.test.ts +0 -165
  71. package/src/__tests__/crash-tracker.test.ts +0 -89
  72. package/src/__tests__/create-kumiko-server-errors.test.ts +0 -54
  73. package/src/__tests__/create-kumiko-server-options.test.ts +0 -33
  74. package/src/__tests__/create-kumiko-server.integration.test.ts +0 -1007
  75. package/src/__tests__/discover-format.test.ts +0 -32
  76. package/src/__tests__/env-schema.integration.test.ts +0 -248
  77. package/src/__tests__/env-schema.test.ts +0 -132
  78. package/src/__tests__/few-shot-corpus.test.ts +0 -316
  79. package/src/__tests__/framework-version.test.ts +0 -45
  80. package/src/__tests__/init-deploy-cli.test.ts +0 -113
  81. package/src/__tests__/kumiko-schema-check.test.ts +0 -56
  82. package/src/__tests__/merge-extra-context.test.ts +0 -67
  83. package/src/__tests__/resolve-stylesheet.test.ts +0 -132
  84. package/src/__tests__/run-dev-app-boot-validation.test.ts +0 -38
  85. package/src/__tests__/run-dev-app.integration.test.ts +0 -290
  86. package/src/__tests__/runtime-classification.test.ts +0 -26
  87. package/src/__tests__/saas-identity-wire.integration.test.ts +0 -427
  88. package/src/__tests__/scaffold-app-feature.test.ts +0 -216
  89. package/src/__tests__/scaffold-app.test.ts +0 -630
  90. package/src/__tests__/scaffold-demo-tasks.test.ts +0 -47
  91. package/src/__tests__/scaffold-deploy.test.ts +0 -464
  92. package/src/__tests__/scaffold-dev-cycle.integration.test.ts +0 -109
  93. package/src/__tests__/scaffold-feature.test.ts +0 -143
  94. package/src/__tests__/schema-apply.integration.test.ts +0 -191
  95. package/src/__tests__/setup-test-stack-from-features.integration.test.ts +0 -30
  96. package/src/__tests__/setup-test-stack-job-run-logger.integration.test.ts +0 -72
  97. package/src/__tests__/walkthrough.integration.test.ts +0 -140
  98. package/src/__tests__/welcome-banner.test.ts +0 -42
  99. package/src/build-server-bundle.ts +0 -346
  100. package/src/build.ts +0 -28
  101. package/src/codegen/__tests__/render-codegen.test.ts +0 -100
  102. package/src/codegen/__tests__/run-codegen.test.ts +0 -665
  103. package/src/codegen/__tests__/strict-mode-diagnostics.test.ts +0 -347
  104. package/src/codegen/__tests__/watch.test.ts +0 -222
  105. package/src/codegen/index.ts +0 -19
  106. package/src/codegen/render.ts +0 -305
  107. package/src/codegen/run-codegen.ts +0 -278
  108. package/src/codegen/scan-events.ts +0 -565
  109. package/src/codegen/watch.ts +0 -140
  110. package/src/compose-stacks.ts +0 -193
  111. package/src/crash-tracker.ts +0 -56
  112. package/src/create-kumiko-server.ts +0 -1397
  113. package/src/env-schema.ts +0 -72
  114. package/src/few-shot-corpus.ts +0 -370
  115. package/src/framework-version.ts +0 -39
  116. package/src/index.ts +0 -105
  117. package/src/init-deploy-cli.ts +0 -148
  118. package/src/run-dev-app.ts +0 -694
  119. package/src/scaffold-app-feature.ts +0 -283
  120. package/src/scaffold-app.ts +0 -1033
  121. package/src/scaffold-deploy.ts +0 -393
  122. package/src/scaffold-feature.ts +0 -226
  123. package/src/schema-apply.ts +0 -123
  124. package/src/setup-test-stack-from-features.ts +0 -80
  125. package/src/welcome-banner.ts +0 -43
@@ -1,132 +0,0 @@
1
- // Unit-Tests für resolveStylesheet — die Verzweigungen aus
2
- // createKumikoServer's CSS-Setup. Läuft unter vitest auf Node, also
3
- // ohne Bun.serve und ohne Bun.resolveSync; der Default-Resolution-
4
- // Pfad (Bun-only) ist hier explizit als "skip silent" abgedeckt.
5
- //
6
- // Warum als eigener File statt im create-kumiko-server.integration.ts:
7
- // die anderen Tests dort booten ein TestStack (DB + Redis), das brauchen
8
- // wir für reine Resolver-Logik nicht.
9
-
10
- import { describe, expect, test } from "bun:test";
11
- import { mkdirSync, mkdtempSync, realpathSync, rmSync, writeFileSync } from "node:fs";
12
- import { tmpdir } from "node:os";
13
- import { join } from "node:path";
14
- import {
15
- resolveStylesheet,
16
- resolveStylesheetWatch,
17
- STYLESHEET_WATCH_ENV,
18
- } from "../create-kumiko-server";
19
-
20
- describe("resolveStylesheet", () => {
21
- test("string → resolved absolute path", () => {
22
- // Relativ zum CWD aufgelöst — das ist Node's path.resolve-Default.
23
- const out = resolveStylesheet({
24
- features: [],
25
- stylesheet: "./some/stylesheet.css",
26
- });
27
- expect(out).toMatch(/\/some\/stylesheet\.css$/);
28
- expect(out?.startsWith("/")).toBe(true);
29
- });
30
-
31
- test("absolute string bleibt unverändert (path.resolve idempotent)", () => {
32
- const out = resolveStylesheet({
33
- features: [],
34
- stylesheet: "/abs/path/styles.css",
35
- });
36
- expect(out).toBe("/abs/path/styles.css");
37
- });
38
-
39
- test("false → undefined (Pipeline explizit aus)", () => {
40
- const out = resolveStylesheet({
41
- features: [],
42
- stylesheet: false,
43
- clientEntry: "./entry.tsx",
44
- });
45
- expect(out).toBeUndefined();
46
- });
47
-
48
- test("undefined ohne clientEntry → undefined (kein Browser-Bundle, keine CSS)", () => {
49
- const out = resolveStylesheet({
50
- features: [],
51
- });
52
- expect(out).toBeUndefined();
53
- });
54
-
55
- test("undefined + clientEntry: undefined (silent skip)", () => {
56
- // Bun resolveSync findet packages/renderer-web/src/styles.css im
57
- // Repo. Das ist der prod-Pfad — unter Bun (real oder CI) ist der
58
- // Wert ein absoluter path; unter Node (von wo der Test portiert
59
- // wurde) wäre es undefined. Wir akzeptieren beides.
60
- const out = resolveStylesheet({
61
- features: [],
62
- clientEntry: "./entry.tsx",
63
- });
64
- if (typeof Bun === "undefined") {
65
- expect(out).toBeUndefined();
66
- } else {
67
- expect(out).toMatch(/renderer-web\/src\/styles\.css$/);
68
- }
69
- });
70
-
71
- test("Bun.resolveSync failure → undefined (catch path, no throw)", () => {
72
- const tmpDir = realpathSync(mkdtempSync(join(tmpdir(), "kumiko-resolve-catch-")));
73
- const cwdBefore = process.cwd();
74
- process.chdir(tmpDir);
75
- try {
76
- const out = resolveStylesheet({
77
- features: [],
78
- clientEntry: "./entry.tsx",
79
- });
80
- expect(out).toBeUndefined();
81
- } finally {
82
- process.chdir(cwdBefore);
83
- rmSync(tmpDir, { recursive: true, force: true });
84
- }
85
- });
86
-
87
- test("undefined + clientEntry + src/styles.css existiert → returns App-Theme-Override", () => {
88
- // Auto-Detection greift VOR dem renderer-web-Fallback: Wenn die App
89
- // ein eigenes src/styles.css hat (App-Theme-Pattern), wird das
90
- // automatisch als Tailwind-Entry genommen — symmetrisch zum prod-
91
- // Build (kumiko-build:resolveStylesheetEntry). Ohne diesen Check
92
- // müsste jede App `stylesheet:` explizit setzen.
93
- // realpath aufgelöst, weil macOS' /var → /private/var Symlink sonst
94
- // einen anderen Pfad rauswirft als process.cwd() nach chdir.
95
- const tmpDir = realpathSync(mkdtempSync(join(tmpdir(), "kumiko-resolve-styles-")));
96
- const srcDir = join(tmpDir, "src");
97
- mkdirSync(srcDir);
98
- const stylesheet = join(srcDir, "styles.css");
99
- writeFileSync(stylesheet, "/* app theme */");
100
-
101
- const cwdBefore = process.cwd();
102
- process.chdir(tmpDir);
103
- try {
104
- const out = resolveStylesheet({
105
- features: [],
106
- clientEntry: "./entry.tsx",
107
- });
108
- expect(out).toBe(stylesheet);
109
- } finally {
110
- process.chdir(cwdBefore);
111
- rmSync(tmpDir, { recursive: true, force: true });
112
- }
113
- });
114
- });
115
-
116
- describe("resolveStylesheetWatch", () => {
117
- test("an explicit option wins over the env", () => {
118
- expect(resolveStylesheetWatch({ stylesheetWatch: false }, {})).toBe(false);
119
- expect(resolveStylesheetWatch({ stylesheetWatch: true }, { [STYLESHEET_WATCH_ENV]: "0" })).toBe(
120
- true,
121
- );
122
- });
123
-
124
- test("no option, env=0 → false", () => {
125
- expect(resolveStylesheetWatch({}, { [STYLESHEET_WATCH_ENV]: "0" })).toBe(false);
126
- });
127
-
128
- test("no option, no or other env value → true", () => {
129
- expect(resolveStylesheetWatch({}, {})).toBe(true);
130
- expect(resolveStylesheetWatch({}, { [STYLESHEET_WATCH_ENV]: "1" })).toBe(true);
131
- });
132
- });
@@ -1,38 +0,0 @@
1
- // runDevApp muss denselben Boot-Validator wie runProdApp ausführen (#359):
2
- // eine ganze Fehlerklasse (unqualifizierte nav-/handler-QNs, unauflösbare
3
- // navigate-Targets, screen-access) passierte früher den Dev-Server still und
4
- // crashte erst den Prod-Pod im CrashLoopBackOff. Hier: ein Feature mit einem
5
- // rowAction-navigate auf einen nie registrierten Screen — runDevApp muss
6
- // SYNCHRON beim Boot werfen, bevor ein Port gebunden oder der codegen-Watcher
7
- // gestartet wird (validateBoot läuft vor watchAndRegenerate).
8
-
9
- import { describe, expect, test } from "bun:test";
10
- import { createEntity, createTextField, defineFeature } from "@cosmicdrift/kumiko-framework/engine";
11
- import { runDevApp } from "../run-dev-app";
12
-
13
- function unresolvableNavFeature() {
14
- return defineFeature("shop", (r) => {
15
- r.entity(
16
- "product",
17
- createEntity({
18
- fields: { name: createTextField({ personal: false, reason: "test_fixture" }) },
19
- }),
20
- );
21
- r.screen({
22
- id: "product-list",
23
- type: "entityList",
24
- entity: "product",
25
- columns: ["name"],
26
- // "ghost-screen" wird nie via r.screen registriert → validateBoot wirft.
27
- rowActions: [{ kind: "navigate", id: "edit", label: "actions.edit", screen: "ghost-screen" }],
28
- });
29
- });
30
- }
31
-
32
- describe("runDevApp boot-validation (#359)", () => {
33
- test("unresolvable navigate-target throws at boot — dev/prod parity, no port bound", async () => {
34
- await expect(runDevApp({ features: [unresolvableNavFeature()] })).rejects.toThrow(
35
- /navigate-target "ghost-screen" does not resolve/,
36
- );
37
- });
38
- });
@@ -1,290 +0,0 @@
1
- // The origin-guard config (allowedOrigins / unsafeSkipOriginCheck) is forwarded
2
- // from runDevApp's auth options through to the server exactly like runProdApp
3
- // (#399/1). The guard fires during server build — AFTER the ephemeral test DB is
4
- // up — so this is an integration test (needs TEST_DATABASE_URL), mirroring the
5
- // run-prod-app forwarding pair. Without it a typo or wrong spread-key on the dev
6
- // path would silently drop the fail-closed guard and dev/prod would diverge.
7
-
8
- import { afterEach, beforeEach, describe, expect, spyOn, test } from "bun:test";
9
- import { authFoundationFeature } from "@cosmicdrift/kumiko-bundled-features/auth-foundation";
10
- import { createPersonalAccessTokensFeature } from "@cosmicdrift/kumiko-bundled-features/personal-access-tokens";
11
- import { createSessionsFeature } from "@cosmicdrift/kumiko-bundled-features/sessions";
12
- import {
13
- requireTemplateResolver,
14
- TEXT_BLOCK_KIND,
15
- type TemplateResolverApi,
16
- } from "@cosmicdrift/kumiko-bundled-features/template-resolver";
17
- import type { AnonymousExtraRoute } from "@cosmicdrift/kumiko-framework/api";
18
- import {
19
- createEntity,
20
- createTextField,
21
- defineFeature,
22
- defineQueryHandler,
23
- type TenantId,
24
- } from "@cosmicdrift/kumiko-framework/engine";
25
- import { TestUsers } from "@cosmicdrift/kumiko-framework/stack";
26
- import * as jose from "jose";
27
- import * as z from "zod";
28
- import type { KumikoServerHandle } from "../create-kumiko-server";
29
- import { runDevApp } from "../run-dev-app";
30
-
31
- function validFeature() {
32
- return defineFeature("shop", (r) => {
33
- r.entity(
34
- "product",
35
- createEntity({
36
- fields: { name: createTextField({ personal: false, reason: "test_fixture" }) },
37
- }),
38
- );
39
- });
40
- }
41
-
42
- const ADMIN = {
43
- email: "origin-guard@example.eu",
44
- password: "test-pw-strong-1234",
45
- displayName: "Admin",
46
- memberships: [],
47
- };
48
-
49
- let handle: KumikoServerHandle | undefined;
50
- const savedJwtSecret = process.env["JWT_SECRET"];
51
-
52
- beforeEach(() => {
53
- process.env["JWT_SECRET"] = "test-rundev-secret-32-chars-min!!!!";
54
- });
55
-
56
- afterEach(async () => {
57
- await handle?.stop();
58
- handle = undefined;
59
- if (savedJwtSecret === undefined) delete process.env["JWT_SECRET"];
60
- else process.env["JWT_SECRET"] = savedJwtSecret;
61
- });
62
-
63
- describe("runDevApp — auth.mail fail-fast on missing JWT_SECRET", () => {
64
- test("options.auth set but JWT_SECRET missing → throws, no dev-fallback", async () => {
65
- // beforeEach sets JWT_SECRET unconditionally for every OTHER test in this
66
- // file — none of them ever exercise the missing-secret path, so it's
67
- // unclear whether the fail-closed guard (requireEnv, not the old dev-
68
- // fallback) actually fires or was silently dropped. Pass a custom
69
- // envSource without JWT_SECRET instead of touching process.env.
70
- const { JWT_SECRET: _drop, ...envWithoutJwtSecret } = process.env;
71
- await expect(
72
- runDevApp({
73
- features: [validFeature()],
74
- auth: { admin: ADMIN, cookieDomain: "example.eu", allowedOrigins: ["https://example.eu"] },
75
- envSource: envWithoutJwtSecret,
76
- }),
77
- ).rejects.toThrow(/JWT_SECRET/);
78
- });
79
- });
80
-
81
- describe("runDevApp — auth allowedOrigins forwarding (#399/1)", () => {
82
- test("cookieDomain without allowedOrigins fails closed — guard is wired through runDevApp", async () => {
83
- await expect(
84
- runDevApp({
85
- features: [validFeature()],
86
- auth: { admin: ADMIN, cookieDomain: "example.eu" },
87
- }),
88
- ).rejects.toThrow(/allowedOrigins is empty/);
89
- });
90
-
91
- test("cookieDomain + allowedOrigins clears the guard — allowlist reaches the server", async () => {
92
- // Without the forwarding fix this would ALSO throw /allowedOrigins is empty/.
93
- handle = await runDevApp({
94
- features: [validFeature()],
95
- // Port 0 → OS-assigned ephemeral port, no fixed-port collision in CI.
96
- port: 0,
97
- auth: {
98
- admin: ADMIN,
99
- cookieDomain: "example.eu",
100
- allowedOrigins: ["https://app.example.eu"],
101
- },
102
- });
103
- // A booted handle that did not throw on the origin guard IS the forwarding
104
- // proof — the allowlist reached the server build.
105
- expect(handle).toBeDefined();
106
- });
107
-
108
- test("cookieDomain + unsafeSkipOriginCheck clears the guard without an allowlist", async () => {
109
- // The escape hatch must also forward: cookieDomain alone fails closed
110
- // (first test), but unsafeSkipOriginCheck=true bypasses it. A dropped/
111
- // mis-spread key would let the guard fire → /allowedOrigins is empty/.
112
- handle = await runDevApp({
113
- features: [validFeature()],
114
- port: 0,
115
- auth: {
116
- admin: ADMIN,
117
- cookieDomain: "example.eu",
118
- unsafeSkipOriginCheck: true,
119
- },
120
- });
121
- expect(handle).toBeDefined();
122
- });
123
- });
124
-
125
- describe("runDevApp — trustedProxyHops env fallback (KUMIKO_TRUSTED_PROXY_HOPS)", () => {
126
- const clientIpRoute: AnonymousExtraRoute = {
127
- method: "GET",
128
- path: "/client-ip-probe",
129
- entry: "anonymous",
130
- handler: async (c, deps) => c.json({ clientIp: deps.clientIp }),
131
- };
132
-
133
- test("no trustedProxyHops option, env var set → env value reaches the server's client-ip resolver", async () => {
134
- handle = await runDevApp({
135
- features: [validFeature()],
136
- port: 0,
137
- extraRoutes: [clientIpRoute],
138
- envSource: { ...process.env, KUMIKO_TRUSTED_PROXY_HOPS: "1" },
139
- });
140
- const res = await handle.fetch(
141
- new Request("http://test/client-ip-probe", {
142
- headers: { "x-forwarded-for": "203.0.113.10" },
143
- }),
144
- );
145
- expect(await res.json()).toEqual({ clientIp: "203.0.113.10" });
146
- });
147
-
148
- test("explicit trustedProxyHops option wins over the env var", async () => {
149
- handle = await runDevApp({
150
- features: [validFeature()],
151
- port: 0,
152
- trustedProxyHops: 0,
153
- extraRoutes: [clientIpRoute],
154
- envSource: { ...process.env, KUMIKO_TRUSTED_PROXY_HOPS: "1" },
155
- });
156
- const res = await handle.fetch(
157
- new Request("http://test/client-ip-probe", {
158
- headers: { "x-forwarded-for": "203.0.113.10" },
159
- }),
160
- );
161
- expect(await res.json()).toEqual({ clientIp: "unknown" });
162
- });
163
- });
164
-
165
- describe("runDevApp — extraContext merge order: app values win over boot defaults (707/1)", () => {
166
- test("a caller-supplied extraContext.templateResolver wins over the auto-wired boot default", async () => {
167
- // runDevApp's extraContext factory does `{ ...boot, ...base }` — boot's
168
- // auto-wired templateResolver must lose to a caller-supplied override, exactly
169
- // the "app values win" parity claim this PR made against runProdApp. Every
170
- // OTHER test in this file only checks that runDevApp boots without
171
- // throwing; none dispatch a request that actually reads the merged value.
172
- const sentinel: TemplateResolverApi = {
173
- findExact: async () => ({
174
- id: "sentinel",
175
- version: 1,
176
- tenantId: TestUsers.systemAdmin.tenantId,
177
- slug: "sentinel",
178
- kind: TEXT_BLOCK_KIND,
179
- locale: "en",
180
- title: "from caller extraContext",
181
- folder: null,
182
- content: "",
183
- contentFormat: "markdown",
184
- variableSchema: {},
185
- linkedResources: {},
186
- scope: "system",
187
- parentTemplateId: null,
188
- status: "active",
189
- updatedAt: new Date(0),
190
- }),
191
- resolveTemplate: async () => {
192
- throw new Error("not used in this test");
193
- },
194
- };
195
- const readBlockQuery = defineQueryHandler({
196
- name: "read-block",
197
- schema: z.object({}),
198
- access: { openToAll: { reason: "test handler callable by any signed-in test user" } },
199
- handler: async (_query, ctx) => {
200
- const api = requireTemplateResolver(ctx, "textcheck:query:read-block");
201
- return api.findExact({
202
- tenantId: TestUsers.systemAdmin.tenantId,
203
- slug: "x",
204
- kind: TEXT_BLOCK_KIND,
205
- locale: "en",
206
- });
207
- },
208
- });
209
- const textcheckFeature = defineFeature("textcheck", (r) => {
210
- r.queryHandler(readBlockQuery);
211
- });
212
-
213
- handle = await runDevApp({
214
- features: [validFeature(), textcheckFeature],
215
- port: 0,
216
- extraContext: { templateResolver: sentinel },
217
- });
218
-
219
- const res = await handle.stack.http.queryOk<{ title: string } | null>(
220
- "textcheck:query:read-block",
221
- {},
222
- TestUsers.systemAdmin,
223
- );
224
-
225
- expect(res?.title).toBe("from caller extraContext");
226
- });
227
- });
228
-
229
- // runDevApp had no equivalent of runProdApp's session boot gate (#1262/#1275):
230
- // a forgotten sessions mount left session-list silently empty instead of
231
- // saying anything (#2027). Unlike prod, dev warns rather than aborts —
232
- // mirrors assertPiiBootInvariants' dev/prod split.
233
- describe("runDevApp — session boot gate (#2027)", () => {
234
- const ADMIN = {
235
- email: "session-gate-dev@example.eu",
236
- password: "test-pw-strong-1234",
237
- displayName: "Admin",
238
- emailVerified: true,
239
- memberships: [
240
- {
241
- tenantId: "00000000-0000-4000-8000-000000000002" as TenantId,
242
- tenantKey: "session-gate-dev",
243
- tenantName: "Session Gate Dev",
244
- roles: ["Admin"],
245
- },
246
- ],
247
- };
248
-
249
- test("auth mounted, sessions feature missing → warns but still boots (dev doesn't abort)", async () => {
250
- const warnSpy = spyOn(console, "warn").mockImplementation(() => {});
251
- try {
252
- handle = await runDevApp({
253
- features: [validFeature()],
254
- port: 0,
255
- auth: { admin: ADMIN },
256
- });
257
- expect(handle).toBeDefined();
258
- expect(warnSpy).toHaveBeenCalledWith(expect.stringContaining("sessionStore"));
259
- expect(warnSpy).toHaveBeenCalledWith(expect.stringContaining("[runDevApp]"));
260
- } finally {
261
- warnSpy.mockRestore();
262
- }
263
- });
264
-
265
- test("auth mounted, sessions feature mounted → login actually creates a session (sid on the JWT)", async () => {
266
- // The bug this issue reports: a mounted sessions feature whose sessionCreator
267
- // never reached the auth config, so login never got a `sid` and
268
- // store_user_sessions never got a row — session-list stayed empty with no
269
- // error anywhere. A boot that doesn't throw proves nothing about that; only
270
- // a real login + decoded JWT does.
271
- handle = await runDevApp({
272
- features: [
273
- authFoundationFeature,
274
- createPersonalAccessTokensFeature({ scopes: {} }),
275
- createSessionsFeature(),
276
- ],
277
- port: 0,
278
- auth: { admin: ADMIN },
279
- });
280
-
281
- const login = await handle.stack.http.raw("POST", "/api/auth/login", {
282
- email: ADMIN.email,
283
- password: ADMIN.password,
284
- });
285
- expect(login.status).toBe(200);
286
- const body = (await login.json()) as { token?: string };
287
- expect(body.token).toBeTypeOf("string");
288
- expect(typeof jose.decodeJwt(body.token!).jti).toBe("string");
289
- });
290
- });
@@ -1,26 +0,0 @@
1
- import { describe, expect, it } from "bun:test";
2
- import { readFileSync } from "node:fs";
3
- import { fileURLToPath } from "node:url";
4
-
5
- // Mirrors infra/guards/runtime-isolation-classify.ts `classifyByDirective`
6
- // as of kumiko-framework#2337 (first 600 bytes, first 8 lines,
7
- // `// @runtime <kind>`) — guards against a reformat of compose-stacks.ts
8
- // pushing the directive out of that window. Doesn't catch infra changing
9
- // the window/regex itself; that lives in the infra repo's own tests.
10
- const ALL_RUNTIMES = new Set(["runtime", "client", "dev", "tooling", "test"]);
11
-
12
- function classifyByDirective(filePath: string): string | null {
13
- const head = readFileSync(filePath, "utf8").slice(0, 600);
14
- for (const line of head.split("\n").slice(0, 8)) {
15
- const match = line.match(/\/\/\s*@runtime\s+(\w+)/);
16
- if (match && ALL_RUNTIMES.has(match[1] ?? "")) return match[1] ?? null;
17
- }
18
- return null;
19
- }
20
-
21
- describe("compose-stacks.ts runtime directive", () => {
22
- it("carries a @runtime runtime directive the guard's classifyByDirective will find", () => {
23
- const composeStacksPath = fileURLToPath(new URL("../compose-stacks.ts", import.meta.url));
24
- expect(classifyByDirective(composeStacksPath)).toBe("runtime");
25
- });
26
- });