@cosmicdrift/kumiko-dev-server 0.129.0 → 0.130.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
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cosmicdrift/kumiko-dev-server",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.130.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,6 +30,14 @@
|
|
|
30
30
|
"types": "./src/compose-features.ts",
|
|
31
31
|
"default": "./src/compose-features.ts"
|
|
32
32
|
},
|
|
33
|
+
"./compose-stacks": {
|
|
34
|
+
"types": "./src/compose-stacks.ts",
|
|
35
|
+
"default": "./src/compose-stacks.ts"
|
|
36
|
+
},
|
|
37
|
+
"./setup-test-stack-from-features": {
|
|
38
|
+
"types": "./src/setup-test-stack-from-features.ts",
|
|
39
|
+
"default": "./src/setup-test-stack-from-features.ts"
|
|
40
|
+
},
|
|
33
41
|
"./schema-apply": {
|
|
34
42
|
"types": "./src/schema-apply.ts",
|
|
35
43
|
"default": "./src/schema-apply.ts"
|
|
@@ -50,8 +58,8 @@
|
|
|
50
58
|
"kumiko-schema-check": "./bin/kumiko-schema-check.ts"
|
|
51
59
|
},
|
|
52
60
|
"dependencies": {
|
|
53
|
-
"@cosmicdrift/kumiko-bundled-features": "0.
|
|
54
|
-
"@cosmicdrift/kumiko-framework": "0.
|
|
61
|
+
"@cosmicdrift/kumiko-bundled-features": "0.130.0",
|
|
62
|
+
"@cosmicdrift/kumiko-framework": "0.130.0",
|
|
55
63
|
"ts-morph": "^28.0.0"
|
|
56
64
|
},
|
|
57
65
|
"publishConfig": {
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
import { describe, expect, test } from "bun:test";
|
|
2
|
+
import {
|
|
3
|
+
composeFileStack,
|
|
4
|
+
composeGdprStack,
|
|
5
|
+
composeMailStack,
|
|
6
|
+
composeOpsStack,
|
|
7
|
+
composePagesStack,
|
|
8
|
+
composeRendererStack,
|
|
9
|
+
composeUserDataRightsStack,
|
|
10
|
+
stackFeatureNames,
|
|
11
|
+
} from "../compose-stacks";
|
|
12
|
+
|
|
13
|
+
describe("composeStacks", () => {
|
|
14
|
+
test("composeRendererStack → template-resolver, renderer-foundation, renderer-simple", () => {
|
|
15
|
+
expect(stackFeatureNames(composeRendererStack())).toEqual([
|
|
16
|
+
"template-resolver",
|
|
17
|
+
"renderer-foundation",
|
|
18
|
+
"renderer-simple",
|
|
19
|
+
]);
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
test("composePagesStack → text-content + legal-pages", () => {
|
|
23
|
+
expect(stackFeatureNames(composePagesStack())).toEqual(["text-content", "legal-pages"]);
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
test("composeMailStack mounts foundation + selected transports", () => {
|
|
27
|
+
expect(stackFeatureNames(composeMailStack({ transports: ["inmemory", "smtp"] }))).toEqual([
|
|
28
|
+
"mail-foundation",
|
|
29
|
+
"mail-transport-inmemory",
|
|
30
|
+
"mail-transport-smtp",
|
|
31
|
+
]);
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
test("composeFileStack mounts foundation + providers + files entity", () => {
|
|
35
|
+
expect(stackFeatureNames(composeFileStack({ providers: ["inmemory", "s3"] }))).toEqual([
|
|
36
|
+
"file-foundation",
|
|
37
|
+
"file-provider-inmemory",
|
|
38
|
+
"file-provider-s3",
|
|
39
|
+
"files",
|
|
40
|
+
]);
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
test("composeGdprStack retention-first (money-horse / publicstatus pattern)", () => {
|
|
44
|
+
expect(stackFeatureNames(composeGdprStack({ tenantLifecycle: true, sessions: true }))).toEqual([
|
|
45
|
+
"data-retention",
|
|
46
|
+
"compliance-profiles",
|
|
47
|
+
"tenant-lifecycle",
|
|
48
|
+
"sessions",
|
|
49
|
+
]);
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
test("composeGdprStack compliance-first (studio pattern)", () => {
|
|
53
|
+
expect(stackFeatureNames(composeGdprStack({ order: "compliance-first" }))).toEqual([
|
|
54
|
+
"compliance-profiles",
|
|
55
|
+
"data-retention",
|
|
56
|
+
]);
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
test("composeUserDataRightsStack → udr + defaults", () => {
|
|
60
|
+
expect(stackFeatureNames(composeUserDataRightsStack())).toEqual([
|
|
61
|
+
"user-data-rights",
|
|
62
|
+
"user-data-rights-defaults",
|
|
63
|
+
]);
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
test("composeOpsStack defaults delivery + audit + jobs", () => {
|
|
67
|
+
expect(stackFeatureNames(composeOpsStack())).toEqual(["delivery", "audit", "jobs"]);
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
test("composeOpsStack rateLimiting opt-in", () => {
|
|
71
|
+
expect(stackFeatureNames(composeOpsStack({ rateLimiting: true }))).toEqual([
|
|
72
|
+
"delivery",
|
|
73
|
+
"audit",
|
|
74
|
+
"jobs",
|
|
75
|
+
"rate-limiting",
|
|
76
|
+
]);
|
|
77
|
+
});
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
/** Parity: preset name-sets match blocks used in studio / money-horse / publicstatus run-config. */
|
|
81
|
+
describe("composeStacks parity (feature names)", () => {
|
|
82
|
+
test("studio SaaS blocks are covered by presets", () => {
|
|
83
|
+
const names = stackFeatureNames([
|
|
84
|
+
...composeOpsStack({ rateLimiting: true }),
|
|
85
|
+
...composePagesStack(),
|
|
86
|
+
...composeMailStack({ transports: ["inmemory", "smtp"] }),
|
|
87
|
+
...composeFileStack({ providers: ["inmemory", "s3"] }),
|
|
88
|
+
...composeGdprStack({ order: "compliance-first" }),
|
|
89
|
+
...composeUserDataRightsStack(),
|
|
90
|
+
]);
|
|
91
|
+
for (const expected of [
|
|
92
|
+
"delivery",
|
|
93
|
+
"audit",
|
|
94
|
+
"jobs",
|
|
95
|
+
"rate-limiting",
|
|
96
|
+
"text-content",
|
|
97
|
+
"legal-pages",
|
|
98
|
+
"mail-foundation",
|
|
99
|
+
"file-foundation",
|
|
100
|
+
"files",
|
|
101
|
+
"compliance-profiles",
|
|
102
|
+
"data-retention",
|
|
103
|
+
"user-data-rights",
|
|
104
|
+
"user-data-rights-defaults",
|
|
105
|
+
]) {
|
|
106
|
+
expect(names).toContain(expected);
|
|
107
|
+
}
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
test("money-horse GDPR + file block names", () => {
|
|
111
|
+
const names = stackFeatureNames([
|
|
112
|
+
...composePagesStack(),
|
|
113
|
+
...composeRendererStack(),
|
|
114
|
+
...composeOpsStack({ delivery: true, audit: false, jobs: false }),
|
|
115
|
+
...composeGdprStack({ sessions: true }),
|
|
116
|
+
...composeFileStack({ providers: ["s3-env"] }),
|
|
117
|
+
...composeUserDataRightsStack(),
|
|
118
|
+
]);
|
|
119
|
+
for (const expected of [
|
|
120
|
+
"text-content",
|
|
121
|
+
"legal-pages",
|
|
122
|
+
"data-retention",
|
|
123
|
+
"compliance-profiles",
|
|
124
|
+
"sessions",
|
|
125
|
+
"file-foundation",
|
|
126
|
+
"file-provider-s3-env",
|
|
127
|
+
"files",
|
|
128
|
+
"user-data-rights",
|
|
129
|
+
]) {
|
|
130
|
+
expect(names).toContain(expected);
|
|
131
|
+
}
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
test("publicstatus core stack block names", () => {
|
|
135
|
+
const names = stackFeatureNames([
|
|
136
|
+
...composePagesStack(),
|
|
137
|
+
...composeGdprStack({ tenantLifecycle: true }),
|
|
138
|
+
...composeOpsStack({ delivery: true, audit: true, jobs: true, sessions: false }),
|
|
139
|
+
...composeRendererStack(),
|
|
140
|
+
...composeFileStack({ providers: ["inmemory"] }),
|
|
141
|
+
...composeUserDataRightsStack(),
|
|
142
|
+
]);
|
|
143
|
+
for (const expected of [
|
|
144
|
+
"text-content",
|
|
145
|
+
"legal-pages",
|
|
146
|
+
"tenant-lifecycle",
|
|
147
|
+
"audit",
|
|
148
|
+
"delivery",
|
|
149
|
+
"jobs",
|
|
150
|
+
"renderer-simple",
|
|
151
|
+
"files",
|
|
152
|
+
]) {
|
|
153
|
+
expect(names).toContain(expected);
|
|
154
|
+
}
|
|
155
|
+
});
|
|
156
|
+
});
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { describe, expect, test } from "bun:test";
|
|
2
|
+
import { defineFeature } from "@cosmicdrift/kumiko-framework/engine";
|
|
3
|
+
import { setupTestStackFromFeatures } from "../setup-test-stack-from-features";
|
|
4
|
+
|
|
5
|
+
const noopFeature = defineFeature("noop-app", () => {});
|
|
6
|
+
|
|
7
|
+
describe("setupTestStackFromFeatures", () => {
|
|
8
|
+
test("includeBundled prepends config/user/tenant/auth", async () => {
|
|
9
|
+
const stack = await setupTestStackFromFeatures([noopFeature], { includeBundled: true });
|
|
10
|
+
try {
|
|
11
|
+
for (const name of ["config", "user", "tenant", "auth-email-password", "noop-app"]) {
|
|
12
|
+
expect(stack.registry.getFeature(name)).toBeDefined();
|
|
13
|
+
}
|
|
14
|
+
} finally {
|
|
15
|
+
await stack.cleanup();
|
|
16
|
+
}
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
test("config preset wires configResolver in extraContext", async () => {
|
|
20
|
+
const stack = await setupTestStackFromFeatures([noopFeature], {
|
|
21
|
+
includeBundled: true,
|
|
22
|
+
presets: ["config"],
|
|
23
|
+
});
|
|
24
|
+
try {
|
|
25
|
+
expect(stack.registry.getFeature("config")).toBeDefined();
|
|
26
|
+
} finally {
|
|
27
|
+
await stack.cleanup();
|
|
28
|
+
}
|
|
29
|
+
});
|
|
30
|
+
});
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
// Composable SaaS stack presets — explicit opt-in blocks for run-config.ts.
|
|
2
|
+
// Presets export feature instances only; no implicit PAT scopes, anonymousAccess,
|
|
3
|
+
// or tier maps (those stay in bin/server.ts / app run-config).
|
|
4
|
+
|
|
5
|
+
import { createAuditFeature } from "@cosmicdrift/kumiko-bundled-features/audit";
|
|
6
|
+
import { createComplianceProfilesFeature } from "@cosmicdrift/kumiko-bundled-features/compliance-profiles";
|
|
7
|
+
import { createDataRetentionFeature } from "@cosmicdrift/kumiko-bundled-features/data-retention";
|
|
8
|
+
import { createDeliveryFeature } from "@cosmicdrift/kumiko-bundled-features/delivery";
|
|
9
|
+
import { fileFoundationFeature } from "@cosmicdrift/kumiko-bundled-features/file-foundation";
|
|
10
|
+
import { fileProviderInMemoryFeature } from "@cosmicdrift/kumiko-bundled-features/file-provider-inmemory";
|
|
11
|
+
import { fileProviderS3Feature } from "@cosmicdrift/kumiko-bundled-features/file-provider-s3";
|
|
12
|
+
import { fileProviderS3EnvFeature } from "@cosmicdrift/kumiko-bundled-features/file-provider-s3-env";
|
|
13
|
+
import { createFilesFeature } from "@cosmicdrift/kumiko-bundled-features/files";
|
|
14
|
+
import { createJobsFeature } from "@cosmicdrift/kumiko-bundled-features/jobs";
|
|
15
|
+
import {
|
|
16
|
+
createLegalPagesFeature,
|
|
17
|
+
type LegalPagesOptions,
|
|
18
|
+
} from "@cosmicdrift/kumiko-bundled-features/legal-pages";
|
|
19
|
+
import { mailFoundationFeature } from "@cosmicdrift/kumiko-bundled-features/mail-foundation";
|
|
20
|
+
import { mailTransportInMemoryFeature } from "@cosmicdrift/kumiko-bundled-features/mail-transport-inmemory";
|
|
21
|
+
import { mailTransportSmtpFeature } from "@cosmicdrift/kumiko-bundled-features/mail-transport-smtp";
|
|
22
|
+
import { createRateLimitingFeature } from "@cosmicdrift/kumiko-bundled-features/rate-limiting";
|
|
23
|
+
import { createRendererFoundationFeature } from "@cosmicdrift/kumiko-bundled-features/renderer-foundation";
|
|
24
|
+
import { createRendererSimpleFeature } from "@cosmicdrift/kumiko-bundled-features/renderer-simple";
|
|
25
|
+
import { createSessionsFeature } from "@cosmicdrift/kumiko-bundled-features/sessions";
|
|
26
|
+
import { createTemplateResolverFeature } from "@cosmicdrift/kumiko-bundled-features/template-resolver";
|
|
27
|
+
import { createTenantLifecycleFeature } from "@cosmicdrift/kumiko-bundled-features/tenant-lifecycle";
|
|
28
|
+
import { createTextContentFeature } from "@cosmicdrift/kumiko-bundled-features/text-content";
|
|
29
|
+
import {
|
|
30
|
+
createUserDataRightsFeature,
|
|
31
|
+
type UserDataRightsOptions,
|
|
32
|
+
} from "@cosmicdrift/kumiko-bundled-features/user-data-rights";
|
|
33
|
+
import { createUserDataRightsDefaultsFeature } from "@cosmicdrift/kumiko-bundled-features/user-data-rights-defaults";
|
|
34
|
+
import type { FeatureDefinition } from "@cosmicdrift/kumiko-framework/engine";
|
|
35
|
+
|
|
36
|
+
export type FileProviderKind = "inmemory" | "s3" | "s3-env";
|
|
37
|
+
export type MailTransportKind = "inmemory" | "smtp";
|
|
38
|
+
export type GdprStackOrder = "retention-first" | "compliance-first";
|
|
39
|
+
|
|
40
|
+
export type FileStackOptions = {
|
|
41
|
+
readonly providers: readonly FileProviderKind[];
|
|
42
|
+
readonly includeFilesFeature?: boolean;
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
export type MailStackOptions = {
|
|
46
|
+
readonly transports: readonly MailTransportKind[];
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
export type PagesStackOptions = {
|
|
50
|
+
readonly wrapLayout?: LegalPagesOptions["wrapLayout"];
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
export type GdprStackOptions = {
|
|
54
|
+
readonly order?: GdprStackOrder;
|
|
55
|
+
readonly sessions?: boolean;
|
|
56
|
+
readonly tenantLifecycle?: boolean;
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
export type UserDataRightsStackOptions = {
|
|
60
|
+
readonly userDataRights?: UserDataRightsOptions;
|
|
61
|
+
readonly includeDefaults?: boolean;
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
export type OpsStackOptions = {
|
|
65
|
+
readonly delivery?: boolean;
|
|
66
|
+
readonly audit?: boolean;
|
|
67
|
+
readonly jobs?: boolean;
|
|
68
|
+
readonly sessions?: boolean;
|
|
69
|
+
readonly rateLimiting?: boolean;
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
export function stackFeatureNames(features: readonly FeatureDefinition[]): string[] {
|
|
73
|
+
return features.map((f) => f.name);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
export function composePagesStack(options: PagesStackOptions = {}): FeatureDefinition[] {
|
|
77
|
+
return [
|
|
78
|
+
createTextContentFeature(),
|
|
79
|
+
createLegalPagesFeature(
|
|
80
|
+
options.wrapLayout !== undefined ? { wrapLayout: options.wrapLayout } : {},
|
|
81
|
+
),
|
|
82
|
+
];
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
export function composeRendererStack(): FeatureDefinition[] {
|
|
86
|
+
return [
|
|
87
|
+
createTemplateResolverFeature(),
|
|
88
|
+
createRendererFoundationFeature(),
|
|
89
|
+
createRendererSimpleFeature(),
|
|
90
|
+
];
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
export function composeMailStack(options: MailStackOptions): FeatureDefinition[] {
|
|
94
|
+
const out: FeatureDefinition[] = [mailFoundationFeature];
|
|
95
|
+
for (const transport of options.transports) {
|
|
96
|
+
if (transport === "inmemory") out.push(mailTransportInMemoryFeature);
|
|
97
|
+
if (transport === "smtp") out.push(mailTransportSmtpFeature);
|
|
98
|
+
}
|
|
99
|
+
return out;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
export function composeFileStack(options: FileStackOptions): FeatureDefinition[] {
|
|
103
|
+
const out: FeatureDefinition[] = [fileFoundationFeature];
|
|
104
|
+
for (const provider of options.providers) {
|
|
105
|
+
if (provider === "inmemory") out.push(fileProviderInMemoryFeature);
|
|
106
|
+
if (provider === "s3") out.push(fileProviderS3Feature);
|
|
107
|
+
if (provider === "s3-env") out.push(fileProviderS3EnvFeature);
|
|
108
|
+
}
|
|
109
|
+
if (options.includeFilesFeature ?? true) out.push(createFilesFeature());
|
|
110
|
+
return out;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/** Retention + compliance (+ optional lifecycle/sessions). UDR/files mount separately — order varies per app. */
|
|
114
|
+
export function composeGdprStack(options: GdprStackOptions = {}): FeatureDefinition[] {
|
|
115
|
+
const order = options.order ?? "retention-first";
|
|
116
|
+
const retention = createDataRetentionFeature();
|
|
117
|
+
const compliance = createComplianceProfilesFeature();
|
|
118
|
+
const out: FeatureDefinition[] =
|
|
119
|
+
order === "compliance-first" ? [compliance, retention] : [retention, compliance];
|
|
120
|
+
if (options.tenantLifecycle) out.push(createTenantLifecycleFeature());
|
|
121
|
+
if (options.sessions) out.push(createSessionsFeature());
|
|
122
|
+
return out;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
export function composeUserDataRightsStack(
|
|
126
|
+
options: UserDataRightsStackOptions = {},
|
|
127
|
+
): FeatureDefinition[] {
|
|
128
|
+
const includeDefaults = options.includeDefaults ?? true;
|
|
129
|
+
const out: FeatureDefinition[] = [createUserDataRightsFeature(options.userDataRights ?? {})];
|
|
130
|
+
if (includeDefaults) out.push(createUserDataRightsDefaultsFeature());
|
|
131
|
+
return out;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
export function composeOpsStack(options: OpsStackOptions = {}): FeatureDefinition[] {
|
|
135
|
+
const delivery = options.delivery ?? true;
|
|
136
|
+
const audit = options.audit ?? true;
|
|
137
|
+
const jobs = options.jobs ?? true;
|
|
138
|
+
const sessions = options.sessions ?? false;
|
|
139
|
+
const rateLimiting = options.rateLimiting ?? false;
|
|
140
|
+
const out: FeatureDefinition[] = [];
|
|
141
|
+
if (delivery) out.push(createDeliveryFeature());
|
|
142
|
+
if (audit) out.push(createAuditFeature());
|
|
143
|
+
if (jobs) out.push(createJobsFeature());
|
|
144
|
+
if (sessions) out.push(createSessionsFeature());
|
|
145
|
+
if (rateLimiting) out.push(createRateLimitingFeature());
|
|
146
|
+
return out;
|
|
147
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -26,6 +26,25 @@ export {
|
|
|
26
26
|
scanEvents,
|
|
27
27
|
} from "./codegen";
|
|
28
28
|
export { type ComposeFeaturesOptions, composeFeatures } from "./compose-features";
|
|
29
|
+
export {
|
|
30
|
+
composeFileStack,
|
|
31
|
+
composeGdprStack,
|
|
32
|
+
composeMailStack,
|
|
33
|
+
composeOpsStack,
|
|
34
|
+
composePagesStack,
|
|
35
|
+
composeRendererStack,
|
|
36
|
+
composeUserDataRightsStack,
|
|
37
|
+
type FileProviderKind,
|
|
38
|
+
type FileStackOptions,
|
|
39
|
+
type GdprStackOptions,
|
|
40
|
+
type GdprStackOrder,
|
|
41
|
+
type MailStackOptions,
|
|
42
|
+
type MailTransportKind,
|
|
43
|
+
type OpsStackOptions,
|
|
44
|
+
type PagesStackOptions,
|
|
45
|
+
stackFeatureNames,
|
|
46
|
+
type UserDataRightsStackOptions,
|
|
47
|
+
} from "./compose-stacks";
|
|
29
48
|
export {
|
|
30
49
|
type CreateKumikoServerOptions,
|
|
31
50
|
createKumikoServer,
|
|
@@ -73,4 +92,9 @@ export type {
|
|
|
73
92
|
export { scaffoldDeploy } from "./scaffold-deploy";
|
|
74
93
|
export type { ScaffoldFeatureOptions, ScaffoldFeatureResult } from "./scaffold-feature";
|
|
75
94
|
export { scaffoldFeature } from "./scaffold-feature";
|
|
95
|
+
export {
|
|
96
|
+
type SetupTestStackFromFeaturesOptions,
|
|
97
|
+
setupTestStackFromFeatures,
|
|
98
|
+
type TestStackPreset,
|
|
99
|
+
} from "./setup-test-stack-from-features";
|
|
76
100
|
export { renderWelcomeBanner, type WelcomeBannerInput } from "./welcome-banner";
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
// Test helper: composeFeatures + setupTestStack in one call.
|
|
2
|
+
// Apps pass the same feature list as run-config (APP_FEATURES / buildAppFeatures).
|
|
3
|
+
|
|
4
|
+
import { createConfigResolver } from "@cosmicdrift/kumiko-bundled-features/config";
|
|
5
|
+
import { createTextContentApi } from "@cosmicdrift/kumiko-bundled-features/text-content";
|
|
6
|
+
import type { FeatureDefinition } from "@cosmicdrift/kumiko-framework/engine";
|
|
7
|
+
import {
|
|
8
|
+
setupTestStack,
|
|
9
|
+
type TestStack,
|
|
10
|
+
type TestStackOptions,
|
|
11
|
+
} from "@cosmicdrift/kumiko-framework/stack";
|
|
12
|
+
import { type ComposeFeaturesOptions, composeFeatures } from "./compose-features";
|
|
13
|
+
|
|
14
|
+
export type TestStackPreset = "config" | "text-content";
|
|
15
|
+
|
|
16
|
+
export type SetupTestStackFromFeaturesOptions = Omit<TestStackOptions, "features"> & {
|
|
17
|
+
readonly includeBundled?: boolean;
|
|
18
|
+
readonly authOptions?: ComposeFeaturesOptions["authOptions"];
|
|
19
|
+
readonly presets?: readonly TestStackPreset[];
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
function mergeExtraContext(
|
|
23
|
+
base: TestStackOptions["extraContext"],
|
|
24
|
+
presets: readonly TestStackPreset[],
|
|
25
|
+
): TestStackOptions["extraContext"] {
|
|
26
|
+
if (presets.length === 0) return base;
|
|
27
|
+
|
|
28
|
+
return (deps) => {
|
|
29
|
+
const fromBase =
|
|
30
|
+
typeof base === "function" ? base(deps) : base !== undefined ? { ...base } : {};
|
|
31
|
+
const merged: Record<string, unknown> = { ...fromBase };
|
|
32
|
+
|
|
33
|
+
if (presets.includes("config")) {
|
|
34
|
+
const configResolver = createConfigResolver();
|
|
35
|
+
merged["configResolver"] = configResolver;
|
|
36
|
+
}
|
|
37
|
+
if (presets.includes("text-content")) {
|
|
38
|
+
merged["textContent"] = createTextContentApi(deps.db);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
return merged;
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export async function setupTestStackFromFeatures(
|
|
46
|
+
appFeatures: readonly FeatureDefinition[],
|
|
47
|
+
options: SetupTestStackFromFeaturesOptions = {},
|
|
48
|
+
): Promise<TestStack> {
|
|
49
|
+
const { includeBundled = false, authOptions, presets = [], ...stackOptions } = options;
|
|
50
|
+
const features = composeFeatures(appFeatures, { includeBundled, authOptions });
|
|
51
|
+
const extraContext = mergeExtraContext(stackOptions.extraContext, presets);
|
|
52
|
+
|
|
53
|
+
return setupTestStack({
|
|
54
|
+
...stackOptions,
|
|
55
|
+
features,
|
|
56
|
+
...(extraContext !== undefined && { extraContext }),
|
|
57
|
+
});
|
|
58
|
+
}
|