create-kumiko-app 0.4.243 → 0.4.245
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
2
|
"source": "samples/apps/use-all-bundled APP_FEATURES (composeFeatures includeBundled + signup)",
|
|
3
|
-
"featureCount":
|
|
3
|
+
"featureCount": 69,
|
|
4
4
|
"features": [
|
|
5
5
|
{
|
|
6
6
|
"name": "admin-shell",
|
|
@@ -31,7 +31,9 @@
|
|
|
31
31
|
"name": "audit",
|
|
32
32
|
"description": "Exposes the framework's event store as a paginated, filterable audit log via the `audit:query:list` handler (accessible to `Admin` and `SystemAdmin` roles). No separate table or projection — the event store is the audit trail by construction: every entity write already records who, when, what entity, and the event payload with PII stripped. Filter by `aggregateType`, `aggregateId`, `eventType`, `userId`, or time range.",
|
|
33
33
|
"toggleableDefault": null,
|
|
34
|
-
"requires": [
|
|
34
|
+
"requires": [
|
|
35
|
+
"tenant"
|
|
36
|
+
],
|
|
35
37
|
"optionalRequires": [],
|
|
36
38
|
"configReads": [],
|
|
37
39
|
"exposesApis": [],
|
|
@@ -921,6 +923,38 @@
|
|
|
921
923
|
"recommended": false
|
|
922
924
|
}
|
|
923
925
|
},
|
|
926
|
+
{
|
|
927
|
+
"name": "files-tenant-data",
|
|
928
|
+
"description": "GDPR coverage for the `files` feature's `fileRef` entity: tenant-destroy row purge (EXT_TENANT_DATA) and full storage-prefix binary wipe (EXT_STORAGE_PROVIDER), plus a manual backfill/GC job that sweeps derivatives orphaned by a forget/tenant-destroy that ran before #2461 wired binary cleanup into those flows. Requires `tenant-lifecycle`, optionalRequires `files`.",
|
|
929
|
+
"toggleableDefault": null,
|
|
930
|
+
"requires": [
|
|
931
|
+
"tenant-lifecycle"
|
|
932
|
+
],
|
|
933
|
+
"optionalRequires": [
|
|
934
|
+
"files"
|
|
935
|
+
],
|
|
936
|
+
"configReads": [],
|
|
937
|
+
"exposesApis": [],
|
|
938
|
+
"usesApis": [],
|
|
939
|
+
"extensionsUsed": [
|
|
940
|
+
{
|
|
941
|
+
"extensionName": "tenantData",
|
|
942
|
+
"entityName": "fileRef"
|
|
943
|
+
},
|
|
944
|
+
{
|
|
945
|
+
"extensionName": "storageProvider",
|
|
946
|
+
"entityName": "fileRef"
|
|
947
|
+
}
|
|
948
|
+
],
|
|
949
|
+
"configKeys": [],
|
|
950
|
+
"secrets": [],
|
|
951
|
+
"writeHandlers": [],
|
|
952
|
+
"uiHints": {
|
|
953
|
+
"displayLabel": "Files · Tenant-Destroy & Derivative GC",
|
|
954
|
+
"category": "compliance",
|
|
955
|
+
"recommended": false
|
|
956
|
+
}
|
|
957
|
+
},
|
|
924
958
|
{
|
|
925
959
|
"name": "folders",
|
|
926
960
|
"description": "Generic, host-agnostic hierarchical folders for any entity. Owns two event-sourced entities — the per-tenant `folder` tree (`read_folders`, self-referential via parentId) and SINGLE-membership `folder-assignment` rows keyed by (entityType, entityId) (`read_folder_assignments`) — so filing an entity adds NO column to the host and needs no relational pivot or JOIN. The folder catalog uses the generic entity handlers (create, update [= rename, optimistic-locked], delete, list, detail); set-folder puts/moves an entity into a folder (one folder per entity) and clear-folder unfiles it (both idempotent). Read which folder an entity is in, or which entities a folder holds, by listing `folder-assignment` filtered on `entityId` or `folderId`. Every path uses one access rule — adopt the host's model with createFoldersFeature({ access: { openToAll: true } }) or pin roles. Pass { toggleable: { default: false } } to make the whole feature tier-gatable via the tier-engine (no host hook).",
|
|
@@ -2110,7 +2144,7 @@
|
|
|
2110
2144
|
},
|
|
2111
2145
|
{
|
|
2112
2146
|
"name": "tags",
|
|
2113
|
-
"description": "Generic, host-agnostic tagging for any entity. Owns two event-sourced entities — the per-tenant `tag` catalog (`read_tags`, with optional `color` and `scope`) and `tag-assignment` join rows keyed by (entityType, entityId) (`read_tag_assignments`) — so tagging adds NO column to the host entity and needs no relational pivot or JOIN.
|
|
2147
|
+
"description": "Generic, host-agnostic tagging for any entity. Owns two event-sourced entities — the per-tenant `tag` catalog (`read_tags`, with optional `color` and `scope`) and `tag-assignment` join rows keyed by (entityType, entityId) (`read_tag_assignments`) — so tagging adds NO column to the host entity and needs no relational pivot or JOIN. Catalog screens are declarative (`entityList` + `entityEdit`) and use convention QNs `tag:{create,update,delete}`; TagManager/TagPicker keep `create-tag`/`update-tag`/`delete-tag`. Also: `assign-tag` (idempotent), `remove-tag` (idempotent) and list queries for the catalog and the assignments. Read which tags an entity has, or which entities carry a tag, by listing `tag-assignment` filtered on `entityId` or `tagId` and composing in the read-layer. A tag with empty `scope` is global; a `scope` of an entityType restricts it to that type in the picker. Every path uses one access rule — adopt the host's model with createTagsFeature({ access: { openToAll: true } }) or pin roles with createTagsFeature({ roles }). Pass { toggleable: { default: false } } to make the whole feature tier-gatable via the tier-engine (no host hook).",
|
|
2114
2148
|
"toggleableDefault": null,
|
|
2115
2149
|
"requires": [],
|
|
2116
2150
|
"optionalRequires": [],
|
|
@@ -2468,7 +2502,8 @@
|
|
|
2468
2502
|
"computed": false,
|
|
2469
2503
|
"options": [
|
|
2470
2504
|
"de",
|
|
2471
|
-
"en"
|
|
2505
|
+
"en",
|
|
2506
|
+
"es"
|
|
2472
2507
|
],
|
|
2473
2508
|
"bounds": null,
|
|
2474
2509
|
"pattern": null,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "create-kumiko-app",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.245",
|
|
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>",
|
|
@@ -27,9 +27,9 @@
|
|
|
27
27
|
}
|
|
28
28
|
},
|
|
29
29
|
"dependencies": {
|
|
30
|
-
"@cosmicdrift/kumiko-dev-server": "0.
|
|
31
|
-
"@cosmicdrift/kumiko-framework": "0.
|
|
32
|
-
"@cosmicdrift/kumiko-server-runtime": "0.
|
|
30
|
+
"@cosmicdrift/kumiko-dev-server": "0.222.0",
|
|
31
|
+
"@cosmicdrift/kumiko-framework": "0.222.0",
|
|
32
|
+
"@cosmicdrift/kumiko-server-runtime": "0.222.0",
|
|
33
33
|
"@inquirer/core": "^10.0.0",
|
|
34
34
|
"@inquirer/prompts": "^7.4.0"
|
|
35
35
|
},
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
// Vendored manifest.json must match the source-of-truth in
|
|
2
|
+
// samples/apps/use-all-bundled/feature-manifest.json. The picker reads
|
|
3
|
+
// the vendored copy at runtime; a stale copy lets the picker show old
|
|
4
|
+
// features or miss new ones. Refresh by running `bun run gen:manifest`
|
|
5
|
+
// inside samples/apps/use-all-bundled (writes both copies at once).
|
|
6
|
+
|
|
7
|
+
import { describe, expect, test } from "bun:test";
|
|
8
|
+
import { readFileSync } from "node:fs";
|
|
9
|
+
import { dirname, resolve } from "node:path";
|
|
10
|
+
import { fileURLToPath } from "node:url";
|
|
11
|
+
|
|
12
|
+
const HERE = dirname(fileURLToPath(import.meta.url));
|
|
13
|
+
const VENDOR = resolve(HERE, "..", "..", "feature-manifest.json");
|
|
14
|
+
const SOURCE = resolve(
|
|
15
|
+
HERE,
|
|
16
|
+
"..",
|
|
17
|
+
"..",
|
|
18
|
+
"..",
|
|
19
|
+
"..",
|
|
20
|
+
"samples",
|
|
21
|
+
"apps",
|
|
22
|
+
"use-all-bundled",
|
|
23
|
+
"feature-manifest.json",
|
|
24
|
+
);
|
|
25
|
+
|
|
26
|
+
describe("vendored manifest", () => {
|
|
27
|
+
test("byte-identical to samples/apps/use-all-bundled/feature-manifest.json", () => {
|
|
28
|
+
const vendor = readFileSync(VENDOR, "utf-8");
|
|
29
|
+
const source = readFileSync(SOURCE, "utf-8");
|
|
30
|
+
expect(vendor).toBe(source);
|
|
31
|
+
});
|
|
32
|
+
});
|
|
@@ -6,9 +6,11 @@
|
|
|
6
6
|
// feature satisfies — cli.test.ts's file-existence checks can't catch that,
|
|
7
7
|
// only actually booting the resolved set through validateBoot can.
|
|
8
8
|
|
|
9
|
-
import { describe, expect, test } from "bun:test";
|
|
9
|
+
import { afterAll, describe, expect, test } from "bun:test";
|
|
10
|
+
import { mkdirSync, mkdtempSync, rmSync } from "node:fs";
|
|
11
|
+
import { join } from "node:path";
|
|
10
12
|
import { frameworkCoreEnvSchema } from "@cosmicdrift/kumiko-dev-server";
|
|
11
|
-
import { resolveKmsWiring } from "@cosmicdrift/kumiko-framework/crypto";
|
|
13
|
+
import { requireKmsWiring, resolveKmsWiring } from "@cosmicdrift/kumiko-framework/crypto";
|
|
12
14
|
import {
|
|
13
15
|
createRegistry,
|
|
14
16
|
type FeatureDefinition,
|
|
@@ -21,6 +23,7 @@ import { runProdApp } from "@cosmicdrift/kumiko-server-runtime";
|
|
|
21
23
|
import { composeFeatures } from "@cosmicdrift/kumiko-server-runtime/compose-features";
|
|
22
24
|
import { resolveDeps } from "../dep-resolver";
|
|
23
25
|
import { FEATURE_CONSTRUCTORS } from "../feature-constructors";
|
|
26
|
+
import { runCreate } from "../index";
|
|
24
27
|
import { loadManifest } from "../manifest";
|
|
25
28
|
import { buildChoices } from "../picker";
|
|
26
29
|
|
|
@@ -125,7 +128,19 @@ describe("--yes resolved set through runProdApp's PII boot gate (issue-2330 regr
|
|
|
125
128
|
);
|
|
126
129
|
});
|
|
127
130
|
|
|
128
|
-
test("
|
|
131
|
+
test("requireKmsWiring aborts when NODE_ENV=production and the trio is empty", () => {
|
|
132
|
+
expect(() =>
|
|
133
|
+
requireKmsWiring(
|
|
134
|
+
{},
|
|
135
|
+
{
|
|
136
|
+
logPrefix: "[test]",
|
|
137
|
+
plaintextReason: "should-not-fallback",
|
|
138
|
+
},
|
|
139
|
+
),
|
|
140
|
+
).toThrow(/PLATFORM_KEK|SUBJECT_KEYS|BLIND_INDEX|trio|required/i);
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
test("with resolveKmsWiring's fallback (generated bin/main.ts non-prod path), boot succeeds", async () => {
|
|
129
144
|
// Empty object, not process.env: a dev machine with the real trio set
|
|
130
145
|
// would otherwise flip this into the ActiveKmsWiring branch and the test
|
|
131
146
|
// would stop exercising the plaintext fallback it's meant to pin.
|
|
@@ -157,3 +172,69 @@ describe("--yes resolved set through runProdApp's PII boot gate (issue-2330 regr
|
|
|
157
172
|
expect(handle).toBeDefined();
|
|
158
173
|
});
|
|
159
174
|
});
|
|
175
|
+
|
|
176
|
+
// The suite above rebuilds the wiring in-process and pins that the pieces
|
|
177
|
+
// are individually correct — it never imports the file scaffoldApp actually
|
|
178
|
+
// writes, so a typo in bin/main.ts's import specifier or a `...kmsWiring`
|
|
179
|
+
// spread landing in the wrong object literal would stay green. This drives
|
|
180
|
+
// the real generated bin/main.ts as a subprocess instead. Fixtures live
|
|
181
|
+
// under __tests__/.tmp-fixtures/ (gitignored, same trick as
|
|
182
|
+
// dev-server/src/codegen/__tests__/watch.test.ts) so Bun's node_modules
|
|
183
|
+
// walk-up from bin/main.ts still finds the repo's hoisted @cosmicdrift/*
|
|
184
|
+
// symlinks — a system tmpdir sits outside the workspace and can't resolve
|
|
185
|
+
// them (see walkthrough.integration.test.ts's comment on the same issue).
|
|
186
|
+
describe("generated bin/main.ts actually boots (issue-2330 regression, real subprocess)", () => {
|
|
187
|
+
const FIXTURE_ROOT = join(import.meta.dir, ".tmp-fixtures");
|
|
188
|
+
const createdDirs: string[] = [];
|
|
189
|
+
|
|
190
|
+
afterAll(() => {
|
|
191
|
+
for (const d of createdDirs) {
|
|
192
|
+
try {
|
|
193
|
+
rmSync(d, { recursive: true, force: true });
|
|
194
|
+
} catch {
|
|
195
|
+
// ignore — best-effort
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
});
|
|
199
|
+
|
|
200
|
+
test("KUMIKO_DRY_RUN_ENV=boot bun bin/main.ts exits 0 against the file scaffoldApp wrote", async () => {
|
|
201
|
+
mkdirSync(FIXTURE_ROOT, { recursive: true });
|
|
202
|
+
const cwd = mkdtempSync(join(FIXTURE_ROOT, "boot-"));
|
|
203
|
+
createdDirs.push(cwd);
|
|
204
|
+
|
|
205
|
+
const exitCode = await runCreate({ name: "boot-fixture", yes: true, cwd, log: () => {} });
|
|
206
|
+
expect(exitCode).toBe(0);
|
|
207
|
+
|
|
208
|
+
const appDir = join(cwd, "boot-fixture");
|
|
209
|
+
const proc = Bun.spawn({
|
|
210
|
+
cmd: ["bun", "bin/main.ts"],
|
|
211
|
+
cwd: appDir,
|
|
212
|
+
env: {
|
|
213
|
+
...process.env,
|
|
214
|
+
KUMIKO_DRY_RUN_ENV: "boot",
|
|
215
|
+
JWT_SECRET: "a".repeat(32),
|
|
216
|
+
KUMIKO_SECRETS_MASTER_KEY_V1: Buffer.alloc(32, 7).toString("base64"),
|
|
217
|
+
DATABASE_URL: "postgres://dummy:dummy@127.0.0.1:1/dummy",
|
|
218
|
+
REDIS_URL: "redis://127.0.0.1:1",
|
|
219
|
+
// Pinned empty, not inherited from process.env: a dev machine with
|
|
220
|
+
// the real trio set would otherwise flip resolveKmsWiring into the
|
|
221
|
+
// ActiveKmsWiring branch (and its KMS health check) instead of the
|
|
222
|
+
// plaintext fallback this test exercises.
|
|
223
|
+
PLATFORM_KEK: "",
|
|
224
|
+
SUBJECT_KEYS_DATABASE_URL: "",
|
|
225
|
+
KUMIKO_BLIND_INDEX_KEY: "",
|
|
226
|
+
},
|
|
227
|
+
stdout: "pipe",
|
|
228
|
+
stderr: "pipe",
|
|
229
|
+
});
|
|
230
|
+
const [stdout, stderr, code] = await Promise.all([
|
|
231
|
+
new Response(proc.stdout).text(),
|
|
232
|
+
new Response(proc.stderr).text(),
|
|
233
|
+
proc.exited,
|
|
234
|
+
]);
|
|
235
|
+
|
|
236
|
+
expect(code, `stdout:\n${stdout}\nstderr:\n${stderr}`).toBe(0);
|
|
237
|
+
expect(`${stdout}${stderr}`).not.toContain("BOOT ABORTED");
|
|
238
|
+
expect(stdout).toContain("boot validation OK");
|
|
239
|
+
}, 30_000);
|
|
240
|
+
});
|