@cosmicdrift/kumiko-framework 0.335.0 → 0.336.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/anonymous-live-entities.d.ts +2 -0
- package/dist/api/anonymous-live-entities.js +17 -0
- package/dist/api/index.d.ts +2 -1
- package/dist/api/index.js +1 -0
- package/dist/api/routes.js +1 -1
- package/dist/api/server.js +6 -3
- package/dist/api/sse-route.d.ts +4 -1
- package/dist/api/sse-route.js +38 -11
- package/dist/crypto/event-pii.js +3 -3
- package/dist/db/blind-index-cleanup.js +27 -16
- package/dist/db/event-store-executor-read.js +6 -2
- package/dist/engine/boot-validator/access-declarations.js +2 -1
- package/dist/engine/boot-validator/query-output-columns.js +12 -0
- package/dist/engine/boot-validator/screens.js +22 -13
- package/dist/engine/build-app-schema.js +5 -2
- package/dist/engine/create-app.js +10 -1
- package/dist/engine/define-handler.js +3 -5
- package/dist/engine/extension-names.d.ts +4 -0
- package/dist/engine/extension-names.js +4 -0
- package/dist/engine/feature-ast/extractors/handlers.js +1 -0
- package/dist/engine/feature-entity-handlers.d.ts +1 -0
- package/dist/engine/feature-entity-handlers.js +2 -0
- package/dist/engine/registry-validate.d.ts +1 -0
- package/dist/engine/registry-validate.js +14 -0
- package/dist/engine/registry.js +2 -1
- package/dist/engine/steps/_event-match.js +12 -4
- package/dist/engine/system-user.d.ts +1 -0
- package/dist/engine/system-user.js +3 -0
- package/dist/env/dry-run.js +10 -3
- package/dist/event-store/event-store.js +4 -1
- package/dist/jobs/job-runner.js +29 -7
- package/dist/migrations/pending-rebuilds.js +4 -3
- package/dist/observability/index.d.ts +1 -1
- package/dist/observability/index.js +1 -1
- package/dist/observability/standard-metrics.d.ts +4 -0
- package/dist/observability/standard-metrics.js +16 -0
- package/dist/pipeline/dispatch-batch.js +2 -2
- package/dist/pipeline/dispatcher.js +1 -1
- package/dist/pipeline/event-dispatcher-delivery.js +5 -0
- package/dist/pipeline/event-dispatcher.js +4 -1
- package/dist/pipeline/system-hooks.js +10 -13
- package/dist/pipeline/write-origin.js +13 -6
- package/dist/stack/drain-event-consumers.js +3 -0
- package/dist/stack/drain-jobs.d.ts +3 -1
- package/dist/stack/drain-jobs.js +31 -1
- package/package.json +4 -4
- package/src/changes.json +64 -0
- package/src/scripts/codemod/pii-personal-migration.ts +65 -45
- package/src/scripts/codemod/unprocessable-error-details-reason.ts +61 -24
|
@@ -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);
|
|
@@ -440,6 +440,8 @@ function findTargetFiles(rootDir: string): string[] {
|
|
|
440
440
|
|
|
441
441
|
export type StanceClass = "direct" | "user-owned" | "user-reference" | "near-miss" | "unclassified";
|
|
442
442
|
|
|
443
|
+
export type HintCategory = "direct" | "user-owned" | "user-reference";
|
|
444
|
+
|
|
443
445
|
export type StanceSite = {
|
|
444
446
|
readonly line: number;
|
|
445
447
|
readonly field: string;
|
|
@@ -447,14 +449,23 @@ export type StanceSite = {
|
|
|
447
449
|
readonly callee: string;
|
|
448
450
|
readonly stance: StanceClass;
|
|
449
451
|
readonly hint: string | undefined;
|
|
452
|
+
readonly nearMissOf: HintCategory | undefined;
|
|
450
453
|
};
|
|
451
454
|
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
...
|
|
455
|
+
type CategorizedHint = readonly [hint: string, category: HintCategory];
|
|
456
|
+
|
|
457
|
+
const ALL_PII_NAME_HINTS: readonly CategorizedHint[] = [
|
|
458
|
+
...[...PII_DIRECT_NAME_HINTS].map((hint): CategorizedHint => [hint, "direct"]),
|
|
459
|
+
...[...PII_USER_OWNED_NAME_HINTS].map((hint): CategorizedHint => [hint, "user-owned"]),
|
|
460
|
+
...[...PII_USER_REFERENCE_NAME_HINTS].map((hint): CategorizedHint => [hint, "user-reference"]),
|
|
456
461
|
];
|
|
457
462
|
|
|
463
|
+
type FieldClassification = {
|
|
464
|
+
readonly stance: StanceClass;
|
|
465
|
+
readonly hint: string | undefined;
|
|
466
|
+
readonly nearMissOf: HintCategory | undefined;
|
|
467
|
+
};
|
|
468
|
+
|
|
458
469
|
const REPORT_STANCE_CALLEES = new Set(["createTextField", "createLongTextField"]);
|
|
459
470
|
|
|
460
471
|
// Mirrors guard-text-field-stance.ts's hasPersonalStance exactly.
|
|
@@ -539,11 +550,15 @@ function hintOccursAtBoundary(fieldLower: string, fieldOriginal: string, hint: s
|
|
|
539
550
|
}
|
|
540
551
|
}
|
|
541
552
|
|
|
542
|
-
function findLongestBoundaryHint(
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
553
|
+
function findLongestBoundaryHint(
|
|
554
|
+
fieldLower: string,
|
|
555
|
+
fieldOriginal: string,
|
|
556
|
+
): CategorizedHint | undefined {
|
|
557
|
+
let best: CategorizedHint | undefined;
|
|
558
|
+
for (const entry of ALL_PII_NAME_HINTS) {
|
|
559
|
+
const [hint] = entry;
|
|
560
|
+
if (best && hint.length <= best[0].length) continue;
|
|
561
|
+
if (hintOccursAtBoundary(fieldLower, fieldOriginal, hint)) best = entry;
|
|
547
562
|
}
|
|
548
563
|
return best;
|
|
549
564
|
}
|
|
@@ -564,29 +579,36 @@ function segmentAlignedSuffixes(fieldOriginal: string): string[] {
|
|
|
564
579
|
// (e.g. "...UserId" of "assigneeUserId") otherwise slips through undetected.
|
|
565
580
|
// The suffix must be the tail of the hint: plain `includes` matched "number"
|
|
566
581
|
// inside "phonenumber" for "orderNumber".
|
|
567
|
-
function findShortestHintContainingSuffix(fieldOriginal: string):
|
|
568
|
-
let best:
|
|
582
|
+
function findShortestHintContainingSuffix(fieldOriginal: string): CategorizedHint | undefined {
|
|
583
|
+
let best: CategorizedHint | undefined;
|
|
569
584
|
for (const suffix of segmentAlignedSuffixes(fieldOriginal)) {
|
|
570
585
|
if (suffix.length < 5) continue;
|
|
571
|
-
for (const
|
|
586
|
+
for (const entry of ALL_PII_NAME_HINTS) {
|
|
587
|
+
const [hint] = entry;
|
|
572
588
|
if (hint === suffix || !hint.endsWith(suffix)) continue;
|
|
573
|
-
if (!best || hint.length < best.length) best =
|
|
589
|
+
if (!best || hint.length < best[0].length) best = entry;
|
|
574
590
|
}
|
|
575
591
|
}
|
|
576
592
|
return best;
|
|
577
593
|
}
|
|
578
594
|
|
|
579
|
-
function classifyFieldStance(field: string):
|
|
595
|
+
function classifyFieldStance(field: string): FieldClassification {
|
|
580
596
|
const fieldLower = field.replaceAll("_", "").toLowerCase();
|
|
581
|
-
if (PII_DIRECT_NAME_HINTS.has(fieldLower))
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
597
|
+
if (PII_DIRECT_NAME_HINTS.has(fieldLower)) {
|
|
598
|
+
return { stance: "direct", hint: fieldLower, nearMissOf: undefined };
|
|
599
|
+
}
|
|
600
|
+
if (PII_USER_OWNED_NAME_HINTS.has(fieldLower)) {
|
|
601
|
+
return { stance: "user-owned", hint: fieldLower, nearMissOf: undefined };
|
|
602
|
+
}
|
|
603
|
+
if (PII_USER_REFERENCE_NAME_HINTS.has(fieldLower)) {
|
|
604
|
+
return { stance: "user-reference", hint: fieldLower, nearMissOf: undefined };
|
|
605
|
+
}
|
|
606
|
+
const nearMiss =
|
|
607
|
+
findLongestBoundaryHint(field.toLowerCase(), field) ?? findShortestHintContainingSuffix(field);
|
|
608
|
+
if (nearMiss) {
|
|
609
|
+
return { stance: "near-miss", hint: nearMiss[0], nearMissOf: nearMiss[1] };
|
|
610
|
+
}
|
|
611
|
+
return { stance: "unclassified", hint: undefined, nearMissOf: undefined };
|
|
590
612
|
}
|
|
591
613
|
|
|
592
614
|
function createReportStanceProject(): Project {
|
|
@@ -627,9 +649,9 @@ function collectStanceSites(sourceFile: SourceFile): StanceSite[] {
|
|
|
627
649
|
// A call with no enclosing field (e.g. a bare createTextField() at top
|
|
628
650
|
// level) has no real name to classify — `createTextField(...)` would
|
|
629
651
|
// otherwise false-positive as a near-miss on its own "Text".
|
|
630
|
-
const { stance, hint } = enclosingField
|
|
652
|
+
const { stance, hint, nearMissOf }: FieldClassification = enclosingField
|
|
631
653
|
? classifyFieldStance(enclosingField)
|
|
632
|
-
: { stance: "unclassified"
|
|
654
|
+
: { stance: "unclassified", hint: undefined, nearMissOf: undefined };
|
|
633
655
|
|
|
634
656
|
sites.push({
|
|
635
657
|
line: call.getStartLineNumber(),
|
|
@@ -638,22 +660,17 @@ function collectStanceSites(sourceFile: SourceFile): StanceSite[] {
|
|
|
638
660
|
callee,
|
|
639
661
|
stance,
|
|
640
662
|
hint,
|
|
663
|
+
nearMissOf,
|
|
641
664
|
});
|
|
642
665
|
}
|
|
643
666
|
return sites;
|
|
644
667
|
}
|
|
645
668
|
|
|
646
|
-
function
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
if (file.endsWith(".d.ts")) continue;
|
|
652
|
-
const abs = resolve(rootDir, file);
|
|
653
|
-
if (EXCLUDE.some((p) => abs.includes(p))) continue;
|
|
654
|
-
files.push(abs);
|
|
655
|
-
}
|
|
656
|
-
return files.sort();
|
|
669
|
+
function formatHintSuffix(site: StanceSite): string {
|
|
670
|
+
if (!site.hint) return "";
|
|
671
|
+
return site.nearMissOf
|
|
672
|
+
? ` (hint: ${site.hint}, near ${site.nearMissOf})`
|
|
673
|
+
: ` (hint: ${site.hint})`;
|
|
657
674
|
}
|
|
658
675
|
|
|
659
676
|
function reportStance(rootDir: string): void {
|
|
@@ -671,7 +688,7 @@ function reportStance(rootDir: string): void {
|
|
|
671
688
|
let total = 0;
|
|
672
689
|
|
|
673
690
|
const project = createReportStanceProject();
|
|
674
|
-
for (const file of
|
|
691
|
+
for (const file of findSourceFiles(rootDir)) {
|
|
675
692
|
const sites = reportStanceForSource(readFileSync(file, "utf8"), file, project);
|
|
676
693
|
if (sites.length === 0) continue;
|
|
677
694
|
|
|
@@ -680,7 +697,7 @@ function reportStance(rootDir: string): void {
|
|
|
680
697
|
totals[site.stance]++;
|
|
681
698
|
total++;
|
|
682
699
|
const entity = site.entity ?? "<unresolved>";
|
|
683
|
-
const hintSuffix =
|
|
700
|
+
const hintSuffix = formatHintSuffix(site);
|
|
684
701
|
console.log(
|
|
685
702
|
` ${site.line} ${site.field} entity=${entity} ${site.callee} ${site.stance}${hintSuffix}`,
|
|
686
703
|
);
|
|
@@ -711,7 +728,10 @@ async function main(): Promise<void> {
|
|
|
711
728
|
}
|
|
712
729
|
|
|
713
730
|
const dryRun = process.argv.includes("--dry-run");
|
|
714
|
-
|
|
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"]);
|
|
715
735
|
const project = new Project({
|
|
716
736
|
skipAddingFilesFromTsConfig: true,
|
|
717
737
|
skipFileDependencyResolution: true,
|
|
@@ -105,9 +105,25 @@ function resolveDetailsProperty(optsLiteral: ObjectLiteralExpression): DetailsRe
|
|
|
105
105
|
/** True if removing this shorthand-bound identifier would leave its declaration with no other use. */
|
|
106
106
|
function wouldLeaveUnusedVariable(nameNode: Identifier): boolean {
|
|
107
107
|
try {
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
108
|
+
// The shorthand name resolves to the object-literal property symbol too, and
|
|
109
|
+
// a declaration counts as a reference, so look up the variable's own uses.
|
|
110
|
+
const shorthand = nameNode.getParent();
|
|
111
|
+
const variableSymbol = Node.isShorthandPropertyAssignment(shorthand)
|
|
112
|
+
? shorthand.getValueSymbol()
|
|
113
|
+
: undefined;
|
|
114
|
+
const declarations = variableSymbol?.getDeclarations() ?? [];
|
|
115
|
+
const declarationNames = declarations.flatMap((declaration) =>
|
|
116
|
+
Node.isVariableDeclaration(declaration) || Node.isParameterDeclaration(declaration)
|
|
117
|
+
? [declaration.getNameNode()]
|
|
118
|
+
: [],
|
|
119
|
+
);
|
|
120
|
+
// No resolvable declaration: can't verify, treat as unsafe.
|
|
121
|
+
if (declarationNames.length === 0) return true;
|
|
122
|
+
return declarationNames
|
|
123
|
+
.flatMap((declarationName) =>
|
|
124
|
+
Node.isIdentifier(declarationName) ? declarationName.findReferencesAsNodes() : [],
|
|
125
|
+
)
|
|
126
|
+
.every((usage) => usage === nameNode);
|
|
111
127
|
} catch {
|
|
112
128
|
// Can't verify — treat as unsafe rather than guess.
|
|
113
129
|
return true;
|
|
@@ -122,12 +138,11 @@ type RemovalResult =
|
|
|
122
138
|
/** Removes the `reason` property from a `details` object literal, in place. */
|
|
123
139
|
function removeReasonProperty(detailsLiteral: ObjectLiteralExpression): RemovalResult {
|
|
124
140
|
const properties = detailsLiteral.getProperties();
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
}
|
|
141
|
+
const hasSpread = properties.some((p) => Node.isSpreadAssignment(p));
|
|
142
|
+
const spreadSkip: RemovalResult = {
|
|
143
|
+
kind: "skip",
|
|
144
|
+
note: "details object literal has a spread next to an explicit reason — not statically decidable",
|
|
145
|
+
};
|
|
131
146
|
|
|
132
147
|
for (const prop of properties) {
|
|
133
148
|
if (Node.isPropertyAssignment(prop)) {
|
|
@@ -145,12 +160,14 @@ function removeReasonProperty(detailsLiteral: ObjectLiteralExpression): RemovalR
|
|
|
145
160
|
? nameNode.getLiteralText()
|
|
146
161
|
: undefined;
|
|
147
162
|
if (propName === "reason") {
|
|
163
|
+
if (hasSpread) return spreadSkip;
|
|
148
164
|
prop.remove();
|
|
149
165
|
return { kind: "removed" };
|
|
150
166
|
}
|
|
151
167
|
continue;
|
|
152
168
|
}
|
|
153
169
|
if (Node.isShorthandPropertyAssignment(prop) && prop.getName() === "reason") {
|
|
170
|
+
if (hasSpread) return spreadSkip;
|
|
154
171
|
if (wouldLeaveUnusedVariable(prop.getNameNode())) {
|
|
155
172
|
return {
|
|
156
173
|
kind: "skip",
|
|
@@ -166,13 +183,20 @@ function removeReasonProperty(detailsLiteral: ObjectLiteralExpression): RemovalR
|
|
|
166
183
|
|
|
167
184
|
type CallOutcome = "changed" | "skipped" | "not-a-match";
|
|
168
185
|
|
|
169
|
-
|
|
186
|
+
// "manual": a concrete `reason` the codemod found but cannot remove safely.
|
|
187
|
+
// "unverified": opts/details not statically analyzable — may well be fine, so
|
|
188
|
+
// it is listed apart to keep the actionable list short.
|
|
189
|
+
type SkipKind = "manual" | "unverified";
|
|
190
|
+
type ReportSkip = (note: string, kind: SkipKind) => void;
|
|
191
|
+
|
|
192
|
+
function processNewExpression(args: readonly Node[], report: ReportSkip): CallOutcome {
|
|
170
193
|
const optsArg = args[1];
|
|
171
194
|
if (!optsArg) return "not-a-match";
|
|
172
195
|
|
|
173
196
|
if (!Node.isObjectLiteralExpression(optsArg)) {
|
|
174
197
|
report(
|
|
175
198
|
"second argument (opts) is not a statically-analyzable object literal — cannot verify details.reason",
|
|
199
|
+
"unverified",
|
|
176
200
|
);
|
|
177
201
|
return "skipped";
|
|
178
202
|
}
|
|
@@ -180,14 +204,14 @@ function processNewExpression(args: readonly Node[], report: (note: string) => v
|
|
|
180
204
|
const detailsRes = resolveDetailsProperty(optsArg);
|
|
181
205
|
if (detailsRes.kind === "absent") return "not-a-match";
|
|
182
206
|
if (detailsRes.kind === "unresolvable") {
|
|
183
|
-
report(detailsRes.note);
|
|
207
|
+
report(detailsRes.note, "unverified");
|
|
184
208
|
return "skipped";
|
|
185
209
|
}
|
|
186
210
|
|
|
187
211
|
const removal = removeReasonProperty(detailsRes.literal);
|
|
188
212
|
if (removal.kind === "absent") return "not-a-match";
|
|
189
213
|
if (removal.kind === "skip") {
|
|
190
|
-
report(removal.note);
|
|
214
|
+
report(removal.note, "manual");
|
|
191
215
|
return "skipped";
|
|
192
216
|
}
|
|
193
217
|
return "changed";
|
|
@@ -195,16 +219,14 @@ function processNewExpression(args: readonly Node[], report: (note: string) => v
|
|
|
195
219
|
|
|
196
220
|
// `failUnprocessable(reason, details)` takes the details literal directly as
|
|
197
221
|
// its second argument (no `{ details }` wrapper like the ctor's opts).
|
|
198
|
-
function processFailUnprocessableCall(
|
|
199
|
-
args: readonly Node[],
|
|
200
|
-
report: (note: string) => void,
|
|
201
|
-
): CallOutcome {
|
|
222
|
+
function processFailUnprocessableCall(args: readonly Node[], report: ReportSkip): CallOutcome {
|
|
202
223
|
const detailsArg = args[1];
|
|
203
224
|
if (!detailsArg) return "not-a-match";
|
|
204
225
|
|
|
205
226
|
if (!Node.isObjectLiteralExpression(detailsArg)) {
|
|
206
227
|
report(
|
|
207
228
|
"second argument (details) is not a statically-analyzable object literal — cannot verify details.reason",
|
|
229
|
+
"unverified",
|
|
208
230
|
);
|
|
209
231
|
return "skipped";
|
|
210
232
|
}
|
|
@@ -212,18 +234,23 @@ function processFailUnprocessableCall(
|
|
|
212
234
|
const removal = removeReasonProperty(detailsArg);
|
|
213
235
|
if (removal.kind === "absent") return "not-a-match";
|
|
214
236
|
if (removal.kind === "skip") {
|
|
215
|
-
report(removal.note);
|
|
237
|
+
report(removal.note, "manual");
|
|
216
238
|
return "skipped";
|
|
217
239
|
}
|
|
218
240
|
return "changed";
|
|
219
241
|
}
|
|
220
242
|
|
|
221
|
-
type SkipEntry = {
|
|
243
|
+
type SkipEntry = {
|
|
244
|
+
readonly file: string;
|
|
245
|
+
readonly line: number;
|
|
246
|
+
readonly note: string;
|
|
247
|
+
readonly kind: SkipKind;
|
|
248
|
+
};
|
|
222
249
|
|
|
223
250
|
type MigratableCall = {
|
|
224
251
|
readonly exportName: "UnprocessableError" | "failUnprocessable";
|
|
225
252
|
readonly localKind: "class" | "function";
|
|
226
|
-
readonly process: (args: readonly Node[], report:
|
|
253
|
+
readonly process: (args: readonly Node[], report: ReportSkip) => CallOutcome;
|
|
227
254
|
};
|
|
228
255
|
|
|
229
256
|
const UNPROCESSABLE_ERROR_CTOR: MigratableCall = {
|
|
@@ -256,7 +283,7 @@ function migrateCallSite(
|
|
|
256
283
|
const note = binding
|
|
257
284
|
? `${target.exportName} is imported from "${binding.moduleSpecifier}", not a @cosmicdrift/kumiko-* package`
|
|
258
285
|
: `${target.exportName} has no @cosmicdrift/kumiko-* import in this file (likely a locally defined ${target.localKind})`;
|
|
259
|
-
skips.push({ file, line, note });
|
|
286
|
+
skips.push({ file, line, note, kind: "manual" });
|
|
260
287
|
return false;
|
|
261
288
|
}
|
|
262
289
|
if (!KUMIKO_MODULE_RE.test(binding.moduleSpecifier)) {
|
|
@@ -264,11 +291,14 @@ function migrateCallSite(
|
|
|
264
291
|
file,
|
|
265
292
|
line,
|
|
266
293
|
note: `${target.exportName} is imported from "${binding.moduleSpecifier}", not a @cosmicdrift/kumiko-* package`,
|
|
294
|
+
kind: "manual",
|
|
267
295
|
});
|
|
268
296
|
return false;
|
|
269
297
|
}
|
|
270
298
|
|
|
271
|
-
const outcome = target.process(site.getArguments(), (note) =>
|
|
299
|
+
const outcome = target.process(site.getArguments(), (note, kind) =>
|
|
300
|
+
skips.push({ file, line, note, kind }),
|
|
301
|
+
);
|
|
272
302
|
return outcome === "changed";
|
|
273
303
|
}
|
|
274
304
|
|
|
@@ -320,9 +350,16 @@ async function main(): Promise<void> {
|
|
|
320
350
|
console.log(
|
|
321
351
|
`Touched ${touchedFiles} files, removed ${removedTotal} redundant "reason" propert(y/ies).`,
|
|
322
352
|
);
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
console.log(
|
|
353
|
+
for (const kind of ["manual", "unverified"] as const) {
|
|
354
|
+
const ofKind = skips.filter((s) => s.kind === kind);
|
|
355
|
+
console.log(
|
|
356
|
+
kind === "manual"
|
|
357
|
+
? `Skipped ${ofKind.length} site(s) needing manual work:`
|
|
358
|
+
: `Unverified ${ofKind.length} site(s) (opts/details not statically analyzable, may be fine):`,
|
|
359
|
+
);
|
|
360
|
+
for (const s of ofKind) {
|
|
361
|
+
console.log(` ${relative(rootDir, s.file)}:${s.line} — ${s.note}`);
|
|
362
|
+
}
|
|
326
363
|
}
|
|
327
364
|
console.log();
|
|
328
365
|
}
|