create-kumiko-app 0.4.149 → 0.4.151
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/feature-manifest.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
|
-
"source": "samples/apps/use-all-bundled APP_FEATURES (composeFeatures includeBundled)",
|
|
3
|
-
"featureCount":
|
|
2
|
+
"source": "samples/apps/use-all-bundled APP_FEATURES (composeFeatures includeBundled + signup)",
|
|
3
|
+
"featureCount": 59,
|
|
4
4
|
"features": [
|
|
5
5
|
{
|
|
6
6
|
"name": "admin-shell",
|
|
@@ -52,7 +52,8 @@
|
|
|
52
52
|
"toggleableDefault": null,
|
|
53
53
|
"requires": [
|
|
54
54
|
"user",
|
|
55
|
-
"tenant"
|
|
55
|
+
"tenant",
|
|
56
|
+
"delivery"
|
|
56
57
|
],
|
|
57
58
|
"optionalRequires": [],
|
|
58
59
|
"configReads": [],
|
|
@@ -64,7 +65,9 @@
|
|
|
64
65
|
"writeHandlers": [
|
|
65
66
|
"auth-email-password:write:change-password",
|
|
66
67
|
"auth-email-password:write:login",
|
|
67
|
-
"auth-email-password:write:logout"
|
|
68
|
+
"auth-email-password:write:logout",
|
|
69
|
+
"auth-email-password:write:signup-confirm",
|
|
70
|
+
"auth-email-password:write:signup-request"
|
|
68
71
|
],
|
|
69
72
|
"uiHints": {
|
|
70
73
|
"displayLabel": "Auth · Email + Password",
|
|
@@ -202,6 +205,20 @@
|
|
|
202
205
|
"secrets": [],
|
|
203
206
|
"writeHandlers": []
|
|
204
207
|
},
|
|
208
|
+
{
|
|
209
|
+
"name": "auth-self-registration",
|
|
210
|
+
"description": "Runtime on/off switch for the auth-email-password self-signup flow. Handler-less: composing it registers \"auth-self-registration\" as a toggleable feature (default ON). auth-email-password's signup-request handler and its `signupRegistrationStatus` query both read `ctx.hasFeature(\"auth-self-registration\")` — the query stays reachable when the toggle is off (deliberately not gated itself) so the public signup page can hide its own link/form. Only meaningful when `signup` is configured on `createAuthEmailPasswordFeature` — compose alongside it, then flip via the feature-toggles admin screen.",
|
|
211
|
+
"toggleableDefault": true,
|
|
212
|
+
"requires": [],
|
|
213
|
+
"optionalRequires": [],
|
|
214
|
+
"configReads": [],
|
|
215
|
+
"exposesApis": [],
|
|
216
|
+
"usesApis": [],
|
|
217
|
+
"extensionsUsed": [],
|
|
218
|
+
"configKeys": [],
|
|
219
|
+
"secrets": [],
|
|
220
|
+
"writeHandlers": []
|
|
221
|
+
},
|
|
205
222
|
{
|
|
206
223
|
"name": "billing-foundation",
|
|
207
224
|
"description": "Plugin host for subscription billing — manages the `read_subscriptions` projection table and exposes 5 domain events (subscription created/updated/canceled, invoice paid/failed) appended by the foundation's own `billing-foundation:write:process-event` write-handler after provider plugins verify and normalize each webhook. Also ships `billing-foundation:write:create-checkout-session` and `billing-foundation:write:create-portal-session` write-handlers, a `billing-foundation:query:subscription:list` query handler, and a `createSubscriptionWebhookHandler` factory for the `/api/subscription/webhook/:providerName` route. Low-level building block — use `subscription-stripe` or `subscription-mollie` unless you are writing a new payment provider.",
|
|
@@ -493,7 +510,10 @@
|
|
|
493
510
|
"encrypted": false,
|
|
494
511
|
"computed": false,
|
|
495
512
|
"options": null,
|
|
496
|
-
"bounds":
|
|
513
|
+
"bounds": {
|
|
514
|
+
"min": 1,
|
|
515
|
+
"max": 500
|
|
516
|
+
},
|
|
497
517
|
"pattern": null,
|
|
498
518
|
"writeRoles": [
|
|
499
519
|
"TenantAdmin",
|
|
@@ -515,7 +535,9 @@
|
|
|
515
535
|
"computed": false,
|
|
516
536
|
"options": null,
|
|
517
537
|
"bounds": null,
|
|
518
|
-
"pattern":
|
|
538
|
+
"pattern": {
|
|
539
|
+
"regex": "^[a-z]{3}(\\+[a-z]{3})*$"
|
|
540
|
+
},
|
|
519
541
|
"writeRoles": [
|
|
520
542
|
"TenantAdmin",
|
|
521
543
|
"SystemAdmin"
|
|
@@ -1437,7 +1459,7 @@
|
|
|
1437
1459
|
"uiHints": {
|
|
1438
1460
|
"displayLabel": "Personal Access Tokens",
|
|
1439
1461
|
"category": "identity",
|
|
1440
|
-
"recommended":
|
|
1462
|
+
"recommended": false
|
|
1441
1463
|
}
|
|
1442
1464
|
},
|
|
1443
1465
|
{
|
|
@@ -2136,6 +2158,81 @@
|
|
|
2136
2158
|
"recommended": false
|
|
2137
2159
|
}
|
|
2138
2160
|
},
|
|
2161
|
+
{
|
|
2162
|
+
"name": "tenant-settings",
|
|
2163
|
+
"description": "Per-tenant Currency + Locale defaults, exposed as two config keys (tenant-settings:config:currency, tenant-settings:config:locale) that surface in the self-populating Settings-Hub via `mask`. Pair with defineCreateWithTenantDefaults to auto-fill a money field's currency or a locale field on create instead of hard-coding a literal per entity.",
|
|
2164
|
+
"toggleableDefault": null,
|
|
2165
|
+
"requires": [
|
|
2166
|
+
"config"
|
|
2167
|
+
],
|
|
2168
|
+
"optionalRequires": [],
|
|
2169
|
+
"configReads": [],
|
|
2170
|
+
"exposesApis": [],
|
|
2171
|
+
"usesApis": [],
|
|
2172
|
+
"extensionsUsed": [],
|
|
2173
|
+
"configKeys": [
|
|
2174
|
+
{
|
|
2175
|
+
"key": "currency",
|
|
2176
|
+
"qualifiedName": "tenant-settings:config:currency",
|
|
2177
|
+
"type": "select",
|
|
2178
|
+
"scope": "tenant",
|
|
2179
|
+
"default": "EUR",
|
|
2180
|
+
"encrypted": false,
|
|
2181
|
+
"computed": false,
|
|
2182
|
+
"options": [
|
|
2183
|
+
"EUR",
|
|
2184
|
+
"USD",
|
|
2185
|
+
"GBP",
|
|
2186
|
+
"CHF",
|
|
2187
|
+
"JPY",
|
|
2188
|
+
"SEK",
|
|
2189
|
+
"NOK",
|
|
2190
|
+
"DKK",
|
|
2191
|
+
"PLN",
|
|
2192
|
+
"CZK",
|
|
2193
|
+
"CAD",
|
|
2194
|
+
"AUD",
|
|
2195
|
+
"NZD",
|
|
2196
|
+
"CNY",
|
|
2197
|
+
"INR"
|
|
2198
|
+
],
|
|
2199
|
+
"bounds": null,
|
|
2200
|
+
"pattern": null,
|
|
2201
|
+
"writeRoles": [
|
|
2202
|
+
"TenantAdmin",
|
|
2203
|
+
"Admin",
|
|
2204
|
+
"SystemAdmin"
|
|
2205
|
+
],
|
|
2206
|
+
"readRoles": [
|
|
2207
|
+
"all"
|
|
2208
|
+
]
|
|
2209
|
+
},
|
|
2210
|
+
{
|
|
2211
|
+
"key": "locale",
|
|
2212
|
+
"qualifiedName": "tenant-settings:config:locale",
|
|
2213
|
+
"type": "text",
|
|
2214
|
+
"scope": "tenant",
|
|
2215
|
+
"default": "en",
|
|
2216
|
+
"encrypted": false,
|
|
2217
|
+
"computed": false,
|
|
2218
|
+
"options": null,
|
|
2219
|
+
"bounds": null,
|
|
2220
|
+
"pattern": {
|
|
2221
|
+
"regex": "^[a-z]{2}(-[A-Z]{2})?$"
|
|
2222
|
+
},
|
|
2223
|
+
"writeRoles": [
|
|
2224
|
+
"TenantAdmin",
|
|
2225
|
+
"Admin",
|
|
2226
|
+
"SystemAdmin"
|
|
2227
|
+
],
|
|
2228
|
+
"readRoles": [
|
|
2229
|
+
"all"
|
|
2230
|
+
]
|
|
2231
|
+
}
|
|
2232
|
+
],
|
|
2233
|
+
"secrets": [],
|
|
2234
|
+
"writeHandlers": []
|
|
2235
|
+
},
|
|
2139
2236
|
{
|
|
2140
2237
|
"name": "text-content",
|
|
2141
2238
|
"description": "Generic Markdown text store keyed by `(tenantId, slug, lang)` — one row per combination in the `read_text_blocks` entity table. Provides `text-content:write:set` (TenantAdmin upsert) and `text-content:query:by-slug` (anonymous-capable read); use `SYSTEM_TENANT_ID` as the tenant for app-wide texts such as imprint, privacy policy, or FAQ. Other features (e.g. `legal-pages`) read blocks without a direct code import via the `createTextContentApi` / `requireTextContent` extraContext pattern.",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "create-kumiko-app",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.151",
|
|
4
4
|
"description": "`bun create kumiko-app <name>` — scaffold a new Kumiko app with an interactive feature picker.",
|
|
5
5
|
"license": "BUSL-1.1",
|
|
6
6
|
"author": "Marc Frost <marc@cosmicdriftgamestudio.com>",
|
|
@@ -30,9 +30,9 @@
|
|
|
30
30
|
"vendor:manifest": "bun run scripts/vendor-manifest.ts"
|
|
31
31
|
},
|
|
32
32
|
"dependencies": {
|
|
33
|
-
"@cosmicdrift/kumiko-dev-server": "0.
|
|
34
|
-
"@cosmicdrift/kumiko-framework": "0.
|
|
35
|
-
"@cosmicdrift/kumiko-server-runtime": "0.
|
|
33
|
+
"@cosmicdrift/kumiko-dev-server": "2.0.0",
|
|
34
|
+
"@cosmicdrift/kumiko-framework": "2.0.0",
|
|
35
|
+
"@cosmicdrift/kumiko-server-runtime": "2.0.0",
|
|
36
36
|
"@inquirer/core": "^10.0.0",
|
|
37
37
|
"@inquirer/prompts": "^7.4.0"
|
|
38
38
|
},
|
|
@@ -30,7 +30,10 @@ describe("create-kumiko-app CLI", () => {
|
|
|
30
30
|
expect(code).toBe(0);
|
|
31
31
|
// Onboarding regression gate: the scaffold step is the first thing a new
|
|
32
32
|
// user waits on. Generous ceiling so slow CI runners pass — what we catch
|
|
33
|
-
// is an order-of-magnitude blowup, not seconds.
|
|
33
|
+
// is an order-of-magnitude blowup, not seconds. Needs an explicit test
|
|
34
|
+
// timeout above the 30s ceiling: bun's default (5s) would fail a slow-
|
|
35
|
+
// but-fine CI run on its own timeout before this assertion ever gets to
|
|
36
|
+
// fire, making the "ceiling" the opposite of what the comment claims.
|
|
34
37
|
expect(scaffoldMs, `scaffold took ${Math.round(scaffoldMs)}ms (ceiling 30s)`).toBeLessThan(
|
|
35
38
|
30_000,
|
|
36
39
|
);
|
|
@@ -67,7 +70,7 @@ describe("create-kumiko-app CLI", () => {
|
|
|
67
70
|
|
|
68
71
|
// Setup-impact preview lands before the scaffold actually runs.
|
|
69
72
|
expect(out).toMatch(/→ Scaffolding \d+ features? into \.\/demo-app\//);
|
|
70
|
-
});
|
|
73
|
+
}, 35_000);
|
|
71
74
|
|
|
72
75
|
test("--print-manifest emits JSON, no name needed", async () => {
|
|
73
76
|
const code = await runCreate({
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
// Guards against #1425/1: callExpression (rendered into generated
|
|
2
|
+
// run-config.ts) and callArgs (used at runtime by instantiateScaffoldFeatures)
|
|
3
|
+
// encode the same call twice by hand. If someone edits one without the
|
|
4
|
+
// other, the boot test validates a different feature config than what
|
|
5
|
+
// actually gets scaffolded. This derives the expected callExpression from
|
|
6
|
+
// exportName + callArgs for every entry and diffs it against the literal.
|
|
7
|
+
|
|
8
|
+
import { describe, expect, test } from "bun:test";
|
|
9
|
+
import { FEATURE_CONSTRUCTORS } from "../feature-constructors";
|
|
10
|
+
|
|
11
|
+
// Renders a value as hand-written TS source (unquoted object keys, quoted
|
|
12
|
+
// string values) instead of JSON — JSON.stringify(1) and JSON.stringify("1")
|
|
13
|
+
// both survive a whitespace+quote-strip compare as "1", which would hide a
|
|
14
|
+
// string/number divergence between callExpression and callArgs. Rendering
|
|
15
|
+
// like this and normalizing whitespace only keeps that divergence visible.
|
|
16
|
+
function renderTsLiteral(value: unknown): string {
|
|
17
|
+
if (Array.isArray(value)) return `[${value.map(renderTsLiteral).join(", ")}]`;
|
|
18
|
+
if (value !== null && typeof value === "object") {
|
|
19
|
+
const entries = Object.entries(value as Record<string, unknown>).map(
|
|
20
|
+
([key, val]) => `${key}: ${renderTsLiteral(val)}`,
|
|
21
|
+
);
|
|
22
|
+
return `{ ${entries.join(", ")} }`;
|
|
23
|
+
}
|
|
24
|
+
return JSON.stringify(value);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function expectedCallExpression(exportName: string, callArgs: readonly unknown[]): string {
|
|
28
|
+
const args = callArgs.map(renderTsLiteral).join(", ");
|
|
29
|
+
return `${exportName}(${args})`;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
describe("FEATURE_CONSTRUCTORS callExpression/callArgs consistency", () => {
|
|
33
|
+
for (const [name, entry] of Object.entries(FEATURE_CONSTRUCTORS)) {
|
|
34
|
+
test(`${name}: callExpression matches exportName + callArgs`, () => {
|
|
35
|
+
if (entry.callArgs !== undefined) {
|
|
36
|
+
// callExpression is hand-formatted TS source; compare with
|
|
37
|
+
// whitespace stripped only — NOT quotes, so a string arg ("1") vs.
|
|
38
|
+
// a number arg (1) still diverges after normalize.
|
|
39
|
+
const normalize = (s: string) => s.replace(/\s+/g, "");
|
|
40
|
+
expect(normalize(entry.callExpression)).toBe(
|
|
41
|
+
normalize(expectedCallExpression(entry.exportName, entry.callArgs)),
|
|
42
|
+
);
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
// No callArgs: either `exportName()` (zero-arg factory) or bare
|
|
46
|
+
// `exportName` (object export) — both are legal, but the export name
|
|
47
|
+
// itself must be the prefix either way.
|
|
48
|
+
expect(
|
|
49
|
+
entry.callExpression === entry.exportName ||
|
|
50
|
+
entry.callExpression === `${entry.exportName}()`,
|
|
51
|
+
).toBe(true);
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
});
|
|
@@ -52,11 +52,9 @@ describe("--yes resolved set boots (issue-1174 regression)", () => {
|
|
|
52
52
|
});
|
|
53
53
|
|
|
54
54
|
test("the resolved --yes feature set boots without the GDPR PII-hook-coverage error", async () => {
|
|
55
|
-
// sessions (auto-included
|
|
56
|
-
//
|
|
57
|
-
|
|
58
|
-
// tokenVerifier boot invariant) is resolved into the set too.
|
|
59
|
-
expect(resolved.featureNames).toContain("personal-access-tokens");
|
|
55
|
+
// sessions (auto-included) pulls auth-foundation; #1570 allows zero
|
|
56
|
+
// tokenVerifier when sessionStore is mounted — PAT stays opt-in (#1514).
|
|
57
|
+
expect(resolved.featureNames).not.toContain("personal-access-tokens");
|
|
60
58
|
const instances = await instantiateResolved(resolved.featureNames);
|
|
61
59
|
const composed = composeFeatures(instances, { includeBundled: true });
|
|
62
60
|
expect(() => validateBoot(composed)).not.toThrow();
|
|
@@ -54,6 +54,14 @@ export const FEATURE_CONSTRUCTORS: Readonly<Record<string, ScaffoldFeatureEntry>
|
|
|
54
54
|
exportName: "createAuthEmailPasswordFeature",
|
|
55
55
|
callExpression: "createAuthEmailPasswordFeature()",
|
|
56
56
|
},
|
|
57
|
+
// Only meaningful alongside auth.signup — composeFeatures auto-mounts it
|
|
58
|
+
// when signup is set; listed so scaffolds can pick it explicitly (#1521).
|
|
59
|
+
"auth-self-registration": {
|
|
60
|
+
name: "auth-self-registration",
|
|
61
|
+
importPath: "@cosmicdrift/kumiko-bundled-features/auth-email-password",
|
|
62
|
+
exportName: "createAuthSelfRegistrationToggleFeature",
|
|
63
|
+
callExpression: "createAuthSelfRegistrationToggleFeature()",
|
|
64
|
+
},
|
|
57
65
|
"user-profile": {
|
|
58
66
|
name: "user-profile",
|
|
59
67
|
importPath: "@cosmicdrift/kumiko-bundled-features/user-profile",
|