@cosmicdrift/kumiko-dev-server 0.174.1 → 0.176.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 +4 -4
- package/src/__tests__/compose-stacks.test.ts +9 -5
- package/src/__tests__/merge-extra-context.test.ts +6 -6
- package/src/__tests__/run-dev-app.integration.test.ts +32 -12
- package/src/compose-stacks.ts +6 -2
- package/src/run-dev-app.ts +2 -2
- package/src/setup-test-stack-from-features.ts +4 -4
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cosmicdrift/kumiko-dev-server",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.176.0",
|
|
4
4
|
"description": "Dev-tooling for Kumiko apps: local dev-server bootstrap (runDevApp), scaffolding, codegen. Not shipped into production node_modules — see @cosmicdrift/kumiko-server-runtime for the prod boot path.",
|
|
5
5
|
"license": "BUSL-1.1",
|
|
6
6
|
"author": "Marc Frost <marc@cosmicdriftgamestudio.com>",
|
|
@@ -54,9 +54,9 @@
|
|
|
54
54
|
"kumiko-schema-check": "./bin/kumiko-schema-check.ts"
|
|
55
55
|
},
|
|
56
56
|
"dependencies": {
|
|
57
|
-
"@cosmicdrift/kumiko-bundled-features": "0.
|
|
58
|
-
"@cosmicdrift/kumiko-framework": "0.
|
|
59
|
-
"@cosmicdrift/kumiko-server-runtime": "0.
|
|
57
|
+
"@cosmicdrift/kumiko-bundled-features": "0.176.0",
|
|
58
|
+
"@cosmicdrift/kumiko-framework": "0.176.0",
|
|
59
|
+
"@cosmicdrift/kumiko-server-runtime": "0.176.0",
|
|
60
60
|
"ts-morph": "^28.0.0"
|
|
61
61
|
},
|
|
62
62
|
"publishConfig": {
|
|
@@ -29,8 +29,8 @@ describe("composeStacks", () => {
|
|
|
29
29
|
]);
|
|
30
30
|
});
|
|
31
31
|
|
|
32
|
-
test("composePagesStack →
|
|
33
|
-
expect(stackFeatureNames(composePagesStack())).toEqual(["
|
|
32
|
+
test("composePagesStack → legal-pages (template-resolver comes from the renderer stack)", () => {
|
|
33
|
+
expect(stackFeatureNames(composePagesStack())).toEqual(["legal-pages"]);
|
|
34
34
|
});
|
|
35
35
|
|
|
36
36
|
test("composeMailStack mounts foundation + selected transports", () => {
|
|
@@ -134,6 +134,8 @@ describe("composeStacks boots for real", () => {
|
|
|
134
134
|
createPersonalAccessTokensFeature({ scopes: {} }),
|
|
135
135
|
...composeOpsStack({ rateLimiting: true }),
|
|
136
136
|
...composePagesStack(),
|
|
137
|
+
// legal-pages requires template-resolver, which ships in the renderer stack.
|
|
138
|
+
...composeRendererStack(),
|
|
137
139
|
...composeMailStack({ transports: ["inmemory"] }),
|
|
138
140
|
...composeFileStack({ providers: ["inmemory"] }),
|
|
139
141
|
...composeGdprStack({ order: "compliance-first", sessions: true }),
|
|
@@ -192,6 +194,8 @@ describe("composeStacks intended block names (snapshot, not live parity)", () =>
|
|
|
192
194
|
const names = stackFeatureNames([
|
|
193
195
|
...composeOpsStack({ rateLimiting: true }),
|
|
194
196
|
...composePagesStack(),
|
|
197
|
+
// Pages content sits on template-resolver, which the renderer stack mounts.
|
|
198
|
+
...composeRendererStack(),
|
|
195
199
|
...composeMailStack({ transports: ["inmemory", "smtp"] }),
|
|
196
200
|
...composeFileStack({ providers: ["inmemory", "s3"] }),
|
|
197
201
|
...composeGdprStack({ order: "compliance-first" }),
|
|
@@ -202,7 +206,7 @@ describe("composeStacks intended block names (snapshot, not live parity)", () =>
|
|
|
202
206
|
"audit",
|
|
203
207
|
"jobs",
|
|
204
208
|
"rate-limiting",
|
|
205
|
-
"
|
|
209
|
+
"template-resolver",
|
|
206
210
|
"legal-pages",
|
|
207
211
|
"mail-foundation",
|
|
208
212
|
"file-foundation",
|
|
@@ -226,7 +230,7 @@ describe("composeStacks intended block names (snapshot, not live parity)", () =>
|
|
|
226
230
|
...composeUserDataRightsStack(),
|
|
227
231
|
]);
|
|
228
232
|
for (const expected of [
|
|
229
|
-
"
|
|
233
|
+
"template-resolver",
|
|
230
234
|
"legal-pages",
|
|
231
235
|
"data-retention",
|
|
232
236
|
"compliance-profiles",
|
|
@@ -250,7 +254,7 @@ describe("composeStacks intended block names (snapshot, not live parity)", () =>
|
|
|
250
254
|
...composeUserDataRightsStack(),
|
|
251
255
|
]);
|
|
252
256
|
for (const expected of [
|
|
253
|
-
"
|
|
257
|
+
"template-resolver",
|
|
254
258
|
"legal-pages",
|
|
255
259
|
"tenant-lifecycle",
|
|
256
260
|
"audit",
|
|
@@ -3,7 +3,7 @@ import { mergeExtraContext } from "../setup-test-stack-from-features";
|
|
|
3
3
|
|
|
4
4
|
// #882/4: the integration test only asserted getFeature("config") — which
|
|
5
5
|
// includeBundled already provides — never that presets:["config"] actually
|
|
6
|
-
// merges configResolver/
|
|
6
|
+
// merges configResolver/templateResolver into extraContext. Unit-test the merge
|
|
7
7
|
// directly instead.
|
|
8
8
|
|
|
9
9
|
const fakeDeps = {
|
|
@@ -41,16 +41,16 @@ describe("mergeExtraContext", () => {
|
|
|
41
41
|
expect(result["configResolver"]).toBeDefined();
|
|
42
42
|
});
|
|
43
43
|
|
|
44
|
-
test("
|
|
45
|
-
const merged = mergeExtraContext(undefined, ["
|
|
44
|
+
test("template-resolver preset merges templateResolver, built from deps.db", () => {
|
|
45
|
+
const merged = mergeExtraContext(undefined, ["template-resolver"]);
|
|
46
46
|
const result = (merged as (deps: typeof fakeDeps) => Record<string, unknown>)(fakeDeps);
|
|
47
|
-
expect(result["
|
|
47
|
+
expect(result["templateResolver"]).toBeDefined();
|
|
48
48
|
});
|
|
49
49
|
|
|
50
50
|
test("both presets merge both fields", () => {
|
|
51
|
-
const merged = mergeExtraContext(undefined, ["config", "
|
|
51
|
+
const merged = mergeExtraContext(undefined, ["config", "template-resolver"]);
|
|
52
52
|
const result = (merged as (deps: typeof fakeDeps) => Record<string, unknown>)(fakeDeps);
|
|
53
53
|
expect(result["configResolver"]).toBeDefined();
|
|
54
|
-
expect(result["
|
|
54
|
+
expect(result["templateResolver"]).toBeDefined();
|
|
55
55
|
});
|
|
56
56
|
});
|
|
@@ -7,9 +7,10 @@
|
|
|
7
7
|
|
|
8
8
|
import { afterEach, beforeEach, describe, expect, test } from "bun:test";
|
|
9
9
|
import {
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
10
|
+
requireTemplateResolver,
|
|
11
|
+
TEXT_BLOCK_KIND,
|
|
12
|
+
type TemplateResolverApi,
|
|
13
|
+
} from "@cosmicdrift/kumiko-bundled-features/template-resolver";
|
|
13
14
|
import {
|
|
14
15
|
createEntity,
|
|
15
16
|
createTextField,
|
|
@@ -111,28 +112,47 @@ describe("runDevApp — auth allowedOrigins forwarding (#399/1)", () => {
|
|
|
111
112
|
});
|
|
112
113
|
|
|
113
114
|
describe("runDevApp — extraContext merge order: app values win over boot defaults (707/1)", () => {
|
|
114
|
-
test("a caller-supplied extraContext.
|
|
115
|
+
test("a caller-supplied extraContext.templateResolver wins over the auto-wired boot default", async () => {
|
|
115
116
|
// runDevApp's extraContext factory does `{ ...boot, ...base }` — boot's
|
|
116
|
-
// auto-wired
|
|
117
|
+
// auto-wired templateResolver must lose to a caller-supplied override, exactly
|
|
117
118
|
// the "app values win" parity claim this PR made against runProdApp. Every
|
|
118
119
|
// OTHER test in this file only checks that runDevApp boots without
|
|
119
120
|
// throwing; none dispatch a request that actually reads the merged value.
|
|
120
|
-
const sentinel:
|
|
121
|
-
|
|
121
|
+
const sentinel: TemplateResolverApi = {
|
|
122
|
+
findExact: async () => ({
|
|
123
|
+
id: "sentinel",
|
|
124
|
+
version: 1,
|
|
125
|
+
tenantId: TestUsers.systemAdmin.tenantId,
|
|
122
126
|
slug: "sentinel",
|
|
123
|
-
|
|
127
|
+
kind: TEXT_BLOCK_KIND,
|
|
128
|
+
locale: "en",
|
|
124
129
|
title: "from caller extraContext",
|
|
125
|
-
|
|
130
|
+
folder: null,
|
|
131
|
+
content: "",
|
|
132
|
+
contentFormat: "markdown",
|
|
133
|
+
variableSchema: {},
|
|
134
|
+
linkedResources: {},
|
|
135
|
+
scope: "system",
|
|
136
|
+
parentTemplateId: null,
|
|
137
|
+
status: "active",
|
|
126
138
|
updatedAt: new Date(0),
|
|
127
139
|
}),
|
|
140
|
+
resolveTemplate: async () => {
|
|
141
|
+
throw new Error("not used in this test");
|
|
142
|
+
},
|
|
128
143
|
};
|
|
129
144
|
const readBlockQuery = defineQueryHandler({
|
|
130
145
|
name: "read-block",
|
|
131
146
|
schema: z.object({}),
|
|
132
147
|
access: { openToAll: true },
|
|
133
148
|
handler: async (_query, ctx) => {
|
|
134
|
-
const api =
|
|
135
|
-
return api.
|
|
149
|
+
const api = requireTemplateResolver(ctx, "textcheck:query:read-block");
|
|
150
|
+
return api.findExact({
|
|
151
|
+
tenantId: TestUsers.systemAdmin.tenantId,
|
|
152
|
+
slug: "x",
|
|
153
|
+
kind: TEXT_BLOCK_KIND,
|
|
154
|
+
locale: "en",
|
|
155
|
+
});
|
|
136
156
|
},
|
|
137
157
|
});
|
|
138
158
|
const textcheckFeature = defineFeature("textcheck", (r) => {
|
|
@@ -142,7 +162,7 @@ describe("runDevApp — extraContext merge order: app values win over boot defau
|
|
|
142
162
|
handle = await runDevApp({
|
|
143
163
|
features: [validFeature(), textcheckFeature],
|
|
144
164
|
port: 0,
|
|
145
|
-
extraContext: {
|
|
165
|
+
extraContext: { templateResolver: sentinel },
|
|
146
166
|
});
|
|
147
167
|
|
|
148
168
|
const res = await handle.stack.http.queryOk<{ title: string } | null>(
|
package/src/compose-stacks.ts
CHANGED
|
@@ -30,7 +30,7 @@ import { createRendererSimpleFeature } from "@cosmicdrift/kumiko-bundled-feature
|
|
|
30
30
|
import { createSessionsFeature } from "@cosmicdrift/kumiko-bundled-features/sessions";
|
|
31
31
|
import { createTemplateResolverFeature } from "@cosmicdrift/kumiko-bundled-features/template-resolver";
|
|
32
32
|
import { createTenantLifecycleFeature } from "@cosmicdrift/kumiko-bundled-features/tenant-lifecycle";
|
|
33
|
-
|
|
33
|
+
|
|
34
34
|
import {
|
|
35
35
|
createUserDataRightsFeature,
|
|
36
36
|
type UserDataRightsOptions,
|
|
@@ -98,8 +98,12 @@ export function stackFeatureNames(features: readonly FeatureDefinition[]): strin
|
|
|
98
98
|
}
|
|
99
99
|
|
|
100
100
|
export function composePagesStack(options: PagesStackOptions = {}): FeatureDefinition[] {
|
|
101
|
+
// Pages content lives in template-resolver (kind text-block), which the
|
|
102
|
+
// renderer stack already mounts — an app composing pages WITHOUT the renderer
|
|
103
|
+
// stack has to add createTemplateResolverFeature() itself, or legal-pages'
|
|
104
|
+
// r.requires fails the boot. Mounting it here instead would collide with
|
|
105
|
+
// composeRendererStack (createRegistry rejects duplicate features).
|
|
101
106
|
return [
|
|
102
|
-
createTextContentFeature(),
|
|
103
107
|
createLegalPagesFeature(
|
|
104
108
|
options.wrapLayout !== undefined ? { wrapLayout: options.wrapLayout } : {},
|
|
105
109
|
),
|
package/src/run-dev-app.ts
CHANGED
|
@@ -204,7 +204,7 @@ export type RunDevAppOptions = {
|
|
|
204
204
|
/** Eigene Seed-Funktionen, laufen nach dem Admin (wenn auth) in
|
|
205
205
|
* Array-Reihenfolge. */
|
|
206
206
|
readonly seeds?: readonly SeedFn[];
|
|
207
|
-
/** Extra-AppContext-Keys. `
|
|
207
|
+
/** Extra-AppContext-Keys. `templateResolver` (immer) + `secrets` (wenn das
|
|
208
208
|
* secrets-Feature gemountet ist) + `configResolver` (auth-mode) werden
|
|
209
209
|
* automatisch ergänzt — App-Werte gewinnen. Symmetrisch zu runProdApp. */
|
|
210
210
|
readonly extraContext?: CreateKumikoServerOptions["extraContext"];
|
|
@@ -379,7 +379,7 @@ export async function runDevApp(options: RunDevAppOptions): Promise<KumikoServer
|
|
|
379
379
|
bootCrypto.configCipher,
|
|
380
380
|
)
|
|
381
381
|
: options.extraContext;
|
|
382
|
-
// Auto-wire
|
|
382
|
+
// Auto-wire templateResolver (immer) + secrets (feature-gated), symmetrisch zu
|
|
383
383
|
// runProdApp. Anders als prod existiert die db hier erst im Factory-deps
|
|
384
384
|
// (createKumikoServer baut den Stack), darum als Factory: buildBootExtraContext
|
|
385
385
|
// mit hasAuth:false (configResolver kommt schon aus cfgExtra), App-Werte
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
// Apps pass the same feature list as run-config (APP_FEATURES / buildAppFeatures).
|
|
3
3
|
|
|
4
4
|
import { createConfigResolver } from "@cosmicdrift/kumiko-bundled-features/config";
|
|
5
|
-
import {
|
|
5
|
+
import { createTemplateResolverApi } from "@cosmicdrift/kumiko-bundled-features/template-resolver";
|
|
6
6
|
import type { FeatureDefinition } from "@cosmicdrift/kumiko-framework/engine";
|
|
7
7
|
import {
|
|
8
8
|
setupTestStack,
|
|
@@ -14,7 +14,7 @@ import {
|
|
|
14
14
|
composeFeatures,
|
|
15
15
|
} from "@cosmicdrift/kumiko-server-runtime/compose-features";
|
|
16
16
|
|
|
17
|
-
export type TestStackPreset = "config" | "
|
|
17
|
+
export type TestStackPreset = "config" | "template-resolver";
|
|
18
18
|
|
|
19
19
|
export type SetupTestStackFromFeaturesOptions = Omit<TestStackOptions, "features"> & {
|
|
20
20
|
readonly includeBundled?: boolean;
|
|
@@ -37,8 +37,8 @@ export function mergeExtraContext(
|
|
|
37
37
|
const configResolver = createConfigResolver();
|
|
38
38
|
merged["configResolver"] = configResolver;
|
|
39
39
|
}
|
|
40
|
-
if (presets.includes("
|
|
41
|
-
merged["
|
|
40
|
+
if (presets.includes("template-resolver")) {
|
|
41
|
+
merged["templateResolver"] = createTemplateResolverApi(deps.db);
|
|
42
42
|
}
|
|
43
43
|
|
|
44
44
|
return merged;
|