create-kumiko-app 0.4.242 → 0.4.244
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
|
@@ -2110,7 +2110,7 @@
|
|
|
2110
2110
|
},
|
|
2111
2111
|
{
|
|
2112
2112
|
"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.
|
|
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. 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
2114
|
"toggleableDefault": null,
|
|
2115
2115
|
"requires": [],
|
|
2116
2116
|
"optionalRequires": [],
|
|
@@ -2125,6 +2125,9 @@
|
|
|
2125
2125
|
"tags:write:create-tag",
|
|
2126
2126
|
"tags:write:delete-tag",
|
|
2127
2127
|
"tags:write:remove-tag",
|
|
2128
|
+
"tags:write:tag:create",
|
|
2129
|
+
"tags:write:tag:delete",
|
|
2130
|
+
"tags:write:tag:update",
|
|
2128
2131
|
"tags:write:update-tag"
|
|
2129
2132
|
],
|
|
2130
2133
|
"uiHints": {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "create-kumiko-app",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.244",
|
|
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.221.0",
|
|
31
|
+
"@cosmicdrift/kumiko-framework": "0.221.0",
|
|
32
|
+
"@cosmicdrift/kumiko-server-runtime": "0.221.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
|
+
});
|