@cosmicdrift/kumiko-framework 0.220.0 → 0.221.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 +3 -3
- package/src/__tests__/store-table.integration.test.ts +2 -1
- package/src/__tests__/upgrade-cli.test.ts +81 -12
- package/src/api/__tests__/request-locale.integration.test.ts +2 -2
- package/src/api/api-constants.ts +10 -0
- package/src/api/index.ts +1 -0
- package/src/api/server.ts +17 -1
- package/src/bun-db/__tests__/coerce-row-plain-date.test.ts +2 -0
- package/src/bun-db/__tests__/coerce-row-temporal.test.ts +2 -1
- package/src/bun-db/index.ts +1 -0
- package/src/bun-db/query.ts +30 -14
- package/src/crypto/index.ts +1 -0
- package/src/crypto/is-self-pii-field.ts +8 -0
- package/src/crypto/subject-resolver.ts +4 -3
- package/src/db/__tests__/event-store-executor-list.integration.test.ts +31 -2
- package/src/db/__tests__/migrate-generator.test.ts +12 -0
- package/src/db/__tests__/multi-row-insert.integration.test.ts +2 -0
- package/src/db/__tests__/schema-migration.integration.test.ts +1 -0
- package/src/db/__tests__/source-shadow-create.integration.test.ts +2 -0
- package/src/db/blind-index-cleanup.ts +36 -19
- package/src/db/event-store-executor-context.ts +2 -2
- package/src/db/event-store-executor-read.ts +7 -6
- package/src/db/event-store-executor-write.ts +103 -49
- package/src/db/index.ts +2 -0
- package/src/db/migrate-generator.ts +14 -0
- package/src/db/queries/__tests__/unsafe-read-retrying.test.ts +8 -1
- package/src/db/queries/backfill-pii.ts +13 -10
- package/src/db/queries/raw-sql.ts +14 -2
- package/src/db/queries/seed-context.ts +8 -4
- package/src/derivatives/__tests__/variant-route.integration.test.ts +3 -0
- package/src/engine/__tests__/boot-validator-pii-retention.test.ts +32 -26
- package/src/engine/__tests__/boot-validator.test.ts +29 -3
- package/src/engine/__tests__/role-assignment.test.ts +41 -17
- package/src/engine/__tests__/schema-builder.test.ts +3 -3
- package/src/engine/boot-validator/__tests__/i18n-keys.test.ts +147 -14
- package/src/engine/boot-validator/entity-handler.ts +5 -0
- package/src/engine/boot-validator/pii-retention.ts +16 -4
- package/src/engine/boot-validator/screens.ts +9 -2
- package/src/engine/embedded-derived.ts +11 -10
- package/src/engine/feature-ast/__tests__/patch.test.ts +10 -0
- package/src/engine/feature-ast/patch.ts +9 -0
- package/src/engine/role-assignment.ts +36 -18
- package/src/errors/__tests__/classes.test.ts +5 -0
- package/src/errors/__tests__/write-failures.test.ts +3 -3
- package/src/errors/kumiko-error.ts +11 -11
- package/src/event-store/__tests__/backfill-pii.integration.test.ts +58 -0
- package/src/event-store/__tests__/perf.integration.test.ts +5 -1
- package/src/event-store/__tests__/unscoped-stream-primitives.guard.test.ts +1 -0
- package/src/event-store/event-store.ts +7 -0
- package/src/event-store/index.ts +1 -0
- package/src/files/__tests__/files.integration.test.ts +181 -2
- package/src/files/__tests__/storage-tracking.integration.test.ts +3 -0
- package/src/files/file-routes.ts +53 -6
- package/src/i18n/__tests__/mail-registry.test.ts +13 -1
- package/src/i18n/__tests__/request-locale.test.ts +19 -0
- package/src/i18n/index.ts +7 -1
- package/src/i18n/mail-registry.ts +10 -0
- package/src/i18n/request-locale.ts +11 -2
- package/src/i18n/required-surface-keys.ts +3 -1
- package/src/lifecycle/signal-handlers.ts +2 -0
- package/src/pipeline/__tests__/distributed-lock.integration.test.ts +12 -0
- package/src/pipeline/__tests__/event-dispatcher-pg-listen.integration.test.ts +26 -42
- package/src/pipeline/__tests__/load-aggregate-query.integration.test.ts +8 -6
- package/src/pipeline/distributed-lock.ts +3 -0
- package/src/schema-cli.ts +9 -4
- package/src/scripts/codemod/crypto-shredding-testing-move.ts +64 -32
- package/src/scripts/codemod/pii-personal-migration.ts +30 -14
- package/src/search/purge-subject.ts +4 -3
- package/src/search/reindex-entity.ts +2 -2
- package/src/stack/__tests__/request-helper.integration.test.ts +24 -10
- package/src/stack/test-stack.ts +3 -0
- package/src/ui-types/index.ts +1 -0
- package/src/upgrade-cli.ts +100 -12
package/src/upgrade-cli.ts
CHANGED
|
@@ -4,6 +4,7 @@ import {
|
|
|
4
4
|
readdirSync,
|
|
5
5
|
readFileSync,
|
|
6
6
|
realpathSync,
|
|
7
|
+
statSync,
|
|
7
8
|
writeFileSync,
|
|
8
9
|
} from "node:fs";
|
|
9
10
|
import { isAbsolute, join, relative, resolve } from "node:path";
|
|
@@ -170,7 +171,23 @@ export function findFeaturesDirs(cwd: string): string[] {
|
|
|
170
171
|
// node_modules after a plain npm/bun install (fw#2301).
|
|
171
172
|
export function findCodemodScriptsRoot(repoRoot: string): string | null {
|
|
172
173
|
const local = join(repoRoot, "packages/framework/src");
|
|
173
|
-
if (existsSync(join(local, CODEMOD_SUBDIR)))
|
|
174
|
+
if (existsSync(join(local, CODEMOD_SUBDIR))) {
|
|
175
|
+
// If package.json exists, require it to be kumiko-framework so a generic
|
|
176
|
+
// monorepo `packages/framework` cannot shadow the installed package.
|
|
177
|
+
// Missing package.json (test fixtures / incomplete trees) keeps prior behavior.
|
|
178
|
+
const pkgPath = join(repoRoot, "packages/framework/package.json");
|
|
179
|
+
if (existsSync(pkgPath)) {
|
|
180
|
+
try {
|
|
181
|
+
const pkg = JSON.parse(readFileSync(pkgPath, "utf8")) as { name?: string };
|
|
182
|
+
if (pkg.name === "@cosmicdrift/kumiko-framework") return local;
|
|
183
|
+
// wrong name → fall through
|
|
184
|
+
} catch {
|
|
185
|
+
// unreadable → fall through
|
|
186
|
+
}
|
|
187
|
+
} else {
|
|
188
|
+
return local;
|
|
189
|
+
}
|
|
190
|
+
}
|
|
174
191
|
|
|
175
192
|
let dir = repoRoot;
|
|
176
193
|
for (let i = 0; i < 10; i++) {
|
|
@@ -187,7 +204,10 @@ export function findCodemodScriptsRoot(repoRoot: string): string | null {
|
|
|
187
204
|
// Resolves a changes.json `codemod` field to an absolute script path,
|
|
188
205
|
// refusing anything that would escape scripts/codemod/ (path traversal,
|
|
189
206
|
// absolute paths, symlinks pointing outward) or that isn't a real .ts file.
|
|
190
|
-
|
|
207
|
+
// First arg is the consumer repo root (same as findCodemodScriptsRoot) —
|
|
208
|
+
// not the scripts root — so out-of-tree callers keep compiling against the
|
|
209
|
+
// public upgrade-cli subpath without silently resolving to null (fw#2341).
|
|
210
|
+
function resolveCodemodScriptAt(
|
|
191
211
|
codemodScriptsRoot: string,
|
|
192
212
|
codemodField: string | undefined,
|
|
193
213
|
): string | null {
|
|
@@ -213,6 +233,15 @@ export function resolveCodemodScript(
|
|
|
213
233
|
return resolved;
|
|
214
234
|
}
|
|
215
235
|
|
|
236
|
+
export function resolveCodemodScript(
|
|
237
|
+
repoRoot: string,
|
|
238
|
+
codemodField: string | undefined,
|
|
239
|
+
): string | null {
|
|
240
|
+
const scriptsRoot = findCodemodScriptsRoot(repoRoot);
|
|
241
|
+
if (!scriptsRoot) return null;
|
|
242
|
+
return resolveCodemodScriptAt(scriptsRoot, codemodField);
|
|
243
|
+
}
|
|
244
|
+
|
|
216
245
|
type CodemodRunResult = { readonly ok: boolean; readonly output: string };
|
|
217
246
|
|
|
218
247
|
// Array-form argv only — never a shell string. The script itself decides
|
|
@@ -254,6 +283,42 @@ function writeUpgradeMarker(targetDir: string, marker: UpgradeMarker): void {
|
|
|
254
283
|
writeFileSync(join(dir, "upgrade-state.json"), `${JSON.stringify(marker, null, 2)}\n`, "utf-8");
|
|
255
284
|
}
|
|
256
285
|
|
|
286
|
+
/** Highest pending version strictly below the earliest open manual breaking
|
|
287
|
+
* entry (no codemod). Falls back to highest non-breaking pending when every
|
|
288
|
+
* pending version is at/after that manual — never advances onto the manual. */
|
|
289
|
+
/** Highest pending version strictly below the earliest open manual breaking
|
|
290
|
+
* entry (no codemod). Falls back to highest non-breaking pending when every
|
|
291
|
+
* pending version is at/after that manual. If only manuals remain, returns
|
|
292
|
+
* `fallbackInstalled` so the marker stamp moves without claiming manuals done. */
|
|
293
|
+
function markerVersionForPending(
|
|
294
|
+
pending: readonly ChangelogEntry[],
|
|
295
|
+
manualEntries: readonly ChangelogEntry[],
|
|
296
|
+
fallbackInstalled: string,
|
|
297
|
+
): string {
|
|
298
|
+
const earliestManual = [...manualEntries].sort((a, b) =>
|
|
299
|
+
compareVersions(a.version, b.version),
|
|
300
|
+
)[0];
|
|
301
|
+
const eligible = pending.filter(
|
|
302
|
+
(e) => earliestManual === undefined || compareVersions(e.version, earliestManual.version) < 0,
|
|
303
|
+
);
|
|
304
|
+
const headEligible = eligible[0];
|
|
305
|
+
if (headEligible !== undefined) {
|
|
306
|
+
return eligible.reduce(
|
|
307
|
+
(max, e) => (compareVersions(e.version, max) > 0 ? e.version : max),
|
|
308
|
+
headEligible.version,
|
|
309
|
+
);
|
|
310
|
+
}
|
|
311
|
+
const nonBreaking = pending.filter((e) => e.type !== "breaking");
|
|
312
|
+
const headNonBreaking = nonBreaking[0];
|
|
313
|
+
if (headNonBreaking !== undefined) {
|
|
314
|
+
return nonBreaking.reduce(
|
|
315
|
+
(max, e) => (compareVersions(e.version, max) > 0 ? e.version : max),
|
|
316
|
+
headNonBreaking.version,
|
|
317
|
+
);
|
|
318
|
+
}
|
|
319
|
+
return fallbackInstalled;
|
|
320
|
+
}
|
|
321
|
+
|
|
257
322
|
// Runs every pending breaking entry's codemod, oldest version first (so a
|
|
258
323
|
// later codemod can assume an earlier one already ran). Stops on the first
|
|
259
324
|
// failure — no partial marker. Writes the marker whenever dryRun is false —
|
|
@@ -265,13 +330,16 @@ async function applyCodemods(
|
|
|
265
330
|
repoRoot: string,
|
|
266
331
|
targetDir: string,
|
|
267
332
|
dryRun: boolean,
|
|
268
|
-
|
|
333
|
+
// Installed version for the zero-pending bootstrap marker — never a
|
|
334
|
+
// `--from` filter override (that would permanently skip real pending
|
|
335
|
+
// codemods once the CI guard compares against the fake marker).
|
|
336
|
+
markerVersion: string,
|
|
269
337
|
): Promise<number> {
|
|
270
338
|
if (pending.length === 0) {
|
|
271
339
|
out.log(" ✓ Nothing new since your version.");
|
|
272
340
|
if (!dryRun) {
|
|
273
341
|
writeUpgradeMarker(targetDir, {
|
|
274
|
-
version:
|
|
342
|
+
version: markerVersion,
|
|
275
343
|
appliedAt: Temporal.Now.instant().toString(),
|
|
276
344
|
codemods: [],
|
|
277
345
|
});
|
|
@@ -296,15 +364,28 @@ async function applyCodemods(
|
|
|
296
364
|
? " No automatable codemods among the pending breaking changes."
|
|
297
365
|
: " ✓ No breaking changes pending.",
|
|
298
366
|
);
|
|
367
|
+
if (!dryRun) {
|
|
368
|
+
const markerVer = markerVersionForPending(pending, manualEntries, markerVersion);
|
|
369
|
+
writeUpgradeMarker(targetDir, {
|
|
370
|
+
version: markerVer,
|
|
371
|
+
appliedAt: Temporal.Now.instant().toString(),
|
|
372
|
+
codemods: [],
|
|
373
|
+
});
|
|
374
|
+
out.log(` ✓ Applied 0 codemod(s). Wrote ${join(targetDir, ".kumiko/upgrade-state.json")}`);
|
|
375
|
+
}
|
|
299
376
|
return 0;
|
|
300
377
|
}
|
|
301
378
|
|
|
302
379
|
const codemodScriptsRoot = findCodemodScriptsRoot(repoRoot);
|
|
380
|
+
if (codemodScriptsRoot === null) {
|
|
381
|
+
out.err(
|
|
382
|
+
` ✗ could not locate @cosmicdrift/kumiko-framework/src/scripts/codemod — is the framework installed? searched from ${repoRoot} upward`,
|
|
383
|
+
);
|
|
384
|
+
return 1;
|
|
385
|
+
}
|
|
303
386
|
const ran: UpgradeMarkerCodemod[] = [];
|
|
304
387
|
for (const e of codemodEntries) {
|
|
305
|
-
const scriptPath =
|
|
306
|
-
? resolveCodemodScript(codemodScriptsRoot, e.codemod)
|
|
307
|
-
: null;
|
|
388
|
+
const scriptPath = resolveCodemodScript(repoRoot, e.codemod);
|
|
308
389
|
if (!scriptPath) {
|
|
309
390
|
out.err(` ✗ ${e.version} · ${e.title} — invalid codemod path "${e.codemod}"`);
|
|
310
391
|
return 1;
|
|
@@ -325,10 +406,7 @@ async function applyCodemods(
|
|
|
325
406
|
return 0;
|
|
326
407
|
}
|
|
327
408
|
|
|
328
|
-
const latestVersion = pending
|
|
329
|
-
(max, e) => (compareVersions(e.version, max) > 0 ? e.version : max),
|
|
330
|
-
pending[0]!.version,
|
|
331
|
-
);
|
|
409
|
+
const latestVersion = markerVersionForPending(pending, manualEntries, markerVersion);
|
|
332
410
|
writeUpgradeMarker(targetDir, {
|
|
333
411
|
version: latestVersion,
|
|
334
412
|
appliedAt: Temporal.Now.instant().toString(),
|
|
@@ -396,9 +474,19 @@ export async function runUpgradeCli(
|
|
|
396
474
|
if (getFlag(args, "apply")) {
|
|
397
475
|
const dirFlag = getStringFlag(args, "dir");
|
|
398
476
|
const targetDir = dirFlag ? resolve(dirFlag) : cwd;
|
|
477
|
+
if (!existsSync(targetDir) || !statSync(targetDir).isDirectory()) {
|
|
478
|
+
out.err("");
|
|
479
|
+
out.err(` --dir path is not a directory: ${targetDir}`);
|
|
480
|
+
out.err("");
|
|
481
|
+
return 1;
|
|
482
|
+
}
|
|
483
|
+
// Marker must reflect what is actually installed under the target (or
|
|
484
|
+
// cwd), not a `--from` filter override — otherwise CI stays green forever.
|
|
485
|
+
const markerVersion =
|
|
486
|
+
(dirFlag ? readCurrentVersion(targetDir) : null) ?? installedVersion ?? currentVersion;
|
|
399
487
|
const dryRun = getFlag(args, "dry-run");
|
|
400
488
|
out.log("");
|
|
401
|
-
const code = await applyCodemods(out, pending, repoRoot, targetDir, dryRun,
|
|
489
|
+
const code = await applyCodemods(out, pending, repoRoot, targetDir, dryRun, markerVersion);
|
|
402
490
|
out.log("");
|
|
403
491
|
return code;
|
|
404
492
|
}
|