@cosmicdrift/kumiko-framework 0.336.0 → 0.337.1
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/dist/api/auth-middleware.js +43 -19
- package/dist/api/auth-routes.d.ts +1 -0
- package/dist/api/auth-routes.js +20 -0
- package/dist/api/redis-sse-broker.d.ts +1 -0
- package/dist/api/redis-sse-broker.js +4 -5
- package/dist/api/routes.js +1 -1
- package/dist/api/server.js +3 -3
- package/dist/api/sse-route.js +14 -1
- package/dist/bun-db/query.js +6 -4
- package/dist/crypto/event-pii.js +3 -3
- package/dist/crypto/kek-source.js +1 -0
- package/dist/db/blind-index-cleanup.js +1 -1
- package/dist/db/event-store-executor-read.js +15 -4
- package/dist/db/queries/event-consumer.d.ts +2 -2
- package/dist/db/queries/event-consumer.js +5 -4
- package/dist/db/queries/event-store.js +6 -6
- package/dist/db/queries/projection-rebuild.js +4 -4
- package/dist/db/queries/seed-context.js +6 -6
- package/dist/derivatives/derivatives-context.js +5 -2
- package/dist/engine/boot-validator/access-declarations.js +2 -1
- package/dist/engine/boot-validator/projection-list-screens.d.ts +1 -1
- package/dist/engine/boot-validator/projection-list-screens.js +13 -0
- package/dist/engine/boot-validator/screens.js +12 -0
- package/dist/engine/build-app-schema.js +2 -1
- package/dist/engine/build-config-feature-schema.js +4 -0
- package/dist/engine/define-feature.js +1 -0
- package/dist/engine/define-handler.js +3 -5
- package/dist/engine/feature-ast/extractors/index.d.ts +1 -1
- package/dist/engine/feature-ast/extractors/index.js +1 -1
- package/dist/engine/feature-ast/extractors/jobs-routes.js +6 -2
- package/dist/engine/feature-ast/extractors/round3.d.ts +2 -1
- package/dist/engine/feature-ast/extractors/round3.js +11 -0
- package/dist/engine/feature-ast/extractors/shared.js +1 -3
- package/dist/engine/feature-ast/index.d.ts +1 -1
- package/dist/engine/feature-ast/parse.js +3 -1
- package/dist/engine/feature-ast/patch.d.ts +3 -0
- package/dist/engine/feature-ast/patch.js +8 -11
- package/dist/engine/feature-ast/patcher.d.ts +6 -1
- package/dist/engine/feature-ast/patcher.js +3 -0
- package/dist/engine/feature-ast/pattern-change-schema.d.ts +590 -1
- package/dist/engine/feature-ast/pattern-change-schema.js +17 -2
- package/dist/engine/feature-ast/patterns.d.ts +8 -2
- package/dist/engine/feature-ast/patterns.js +1 -0
- package/dist/engine/feature-ast/render.js +9 -0
- package/dist/engine/feature-builder-state.d.ts +2 -1
- package/dist/engine/feature-builder-state.js +1 -0
- package/dist/engine/feature-config-events-jobs.d.ts +4 -1
- package/dist/engine/feature-config-events-jobs.js +20 -0
- package/dist/engine/feature-ui-extensions.js +15 -21
- package/dist/engine/index.d.ts +1 -1
- package/dist/engine/pattern-library/library.js +2 -1
- package/dist/engine/pattern-library/static-schemas.d.ts +1 -0
- package/dist/engine/pattern-library/static-schemas.js +47 -4
- package/dist/engine/registry-facade.js +7 -0
- package/dist/engine/registry-ingest.js +7 -0
- package/dist/engine/registry-state.d.ts +2 -1
- package/dist/engine/registry-state.js +1 -0
- package/dist/engine/registry-validate.d.ts +1 -1
- package/dist/engine/registry-validate.js +39 -17
- package/dist/engine/registry.js +2 -2
- package/dist/engine/types/index.d.ts +2 -2
- package/dist/engine/types/index.js +1 -1
- package/dist/engine/types/screen.d.ts +1 -1
- package/dist/engine/types/screen.js +1 -1
- package/dist/errors/classes.d.ts +5 -0
- package/dist/errors/classes.js +15 -0
- package/dist/errors/index.d.ts +1 -1
- package/dist/errors/index.js +1 -1
- package/dist/i18n/required-surface-keys.js +3 -5
- package/dist/migrations/pending-rebuilds.d.ts +2 -0
- package/dist/migrations/pending-rebuilds.js +7 -3
- package/dist/pipeline/dispatch-batch.js +2 -2
- package/dist/pipeline/dispatch-query.js +3 -5
- package/dist/pipeline/dispatch-stream.js +2 -4
- package/dist/pipeline/dispatch-write.js +2 -4
- package/dist/pipeline/dispatcher.js +1 -1
- package/dist/pipeline/event-dispatcher-delivery.d.ts +0 -1
- package/dist/pipeline/event-dispatcher-delivery.js +1 -8
- package/dist/pipeline/event-dispatcher.js +16 -25
- package/dist/pipeline/handler-access-error.d.ts +3 -0
- package/dist/pipeline/handler-access-error.js +17 -0
- package/dist/pipeline/irreversible-operation-gate.js +9 -3
- package/dist/pipeline/member-read-only-transaction.js +8 -3
- package/dist/pipeline/system-hooks.js +7 -1
- package/dist/pipeline/system-identity-switch.js +1 -1
- package/dist/search/meilisearch-adapter.js +0 -2
- package/dist/stack/push-entity-projection-tables.js +5 -3
- package/dist/stack/redis.d.ts +1 -0
- package/dist/stack/redis.js +11 -2
- package/dist/stack/table-helpers.d.ts +3 -1
- package/dist/stack/table-helpers.js +9 -7
- package/dist/stack/test-stack.js +1 -7
- package/dist/testing/handler-context.js +3 -6
- package/dist/ui-types/index.d.ts +1 -1
- package/dist/ui-types/index.js +1 -1
- package/dist/upgrade-cli.js +29 -2
- package/package.json +4 -4
- package/src/changes.json +98 -0
- package/src/scripts/codemod/collect-source-files.ts +48 -0
- package/src/scripts/codemod/migrate-cross-tenant.ts +3 -23
- package/src/scripts/codemod/migrate-db-raw.ts +6 -23
- package/src/scripts/codemod/migrate-open-to-all.ts +4 -24
- package/src/scripts/codemod/pii-personal-migration.ts +11 -21
- package/src/scripts/codemod/unprocessable-error-details-reason.ts +47 -3
package/dist/ui-types/index.js
CHANGED
|
@@ -24,7 +24,7 @@ export { computeDerivedCellValue, roundDerivedCellValue } from "../engine/embedd
|
|
|
24
24
|
export { parseRefTarget } from "../engine/parse-ref-target.js";
|
|
25
25
|
export { evalFieldCondition, explicitListScreenId, isExtensionEditSection, isFieldsEditSection, isFormatSpec, isWriteFormEditSection, normalizeEditField, normalizeListColumn, resolveNavParentScreen, sectionFieldSpecs, } from "../engine/screen-helpers.js";
|
|
26
26
|
export { isOpenToAllGranted, isUiAccessGranted } from "../engine/types/handlers.js";
|
|
27
|
-
export { metricField } from "../engine/types/screen.js";
|
|
27
|
+
export { metricField, relatedListGroupHeaderLabel, relatedListGroupKey, } from "../engine/types/screen.js";
|
|
28
28
|
export { ACTION_FORM_ENTITY, PROJECTION_DETAIL_ENTITY, WRITE_FORM_SECTION_ENTITY, } from "../i18n/required-surface-keys.js";
|
|
29
29
|
export { formalLocaleTag } from "./formality.js";
|
|
30
30
|
export { LIST_ROW_META_COLUMNS, LIST_ROW_META_REFERENCES, REFERENCE_LOOKUP_SOURCES, SYSTEM_REFERENCE_LABELS, } from "./list-row-meta.js";
|
package/dist/upgrade-cli.js
CHANGED
|
@@ -109,10 +109,12 @@ export function findPackageChangelogFiles(cwd) {
|
|
|
109
109
|
for (const name of pkgNames) {
|
|
110
110
|
if (seenNames.has(name))
|
|
111
111
|
continue;
|
|
112
|
+
// Claimed by the nearest scope even without a changelog, so a farther
|
|
113
|
+
// node_modules can't contribute another version of the same package.
|
|
114
|
+
seenNames.add(name);
|
|
112
115
|
const changelogPath = join(scopeDir, name, "src", "changes.json");
|
|
113
116
|
if (!existsSync(changelogPath))
|
|
114
117
|
continue;
|
|
115
|
-
seenNames.add(name);
|
|
116
118
|
const realPath = realpathSync(changelogPath);
|
|
117
119
|
if (seenRealPaths.has(realPath))
|
|
118
120
|
continue;
|
|
@@ -305,9 +307,30 @@ function logManualMarkerOutcome(out, manualEntries, markerVersion) {
|
|
|
305
307
|
: ` ⚠ Marker moved past the manual change(s) above to ${markerVersion}; the upgrade guard no longer lists them, so make sure they are migrated.`);
|
|
306
308
|
}
|
|
307
309
|
}
|
|
310
|
+
// Records the codemods that already wrote files before a later one failed, so
|
|
311
|
+
// a re-run resumes after them instead of replaying non-idempotent codemods.
|
|
312
|
+
function writePartialMarker(out, targetDir, pending, manualEntries, failedVersion, ran) {
|
|
313
|
+
// skip: no codemod wrote files before the failure, nothing to resume after
|
|
314
|
+
if (ran.length === 0)
|
|
315
|
+
return;
|
|
316
|
+
const doneBeforeFailure = pending.filter((e) => compareVersions(e.version, failedVersion) < 0);
|
|
317
|
+
const version = markerVersionForPending(doneBeforeFailure, manualEntries, "");
|
|
318
|
+
// skip: no pending entry completed before the failure, so no marker version to record
|
|
319
|
+
if (version === "")
|
|
320
|
+
return;
|
|
321
|
+
const pendingManual = manualEntries.map((e) => ({ version: e.version, title: e.title }));
|
|
322
|
+
writeUpgradeMarker(targetDir, {
|
|
323
|
+
version,
|
|
324
|
+
appliedAt: Temporal.Now.instant().toString(),
|
|
325
|
+
codemods: ran,
|
|
326
|
+
...(pendingManual.length > 0 && { pendingManual }),
|
|
327
|
+
});
|
|
328
|
+
out.err(` ⚠ ${ran.length} codemod(s) already applied; wrote marker at ${version} so a re-run resumes at the failed one.`);
|
|
329
|
+
}
|
|
308
330
|
// Runs every pending breaking entry's codemod, oldest version first (so a
|
|
309
331
|
// later codemod can assume an earlier one already ran). Stops on the first
|
|
310
|
-
// failure
|
|
332
|
+
// failure; codemods that already ran are kept in a partial marker. Writes the
|
|
333
|
+
// marker whenever dryRun is false —
|
|
311
334
|
// even with zero pending entries, so an already-current app still gets a
|
|
312
335
|
// bootstrap marker recording its installed version (fw#2299).
|
|
313
336
|
// kumiko-lint-ignore complexity-budget sequential codemod runner with zero-pending bootstrap marker
|
|
@@ -364,6 +387,8 @@ markerVersion) {
|
|
|
364
387
|
const scriptPath = resolveCodemodScript(repoRoot, e.codemod);
|
|
365
388
|
if (!scriptPath) {
|
|
366
389
|
out.err(` ✗ ${e.version} · ${e.title} — invalid codemod path "${e.codemod}"`);
|
|
390
|
+
if (!dryRun)
|
|
391
|
+
writePartialMarker(out, targetDir, pending, manualEntries, e.version, ran);
|
|
367
392
|
return 1;
|
|
368
393
|
}
|
|
369
394
|
out.log(` → ${e.version} · running ${e.codemod}${dryRun ? " (dry-run)" : ""}`);
|
|
@@ -372,6 +397,8 @@ markerVersion) {
|
|
|
372
397
|
out.log(result.output);
|
|
373
398
|
if (!result.ok) {
|
|
374
399
|
out.err(` ✗ ${e.version} · ${e.codemod} failed`);
|
|
400
|
+
if (!dryRun)
|
|
401
|
+
writePartialMarker(out, targetDir, pending, manualEntries, e.version, ran);
|
|
375
402
|
return 1;
|
|
376
403
|
}
|
|
377
404
|
ran.push({ version: e.version, codemod: e.codemod, title: e.title });
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cosmicdrift/kumiko-framework",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.337.1",
|
|
4
4
|
"description": "Framework core — engine, pipeline, API, DB, and every other bit that makes Kumiko go.",
|
|
5
5
|
"license": "BUSL-1.1",
|
|
6
6
|
"author": "Marc Frost <marc@cosmicdriftgamestudio.com>",
|
|
@@ -206,8 +206,8 @@
|
|
|
206
206
|
"./package.json": "./package.json"
|
|
207
207
|
},
|
|
208
208
|
"dependencies": {
|
|
209
|
-
"@cosmicdrift/kumiko-http": "0.
|
|
210
|
-
"@cosmicdrift/kumiko-types": "0.
|
|
209
|
+
"@cosmicdrift/kumiko-http": "0.337.1",
|
|
210
|
+
"@cosmicdrift/kumiko-types": "0.337.1",
|
|
211
211
|
"bullmq": "^5.76.7",
|
|
212
212
|
"bun-types": "^1.3.13",
|
|
213
213
|
"hono": "^4.13.1",
|
|
@@ -223,7 +223,7 @@
|
|
|
223
223
|
"zod": "^4.4.3"
|
|
224
224
|
},
|
|
225
225
|
"devDependencies": {
|
|
226
|
-
"@cosmicdrift/kumiko-dispatcher-live": "0.
|
|
226
|
+
"@cosmicdrift/kumiko-dispatcher-live": "0.337.1",
|
|
227
227
|
"bun-types": "^1.3.13",
|
|
228
228
|
"pino-pretty": "^13.1.3"
|
|
229
229
|
},
|
package/src/changes.json
CHANGED
|
@@ -1,4 +1,102 @@
|
|
|
1
1
|
[
|
|
2
|
+
{
|
|
3
|
+
"version": "0.337.1",
|
|
4
|
+
"type": "improvement",
|
|
5
|
+
"title": "Designer reads and creates r.secretNamespace, edits writeRoles on secrets",
|
|
6
|
+
"detail": "The Designer source model now parses `r.secretNamespace(...)` as its own pattern kind `secretNamespace` instead of falling back to `unknown`, renders it back and can add it through the pattern library. A Zod `nameSchema` or an options identifier is kept verbatim. The `r.secret` form gains a `writeRoles` list and no longer offers the `system` scope, which the secret type never allowed."
|
|
7
|
+
},
|
|
8
|
+
{
|
|
9
|
+
"version": "0.337.0",
|
|
10
|
+
"type": "fix",
|
|
11
|
+
"title": "variant() ignores caller-supplied resolvedOverlays",
|
|
12
|
+
"detail": "`derivatives.variant()` now discards a caller-supplied `resolvedOverlays`, so a literal overlay payload can no longer bypass the overlay resolver. The guarded admin seed throws instead of silently picking by id when a user row's `insertedAt` is not an instant."
|
|
13
|
+
},
|
|
14
|
+
{
|
|
15
|
+
"version": "0.337.0",
|
|
16
|
+
"type": "breaking",
|
|
17
|
+
"title": "Anonymous callers on protected handlers get 401 unauthenticated, and a stale auth cookie no longer blocks public handlers",
|
|
18
|
+
"migration": "Tests or clients that expect 403 / access_denied for an anonymous caller (no session, the anonymousAccess user, or a JWT carrying only the anonymous role) on a role-gated or openToAll handler now get 401 / unauthenticated; update those assertions. Signed-in callers without the role still get 403 access_denied. With anonymousAccess wired, a request whose auth cookie fails verification or whose session is no longer live continues as anonymous outside /api/auth/* (public handlers answer 200, protected ones 401 unauthenticated) instead of failing with 401 invalid_token / session_invalid; bearer tokens and /api/auth/* keep the explicit 401."
|
|
19
|
+
},
|
|
20
|
+
{
|
|
21
|
+
"version": "0.337.0",
|
|
22
|
+
"type": "fix",
|
|
23
|
+
"title": "Anonymous SSE signals no longer carry row id or version",
|
|
24
|
+
"detail": "An anonymous /api/sse connection gets every row signal of a declared liveEntities entity, including rows the anonymous query hides. The frame now carries only aggregateType, eventType and createdAt, so a public viewer can no longer see which hidden rows exist or change; signed-in connections keep id and version. LiveEvent.data.id and version are optional in kumiko-renderer accordingly."
|
|
25
|
+
},
|
|
26
|
+
{
|
|
27
|
+
"version": "0.337.0",
|
|
28
|
+
"type": "breaking",
|
|
29
|
+
"title": "buildServer refuses partially wired session callbacks",
|
|
30
|
+
"migration": "If you call buildServer with auth.sessionCreator or auth.sessionRevoker, also wire the other two of sessionCreator, sessionRevoker and sessionChecker (the sessions feature's sessionStore provides all three; runProdApp and runDevApp already do this). Without a sessionChecker a logout never invalidated the JWT. A sessionChecker alone stays allowed."
|
|
31
|
+
},
|
|
32
|
+
{
|
|
33
|
+
"version": "0.337.0",
|
|
34
|
+
"type": "fix",
|
|
35
|
+
"title": "Job triggers accept short handler and event names",
|
|
36
|
+
"detail": "r.job trigger.on now qualifies a short name against the job's own feature (write handler, then query handler, then defineEvent), like notification triggers, and the boot error lists every name it tried."
|
|
37
|
+
},
|
|
38
|
+
{
|
|
39
|
+
"version": "0.337.0",
|
|
40
|
+
"type": "improvement",
|
|
41
|
+
"title": "Boot rejects a collapsed relatedList group without a header; expandableRow groupBy keys are required i18n keys",
|
|
42
|
+
"detail": "Boot fails when `groupBy.collapsedWhen` names a group that has neither `label` nor a `labels` entry, because its rows could never be opened. The required surface keys now include `groupBy.label` and `groupBy.labels` of an entityList `expandableRow`, as they already did for projectionDetail relatedList sections.",
|
|
43
|
+
"migration": "Add translations for expandableRow `groupBy` header keys if the i18n check reports them missing."
|
|
44
|
+
},
|
|
45
|
+
{
|
|
46
|
+
"version": "0.337.0",
|
|
47
|
+
"type": "fix",
|
|
48
|
+
"title": "json format falls back to text, IPv4-first egress pin, structural claim comparison for identity switch",
|
|
49
|
+
"detail": "The `json` format no longer throws on BigInt or circular values and falls back to text. `resolvePublicHost` prefers an IPv4 address on dual-stack hosts. Identity-switch claim comparison no longer treats structurally equal object claims in a different key order as a different identity."
|
|
50
|
+
},
|
|
51
|
+
{
|
|
52
|
+
"version": "0.337.0",
|
|
53
|
+
"type": "fix",
|
|
54
|
+
"title": "reference search and sort skip soft-deleted targets, blind-index sweep resolves tables in public, key-manager retry releases 5xx bodies",
|
|
55
|
+
"detail": "List search and sort by a reference field now ignore soft-deleted target rows: a deleted customer's name no longer matches a reference search and no longer decides the position in a label sort. The blind-index sweep checks table existence in the `public` schema instead of relying on the session `search_path`. The Scaleway key-manager retry cancels the response body of a retried 5xx instead of leaving it open."
|
|
56
|
+
},
|
|
57
|
+
{
|
|
58
|
+
"version": "0.337.0",
|
|
59
|
+
"type": "fix",
|
|
60
|
+
"title": "boot validator rejects redirect on list row actions, httpRoute with non-literal anonymous is a parse error, local const shadows same-named import in feature-ast, r.nav strips undefined properties",
|
|
61
|
+
"detail": "The boot validator now rejects a `redirect` on `entityList`, `projectionList` and `relatedList` row actions, where it was silently ignored. The feature-ast parser reports an `httpRoute` whose `anonymous` is a non-literal expression as a parse error instead of rewriting it to `false` on round-trip, and a local non-literal `const` no longer resolves to a same-named import. `r.nav()` strips explicitly-`undefined` properties for every registration path."
|
|
62
|
+
},
|
|
63
|
+
{
|
|
64
|
+
"version": "0.337.0",
|
|
65
|
+
"type": "fix",
|
|
66
|
+
"title": "isRedisSseBroker narrows on a kind discriminant, tenant-lifecycle gate cache is bounded",
|
|
67
|
+
"detail": "`isRedisSseBroker` now narrows on a `kind: \"redis\"` discriminant that `createRedisSseBroker` sets, so an app-owned broker that happens to expose `close()` is no longer mistaken for the Redis one. The tenant-lifecycle gate cache is capped at 5000 entries, so anonymous requests with random `X-Tenant` ids can no longer grow it without bound."
|
|
68
|
+
},
|
|
69
|
+
{
|
|
70
|
+
"version": "0.337.0",
|
|
71
|
+
"type": "fix",
|
|
72
|
+
"title": "search extensions never see sensitive row fields on named events, streams cannot hard-delete (gate message now says so), codemod walkers and namespace imports hardened",
|
|
73
|
+
"detail": "The search consumer no longer hands `sensitive` columns of the projection row to `searchPayloadExtension` contributors when a named domain event triggers the re-index. A `skipApplyErrors` rebuild option passed for a multi-stream projection is now logged as ignored instead of dropped silently. The irreversible-operation gate error for stream entry handlers says that streams cannot declare an agent risk (streams always resolve `mid`, so a hard delete or `forget` inside a stream is denied; dispatch a risk `high` write handler directly). The `migrate-open-to-all` codemod skips symlinks and `dist`/`build`/`.next`/`.git`, and the `unprocessable-error-details-reason` codemod follows namespace imports and reports member-access callees it cannot resolve."
|
|
74
|
+
},
|
|
75
|
+
{
|
|
76
|
+
"version": "0.337.0",
|
|
77
|
+
"type": "fix",
|
|
78
|
+
"title": "search remove no longer creates indexes, upgrade --apply keeps a partial marker on codemod failure, test stack syncs indexes and cleans queue keys, codemod walkers skip symlinks",
|
|
79
|
+
"detail": "Meilisearch `remove` and `removeBatch` no longer configure (and thereby implicitly create) the tenant index before deleting. `kumiko-upgrade --apply` writes a partial upgrade marker when a later codemod fails, so a re-run resumes at the failed codemod instead of replaying the ones that already wrote files. The changelog lookup claims a package name in the nearest `node_modules` even when it has no `changes.json`, instead of listing a farther version's changelog. The test stack syncs missing indexes (including unique) on a persistent dev DB and removes the BullMQ keys of its derived per-stack queues on cleanup. `bridgeStub` defaults to the real anonymous identity. The `migrate-cross-tenant` and `migrate-db-raw` codemods share one walker with `migrate-open-to-all` that skips symlinks and `dist`/`build`/`.next`/`.git`."
|
|
80
|
+
},
|
|
81
|
+
{
|
|
82
|
+
"version": "0.337.0",
|
|
83
|
+
"type": "fix",
|
|
84
|
+
"title": "Removed unreachable PAT rate-limit guard from the httpRoute chain",
|
|
85
|
+
"detail": "The httpRoute guard chain no longer includes the PAT rate-limit guard. PATs are rejected on httpRoutes before it could count, so behavior is unchanged."
|
|
86
|
+
},
|
|
87
|
+
{
|
|
88
|
+
"version": "0.337.0",
|
|
89
|
+
"type": "improvement",
|
|
90
|
+
"title": "r.secretNamespace declares a family of runtime-named secret keys",
|
|
91
|
+
"detail": "`r.secretNamespace(name, { label, scope: \"tenant\", writeRoles?, nameSchema? })` declares the prefix `<feature>:<name>.` (kebab-cased) and returns `{ prefix, keyFor(name) }`. `secrets:write:set` and `secrets:write:delete` accept a key under that prefix when the suffix is non-empty and passes `nameSchema`; `writeRoles` applies to every key in the namespace. Namespaces are kept out of `getAllSecretKeys` and the generated secrets screen. `Registry.findSecretNamespace(key)` resolves the namespace of a key. step-dispatcher declares `webhook-auth`, so webhook auth secrets (`step-dispatcher:webhook-auth.<name>`) stay settable through the API.",
|
|
92
|
+
"migration": "No code change needed. A feature that stores secrets under a runtime-chosen suffix declares a namespace instead of one `r.secret` per key."
|
|
93
|
+
},
|
|
94
|
+
{
|
|
95
|
+
"version": "0.336.1",
|
|
96
|
+
"type": "fix",
|
|
97
|
+
"title": "Cross-tenant deprecation warning names a runnable codemod path",
|
|
98
|
+
"detail": "The `deprecation:entity-handler-cross-tenant` boot warning now names the codemod path that runs from a consumer repo. The `reason` strings on the `passwordHash`, PAT `tokenHash` and PAT `prefix` fields no longer carry an issue-number suffix. The `pii-personal-migration` codemod now skips `.d.ts` files in `--report-stance` mode as well."
|
|
99
|
+
},
|
|
2
100
|
{
|
|
3
101
|
"version": "0.336.0",
|
|
4
102
|
"type": "fix",
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { lstatSync, readdirSync, statSync } from "node:fs";
|
|
2
|
+
import { join, resolve } from "node:path";
|
|
3
|
+
|
|
4
|
+
const BUILD_AND_VENDOR_DIR_NAMES: readonly string[] = [
|
|
5
|
+
"node_modules",
|
|
6
|
+
"dist",
|
|
7
|
+
"build",
|
|
8
|
+
".next",
|
|
9
|
+
".git",
|
|
10
|
+
];
|
|
11
|
+
|
|
12
|
+
export interface CollectSourceFilesOptions {
|
|
13
|
+
readonly isMigratableFile: (name: string) => boolean;
|
|
14
|
+
readonly extraSkippedDirNames?: readonly string[];
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export function collectSourceFiles(
|
|
18
|
+
paths: readonly string[],
|
|
19
|
+
options: CollectSourceFilesOptions,
|
|
20
|
+
): string[] {
|
|
21
|
+
const skippedDirNames = new Set([
|
|
22
|
+
...BUILD_AND_VENDOR_DIR_NAMES,
|
|
23
|
+
...(options.extraSkippedDirNames ?? []),
|
|
24
|
+
]);
|
|
25
|
+
|
|
26
|
+
function walkDir(dir: string, out: string[]): void {
|
|
27
|
+
for (const name of readdirSync(dir)) {
|
|
28
|
+
if (skippedDirNames.has(name)) continue;
|
|
29
|
+
const full = join(dir, name);
|
|
30
|
+
const stat = lstatSync(full);
|
|
31
|
+
// Symlinks can point back at a parent directory (workspace links) and loop forever.
|
|
32
|
+
if (stat.isSymbolicLink()) continue;
|
|
33
|
+
if (stat.isDirectory()) {
|
|
34
|
+
walkDir(full, out);
|
|
35
|
+
continue;
|
|
36
|
+
}
|
|
37
|
+
if (options.isMigratableFile(name)) out.push(full);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const files: string[] = [];
|
|
42
|
+
for (const p of paths) {
|
|
43
|
+
const abs = resolve(p);
|
|
44
|
+
if (statSync(abs).isDirectory()) walkDir(abs, files);
|
|
45
|
+
else if (options.isMigratableFile(abs)) files.push(abs);
|
|
46
|
+
}
|
|
47
|
+
return files;
|
|
48
|
+
}
|
|
@@ -6,8 +6,7 @@
|
|
|
6
6
|
// Usage:
|
|
7
7
|
// bun scripts/codemod/migrate-cross-tenant.ts [--dry-run] <path...>
|
|
8
8
|
|
|
9
|
-
import {
|
|
10
|
-
import { join, resolve } from "node:path";
|
|
9
|
+
import { readFileSync, writeFileSync } from "node:fs";
|
|
11
10
|
import {
|
|
12
11
|
type CallExpression,
|
|
13
12
|
Node,
|
|
@@ -16,6 +15,7 @@ import {
|
|
|
16
15
|
type PropertyAssignment,
|
|
17
16
|
SyntaxKind,
|
|
18
17
|
} from "ts-morph";
|
|
18
|
+
import { collectSourceFiles } from "./collect-source-files.js";
|
|
19
19
|
|
|
20
20
|
export interface Rewrite {
|
|
21
21
|
readonly file: string;
|
|
@@ -264,28 +264,8 @@ function isMigratableFile(name: string): boolean {
|
|
|
264
264
|
return /\.(ts|tsx)$/.test(name);
|
|
265
265
|
}
|
|
266
266
|
|
|
267
|
-
function walkDir(dir: string, out: string[]): void {
|
|
268
|
-
for (const name of readdirSync(dir)) {
|
|
269
|
-
if (name === "node_modules") continue;
|
|
270
|
-
const full = join(dir, name);
|
|
271
|
-
const stat = statSync(full);
|
|
272
|
-
if (stat.isDirectory()) {
|
|
273
|
-
walkDir(full, out);
|
|
274
|
-
continue;
|
|
275
|
-
}
|
|
276
|
-
if (isMigratableFile(name)) out.push(full);
|
|
277
|
-
}
|
|
278
|
-
}
|
|
279
|
-
|
|
280
267
|
function collectFiles(paths: string[]): string[] {
|
|
281
|
-
|
|
282
|
-
for (const p of paths) {
|
|
283
|
-
const abs = resolve(p);
|
|
284
|
-
const stat = statSync(abs);
|
|
285
|
-
if (stat.isDirectory()) walkDir(abs, files);
|
|
286
|
-
else if (isMigratableFile(abs)) files.push(abs);
|
|
287
|
-
}
|
|
288
|
-
return files;
|
|
268
|
+
return collectSourceFiles(paths, { isMigratableFile });
|
|
289
269
|
}
|
|
290
270
|
|
|
291
271
|
async function main(): Promise<void> {
|
|
@@ -7,9 +7,9 @@
|
|
|
7
7
|
// Usage:
|
|
8
8
|
// bun scripts/codemod/migrate-db-raw.ts [--dry-run] [--global-tables a,b] <path...>
|
|
9
9
|
|
|
10
|
-
import {
|
|
11
|
-
import { join, resolve } from "node:path";
|
|
10
|
+
import { readFileSync, writeFileSync } from "node:fs";
|
|
12
11
|
import { Node, Project, type SourceFile, SyntaxKind, VariableDeclarationKind } from "ts-morph";
|
|
12
|
+
import { collectSourceFiles } from "./collect-source-files.js";
|
|
13
13
|
|
|
14
14
|
const DEFAULT_GLOBAL_TABLES: ReadonlySet<string> = new Set([
|
|
15
15
|
"userTable",
|
|
@@ -334,28 +334,11 @@ function isMigratableFile(name: string): boolean {
|
|
|
334
334
|
return true;
|
|
335
335
|
}
|
|
336
336
|
|
|
337
|
-
function walkDir(dir: string, out: string[]): void {
|
|
338
|
-
for (const name of readdirSync(dir)) {
|
|
339
|
-
if (name === "node_modules" || name === "__tests__") continue;
|
|
340
|
-
const full = join(dir, name);
|
|
341
|
-
const stat = statSync(full);
|
|
342
|
-
if (stat.isDirectory()) {
|
|
343
|
-
walkDir(full, out);
|
|
344
|
-
continue;
|
|
345
|
-
}
|
|
346
|
-
if (isMigratableFile(name)) out.push(full);
|
|
347
|
-
}
|
|
348
|
-
}
|
|
349
|
-
|
|
350
337
|
function collectFiles(paths: string[]): string[] {
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
if (stat.isDirectory()) walkDir(abs, files);
|
|
356
|
-
else if (isMigratableFile(abs)) files.push(abs);
|
|
357
|
-
}
|
|
358
|
-
return files;
|
|
338
|
+
return collectSourceFiles(paths, {
|
|
339
|
+
isMigratableFile,
|
|
340
|
+
extraSkippedDirNames: ["__tests__"],
|
|
341
|
+
});
|
|
359
342
|
}
|
|
360
343
|
|
|
361
344
|
async function main(): Promise<void> {
|
|
@@ -6,8 +6,7 @@
|
|
|
6
6
|
// Usage:
|
|
7
7
|
// bun scripts/codemod/migrate-open-to-all.ts [--dry-run] [--test-reason "<text>"] <path...>
|
|
8
8
|
|
|
9
|
-
import {
|
|
10
|
-
import { join, resolve } from "node:path";
|
|
9
|
+
import { readFileSync, writeFileSync } from "node:fs";
|
|
11
10
|
import {
|
|
12
11
|
Node,
|
|
13
12
|
type ObjectLiteralExpression,
|
|
@@ -15,6 +14,7 @@ import {
|
|
|
15
14
|
type PropertyAssignment,
|
|
16
15
|
SyntaxKind,
|
|
17
16
|
} from "ts-morph";
|
|
17
|
+
import { collectSourceFiles } from "./collect-source-files.js";
|
|
18
18
|
|
|
19
19
|
const DEFAULT_TEST_REASON = "test handler callable by any signed-in test user";
|
|
20
20
|
|
|
@@ -164,28 +164,8 @@ function isMigratableFile(name: string): boolean {
|
|
|
164
164
|
return /\.(ts|tsx)$/.test(name);
|
|
165
165
|
}
|
|
166
166
|
|
|
167
|
-
function
|
|
168
|
-
|
|
169
|
-
if (name === "node_modules") continue;
|
|
170
|
-
const full = join(dir, name);
|
|
171
|
-
const stat = statSync(full);
|
|
172
|
-
if (stat.isDirectory()) {
|
|
173
|
-
walkDir(full, out);
|
|
174
|
-
continue;
|
|
175
|
-
}
|
|
176
|
-
if (isMigratableFile(name)) out.push(full);
|
|
177
|
-
}
|
|
178
|
-
}
|
|
179
|
-
|
|
180
|
-
function collectFiles(paths: string[]): string[] {
|
|
181
|
-
const files: string[] = [];
|
|
182
|
-
for (const p of paths) {
|
|
183
|
-
const abs = resolve(p);
|
|
184
|
-
const stat = statSync(abs);
|
|
185
|
-
if (stat.isDirectory()) walkDir(abs, files);
|
|
186
|
-
else if (isMigratableFile(abs)) files.push(abs);
|
|
187
|
-
}
|
|
188
|
-
return files;
|
|
167
|
+
export function collectFiles(paths: string[]): string[] {
|
|
168
|
+
return collectSourceFiles(paths, { isMigratableFile });
|
|
189
169
|
}
|
|
190
170
|
|
|
191
171
|
// Remaining manual sites still fail boot validation, so upgrade-cli must not
|
|
@@ -21,7 +21,9 @@
|
|
|
21
21
|
// packages/types/src/fields.ts). Never guesses: anything outside this
|
|
22
22
|
// table is reported (file:line) instead of transformed.
|
|
23
23
|
//
|
|
24
|
-
// Usage: bun scripts/codemod/pii-personal-migration.ts <targetDir> [--dry-run]
|
|
24
|
+
// Usage: bun scripts/codemod/pii-personal-migration.ts <targetDir> [--dry-run | --report-stance]
|
|
25
|
+
// --report-stance is read-only: it writes nothing and classifies fields
|
|
26
|
+
// that carry no personal stance.
|
|
25
27
|
|
|
26
28
|
import { readFileSync } from "node:fs";
|
|
27
29
|
import { relative, resolve } from "node:path";
|
|
@@ -423,14 +425,12 @@ function applyTransform(
|
|
|
423
425
|
obj.insertPropertyAssignments(insertIndex, newProps);
|
|
424
426
|
}
|
|
425
427
|
|
|
426
|
-
function
|
|
428
|
+
function findSourceFiles(rootDir: string, extraExcludes: readonly string[] = []): string[] {
|
|
427
429
|
const glob = new Glob("**/*.{ts,tsx}");
|
|
428
|
-
|
|
429
|
-
// *implementation* of expandPersonalAnnotations (personal -> flags) —
|
|
430
|
-
// not an authored override, so it's not a migration target.
|
|
431
|
-
const EXCLUDE = ["/node_modules/", "/dist/", "/build/", "/engine/factories.ts"];
|
|
430
|
+
const EXCLUDE = ["/node_modules/", "/dist/", "/build/", ...extraExcludes];
|
|
432
431
|
const files: string[] = [];
|
|
433
432
|
for (const file of glob.scanSync({ cwd: rootDir, dot: false })) {
|
|
433
|
+
if (file.endsWith(".d.ts")) continue;
|
|
434
434
|
const abs = resolve(rootDir, file);
|
|
435
435
|
if (EXCLUDE.some((p) => abs.includes(p))) continue;
|
|
436
436
|
files.push(abs);
|
|
@@ -666,19 +666,6 @@ function collectStanceSites(sourceFile: SourceFile): StanceSite[] {
|
|
|
666
666
|
return sites;
|
|
667
667
|
}
|
|
668
668
|
|
|
669
|
-
function findReportStanceFiles(rootDir: string): string[] {
|
|
670
|
-
const glob = new Glob("**/*.{ts,tsx}");
|
|
671
|
-
const EXCLUDE = ["/node_modules/", "/dist/", "/build/"];
|
|
672
|
-
const files: string[] = [];
|
|
673
|
-
for (const file of glob.scanSync({ cwd: rootDir, dot: false })) {
|
|
674
|
-
if (file.endsWith(".d.ts")) continue;
|
|
675
|
-
const abs = resolve(rootDir, file);
|
|
676
|
-
if (EXCLUDE.some((p) => abs.includes(p))) continue;
|
|
677
|
-
files.push(abs);
|
|
678
|
-
}
|
|
679
|
-
return files.sort();
|
|
680
|
-
}
|
|
681
|
-
|
|
682
669
|
function formatHintSuffix(site: StanceSite): string {
|
|
683
670
|
if (!site.hint) return "";
|
|
684
671
|
return site.nearMissOf
|
|
@@ -701,7 +688,7 @@ function reportStance(rootDir: string): void {
|
|
|
701
688
|
let total = 0;
|
|
702
689
|
|
|
703
690
|
const project = createReportStanceProject();
|
|
704
|
-
for (const file of
|
|
691
|
+
for (const file of findSourceFiles(rootDir)) {
|
|
705
692
|
const sites = reportStanceForSource(readFileSync(file, "utf8"), file, project);
|
|
706
693
|
if (sites.length === 0) continue;
|
|
707
694
|
|
|
@@ -741,7 +728,10 @@ async function main(): Promise<void> {
|
|
|
741
728
|
}
|
|
742
729
|
|
|
743
730
|
const dryRun = process.argv.includes("--dry-run");
|
|
744
|
-
|
|
731
|
+
// engine/factories.ts constructs raw ResolvedPiiFlags literals as the
|
|
732
|
+
// *implementation* of expandPersonalAnnotations (personal -> flags) —
|
|
733
|
+
// not an authored override, so it's not a migration target.
|
|
734
|
+
const files = findSourceFiles(rootDir, ["/engine/factories.ts"]);
|
|
745
735
|
const project = new Project({
|
|
746
736
|
skipAddingFilesFromTsConfig: true,
|
|
747
737
|
skipFileDependencyResolution: true,
|
|
@@ -51,14 +51,20 @@ function findTargetFiles(rootDir: string): string[] {
|
|
|
51
51
|
return files.sort();
|
|
52
52
|
}
|
|
53
53
|
|
|
54
|
+
const NAMESPACE_IMPORT = "*";
|
|
55
|
+
|
|
54
56
|
type ImportedBinding = { readonly importedName: string; readonly moduleSpecifier: string };
|
|
55
57
|
|
|
56
|
-
/** Local-name -> where it's imported from, for every named value import in the file. */
|
|
58
|
+
/** Local-name -> where it's imported from, for every named or namespace value import in the file. */
|
|
57
59
|
function collectImportedNames(sourceFile: SourceFile): Map<string, ImportedBinding> {
|
|
58
60
|
const bindings = new Map<string, ImportedBinding>();
|
|
59
61
|
for (const imp of sourceFile.getImportDeclarations()) {
|
|
60
62
|
if (imp.isTypeOnly()) continue;
|
|
61
63
|
const moduleSpecifier = imp.getModuleSpecifierValue();
|
|
64
|
+
const namespaceImport = imp.getNamespaceImport();
|
|
65
|
+
if (namespaceImport) {
|
|
66
|
+
bindings.set(namespaceImport.getText(), { importedName: NAMESPACE_IMPORT, moduleSpecifier });
|
|
67
|
+
}
|
|
62
68
|
for (const spec of imp.getNamedImports()) {
|
|
63
69
|
if (spec.isTypeOnly()) continue;
|
|
64
70
|
const importedName = spec.getName();
|
|
@@ -264,6 +270,17 @@ const FAIL_UNPROCESSABLE_CALL: MigratableCall = {
|
|
|
264
270
|
process: processFailUnprocessableCall,
|
|
265
271
|
};
|
|
266
272
|
|
|
273
|
+
function isKumikoNamespaceReceiver(
|
|
274
|
+
receiver: Node,
|
|
275
|
+
importedNames: ReadonlyMap<string, ImportedBinding>,
|
|
276
|
+
): boolean {
|
|
277
|
+
if (!Node.isIdentifier(receiver)) return false;
|
|
278
|
+
const binding = importedNames.get(receiver.getText());
|
|
279
|
+
return (
|
|
280
|
+
binding?.importedName === NAMESPACE_IMPORT && KUMIKO_MODULE_RE.test(binding.moduleSpecifier)
|
|
281
|
+
);
|
|
282
|
+
}
|
|
283
|
+
|
|
267
284
|
function migrateCallSite(
|
|
268
285
|
sourceFile: SourceFile,
|
|
269
286
|
importedNames: ReadonlyMap<string, ImportedBinding>,
|
|
@@ -272,11 +289,28 @@ function migrateCallSite(
|
|
|
272
289
|
skips: SkipEntry[],
|
|
273
290
|
): boolean {
|
|
274
291
|
const callee = site.getExpression();
|
|
275
|
-
if (!Node.isIdentifier(callee)) return false;
|
|
276
|
-
const localName = callee.getText();
|
|
277
292
|
const line = site.getStartLineNumber();
|
|
278
293
|
const file = sourceFile.getFilePath();
|
|
279
294
|
|
|
295
|
+
if (!Node.isIdentifier(callee)) {
|
|
296
|
+
const unresolvedCallee = callee.getText();
|
|
297
|
+
const resolvedViaNamespace =
|
|
298
|
+
Node.isPropertyAccessExpression(callee) &&
|
|
299
|
+
callee.getName() === target.exportName &&
|
|
300
|
+
isKumikoNamespaceReceiver(callee.getExpression(), importedNames);
|
|
301
|
+
if (resolvedViaNamespace) return processCall(site, target, skips, file, line);
|
|
302
|
+
if (unresolvedCallee.endsWith(target.exportName)) {
|
|
303
|
+
skips.push({
|
|
304
|
+
file,
|
|
305
|
+
line,
|
|
306
|
+
note: `${target.exportName} is called through "${unresolvedCallee}", which is not a @cosmicdrift/kumiko-* namespace import this codemod can resolve`,
|
|
307
|
+
kind: "manual",
|
|
308
|
+
});
|
|
309
|
+
}
|
|
310
|
+
return false;
|
|
311
|
+
}
|
|
312
|
+
const localName = callee.getText();
|
|
313
|
+
|
|
280
314
|
const binding = importedNames.get(localName);
|
|
281
315
|
if (binding?.importedName !== target.exportName) {
|
|
282
316
|
if (localName !== target.exportName) return false;
|
|
@@ -296,6 +330,16 @@ function migrateCallSite(
|
|
|
296
330
|
return false;
|
|
297
331
|
}
|
|
298
332
|
|
|
333
|
+
return processCall(site, target, skips, file, line);
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
function processCall(
|
|
337
|
+
site: Node & { getArguments(): Node[] },
|
|
338
|
+
target: MigratableCall,
|
|
339
|
+
skips: SkipEntry[],
|
|
340
|
+
file: string,
|
|
341
|
+
line: number,
|
|
342
|
+
): boolean {
|
|
299
343
|
const outcome = target.process(site.getArguments(), (note, kind) =>
|
|
300
344
|
skips.push({ file, line, note, kind }),
|
|
301
345
|
);
|