@cosmicdrift/kumiko-cli 0.373.0 → 0.377.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 +10 -10
- package/src/changes.json +18 -0
- package/src/commands/check-managed.ts +1 -0
- package/src/commands/check.ts +1 -1
- package/src/commands/codemod.ts +1 -1
- package/src/commands/kms-wiring.ts +53 -0
- package/src/commands/ops/backfill-pii.ts +2 -3
- package/src/commands/ops/waitlist-migrate.ts +1 -1
- package/src/commands/ports.ts +8 -6
- package/src/commands/project.ts +22 -2
- package/src/commands/sync-managed.ts +1 -0
- package/src/handbuch/render.ts +1 -1
- package/src/missing-peer.ts +1 -1
- package/src/repo-manifest.ts +1 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cosmicdrift/kumiko-cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.377.0",
|
|
4
4
|
"description": "CLI for Kumiko apps. `bunx @cosmicdrift/kumiko-cli new app <name>` scaffolds a runnable workspace; `add feature <name>` mounts a feature; `check` runs boot validation plus the guard suites with the step list derived from kumiko.json; `agent lint`, `project` and `consumer` operate on an existing app; `handbuch build` renders the HTML handbuch.",
|
|
5
5
|
"license": "BUSL-1.1",
|
|
6
6
|
"author": "Marc Frost <marc@cosmicdriftgamestudio.com>",
|
|
@@ -36,10 +36,10 @@
|
|
|
36
36
|
}
|
|
37
37
|
},
|
|
38
38
|
"dependencies": {
|
|
39
|
-
"@cosmicdrift/kumiko-dev-server": "0.
|
|
40
|
-
"@cosmicdrift/kumiko-guards": "0.
|
|
41
|
-
"@cosmicdrift/kumiko-
|
|
42
|
-
"@cosmicdrift/kumiko-testing": "0.
|
|
39
|
+
"@cosmicdrift/kumiko-dev-server": "0.377.0",
|
|
40
|
+
"@cosmicdrift/kumiko-guards": "0.377.0",
|
|
41
|
+
"@cosmicdrift/kumiko-server-runtime": "0.377.0",
|
|
42
|
+
"@cosmicdrift/kumiko-testing": "0.377.0",
|
|
43
43
|
"marked": "^18.0.3",
|
|
44
44
|
"zod": "^4.4.3"
|
|
45
45
|
},
|
|
@@ -47,7 +47,7 @@
|
|
|
47
47
|
"sharp": "^0.35.5",
|
|
48
48
|
"@cosmicdrift/kumiko-bundled-features": ">=0.353.0 <1.0.0",
|
|
49
49
|
"@cosmicdrift/kumiko-framework": ">=0.353.0 <1.0.0",
|
|
50
|
-
"@cosmicdrift/kumiko-
|
|
50
|
+
"@cosmicdrift/kumiko-renderer": ">=0.353.0 <1.0.0"
|
|
51
51
|
},
|
|
52
52
|
"peerDependenciesMeta": {
|
|
53
53
|
"sharp": {
|
|
@@ -59,14 +59,14 @@
|
|
|
59
59
|
"@cosmicdrift/kumiko-framework": {
|
|
60
60
|
"optional": true
|
|
61
61
|
},
|
|
62
|
-
"@cosmicdrift/kumiko-
|
|
62
|
+
"@cosmicdrift/kumiko-renderer": {
|
|
63
63
|
"optional": true
|
|
64
64
|
}
|
|
65
65
|
},
|
|
66
66
|
"devDependencies": {
|
|
67
|
-
"@cosmicdrift/kumiko-bundled-features": "0.
|
|
68
|
-
"@cosmicdrift/kumiko-framework": "0.
|
|
69
|
-
"@cosmicdrift/kumiko-
|
|
67
|
+
"@cosmicdrift/kumiko-bundled-features": "0.377.0",
|
|
68
|
+
"@cosmicdrift/kumiko-framework": "0.377.0",
|
|
69
|
+
"@cosmicdrift/kumiko-renderer": "0.377.0",
|
|
70
70
|
"sharp": "^0.35.5"
|
|
71
71
|
},
|
|
72
72
|
"publishConfig": {
|
package/src/changes.json
CHANGED
|
@@ -1,4 +1,22 @@
|
|
|
1
1
|
[
|
|
2
|
+
{
|
|
3
|
+
"version": "0.377.0",
|
|
4
|
+
"type": "improvement",
|
|
5
|
+
"title": "kumiko codemod lists package-merge",
|
|
6
|
+
"detail": "kumiko codemod package-merge rewrites imports and package.json dependencies of merged packages."
|
|
7
|
+
},
|
|
8
|
+
{
|
|
9
|
+
"version": "0.377.0",
|
|
10
|
+
"type": "fix",
|
|
11
|
+
"title": "project rebuild keeps blind indexes and aborts without crypto, events prune names its aggregate types",
|
|
12
|
+
"detail": "`kumiko project rebuild` now wires the subject KMS and blind-index key from the env like `schema apply` and `ops backfill-pii`, so rebuilding a PII projection no longer nulls its bidx columns. Without the crypto env an app with PII fields aborts the rebuild unless `--allow-plaintext-pii <reason>` is passed. `kumiko events prune` takes the required `--aggregate-types <a,b>` flag; before, it always failed because pruneEvents needs explicit aggregate types."
|
|
13
|
+
},
|
|
14
|
+
{
|
|
15
|
+
"version": "0.376.0",
|
|
16
|
+
"type": "fix",
|
|
17
|
+
"title": "kumiko ports and sync-managed use the shared port reader and print managed warnings",
|
|
18
|
+
"detail": "kumiko ports reads from repo-manifest/app-ports; check managed and sync-managed print the infraRef warning."
|
|
19
|
+
},
|
|
2
20
|
{
|
|
3
21
|
"version": "0.371.0",
|
|
4
22
|
"type": "improvement",
|
|
@@ -41,6 +41,7 @@ export async function runManagedCheckStep(
|
|
|
41
41
|
const local = result.files.filter((report) => report.state === "local");
|
|
42
42
|
const okCount = result.files.length - drift.length - local.length;
|
|
43
43
|
out.log(` ${okCount}/${result.files.length} managed file(s) match the installed template`);
|
|
44
|
+
for (const warning of result.warnings) out.log(` ! ${warning}`);
|
|
44
45
|
for (const report of local) out.log(` ~ ${report.path}: ${describeReport(report)}`);
|
|
45
46
|
for (const report of drift) {
|
|
46
47
|
out.log(` ${result.adopted ? "✗" : "!"} ${report.path}: ${describeReport(report)}`);
|
package/src/commands/check.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { existsSync } from "node:fs";
|
|
2
2
|
import { join, resolve } from "node:path";
|
|
3
|
-
import { REPO_MANIFEST_FILE, type RepoManifest } from "@cosmicdrift/kumiko-repo-manifest";
|
|
3
|
+
import { REPO_MANIFEST_FILE, type RepoManifest } from "@cosmicdrift/kumiko-guards/repo-manifest";
|
|
4
4
|
import type { CliCommand, CliCommandContext } from "./types";
|
|
5
5
|
|
|
6
6
|
const APP_SCHEMA_FILE = "kumiko/schema.ts";
|
package/src/commands/codemod.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { CliCommand, CliCommandContext } from "./types";
|
|
2
2
|
|
|
3
3
|
// The shipped codemods that share the `<dir...> [--check | --dry-run | --write]` CLI of codemod-support.
|
|
4
|
-
const CODEMODS = ["feature-factory", "import-extensions", "s3-imports"] as const;
|
|
4
|
+
const CODEMODS = ["feature-factory", "import-extensions", "package-merge", "s3-imports"] as const;
|
|
5
5
|
type CodemodName = (typeof CODEMODS)[number];
|
|
6
6
|
|
|
7
7
|
function isCodemodName(value: string | undefined): value is CodemodName {
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import type { KmsWiring } from "@cosmicdrift/kumiko-framework/crypto";
|
|
2
|
+
import type { FeatureDefinition } from "@cosmicdrift/kumiko-framework/engine";
|
|
3
|
+
|
|
4
|
+
export const CLI_KMS_SLOTS = [
|
|
5
|
+
"PLATFORM_KEK",
|
|
6
|
+
"PLATFORM_KEK_PREVIOUS",
|
|
7
|
+
"KUMIKO_BLIND_INDEX_KEY",
|
|
8
|
+
] as const;
|
|
9
|
+
|
|
10
|
+
// A rebuild replays applyEntityEvent, which computes bidx columns from the
|
|
11
|
+
// boot-injected blind-index key and subject KMS. This standalone CLI never
|
|
12
|
+
// boots the app, so without wiring them a rebuild nulls the bidx columns (fw#3091).
|
|
13
|
+
// Plaintext is refused for apps with PII carriers, like the prod boot does, unless
|
|
14
|
+
// the operator names a reason.
|
|
15
|
+
export async function wireKmsFromEnv(options: {
|
|
16
|
+
readonly logPrefix: string;
|
|
17
|
+
readonly log: (line: string) => void;
|
|
18
|
+
readonly features: readonly FeatureDefinition[];
|
|
19
|
+
readonly allowPlaintextPii?: string | undefined;
|
|
20
|
+
}): Promise<KmsWiring> {
|
|
21
|
+
const crypto = await import("@cosmicdrift/kumiko-framework/crypto");
|
|
22
|
+
const { assertPiiBootInvariants } = await import(
|
|
23
|
+
"@cosmicdrift/kumiko-server-runtime/pii-boot-gate"
|
|
24
|
+
);
|
|
25
|
+
const wiring = await crypto.resolveKmsWiringAsync(process.env, {
|
|
26
|
+
logPrefix: options.logPrefix,
|
|
27
|
+
slots: CLI_KMS_SLOTS,
|
|
28
|
+
log: options.log,
|
|
29
|
+
});
|
|
30
|
+
try {
|
|
31
|
+
if ("kms" in wiring) {
|
|
32
|
+
assertPiiBootInvariants(options.features, {
|
|
33
|
+
mode: "prod",
|
|
34
|
+
kms: wiring.kms,
|
|
35
|
+
blindIndexKey: wiring.blindIndexKey,
|
|
36
|
+
});
|
|
37
|
+
crypto.configurePiiSubjectKms(wiring.kms);
|
|
38
|
+
crypto.configureBlindIndexKey(wiring.blindIndexKey);
|
|
39
|
+
} else {
|
|
40
|
+
assertPiiBootInvariants(options.features, {
|
|
41
|
+
mode: "prod",
|
|
42
|
+
allowPlaintextPii: options.allowPlaintextPii,
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
} catch (e) {
|
|
46
|
+
await wiring.close();
|
|
47
|
+
const reason = e instanceof Error ? e.message : String(e);
|
|
48
|
+
throw new Error(
|
|
49
|
+
`${options.logPrefix} ${reason} The rebuild would null the blind-index columns. Provide PLATFORM_KEK, SUBJECT_KEYS_DATABASE_URL and KUMIKO_BLIND_INDEX_KEY, or pass --allow-plaintext-pii <reason>.`,
|
|
50
|
+
);
|
|
51
|
+
}
|
|
52
|
+
return wiring;
|
|
53
|
+
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { join } from "node:path";
|
|
2
2
|
import type { DbConnection } from "@cosmicdrift/kumiko-framework/db";
|
|
3
3
|
import type { Registry } from "@cosmicdrift/kumiko-framework/engine";
|
|
4
|
+
import { CLI_KMS_SLOTS } from "../kms-wiring";
|
|
4
5
|
import { loadAppConfig } from "../load-app-config";
|
|
5
6
|
import type { CliCommandContext } from "../types";
|
|
6
7
|
|
|
@@ -86,8 +87,6 @@ export function selectPiiProjections<
|
|
|
86
87
|
|
|
87
88
|
const LOG_PREFIX = "[backfill-pii]";
|
|
88
89
|
|
|
89
|
-
const KMS_SLOTS = ["PLATFORM_KEK", "PLATFORM_KEK_PREVIOUS", "KUMIKO_BLIND_INDEX_KEY"] as const;
|
|
90
|
-
|
|
91
90
|
const BIGINT_SAFE = (_key: string, value: unknown): unknown =>
|
|
92
91
|
typeof value === "bigint" ? String(value) : value;
|
|
93
92
|
|
|
@@ -213,7 +212,7 @@ export async function runBackfillPii(ctx: CliCommandContext): Promise<number> {
|
|
|
213
212
|
try {
|
|
214
213
|
wiring = await crypto.requireKmsWiringAsync(process.env, {
|
|
215
214
|
logPrefix: LOG_PREFIX,
|
|
216
|
-
slots:
|
|
215
|
+
slots: CLI_KMS_SLOTS,
|
|
217
216
|
log: (line) => ctx.out.log(line),
|
|
218
217
|
});
|
|
219
218
|
} catch (e) {
|
|
@@ -57,7 +57,7 @@ export const waitlistMigrateOp = {
|
|
|
57
57
|
});
|
|
58
58
|
ctx.out.log("");
|
|
59
59
|
ctx.out.log(
|
|
60
|
-
` ${dryRun ? "[dry-run] " : ""}waitlist-migrate --from ${from}: read ${report.read}, ${dryRun ? "would create" : "created"} ${report.created}, already present ${report.skipped}, unreadable ${report.unreadable}`,
|
|
60
|
+
` ${dryRun ? "[dry-run] " : ""}waitlist-migrate --from ${from}: read ${report.read}, ${dryRun ? "would create" : "created"} ${report.created}, already present ${report.skipped}, unreadable ${report.unreadable}, legacy streams archived ${report.archivedStreams}`,
|
|
61
61
|
);
|
|
62
62
|
ctx.out.log("");
|
|
63
63
|
return report.unreadable > 0 ? 1 : 0;
|
package/src/commands/ports.ts
CHANGED
|
@@ -1,4 +1,9 @@
|
|
|
1
1
|
import { resolve } from "node:path";
|
|
2
|
+
import {
|
|
3
|
+
findPortCollisions,
|
|
4
|
+
listAppPorts,
|
|
5
|
+
scanWorkspaceApps,
|
|
6
|
+
} from "@cosmicdrift/kumiko-guards/repo-manifest/app-ports";
|
|
2
7
|
import type { CliCommand, CliCommandContext } from "./types";
|
|
3
8
|
|
|
4
9
|
// Fixed column order so the table reads the same in every run.
|
|
@@ -10,8 +15,7 @@ export async function runPorts(ctx: CliCommandContext): Promise<number> {
|
|
|
10
15
|
return 1;
|
|
11
16
|
}
|
|
12
17
|
const root = resolve(ctx.cwd, ctx.argv[0] ?? ".");
|
|
13
|
-
const
|
|
14
|
-
const apps = managed.scanWorkspaceApps(root);
|
|
18
|
+
const apps = scanWorkspaceApps(root);
|
|
15
19
|
if (apps.length === 0) {
|
|
16
20
|
ctx.out.err(` No app with kumiko.config.ts directly below ${root}.`);
|
|
17
21
|
return 1;
|
|
@@ -26,14 +30,12 @@ export async function runPorts(ctx: CliCommandContext): Promise<number> {
|
|
|
26
30
|
ctx.out.log(` ${app.padEnd(20)}(${reason})`);
|
|
27
31
|
continue;
|
|
28
32
|
}
|
|
29
|
-
const byRole = new Map(
|
|
30
|
-
managed.listAppPorts(appConfig.ports).map(({ role, port }) => [role, port]),
|
|
31
|
-
);
|
|
33
|
+
const byRole = new Map(listAppPorts(appConfig.ports).map(({ role, port }) => [role, port]));
|
|
32
34
|
ctx.out.log(
|
|
33
35
|
` ${app.padEnd(20)}${PORT_COLUMNS.map((column) => String(byRole.get(column) ?? "-").padEnd(12)).join("")}`,
|
|
34
36
|
);
|
|
35
37
|
}
|
|
36
|
-
const collisions =
|
|
38
|
+
const collisions = findPortCollisions(apps);
|
|
37
39
|
ctx.out.log("");
|
|
38
40
|
for (const { port, claims } of collisions) {
|
|
39
41
|
ctx.out.err(` ✗ port ${port}: ${claims.map(({ app, role }) => `${app} ${role}`).join(", ")}`);
|
package/src/commands/project.ts
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import { join } from "node:path";
|
|
2
|
+
import { getStringFlag, parseArgs } from "@cosmicdrift/kumiko-framework/arg-parser";
|
|
3
|
+
import { wireKmsFromEnv } from "./kms-wiring";
|
|
2
4
|
import { loadAppConfig } from "./load-app-config";
|
|
3
5
|
import type { CliCommand, CliCommandContext } from "./types";
|
|
4
6
|
|
|
@@ -12,10 +14,18 @@ function isProjectSubcommand(value: string | undefined): value is ProjectSubcomm
|
|
|
12
14
|
export const projectCommand: CliCommand = {
|
|
13
15
|
id: "project",
|
|
14
16
|
description: "Manage projections (list | status <name> | rebuild <name>)",
|
|
15
|
-
help: "Reads kumiko.config.ts in cwd, builds the registry, dispatches against the\nprojection-state table in DATABASE_URL.\n\nSubcommands:\n list List all projections and their state\n status <name> Show detail state for one projection\n rebuild <name>
|
|
17
|
+
help: "Reads kumiko.config.ts in cwd, builds the registry, dispatches against the\nprojection-state table in DATABASE_URL.\n\nSubcommands:\n list List all projections and their state\n status <name> Show detail state for one projection\n rebuild <name> [--allow-plaintext-pii <reason>]\n Full rebuild + report; needs the crypto env for PII apps",
|
|
16
18
|
run: async (ctx) => {
|
|
17
19
|
const sub = ctx.argv[0];
|
|
18
|
-
const
|
|
20
|
+
const rest = parseArgs(ctx.argv.slice(1));
|
|
21
|
+
const arg = rest.positional[0];
|
|
22
|
+
const allowPlaintextPii = getStringFlag(rest, "allow-plaintext-pii");
|
|
23
|
+
if (rest.flags.has("allow-plaintext-pii") && !allowPlaintextPii?.trim()) {
|
|
24
|
+
ctx.out.err("");
|
|
25
|
+
ctx.out.err(" --allow-plaintext-pii requires a non-empty reason");
|
|
26
|
+
ctx.out.err("");
|
|
27
|
+
return 1;
|
|
28
|
+
}
|
|
19
29
|
|
|
20
30
|
if (!isProjectSubcommand(sub)) {
|
|
21
31
|
ctx.out.err("");
|
|
@@ -58,8 +68,17 @@ export const projectCommand: CliCommand = {
|
|
|
58
68
|
|
|
59
69
|
const registry = createRegistry(config.features);
|
|
60
70
|
const { db, close } = createDbConnection({ url: databaseUrl });
|
|
71
|
+
let wiring: Awaited<ReturnType<typeof wireKmsFromEnv>> | undefined;
|
|
61
72
|
|
|
62
73
|
try {
|
|
74
|
+
if (sub === "rebuild") {
|
|
75
|
+
wiring = await wireKmsFromEnv({
|
|
76
|
+
logPrefix: "[kumiko project rebuild]",
|
|
77
|
+
log: (line) => ctx.out.log(line),
|
|
78
|
+
features: config.features,
|
|
79
|
+
allowPlaintextPii,
|
|
80
|
+
});
|
|
81
|
+
}
|
|
63
82
|
await createProjectionStateTable(db);
|
|
64
83
|
switch (sub) {
|
|
65
84
|
case "list":
|
|
@@ -75,6 +94,7 @@ export const projectCommand: CliCommand = {
|
|
|
75
94
|
ctx.out.err("");
|
|
76
95
|
return 1;
|
|
77
96
|
} finally {
|
|
97
|
+
await wiring?.close();
|
|
78
98
|
await close();
|
|
79
99
|
}
|
|
80
100
|
},
|
|
@@ -26,6 +26,7 @@ export async function runSyncManaged(ctx: CliCommandContext): Promise<number> {
|
|
|
26
26
|
appRoot: ctx.cwd,
|
|
27
27
|
uiRoots: manifest.uiRoots ?? [],
|
|
28
28
|
});
|
|
29
|
+
for (const warning of managed.listManagedWarnings(facts)) ctx.out.err(` ! ${warning}`);
|
|
29
30
|
const actions = managed.syncManagedFiles(facts, { write, adopt: ctx.argv.includes("--adopt") });
|
|
30
31
|
ctx.out.log("");
|
|
31
32
|
for (const entry of actions) {
|
package/src/handbuch/render.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { existsSync } from "node:fs";
|
|
2
|
-
import { raw } from "@cosmicdrift/kumiko-headless";
|
|
2
|
+
import { raw } from "@cosmicdrift/kumiko-renderer/headless";
|
|
3
3
|
import { Marked } from "marked";
|
|
4
4
|
import { type BuildInfo, buildInfoLines } from "./buildinfo";
|
|
5
5
|
import { chromeFor } from "./chrome";
|
package/src/missing-peer.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
const APP_PEER_PACKAGES = [
|
|
2
2
|
"@cosmicdrift/kumiko-framework",
|
|
3
3
|
"@cosmicdrift/kumiko-bundled-features",
|
|
4
|
-
"@cosmicdrift/kumiko-
|
|
4
|
+
"@cosmicdrift/kumiko-renderer",
|
|
5
5
|
] as const;
|
|
6
6
|
|
|
7
7
|
// The runtime packages are optional peers (so `new app` runs without them); a
|
package/src/repo-manifest.ts
CHANGED