@cosmicdrift/kumiko-dev-server 0.163.3 → 0.165.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.163.3",
3
+ "version": "0.165.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.163.3",
58
- "@cosmicdrift/kumiko-framework": "0.163.3",
59
- "@cosmicdrift/kumiko-server-runtime": "0.163.3",
57
+ "@cosmicdrift/kumiko-bundled-features": "0.165.0",
58
+ "@cosmicdrift/kumiko-framework": "0.165.0",
59
+ "@cosmicdrift/kumiko-server-runtime": "0.165.0",
60
60
  "ts-morph": "^28.0.0"
61
61
  },
62
62
  "publishConfig": {
@@ -144,6 +144,31 @@ describe("composeStacks boots for real", () => {
144
144
  expect(() => validateBoot(features)).not.toThrow();
145
145
  });
146
146
 
147
+ test("money-horse-shaped composeGdprStack({sessions:true}) without a tokenVerifier provider fails boot", () => {
148
+ // Regression pin for the open design question this stack shape raised:
149
+ // composeGdprStack({sessions:true}) mounts auth-foundation, which
150
+ // hard-requires at least one tokenVerifier provider. Without composing
151
+ // one in (money-horse/solon's real run-config passes none), boot must
152
+ // fail loudly instead of silently shipping an unauthenticatable app.
153
+ const features = composeFeatures([...composeGdprStack({ sessions: true })], {
154
+ includeBundled: true,
155
+ });
156
+ expect(() => validateBoot(features)).toThrow(/no tokenVerifier providers registered/i);
157
+ });
158
+
159
+ test("composeGdprStack({sessions:true, providers:[...]}) passes boot with a tokenVerifier mounted", () => {
160
+ const features = composeFeatures(
161
+ [
162
+ ...composeGdprStack({
163
+ sessions: true,
164
+ providers: [createPersonalAccessTokensFeature({ scopes: {} })],
165
+ }),
166
+ ],
167
+ { includeBundled: true },
168
+ );
169
+ expect(() => validateBoot(features)).not.toThrow();
170
+ });
171
+
147
172
  // auth-mfa's encrypted fields need a runtime KEK (env or configureEntityFieldEncryption
148
173
  // + secrets). Name-list + saas-identity-wire.integration.test.ts cover identity boot.
149
174
  test("identity stack names compose with ops + renderer under includeBundled", () => {
@@ -1,24 +1,15 @@
1
1
  // createKumikoServer error / graceful-degradation paths — boot rejects,
2
2
  // stylesheet pipeline failures, missing CSS route.
3
3
 
4
- import { afterEach, describe, expect, test } from "bun:test";
4
+ import { describe, expect, test } from "bun:test";
5
5
  import { mkdtempSync, rmSync, writeFileSync } from "node:fs";
6
6
  import { tmpdir } from "node:os";
7
7
  import { join } from "node:path";
8
8
  import { defineFeature } from "@cosmicdrift/kumiko-framework/engine";
9
- import { createKumikoServer, type KumikoServerHandle } from "../create-kumiko-server";
9
+ import { createKumikoServer } from "../create-kumiko-server";
10
10
 
11
11
  const emptyFeature = defineFeature("dev-server-errors-probe", () => {});
12
12
 
13
- let handle: KumikoServerHandle | undefined;
14
-
15
- afterEach(async () => {
16
- if (handle) {
17
- await handle.stop();
18
- handle = undefined;
19
- }
20
- });
21
-
22
13
  describe("createKumikoServer — client bundle failure", () => {
23
14
  test("broken clientEntry rejects at boot with client bundle failed", async () => {
24
15
  const tmpDir = mkdtempSync(join(tmpdir(), "kumiko-bundle-fail-"));
@@ -59,6 +59,11 @@ export type GdprStackOptions = {
59
59
  readonly order?: GdprStackOrder;
60
60
  readonly sessions?: boolean;
61
61
  readonly tenantLifecycle?: boolean;
62
+ /** tokenVerifier provider(s) for auth-foundation when `sessions` is on.
63
+ * Without at least one, auth-foundation's boot check throws ("no
64
+ * tokenVerifier providers registered"). Omit only if the app mounts a
65
+ * provider itself elsewhere. */
66
+ readonly providers?: readonly FeatureDefinition[];
62
67
  };
63
68
 
64
69
  export type UserDataRightsStackOptions = {
@@ -79,15 +84,14 @@ export type OpsStackOptions = {
79
84
  /** sessions (+ optional auth-mfa). config/user/tenant/auth-email-password stay
80
85
  * on composeFeatures(includeBundled). Pass `mfa` options to mount TOTP.
81
86
  * When `sessions` is on (the default), auth-foundation is mounted alongside it —
82
- * the framework's registry now hard-requires it (sessions.requires("auth-foundation")).
83
- * Pass `providers` for the tokenVerifier(s) auth-foundation itself requires at least
84
- * one of (e.g. createPersonalAccessTokensFeature({ scopes })) — PAT scopes are
85
- * app-specific (which write-handlers an API token may call), so they stay caller-owned
86
- * instead of a framework default. */
87
+ * the framework's registry now hard-requires it (sessions.requires("auth-foundation")),
88
+ * which in turn needs at least one tokenVerifier provider mounted by the caller
89
+ * (e.g. createPersonalAccessTokensFeature({ scopes })) — PAT scopes are app-specific
90
+ * (which write-handlers an API token may call), so they stay caller-owned instead of
91
+ * a framework default. */
87
92
  export type IdentityStackOptions = {
88
93
  readonly sessions?: boolean;
89
94
  readonly mfa?: AuthMfaFeatureOptions;
90
- readonly providers?: readonly FeatureDefinition[];
91
95
  };
92
96
 
93
97
  export function stackFeatureNames(features: readonly FeatureDefinition[]): string[] {
@@ -139,7 +143,10 @@ export function composeGdprStack(options: GdprStackOptions = {}): FeatureDefinit
139
143
  const out: FeatureDefinition[] =
140
144
  order === "compliance-first" ? [compliance, retention] : [retention, compliance];
141
145
  if (options.tenantLifecycle) out.push(createTenantLifecycleFeature());
142
- if (options.sessions) out.push(authFoundationFeature, createSessionsFeature());
146
+ if (options.sessions) {
147
+ out.push(authFoundationFeature, createSessionsFeature());
148
+ if (options.providers) out.push(...options.providers);
149
+ }
143
150
  return out;
144
151
  }
145
152
 
@@ -172,7 +179,6 @@ export function composeIdentityStack(options: IdentityStackOptions = {}): Featur
172
179
  const out: FeatureDefinition[] = [];
173
180
  if (sessions) {
174
181
  out.push(authFoundationFeature, createSessionsFeature());
175
- if (options.providers) out.push(...options.providers);
176
182
  }
177
183
  if (options.mfa !== undefined) out.push(createAuthMfaFeature(options.mfa));
178
184
  return out;