@schemashift/core 0.10.0 → 0.12.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/dist/index.cjs +1576 -90
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +413 -1
- package/dist/index.d.ts +413 -1
- package/dist/index.js +1542 -86
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
+
var __create = Object.create;
|
|
2
3
|
var __defProp = Object.defineProperty;
|
|
3
4
|
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
5
|
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
5
7
|
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
8
|
var __export = (target, all) => {
|
|
7
9
|
for (var name in all)
|
|
@@ -15,11 +17,20 @@ var __copyProps = (to, from, except, desc) => {
|
|
|
15
17
|
}
|
|
16
18
|
return to;
|
|
17
19
|
};
|
|
20
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
21
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
22
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
23
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
24
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
25
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
26
|
+
mod
|
|
27
|
+
));
|
|
18
28
|
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
29
|
|
|
20
30
|
// src/index.ts
|
|
21
31
|
var index_exports = {};
|
|
22
32
|
__export(index_exports, {
|
|
33
|
+
ApprovalManager: () => ApprovalManager,
|
|
23
34
|
BehavioralWarningAnalyzer: () => BehavioralWarningAnalyzer,
|
|
24
35
|
BundleEstimator: () => BundleEstimator,
|
|
25
36
|
CompatibilityAnalyzer: () => CompatibilityAnalyzer,
|
|
@@ -30,6 +41,8 @@ __export(index_exports, {
|
|
|
30
41
|
FormResolverMigrator: () => FormResolverMigrator,
|
|
31
42
|
GOVERNANCE_TEMPLATES: () => GOVERNANCE_TEMPLATES,
|
|
32
43
|
GovernanceEngine: () => GovernanceEngine,
|
|
44
|
+
GovernanceFixer: () => GovernanceFixer,
|
|
45
|
+
GraphExporter: () => GraphExporter,
|
|
33
46
|
IncrementalTracker: () => IncrementalTracker,
|
|
34
47
|
MigrationAuditLog: () => MigrationAuditLog,
|
|
35
48
|
MigrationChain: () => MigrationChain,
|
|
@@ -39,25 +52,42 @@ __export(index_exports, {
|
|
|
39
52
|
PluginLoader: () => PluginLoader,
|
|
40
53
|
SchemaAnalyzer: () => SchemaAnalyzer,
|
|
41
54
|
SchemaDependencyResolver: () => SchemaDependencyResolver,
|
|
55
|
+
StandardSchemaAdvisor: () => StandardSchemaAdvisor,
|
|
42
56
|
TestScaffolder: () => TestScaffolder,
|
|
43
57
|
TransformEngine: () => TransformEngine,
|
|
44
58
|
TypeDedupDetector: () => TypeDedupDetector,
|
|
59
|
+
WebhookNotifier: () => WebhookNotifier,
|
|
45
60
|
buildCallChain: () => buildCallChain,
|
|
46
61
|
computeParallelBatches: () => computeParallelBatches,
|
|
62
|
+
conditionalValidation: () => conditionalValidation,
|
|
63
|
+
createVerificationReport: () => createVerificationReport,
|
|
64
|
+
dependentFields: () => dependentFields,
|
|
47
65
|
detectFormLibraries: () => detectFormLibraries,
|
|
48
66
|
detectSchemaLibrary: () => detectSchemaLibrary,
|
|
49
67
|
detectStandardSchema: () => detectStandardSchema,
|
|
68
|
+
extractSchemaNames: () => extractSchemaNames,
|
|
69
|
+
formatVerificationReport: () => formatVerificationReport,
|
|
70
|
+
generateSamples: () => generateSamples,
|
|
71
|
+
getAllMigrationTemplates: () => getAllMigrationTemplates,
|
|
50
72
|
getGovernanceTemplate: () => getGovernanceTemplate,
|
|
51
73
|
getGovernanceTemplateNames: () => getGovernanceTemplateNames,
|
|
52
74
|
getGovernanceTemplatesByCategory: () => getGovernanceTemplatesByCategory,
|
|
75
|
+
getMigrationTemplate: () => getMigrationTemplate,
|
|
76
|
+
getMigrationTemplateNames: () => getMigrationTemplateNames,
|
|
77
|
+
getMigrationTemplatesByCategory: () => getMigrationTemplatesByCategory,
|
|
53
78
|
isInsideComment: () => isInsideComment,
|
|
54
79
|
isInsideStringLiteral: () => isInsideStringLiteral,
|
|
55
80
|
loadConfig: () => loadConfig,
|
|
81
|
+
mutuallyExclusive: () => mutuallyExclusive,
|
|
56
82
|
parseCallChain: () => parseCallChain,
|
|
83
|
+
requireIf: () => requireIf,
|
|
84
|
+
requireOneOf: () => requireOneOf,
|
|
57
85
|
shouldSuppressWarning: () => shouldSuppressWarning,
|
|
58
86
|
startsWithBase: () => startsWithBase,
|
|
87
|
+
suggestCrossFieldPattern: () => suggestCrossFieldPattern,
|
|
59
88
|
transformMethodChain: () => transformMethodChain,
|
|
60
|
-
validateConfig: () => validateConfig
|
|
89
|
+
validateConfig: () => validateConfig,
|
|
90
|
+
validateMigrationTemplate: () => validateMigrationTemplate
|
|
61
91
|
});
|
|
62
92
|
module.exports = __toCommonJS(index_exports);
|
|
63
93
|
|
|
@@ -196,6 +226,110 @@ var SchemaAnalyzer = class {
|
|
|
196
226
|
}
|
|
197
227
|
};
|
|
198
228
|
|
|
229
|
+
// src/approval.ts
|
|
230
|
+
var import_node_fs = require("fs");
|
|
231
|
+
var import_node_path = require("path");
|
|
232
|
+
var ApprovalManager = class {
|
|
233
|
+
pendingDir;
|
|
234
|
+
constructor(projectPath) {
|
|
235
|
+
this.pendingDir = (0, import_node_path.join)(projectPath, ".schemashift", "pending");
|
|
236
|
+
}
|
|
237
|
+
/**
|
|
238
|
+
* Create a new migration request for review.
|
|
239
|
+
*/
|
|
240
|
+
createRequest(from, to, files, requestedBy, metadata) {
|
|
241
|
+
const id = `mig-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
|
|
242
|
+
const request = {
|
|
243
|
+
id,
|
|
244
|
+
from,
|
|
245
|
+
to,
|
|
246
|
+
files,
|
|
247
|
+
requestedBy,
|
|
248
|
+
requestedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
249
|
+
status: "pending",
|
|
250
|
+
metadata
|
|
251
|
+
};
|
|
252
|
+
this.ensureDir();
|
|
253
|
+
const filePath = (0, import_node_path.join)(this.pendingDir, `${id}.json`);
|
|
254
|
+
(0, import_node_fs.writeFileSync)(filePath, JSON.stringify(request, null, 2), "utf-8");
|
|
255
|
+
return request;
|
|
256
|
+
}
|
|
257
|
+
/**
|
|
258
|
+
* Review (approve or reject) a pending migration request.
|
|
259
|
+
*/
|
|
260
|
+
review(decision) {
|
|
261
|
+
const request = this.getRequest(decision.requestId);
|
|
262
|
+
if (!request) {
|
|
263
|
+
throw new Error(`Migration request ${decision.requestId} not found`);
|
|
264
|
+
}
|
|
265
|
+
if (request.status !== "pending") {
|
|
266
|
+
throw new Error(`Migration request ${decision.requestId} is already ${request.status}`);
|
|
267
|
+
}
|
|
268
|
+
request.status = decision.status;
|
|
269
|
+
request.reviewedBy = decision.reviewedBy;
|
|
270
|
+
request.reviewedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
271
|
+
request.reason = decision.reason;
|
|
272
|
+
const filePath = (0, import_node_path.join)(this.pendingDir, `${decision.requestId}.json`);
|
|
273
|
+
(0, import_node_fs.writeFileSync)(filePath, JSON.stringify(request, null, 2), "utf-8");
|
|
274
|
+
return request;
|
|
275
|
+
}
|
|
276
|
+
/**
|
|
277
|
+
* Get a specific migration request by ID.
|
|
278
|
+
*/
|
|
279
|
+
getRequest(id) {
|
|
280
|
+
const filePath = (0, import_node_path.join)(this.pendingDir, `${id}.json`);
|
|
281
|
+
if (!(0, import_node_fs.existsSync)(filePath)) {
|
|
282
|
+
return null;
|
|
283
|
+
}
|
|
284
|
+
const content = (0, import_node_fs.readFileSync)(filePath, "utf-8");
|
|
285
|
+
return JSON.parse(content);
|
|
286
|
+
}
|
|
287
|
+
/**
|
|
288
|
+
* List all migration requests, optionally filtered by status.
|
|
289
|
+
*/
|
|
290
|
+
listRequests(status) {
|
|
291
|
+
if (!(0, import_node_fs.existsSync)(this.pendingDir)) {
|
|
292
|
+
return [];
|
|
293
|
+
}
|
|
294
|
+
const files = (0, import_node_fs.readdirSync)(this.pendingDir).filter((f) => f.endsWith(".json"));
|
|
295
|
+
const requests = [];
|
|
296
|
+
for (const file of files) {
|
|
297
|
+
const content = (0, import_node_fs.readFileSync)((0, import_node_path.join)(this.pendingDir, file), "utf-8");
|
|
298
|
+
const request = JSON.parse(content);
|
|
299
|
+
if (!status || request.status === status) {
|
|
300
|
+
requests.push(request);
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
return requests.sort(
|
|
304
|
+
(a, b) => new Date(b.requestedAt).getTime() - new Date(a.requestedAt).getTime()
|
|
305
|
+
);
|
|
306
|
+
}
|
|
307
|
+
/**
|
|
308
|
+
* Get summary counts of all requests.
|
|
309
|
+
*/
|
|
310
|
+
getSummary() {
|
|
311
|
+
const all = this.listRequests();
|
|
312
|
+
return {
|
|
313
|
+
pending: all.filter((r) => r.status === "pending").length,
|
|
314
|
+
approved: all.filter((r) => r.status === "approved").length,
|
|
315
|
+
rejected: all.filter((r) => r.status === "rejected").length,
|
|
316
|
+
total: all.length
|
|
317
|
+
};
|
|
318
|
+
}
|
|
319
|
+
/**
|
|
320
|
+
* Check if a migration has been approved.
|
|
321
|
+
*/
|
|
322
|
+
isApproved(requestId) {
|
|
323
|
+
const request = this.getRequest(requestId);
|
|
324
|
+
return request?.status === "approved";
|
|
325
|
+
}
|
|
326
|
+
ensureDir() {
|
|
327
|
+
if (!(0, import_node_fs.existsSync)(this.pendingDir)) {
|
|
328
|
+
(0, import_node_fs.mkdirSync)(this.pendingDir, { recursive: true });
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
};
|
|
332
|
+
|
|
199
333
|
// src/ast-utils.ts
|
|
200
334
|
var import_ts_morph2 = require("ts-morph");
|
|
201
335
|
function parseCallChain(node) {
|
|
@@ -332,8 +466,8 @@ function transformMethodChain(chain, newBase, factoryMapper, methodMapper) {
|
|
|
332
466
|
|
|
333
467
|
// src/audit-log.ts
|
|
334
468
|
var import_node_crypto = require("crypto");
|
|
335
|
-
var
|
|
336
|
-
var
|
|
469
|
+
var import_node_fs2 = require("fs");
|
|
470
|
+
var import_node_path2 = require("path");
|
|
337
471
|
var AUDIT_DIR = ".schemashift";
|
|
338
472
|
var AUDIT_FILE = "audit-log.json";
|
|
339
473
|
var AUDIT_VERSION = 1;
|
|
@@ -341,8 +475,8 @@ var MigrationAuditLog = class {
|
|
|
341
475
|
logDir;
|
|
342
476
|
logPath;
|
|
343
477
|
constructor(projectPath) {
|
|
344
|
-
this.logDir = (0,
|
|
345
|
-
this.logPath = (0,
|
|
478
|
+
this.logDir = (0, import_node_path2.join)(projectPath, AUDIT_DIR);
|
|
479
|
+
this.logPath = (0, import_node_path2.join)(this.logDir, AUDIT_FILE);
|
|
346
480
|
}
|
|
347
481
|
/**
|
|
348
482
|
* Append a new entry to the audit log.
|
|
@@ -378,11 +512,11 @@ var MigrationAuditLog = class {
|
|
|
378
512
|
* Read the current audit log.
|
|
379
513
|
*/
|
|
380
514
|
read() {
|
|
381
|
-
if (!(0,
|
|
515
|
+
if (!(0, import_node_fs2.existsSync)(this.logPath)) {
|
|
382
516
|
return { version: AUDIT_VERSION, entries: [] };
|
|
383
517
|
}
|
|
384
518
|
try {
|
|
385
|
-
const content = (0,
|
|
519
|
+
const content = (0, import_node_fs2.readFileSync)(this.logPath, "utf-8");
|
|
386
520
|
if (!content.trim()) {
|
|
387
521
|
return { version: AUDIT_VERSION, entries: [] };
|
|
388
522
|
}
|
|
@@ -464,6 +598,88 @@ var MigrationAuditLog = class {
|
|
|
464
598
|
clear() {
|
|
465
599
|
this.write({ version: AUDIT_VERSION, entries: [] });
|
|
466
600
|
}
|
|
601
|
+
/**
|
|
602
|
+
* Export a compliance report in SOC2 or HIPAA format.
|
|
603
|
+
*/
|
|
604
|
+
exportComplianceReport(format) {
|
|
605
|
+
const log = this.read();
|
|
606
|
+
const summary = this.getSummary();
|
|
607
|
+
if (format === "soc2") {
|
|
608
|
+
return this.generateSoc2Report(log, summary);
|
|
609
|
+
}
|
|
610
|
+
return this.generateHipaaReport(log, summary);
|
|
611
|
+
}
|
|
612
|
+
generateSoc2Report(log, summary) {
|
|
613
|
+
const sections = [];
|
|
614
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
615
|
+
sections.push("# SOC2 Compliance Report \u2014 Schema Migration");
|
|
616
|
+
sections.push(`Generated: ${now}`);
|
|
617
|
+
sections.push("");
|
|
618
|
+
sections.push("## Change Control Summary");
|
|
619
|
+
sections.push(`- Total Migrations: ${summary.totalMigrations}`);
|
|
620
|
+
sections.push(`- Total Files Processed: ${summary.totalFiles}`);
|
|
621
|
+
sections.push(`- Successful: ${summary.successCount}`);
|
|
622
|
+
sections.push(`- Failed: ${summary.failureCount}`);
|
|
623
|
+
sections.push(`- Migration Paths: ${summary.migrationPaths.join(", ")}`);
|
|
624
|
+
sections.push("");
|
|
625
|
+
sections.push("## Change Control Entries");
|
|
626
|
+
for (const entry of log.entries) {
|
|
627
|
+
sections.push("");
|
|
628
|
+
sections.push(`### ${entry.filePath}`);
|
|
629
|
+
sections.push(`- Change ID: ${entry.migrationId}`);
|
|
630
|
+
sections.push(`- Timestamp: ${entry.timestamp}`);
|
|
631
|
+
sections.push(`- Action: ${entry.action}`);
|
|
632
|
+
sections.push(`- Migration: ${entry.from} \u2192 ${entry.to}`);
|
|
633
|
+
sections.push(`- Status: ${entry.success ? "Success" : "Failed"}`);
|
|
634
|
+
sections.push(`- Implementer: ${entry.user || "Unknown"}`);
|
|
635
|
+
sections.push(`- Before Hash: ${entry.beforeHash}`);
|
|
636
|
+
if (entry.afterHash) sections.push(`- After Hash: ${entry.afterHash}`);
|
|
637
|
+
sections.push(`- Warnings: ${entry.warningCount}`);
|
|
638
|
+
sections.push(`- Errors: ${entry.errorCount}`);
|
|
639
|
+
if (entry.riskScore !== void 0) sections.push(`- Risk Score: ${entry.riskScore}`);
|
|
640
|
+
if (entry.metadata?.ciProvider) sections.push(`- CI Provider: ${entry.metadata.ciProvider}`);
|
|
641
|
+
if (entry.metadata?.gitCommit) sections.push(`- Git Commit: ${entry.metadata.gitCommit}`);
|
|
642
|
+
if (entry.metadata?.gitBranch) sections.push(`- Git Branch: ${entry.metadata.gitBranch}`);
|
|
643
|
+
}
|
|
644
|
+
sections.push("");
|
|
645
|
+
sections.push("## Rollback Procedure");
|
|
646
|
+
sections.push("SchemaShift maintains automatic backups in `.schemashift/backups/`.");
|
|
647
|
+
sections.push("Use `schemashift rollback [backupId]` to restore files from any backup.");
|
|
648
|
+
sections.push("");
|
|
649
|
+
return sections.join("\n");
|
|
650
|
+
}
|
|
651
|
+
generateHipaaReport(log, summary) {
|
|
652
|
+
const sections = [];
|
|
653
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
654
|
+
sections.push("# HIPAA Compliance Audit Trail \u2014 Schema Migration");
|
|
655
|
+
sections.push(`Generated: ${now}`);
|
|
656
|
+
sections.push("");
|
|
657
|
+
sections.push("## Data Transformation Summary");
|
|
658
|
+
sections.push(`- Total Transformations: ${summary.totalFiles}`);
|
|
659
|
+
sections.push(`- Successful: ${summary.successCount}`);
|
|
660
|
+
sections.push(`- Failed: ${summary.failureCount}`);
|
|
661
|
+
sections.push("");
|
|
662
|
+
sections.push("## Integrity Verification");
|
|
663
|
+
for (const entry of log.entries) {
|
|
664
|
+
sections.push("");
|
|
665
|
+
sections.push(`### ${entry.filePath}`);
|
|
666
|
+
sections.push(`- Timestamp: ${entry.timestamp}`);
|
|
667
|
+
sections.push(`- User: ${entry.user || "Unknown"}`);
|
|
668
|
+
sections.push(`- Action: ${entry.action} (${entry.from} \u2192 ${entry.to})`);
|
|
669
|
+
sections.push(`- Integrity Before: SHA256:${entry.beforeHash}`);
|
|
670
|
+
if (entry.afterHash) sections.push(`- Integrity After: SHA256:${entry.afterHash}`);
|
|
671
|
+
sections.push(`- Status: ${entry.success ? "Completed" : "Failed"}`);
|
|
672
|
+
if (entry.metadata?.hostname) sections.push(`- Host: ${entry.metadata.hostname}`);
|
|
673
|
+
if (entry.metadata?.nodeVersion)
|
|
674
|
+
sections.push(`- Runtime: Node.js ${entry.metadata.nodeVersion}`);
|
|
675
|
+
}
|
|
676
|
+
sections.push("");
|
|
677
|
+
sections.push("## Access Control");
|
|
678
|
+
const users = [...new Set(log.entries.map((e) => e.user).filter(Boolean))];
|
|
679
|
+
sections.push(`- Users Who Performed Migrations: ${users.join(", ") || "Unknown"}`);
|
|
680
|
+
sections.push("");
|
|
681
|
+
return sections.join("\n");
|
|
682
|
+
}
|
|
467
683
|
collectMetadata() {
|
|
468
684
|
return {
|
|
469
685
|
hostname: process.env.HOSTNAME || void 0,
|
|
@@ -475,10 +691,10 @@ var MigrationAuditLog = class {
|
|
|
475
691
|
};
|
|
476
692
|
}
|
|
477
693
|
write(log) {
|
|
478
|
-
if (!(0,
|
|
479
|
-
(0,
|
|
694
|
+
if (!(0, import_node_fs2.existsSync)(this.logDir)) {
|
|
695
|
+
(0, import_node_fs2.mkdirSync)(this.logDir, { recursive: true });
|
|
480
696
|
}
|
|
481
|
-
(0,
|
|
697
|
+
(0, import_node_fs2.writeFileSync)(this.logPath, JSON.stringify(log, null, 2));
|
|
482
698
|
}
|
|
483
699
|
hashContent(content) {
|
|
484
700
|
return (0, import_node_crypto.createHash)("sha256").update(content).digest("hex").substring(0, 16);
|
|
@@ -911,12 +1127,12 @@ var MigrationChain = class {
|
|
|
911
1127
|
};
|
|
912
1128
|
|
|
913
1129
|
// src/compatibility.ts
|
|
914
|
-
var
|
|
915
|
-
var
|
|
1130
|
+
var import_node_fs4 = require("fs");
|
|
1131
|
+
var import_node_path4 = require("path");
|
|
916
1132
|
|
|
917
1133
|
// src/ecosystem.ts
|
|
918
|
-
var
|
|
919
|
-
var
|
|
1134
|
+
var import_node_fs3 = require("fs");
|
|
1135
|
+
var import_node_path3 = require("path");
|
|
920
1136
|
var ECOSYSTEM_RULES = [
|
|
921
1137
|
// ORM integrations
|
|
922
1138
|
{
|
|
@@ -1171,6 +1387,72 @@ var ECOSYSTEM_RULES = [
|
|
|
1171
1387
|
upgradeCommand: "npm install nuqs@latest"
|
|
1172
1388
|
})
|
|
1173
1389
|
},
|
|
1390
|
+
// Server action / routing integrations
|
|
1391
|
+
{
|
|
1392
|
+
package: "next-safe-action",
|
|
1393
|
+
category: "api",
|
|
1394
|
+
migrations: ["zod-v3->v4"],
|
|
1395
|
+
check: () => ({
|
|
1396
|
+
issue: "next-safe-action uses Zod for input validation. Zod v4 type changes may break action definitions.",
|
|
1397
|
+
suggestion: "Upgrade next-safe-action to the latest version with Zod v4 support.",
|
|
1398
|
+
severity: "warning",
|
|
1399
|
+
upgradeCommand: "npm install next-safe-action@latest"
|
|
1400
|
+
})
|
|
1401
|
+
},
|
|
1402
|
+
{
|
|
1403
|
+
package: "@tanstack/router",
|
|
1404
|
+
category: "api",
|
|
1405
|
+
migrations: ["zod-v3->v4"],
|
|
1406
|
+
check: () => ({
|
|
1407
|
+
issue: "@tanstack/router uses Zod for route parameter validation. Zod v4 changes may affect type inference.",
|
|
1408
|
+
suggestion: "Upgrade @tanstack/router to a version with Zod v4 support.",
|
|
1409
|
+
severity: "warning",
|
|
1410
|
+
upgradeCommand: "npm install @tanstack/router@latest"
|
|
1411
|
+
})
|
|
1412
|
+
},
|
|
1413
|
+
{
|
|
1414
|
+
package: "@tanstack/react-query",
|
|
1415
|
+
category: "api",
|
|
1416
|
+
migrations: ["zod-v3->v4"],
|
|
1417
|
+
check: () => ({
|
|
1418
|
+
issue: "@tanstack/react-query may use Zod for query key/param validation via integrations.",
|
|
1419
|
+
suggestion: "Verify any Zod-based query validation still works after the Zod v4 upgrade.",
|
|
1420
|
+
severity: "info"
|
|
1421
|
+
})
|
|
1422
|
+
},
|
|
1423
|
+
{
|
|
1424
|
+
package: "fastify-type-provider-zod",
|
|
1425
|
+
category: "api",
|
|
1426
|
+
migrations: ["zod-v3->v4"],
|
|
1427
|
+
check: () => ({
|
|
1428
|
+
issue: "fastify-type-provider-zod needs a Zod v4-compatible version.",
|
|
1429
|
+
suggestion: "Upgrade fastify-type-provider-zod to a version supporting Zod v4.",
|
|
1430
|
+
severity: "warning",
|
|
1431
|
+
upgradeCommand: "npm install fastify-type-provider-zod@latest"
|
|
1432
|
+
})
|
|
1433
|
+
},
|
|
1434
|
+
{
|
|
1435
|
+
package: "zod-i18n-map",
|
|
1436
|
+
category: "validation-util",
|
|
1437
|
+
migrations: ["zod-v3->v4"],
|
|
1438
|
+
check: () => ({
|
|
1439
|
+
issue: 'zod-i18n-map uses Zod v3 error map format. Error messages changed in v4 (e.g., "Required" is now descriptive).',
|
|
1440
|
+
suggestion: "Check for a Zod v4-compatible version of zod-i18n-map or update custom error maps.",
|
|
1441
|
+
severity: "warning",
|
|
1442
|
+
upgradeCommand: "npm install zod-i18n-map@latest"
|
|
1443
|
+
})
|
|
1444
|
+
},
|
|
1445
|
+
{
|
|
1446
|
+
package: "openapi-zod-client",
|
|
1447
|
+
category: "openapi",
|
|
1448
|
+
migrations: ["zod-v3->v4"],
|
|
1449
|
+
check: () => ({
|
|
1450
|
+
issue: "openapi-zod-client generates Zod v3 schemas from OpenAPI specs. Generated code may need regeneration.",
|
|
1451
|
+
suggestion: "Upgrade openapi-zod-client and regenerate schemas for Zod v4 compatibility.",
|
|
1452
|
+
severity: "warning",
|
|
1453
|
+
upgradeCommand: "npm install openapi-zod-client@latest"
|
|
1454
|
+
})
|
|
1455
|
+
},
|
|
1174
1456
|
// Schema library detection for cross-library migrations
|
|
1175
1457
|
{
|
|
1176
1458
|
package: "@effect/schema",
|
|
@@ -1248,13 +1530,13 @@ var EcosystemAnalyzer = class {
|
|
|
1248
1530
|
const dependencies = [];
|
|
1249
1531
|
const warnings = [];
|
|
1250
1532
|
const blockers = [];
|
|
1251
|
-
const pkgPath = (0,
|
|
1252
|
-
if (!(0,
|
|
1533
|
+
const pkgPath = (0, import_node_path3.join)(projectPath, "package.json");
|
|
1534
|
+
if (!(0, import_node_fs3.existsSync)(pkgPath)) {
|
|
1253
1535
|
return { dependencies, warnings, blockers };
|
|
1254
1536
|
}
|
|
1255
1537
|
let allDeps = {};
|
|
1256
1538
|
try {
|
|
1257
|
-
const pkg = JSON.parse((0,
|
|
1539
|
+
const pkg = JSON.parse((0, import_node_fs3.readFileSync)(pkgPath, "utf-8"));
|
|
1258
1540
|
allDeps = { ...pkg.dependencies, ...pkg.devDependencies };
|
|
1259
1541
|
} catch {
|
|
1260
1542
|
return { dependencies, warnings, blockers };
|
|
@@ -1375,10 +1657,10 @@ var CompatibilityAnalyzer = class {
|
|
|
1375
1657
|
ecosystemAnalyzer = new EcosystemAnalyzer();
|
|
1376
1658
|
detectVersions(projectPath) {
|
|
1377
1659
|
const versions = [];
|
|
1378
|
-
const pkgPath = (0,
|
|
1379
|
-
if (!(0,
|
|
1660
|
+
const pkgPath = (0, import_node_path4.join)(projectPath, "package.json");
|
|
1661
|
+
if (!(0, import_node_fs4.existsSync)(pkgPath)) return versions;
|
|
1380
1662
|
try {
|
|
1381
|
-
const pkg = JSON.parse((0,
|
|
1663
|
+
const pkg = JSON.parse((0, import_node_fs4.readFileSync)(pkgPath, "utf-8"));
|
|
1382
1664
|
const knownLibs = ["zod", "yup", "joi", "io-ts", "valibot"];
|
|
1383
1665
|
const allDeps = { ...pkg.dependencies, ...pkg.devDependencies };
|
|
1384
1666
|
for (const lib of knownLibs) {
|
|
@@ -1530,6 +1812,25 @@ var ComplexityEstimator = class {
|
|
|
1530
1812
|
riskAreas
|
|
1531
1813
|
};
|
|
1532
1814
|
}
|
|
1815
|
+
estimateDuration(estimate) {
|
|
1816
|
+
const EFFORT_RANGES = {
|
|
1817
|
+
trivial: { label: "1\u20135 minutes", range: [1, 5] },
|
|
1818
|
+
low: { label: "5\u201315 minutes", range: [5, 15] },
|
|
1819
|
+
moderate: { label: "15\u201345 minutes", range: [15, 45] },
|
|
1820
|
+
high: { label: "1\u20133 hours", range: [60, 180] },
|
|
1821
|
+
extreme: { label: "3\u20138 hours", range: [180, 480] }
|
|
1822
|
+
};
|
|
1823
|
+
const base = EFFORT_RANGES[estimate.effort];
|
|
1824
|
+
const fileMultiplier = Math.max(1, Math.log2(estimate.totalFiles + 1));
|
|
1825
|
+
const low = Math.round(base.range[0] * fileMultiplier);
|
|
1826
|
+
const high = Math.round(base.range[1] * fileMultiplier);
|
|
1827
|
+
if (high >= 120) {
|
|
1828
|
+
const lowHours = Math.round(low / 60 * 10) / 10;
|
|
1829
|
+
const highHours = Math.round(high / 60 * 10) / 10;
|
|
1830
|
+
return { label: `${lowHours}\u2013${highHours} hours`, rangeMinutes: [low, high] };
|
|
1831
|
+
}
|
|
1832
|
+
return { label: `${low}\u2013${high} minutes`, rangeMinutes: [low, high] };
|
|
1833
|
+
}
|
|
1533
1834
|
calculateEffort(totalSchemas, advancedCount, hasDeepDU) {
|
|
1534
1835
|
if (totalSchemas >= 500 && hasDeepDU) return "extreme";
|
|
1535
1836
|
if (totalSchemas >= 200 || advancedCount >= 20) return "high";
|
|
@@ -1599,9 +1900,115 @@ async function loadConfig(configPath) {
|
|
|
1599
1900
|
};
|
|
1600
1901
|
}
|
|
1601
1902
|
|
|
1903
|
+
// src/cross-field-patterns.ts
|
|
1904
|
+
function requireIf(conditionField, requiredField) {
|
|
1905
|
+
return {
|
|
1906
|
+
name: `requireIf(${conditionField}, ${requiredField})`,
|
|
1907
|
+
description: `${requiredField} is required when ${conditionField} is truthy`,
|
|
1908
|
+
zodCode: [
|
|
1909
|
+
".superRefine((data, ctx) => {",
|
|
1910
|
+
` if (data.${conditionField} && !data.${requiredField}) {`,
|
|
1911
|
+
" ctx.addIssue({",
|
|
1912
|
+
" code: z.ZodIssueCode.custom,",
|
|
1913
|
+
` message: '${requiredField} is required when ${conditionField} is set',`,
|
|
1914
|
+
` path: ['${requiredField}'],`,
|
|
1915
|
+
" });",
|
|
1916
|
+
" }",
|
|
1917
|
+
"})"
|
|
1918
|
+
].join("\n")
|
|
1919
|
+
};
|
|
1920
|
+
}
|
|
1921
|
+
function requireOneOf(fields) {
|
|
1922
|
+
const fieldList = fields.map((f) => `'${f}'`).join(", ");
|
|
1923
|
+
const conditions = fields.map((f) => `data.${f}`).join(" || ");
|
|
1924
|
+
return {
|
|
1925
|
+
name: `requireOneOf(${fields.join(", ")})`,
|
|
1926
|
+
description: `At least one of [${fields.join(", ")}] must be provided`,
|
|
1927
|
+
zodCode: [
|
|
1928
|
+
".superRefine((data, ctx) => {",
|
|
1929
|
+
` if (!(${conditions})) {`,
|
|
1930
|
+
" ctx.addIssue({",
|
|
1931
|
+
" code: z.ZodIssueCode.custom,",
|
|
1932
|
+
` message: 'At least one of [${fields.join(", ")}] is required',`,
|
|
1933
|
+
` path: [${fieldList}],`,
|
|
1934
|
+
" });",
|
|
1935
|
+
" }",
|
|
1936
|
+
"})"
|
|
1937
|
+
].join("\n")
|
|
1938
|
+
};
|
|
1939
|
+
}
|
|
1940
|
+
function mutuallyExclusive(fields) {
|
|
1941
|
+
const checks = fields.map((f) => `(data.${f} ? 1 : 0)`).join(" + ");
|
|
1942
|
+
return {
|
|
1943
|
+
name: `mutuallyExclusive(${fields.join(", ")})`,
|
|
1944
|
+
description: `Only one of [${fields.join(", ")}] can be set at a time`,
|
|
1945
|
+
zodCode: [
|
|
1946
|
+
".superRefine((data, ctx) => {",
|
|
1947
|
+
` const count = ${checks};`,
|
|
1948
|
+
" if (count > 1) {",
|
|
1949
|
+
" ctx.addIssue({",
|
|
1950
|
+
" code: z.ZodIssueCode.custom,",
|
|
1951
|
+
` message: 'Only one of [${fields.join(", ")}] can be set at a time',`,
|
|
1952
|
+
" });",
|
|
1953
|
+
" }",
|
|
1954
|
+
"})"
|
|
1955
|
+
].join("\n")
|
|
1956
|
+
};
|
|
1957
|
+
}
|
|
1958
|
+
function dependentFields(primaryField, dependents) {
|
|
1959
|
+
const checks = dependents.map(
|
|
1960
|
+
(f) => ` if (!data.${f}) {
|
|
1961
|
+
ctx.addIssue({ code: z.ZodIssueCode.custom, message: '${f} is required when ${primaryField} is set', path: ['${f}'] });
|
|
1962
|
+
}`
|
|
1963
|
+
).join("\n");
|
|
1964
|
+
return {
|
|
1965
|
+
name: `dependentFields(${primaryField} -> ${dependents.join(", ")})`,
|
|
1966
|
+
description: `When ${primaryField} is set, [${dependents.join(", ")}] are required`,
|
|
1967
|
+
zodCode: [
|
|
1968
|
+
".superRefine((data, ctx) => {",
|
|
1969
|
+
` if (data.${primaryField}) {`,
|
|
1970
|
+
checks,
|
|
1971
|
+
" }",
|
|
1972
|
+
"})"
|
|
1973
|
+
].join("\n")
|
|
1974
|
+
};
|
|
1975
|
+
}
|
|
1976
|
+
function conditionalValidation(conditionField, conditionValue, targetField, validationMessage) {
|
|
1977
|
+
return {
|
|
1978
|
+
name: `conditionalValidation(${conditionField}=${conditionValue} -> ${targetField})`,
|
|
1979
|
+
description: `Validate ${targetField} when ${conditionField} equals ${conditionValue}`,
|
|
1980
|
+
zodCode: [
|
|
1981
|
+
".superRefine((data, ctx) => {",
|
|
1982
|
+
` if (data.${conditionField} === ${conditionValue} && !data.${targetField}) {`,
|
|
1983
|
+
" ctx.addIssue({",
|
|
1984
|
+
" code: z.ZodIssueCode.custom,",
|
|
1985
|
+
` message: '${validationMessage}',`,
|
|
1986
|
+
` path: ['${targetField}'],`,
|
|
1987
|
+
" });",
|
|
1988
|
+
" }",
|
|
1989
|
+
"})"
|
|
1990
|
+
].join("\n")
|
|
1991
|
+
};
|
|
1992
|
+
}
|
|
1993
|
+
function suggestCrossFieldPattern(whenCode) {
|
|
1994
|
+
const booleanMatch = whenCode.match(/\.when\(['"](\w+)['"]\s*,\s*\{[^}]*is:\s*true/);
|
|
1995
|
+
if (booleanMatch?.[1]) {
|
|
1996
|
+
const field = booleanMatch[1];
|
|
1997
|
+
return requireIf(field, "targetField");
|
|
1998
|
+
}
|
|
1999
|
+
const multiFieldMatch = whenCode.match(/\.when\(\[([^\]]+)\]/);
|
|
2000
|
+
if (multiFieldMatch?.[1]) {
|
|
2001
|
+
const fields = multiFieldMatch[1].split(",").map((f) => f.trim().replace(/['"]/g, "")).filter(Boolean);
|
|
2002
|
+
if (fields.length > 1) {
|
|
2003
|
+
return dependentFields(fields[0] ?? "primary", fields.slice(1));
|
|
2004
|
+
}
|
|
2005
|
+
}
|
|
2006
|
+
return null;
|
|
2007
|
+
}
|
|
2008
|
+
|
|
1602
2009
|
// src/dependency-graph.ts
|
|
1603
|
-
var
|
|
1604
|
-
var
|
|
2010
|
+
var import_node_fs5 = require("fs");
|
|
2011
|
+
var import_node_path5 = require("path");
|
|
1605
2012
|
var SchemaDependencyResolver = class {
|
|
1606
2013
|
resolve(project, filePaths) {
|
|
1607
2014
|
const fileSet = new Set(filePaths);
|
|
@@ -1728,38 +2135,38 @@ function computeParallelBatches(packages, suggestedOrder) {
|
|
|
1728
2135
|
}
|
|
1729
2136
|
var MonorepoResolver = class {
|
|
1730
2137
|
detect(projectPath) {
|
|
1731
|
-
const pkgPath = (0,
|
|
1732
|
-
if ((0,
|
|
2138
|
+
const pkgPath = (0, import_node_path5.join)(projectPath, "package.json");
|
|
2139
|
+
if ((0, import_node_fs5.existsSync)(pkgPath)) {
|
|
1733
2140
|
try {
|
|
1734
|
-
const pkg = JSON.parse((0,
|
|
2141
|
+
const pkg = JSON.parse((0, import_node_fs5.readFileSync)(pkgPath, "utf-8"));
|
|
1735
2142
|
if (pkg.workspaces) return true;
|
|
1736
2143
|
} catch {
|
|
1737
2144
|
}
|
|
1738
2145
|
}
|
|
1739
|
-
if ((0,
|
|
2146
|
+
if ((0, import_node_fs5.existsSync)((0, import_node_path5.join)(projectPath, "pnpm-workspace.yaml"))) return true;
|
|
1740
2147
|
return false;
|
|
1741
2148
|
}
|
|
1742
2149
|
/**
|
|
1743
2150
|
* Detect which workspace manager is being used.
|
|
1744
2151
|
*/
|
|
1745
2152
|
detectManager(projectPath) {
|
|
1746
|
-
if ((0,
|
|
1747
|
-
const pkgPath = (0,
|
|
1748
|
-
if ((0,
|
|
2153
|
+
if ((0, import_node_fs5.existsSync)((0, import_node_path5.join)(projectPath, "pnpm-workspace.yaml"))) return "pnpm";
|
|
2154
|
+
const pkgPath = (0, import_node_path5.join)(projectPath, "package.json");
|
|
2155
|
+
if ((0, import_node_fs5.existsSync)(pkgPath)) {
|
|
1749
2156
|
try {
|
|
1750
|
-
const pkg = JSON.parse((0,
|
|
2157
|
+
const pkg = JSON.parse((0, import_node_fs5.readFileSync)(pkgPath, "utf-8"));
|
|
1751
2158
|
if (pkg.packageManager?.startsWith("yarn")) return "yarn";
|
|
1752
2159
|
if (pkg.packageManager?.startsWith("pnpm")) return "pnpm";
|
|
1753
2160
|
} catch {
|
|
1754
2161
|
}
|
|
1755
2162
|
}
|
|
1756
|
-
if ((0,
|
|
1757
|
-
if ((0,
|
|
2163
|
+
if ((0, import_node_fs5.existsSync)((0, import_node_path5.join)(projectPath, "pnpm-lock.yaml"))) return "pnpm";
|
|
2164
|
+
if ((0, import_node_fs5.existsSync)((0, import_node_path5.join)(projectPath, "yarn.lock"))) return "yarn";
|
|
1758
2165
|
return "npm";
|
|
1759
2166
|
}
|
|
1760
2167
|
analyze(projectPath) {
|
|
1761
|
-
const pkgPath = (0,
|
|
1762
|
-
if (!(0,
|
|
2168
|
+
const pkgPath = (0, import_node_path5.join)(projectPath, "package.json");
|
|
2169
|
+
if (!(0, import_node_fs5.existsSync)(pkgPath)) {
|
|
1763
2170
|
return { isMonorepo: false, packages: [], suggestedOrder: [] };
|
|
1764
2171
|
}
|
|
1765
2172
|
let workspaceGlobs;
|
|
@@ -1774,10 +2181,10 @@ var MonorepoResolver = class {
|
|
|
1774
2181
|
const packages = [];
|
|
1775
2182
|
const resolvedDirs = this.resolveWorkspaceDirs(projectPath, workspaceGlobs);
|
|
1776
2183
|
for (const dir of resolvedDirs) {
|
|
1777
|
-
const wsPkgPath = (0,
|
|
1778
|
-
if (!(0,
|
|
2184
|
+
const wsPkgPath = (0, import_node_path5.join)(dir, "package.json");
|
|
2185
|
+
if (!(0, import_node_fs5.existsSync)(wsPkgPath)) continue;
|
|
1779
2186
|
try {
|
|
1780
|
-
const wsPkg = JSON.parse((0,
|
|
2187
|
+
const wsPkg = JSON.parse((0, import_node_fs5.readFileSync)(wsPkgPath, "utf-8"));
|
|
1781
2188
|
if (!wsPkg.name) continue;
|
|
1782
2189
|
const allDeps = { ...wsPkg.dependencies, ...wsPkg.devDependencies };
|
|
1783
2190
|
const depNames = Object.keys(allDeps);
|
|
@@ -1821,14 +2228,14 @@ var MonorepoResolver = class {
|
|
|
1821
2228
|
* Supports: npm/yarn workspaces (package.json), pnpm-workspace.yaml
|
|
1822
2229
|
*/
|
|
1823
2230
|
resolveWorkspaceGlobs(projectPath) {
|
|
1824
|
-
const pnpmPath = (0,
|
|
1825
|
-
if ((0,
|
|
2231
|
+
const pnpmPath = (0, import_node_path5.join)(projectPath, "pnpm-workspace.yaml");
|
|
2232
|
+
if ((0, import_node_fs5.existsSync)(pnpmPath)) {
|
|
1826
2233
|
return this.parsePnpmWorkspace(pnpmPath);
|
|
1827
2234
|
}
|
|
1828
|
-
const pkgPath = (0,
|
|
1829
|
-
if ((0,
|
|
2235
|
+
const pkgPath = (0, import_node_path5.join)(projectPath, "package.json");
|
|
2236
|
+
if ((0, import_node_fs5.existsSync)(pkgPath)) {
|
|
1830
2237
|
try {
|
|
1831
|
-
const pkg = JSON.parse((0,
|
|
2238
|
+
const pkg = JSON.parse((0, import_node_fs5.readFileSync)(pkgPath, "utf-8"));
|
|
1832
2239
|
if (pkg.workspaces) {
|
|
1833
2240
|
return Array.isArray(pkg.workspaces) ? pkg.workspaces : pkg.workspaces.packages;
|
|
1834
2241
|
}
|
|
@@ -1847,7 +2254,7 @@ var MonorepoResolver = class {
|
|
|
1847
2254
|
* ```
|
|
1848
2255
|
*/
|
|
1849
2256
|
parsePnpmWorkspace(filePath) {
|
|
1850
|
-
const content = (0,
|
|
2257
|
+
const content = (0, import_node_fs5.readFileSync)(filePath, "utf-8");
|
|
1851
2258
|
const globs = [];
|
|
1852
2259
|
let inPackages = false;
|
|
1853
2260
|
for (const line of content.split("\n")) {
|
|
@@ -1872,14 +2279,14 @@ var MonorepoResolver = class {
|
|
|
1872
2279
|
const dirs = [];
|
|
1873
2280
|
for (const glob of globs) {
|
|
1874
2281
|
const clean = glob.replace(/\/?\*$/, "");
|
|
1875
|
-
const base = (0,
|
|
1876
|
-
if (!(0,
|
|
2282
|
+
const base = (0, import_node_path5.resolve)(projectPath, clean);
|
|
2283
|
+
if (!(0, import_node_fs5.existsSync)(base)) continue;
|
|
1877
2284
|
if (glob.endsWith("*")) {
|
|
1878
2285
|
try {
|
|
1879
|
-
const entries = (0,
|
|
2286
|
+
const entries = (0, import_node_fs5.readdirSync)(base, { withFileTypes: true });
|
|
1880
2287
|
for (const entry of entries) {
|
|
1881
2288
|
if (entry.isDirectory()) {
|
|
1882
|
-
dirs.push((0,
|
|
2289
|
+
dirs.push((0, import_node_path5.join)(base, entry.name));
|
|
1883
2290
|
}
|
|
1884
2291
|
}
|
|
1885
2292
|
} catch {
|
|
@@ -1893,8 +2300,8 @@ var MonorepoResolver = class {
|
|
|
1893
2300
|
};
|
|
1894
2301
|
|
|
1895
2302
|
// src/detailed-analyzer.ts
|
|
1896
|
-
var
|
|
1897
|
-
var
|
|
2303
|
+
var import_node_fs6 = require("fs");
|
|
2304
|
+
var import_node_path6 = require("path");
|
|
1898
2305
|
var COMPLEXITY_CHAIN_WEIGHT = 2;
|
|
1899
2306
|
var COMPLEXITY_DEPTH_WEIGHT = 3;
|
|
1900
2307
|
var COMPLEXITY_VALIDATION_WEIGHT = 1;
|
|
@@ -1959,10 +2366,10 @@ var DetailedAnalyzer = class {
|
|
|
1959
2366
|
}
|
|
1960
2367
|
detectLibraryVersions(projectPath) {
|
|
1961
2368
|
const versions = [];
|
|
1962
|
-
const pkgPath = (0,
|
|
1963
|
-
if (!(0,
|
|
2369
|
+
const pkgPath = (0, import_node_path6.join)(projectPath, "package.json");
|
|
2370
|
+
if (!(0, import_node_fs6.existsSync)(pkgPath)) return versions;
|
|
1964
2371
|
try {
|
|
1965
|
-
const pkg = JSON.parse((0,
|
|
2372
|
+
const pkg = JSON.parse((0, import_node_fs6.readFileSync)(pkgPath, "utf-8"));
|
|
1966
2373
|
const knownLibs = ["zod", "yup", "joi", "io-ts", "valibot"];
|
|
1967
2374
|
const allDeps = {
|
|
1968
2375
|
...pkg.dependencies,
|
|
@@ -2137,8 +2544,8 @@ var DetailedAnalyzer = class {
|
|
|
2137
2544
|
|
|
2138
2545
|
// src/drift-detector.ts
|
|
2139
2546
|
var import_node_crypto2 = require("crypto");
|
|
2140
|
-
var
|
|
2141
|
-
var
|
|
2547
|
+
var import_node_fs7 = require("fs");
|
|
2548
|
+
var import_node_path7 = require("path");
|
|
2142
2549
|
var SNAPSHOT_DIR = ".schemashift";
|
|
2143
2550
|
var SNAPSHOT_FILE = "schema-snapshot.json";
|
|
2144
2551
|
var SNAPSHOT_VERSION = 1;
|
|
@@ -2146,8 +2553,8 @@ var DriftDetector = class {
|
|
|
2146
2553
|
snapshotDir;
|
|
2147
2554
|
snapshotPath;
|
|
2148
2555
|
constructor(projectPath) {
|
|
2149
|
-
this.snapshotDir = (0,
|
|
2150
|
-
this.snapshotPath = (0,
|
|
2556
|
+
this.snapshotDir = (0, import_node_path7.join)(projectPath, SNAPSHOT_DIR);
|
|
2557
|
+
this.snapshotPath = (0, import_node_path7.join)(this.snapshotDir, SNAPSHOT_FILE);
|
|
2151
2558
|
}
|
|
2152
2559
|
/**
|
|
2153
2560
|
* Take a snapshot of the current schema state
|
|
@@ -2155,13 +2562,13 @@ var DriftDetector = class {
|
|
|
2155
2562
|
snapshot(files, projectPath) {
|
|
2156
2563
|
const schemas = [];
|
|
2157
2564
|
for (const filePath of files) {
|
|
2158
|
-
if (!(0,
|
|
2159
|
-
const content = (0,
|
|
2565
|
+
if (!(0, import_node_fs7.existsSync)(filePath)) continue;
|
|
2566
|
+
const content = (0, import_node_fs7.readFileSync)(filePath, "utf-8");
|
|
2160
2567
|
const library = this.detectLibraryFromContent(content);
|
|
2161
2568
|
if (library === "unknown") continue;
|
|
2162
2569
|
const schemaNames = this.extractSchemaNames(content);
|
|
2163
2570
|
schemas.push({
|
|
2164
|
-
filePath: (0,
|
|
2571
|
+
filePath: (0, import_node_path7.relative)(projectPath, filePath),
|
|
2165
2572
|
library,
|
|
2166
2573
|
contentHash: this.hashContent(content),
|
|
2167
2574
|
schemaCount: schemaNames.length,
|
|
@@ -2180,20 +2587,20 @@ var DriftDetector = class {
|
|
|
2180
2587
|
* Save a snapshot to disk
|
|
2181
2588
|
*/
|
|
2182
2589
|
saveSnapshot(snapshot) {
|
|
2183
|
-
if (!(0,
|
|
2184
|
-
(0,
|
|
2590
|
+
if (!(0, import_node_fs7.existsSync)(this.snapshotDir)) {
|
|
2591
|
+
(0, import_node_fs7.mkdirSync)(this.snapshotDir, { recursive: true });
|
|
2185
2592
|
}
|
|
2186
|
-
(0,
|
|
2593
|
+
(0, import_node_fs7.writeFileSync)(this.snapshotPath, JSON.stringify(snapshot, null, 2));
|
|
2187
2594
|
}
|
|
2188
2595
|
/**
|
|
2189
2596
|
* Load saved snapshot from disk
|
|
2190
2597
|
*/
|
|
2191
2598
|
loadSnapshot() {
|
|
2192
|
-
if (!(0,
|
|
2599
|
+
if (!(0, import_node_fs7.existsSync)(this.snapshotPath)) {
|
|
2193
2600
|
return null;
|
|
2194
2601
|
}
|
|
2195
2602
|
try {
|
|
2196
|
-
const content = (0,
|
|
2603
|
+
const content = (0, import_node_fs7.readFileSync)(this.snapshotPath, "utf-8");
|
|
2197
2604
|
return JSON.parse(content);
|
|
2198
2605
|
} catch {
|
|
2199
2606
|
return null;
|
|
@@ -2655,6 +3062,250 @@ var GovernanceEngine = class {
|
|
|
2655
3062
|
}
|
|
2656
3063
|
};
|
|
2657
3064
|
|
|
3065
|
+
// src/governance-fixer.ts
|
|
3066
|
+
var GovernanceFixer = class {
|
|
3067
|
+
defaultMaxLength = 1e4;
|
|
3068
|
+
/**
|
|
3069
|
+
* Set the default max length appended by the require-max-length fix.
|
|
3070
|
+
*/
|
|
3071
|
+
setDefaultMaxLength(length) {
|
|
3072
|
+
this.defaultMaxLength = length;
|
|
3073
|
+
}
|
|
3074
|
+
/**
|
|
3075
|
+
* Check if a violation is auto-fixable.
|
|
3076
|
+
*/
|
|
3077
|
+
canFix(violation) {
|
|
3078
|
+
return [
|
|
3079
|
+
"no-any-schemas",
|
|
3080
|
+
"require-descriptions",
|
|
3081
|
+
"require-max-length",
|
|
3082
|
+
"naming-convention",
|
|
3083
|
+
"no-any",
|
|
3084
|
+
"require-description",
|
|
3085
|
+
"required-validations",
|
|
3086
|
+
"require-safeParse"
|
|
3087
|
+
].includes(violation.rule);
|
|
3088
|
+
}
|
|
3089
|
+
/**
|
|
3090
|
+
* Fix a single violation in a source file.
|
|
3091
|
+
* Returns the fixed code for the entire file.
|
|
3092
|
+
*/
|
|
3093
|
+
fix(violation, sourceCode) {
|
|
3094
|
+
switch (violation.rule) {
|
|
3095
|
+
case "no-any-schemas":
|
|
3096
|
+
case "no-any":
|
|
3097
|
+
return this.fixNoAny(violation, sourceCode);
|
|
3098
|
+
case "require-descriptions":
|
|
3099
|
+
case "require-description":
|
|
3100
|
+
return this.fixRequireDescription(violation, sourceCode);
|
|
3101
|
+
case "require-max-length":
|
|
3102
|
+
case "required-validations":
|
|
3103
|
+
return this.fixRequireMaxLength(violation, sourceCode);
|
|
3104
|
+
case "naming-convention":
|
|
3105
|
+
return this.fixNamingConvention(violation, sourceCode);
|
|
3106
|
+
case "require-safeParse":
|
|
3107
|
+
return this.fixRequireSafeParse(violation, sourceCode);
|
|
3108
|
+
default:
|
|
3109
|
+
return {
|
|
3110
|
+
success: false,
|
|
3111
|
+
explanation: `No auto-fix available for rule: ${violation.rule}`,
|
|
3112
|
+
rule: violation.rule,
|
|
3113
|
+
lineNumber: violation.lineNumber
|
|
3114
|
+
};
|
|
3115
|
+
}
|
|
3116
|
+
}
|
|
3117
|
+
/**
|
|
3118
|
+
* Fix all fixable violations in a source file.
|
|
3119
|
+
* Applies fixes from bottom to top to preserve line numbers.
|
|
3120
|
+
*/
|
|
3121
|
+
fixAll(violations, sourceCode) {
|
|
3122
|
+
const fixable = violations.filter((v) => this.canFix(v));
|
|
3123
|
+
const results = [];
|
|
3124
|
+
let currentCode = sourceCode;
|
|
3125
|
+
let fixed = 0;
|
|
3126
|
+
const sorted = [...fixable].sort((a, b) => b.lineNumber - a.lineNumber);
|
|
3127
|
+
for (const violation of sorted) {
|
|
3128
|
+
const result = this.fix(violation, currentCode);
|
|
3129
|
+
results.push(result);
|
|
3130
|
+
if (result.success && result.fixedCode) {
|
|
3131
|
+
currentCode = result.fixedCode;
|
|
3132
|
+
fixed++;
|
|
3133
|
+
}
|
|
3134
|
+
}
|
|
3135
|
+
return {
|
|
3136
|
+
totalViolations: violations.length,
|
|
3137
|
+
fixed,
|
|
3138
|
+
skipped: violations.length - fixed,
|
|
3139
|
+
results
|
|
3140
|
+
};
|
|
3141
|
+
}
|
|
3142
|
+
fixNoAny(violation, sourceCode) {
|
|
3143
|
+
const lines = sourceCode.split("\n");
|
|
3144
|
+
const lineIndex = violation.lineNumber - 1;
|
|
3145
|
+
const line = lines[lineIndex];
|
|
3146
|
+
if (!line) {
|
|
3147
|
+
return {
|
|
3148
|
+
success: false,
|
|
3149
|
+
explanation: `Line ${violation.lineNumber} not found`,
|
|
3150
|
+
rule: violation.rule,
|
|
3151
|
+
lineNumber: violation.lineNumber
|
|
3152
|
+
};
|
|
3153
|
+
}
|
|
3154
|
+
let fixedLine = line;
|
|
3155
|
+
let explanation = "";
|
|
3156
|
+
if (/\bz\.any\(\)/.test(line)) {
|
|
3157
|
+
fixedLine = line.replace(/\bz\.any\(\)/, "z.unknown()");
|
|
3158
|
+
explanation = "Replaced z.any() with z.unknown() for type safety";
|
|
3159
|
+
} else if (/\byup\.mixed\(\)/.test(line)) {
|
|
3160
|
+
fixedLine = line.replace(/\byup\.mixed\(\)/, "yup.mixed().required()");
|
|
3161
|
+
explanation = "Added .required() constraint to yup.mixed()";
|
|
3162
|
+
} else if (/\bt\.any\b/.test(line)) {
|
|
3163
|
+
fixedLine = line.replace(/\bt\.any\b/, "t.unknown");
|
|
3164
|
+
explanation = "Replaced t.any with t.unknown for type safety";
|
|
3165
|
+
} else if (/\bv\.any\(\)/.test(line)) {
|
|
3166
|
+
fixedLine = line.replace(/\bv\.any\(\)/, "v.unknown()");
|
|
3167
|
+
explanation = "Replaced v.any() with v.unknown() for type safety";
|
|
3168
|
+
} else {
|
|
3169
|
+
return {
|
|
3170
|
+
success: false,
|
|
3171
|
+
explanation: "Could not identify any-type pattern to fix",
|
|
3172
|
+
rule: violation.rule,
|
|
3173
|
+
lineNumber: violation.lineNumber
|
|
3174
|
+
};
|
|
3175
|
+
}
|
|
3176
|
+
lines[lineIndex] = fixedLine;
|
|
3177
|
+
return {
|
|
3178
|
+
success: true,
|
|
3179
|
+
fixedCode: lines.join("\n"),
|
|
3180
|
+
explanation,
|
|
3181
|
+
rule: violation.rule,
|
|
3182
|
+
lineNumber: violation.lineNumber
|
|
3183
|
+
};
|
|
3184
|
+
}
|
|
3185
|
+
fixRequireDescription(violation, sourceCode) {
|
|
3186
|
+
const lines = sourceCode.split("\n");
|
|
3187
|
+
const lineIndex = violation.lineNumber - 1;
|
|
3188
|
+
const line = lines[lineIndex];
|
|
3189
|
+
if (!line) {
|
|
3190
|
+
return {
|
|
3191
|
+
success: false,
|
|
3192
|
+
explanation: `Line ${violation.lineNumber} not found`,
|
|
3193
|
+
rule: violation.rule,
|
|
3194
|
+
lineNumber: violation.lineNumber
|
|
3195
|
+
};
|
|
3196
|
+
}
|
|
3197
|
+
let endLineIndex = lineIndex;
|
|
3198
|
+
for (let i = lineIndex; i < lines.length && i < lineIndex + 20; i++) {
|
|
3199
|
+
if (lines[i]?.includes(";")) {
|
|
3200
|
+
endLineIndex = i;
|
|
3201
|
+
break;
|
|
3202
|
+
}
|
|
3203
|
+
}
|
|
3204
|
+
const endLine = lines[endLineIndex] ?? "";
|
|
3205
|
+
const schemaName = violation.schemaName || "schema";
|
|
3206
|
+
const description = `${schemaName} schema`;
|
|
3207
|
+
const semicolonIndex = endLine.lastIndexOf(";");
|
|
3208
|
+
if (semicolonIndex >= 0) {
|
|
3209
|
+
lines[endLineIndex] = `${endLine.slice(0, semicolonIndex)}.describe('${description}')${endLine.slice(semicolonIndex)}`;
|
|
3210
|
+
} else {
|
|
3211
|
+
lines[endLineIndex] = `${endLine}.describe('${description}')`;
|
|
3212
|
+
}
|
|
3213
|
+
return {
|
|
3214
|
+
success: true,
|
|
3215
|
+
fixedCode: lines.join("\n"),
|
|
3216
|
+
explanation: `Added .describe('${description}') to ${schemaName}`,
|
|
3217
|
+
rule: violation.rule,
|
|
3218
|
+
lineNumber: violation.lineNumber
|
|
3219
|
+
};
|
|
3220
|
+
}
|
|
3221
|
+
fixRequireMaxLength(violation, sourceCode) {
|
|
3222
|
+
const lines = sourceCode.split("\n");
|
|
3223
|
+
const lineIndex = violation.lineNumber - 1;
|
|
3224
|
+
const line = lines[lineIndex];
|
|
3225
|
+
if (!line) {
|
|
3226
|
+
return {
|
|
3227
|
+
success: false,
|
|
3228
|
+
explanation: `Line ${violation.lineNumber} not found`,
|
|
3229
|
+
rule: violation.rule,
|
|
3230
|
+
lineNumber: violation.lineNumber
|
|
3231
|
+
};
|
|
3232
|
+
}
|
|
3233
|
+
if (/z\.string\(\)/.test(line)) {
|
|
3234
|
+
lines[lineIndex] = line.replace(/z\.string\(\)/, `z.string().max(${this.defaultMaxLength})`);
|
|
3235
|
+
return {
|
|
3236
|
+
success: true,
|
|
3237
|
+
fixedCode: lines.join("\n"),
|
|
3238
|
+
explanation: `Added .max(${this.defaultMaxLength}) to string schema`,
|
|
3239
|
+
rule: violation.rule,
|
|
3240
|
+
lineNumber: violation.lineNumber
|
|
3241
|
+
};
|
|
3242
|
+
}
|
|
3243
|
+
return {
|
|
3244
|
+
success: false,
|
|
3245
|
+
explanation: "Could not find z.string() pattern to fix on this line",
|
|
3246
|
+
rule: violation.rule,
|
|
3247
|
+
lineNumber: violation.lineNumber
|
|
3248
|
+
};
|
|
3249
|
+
}
|
|
3250
|
+
fixNamingConvention(violation, sourceCode) {
|
|
3251
|
+
const schemaName = violation.schemaName;
|
|
3252
|
+
if (!schemaName) {
|
|
3253
|
+
return {
|
|
3254
|
+
success: false,
|
|
3255
|
+
explanation: "No schema name available for renaming",
|
|
3256
|
+
rule: violation.rule,
|
|
3257
|
+
lineNumber: violation.lineNumber
|
|
3258
|
+
};
|
|
3259
|
+
}
|
|
3260
|
+
const newName = schemaName.endsWith("Schema") ? schemaName : `${schemaName}Schema`;
|
|
3261
|
+
if (newName === schemaName) {
|
|
3262
|
+
return {
|
|
3263
|
+
success: false,
|
|
3264
|
+
explanation: "Schema already matches naming convention",
|
|
3265
|
+
rule: violation.rule,
|
|
3266
|
+
lineNumber: violation.lineNumber
|
|
3267
|
+
};
|
|
3268
|
+
}
|
|
3269
|
+
const fixedCode = sourceCode.replace(new RegExp(`\\b${schemaName}\\b`, "g"), newName);
|
|
3270
|
+
return {
|
|
3271
|
+
success: true,
|
|
3272
|
+
fixedCode,
|
|
3273
|
+
explanation: `Renamed "${schemaName}" to "${newName}"`,
|
|
3274
|
+
rule: violation.rule,
|
|
3275
|
+
lineNumber: violation.lineNumber
|
|
3276
|
+
};
|
|
3277
|
+
}
|
|
3278
|
+
fixRequireSafeParse(violation, sourceCode) {
|
|
3279
|
+
const lines = sourceCode.split("\n");
|
|
3280
|
+
const lineIndex = violation.lineNumber - 1;
|
|
3281
|
+
const line = lines[lineIndex];
|
|
3282
|
+
if (!line) {
|
|
3283
|
+
return {
|
|
3284
|
+
success: false,
|
|
3285
|
+
explanation: `Line ${violation.lineNumber} not found`,
|
|
3286
|
+
rule: violation.rule,
|
|
3287
|
+
lineNumber: violation.lineNumber
|
|
3288
|
+
};
|
|
3289
|
+
}
|
|
3290
|
+
if (line.includes(".parse(") && !line.includes(".safeParse(")) {
|
|
3291
|
+
lines[lineIndex] = line.replace(".parse(", ".safeParse(");
|
|
3292
|
+
return {
|
|
3293
|
+
success: true,
|
|
3294
|
+
fixedCode: lines.join("\n"),
|
|
3295
|
+
explanation: "Replaced .parse() with .safeParse() for safer error handling",
|
|
3296
|
+
rule: violation.rule,
|
|
3297
|
+
lineNumber: violation.lineNumber
|
|
3298
|
+
};
|
|
3299
|
+
}
|
|
3300
|
+
return {
|
|
3301
|
+
success: false,
|
|
3302
|
+
explanation: "Could not find .parse() pattern to fix",
|
|
3303
|
+
rule: violation.rule,
|
|
3304
|
+
lineNumber: violation.lineNumber
|
|
3305
|
+
};
|
|
3306
|
+
}
|
|
3307
|
+
};
|
|
3308
|
+
|
|
2658
3309
|
// src/governance-templates.ts
|
|
2659
3310
|
var GOVERNANCE_TEMPLATES = [
|
|
2660
3311
|
{
|
|
@@ -2903,17 +3554,184 @@ function getGovernanceTemplateNames() {
|
|
|
2903
3554
|
return GOVERNANCE_TEMPLATES.map((t) => t.name);
|
|
2904
3555
|
}
|
|
2905
3556
|
|
|
3557
|
+
// src/graph-exporter.ts
|
|
3558
|
+
var LIBRARY_COLORS = {
|
|
3559
|
+
zod: "#3068B7",
|
|
3560
|
+
yup: "#32CD32",
|
|
3561
|
+
joi: "#FF6347",
|
|
3562
|
+
"io-ts": "#9370DB",
|
|
3563
|
+
valibot: "#FF8C00",
|
|
3564
|
+
arktype: "#20B2AA",
|
|
3565
|
+
superstruct: "#DAA520",
|
|
3566
|
+
effect: "#6A5ACD"
|
|
3567
|
+
};
|
|
3568
|
+
var LIBRARY_MERMAID_STYLES = {
|
|
3569
|
+
zod: "fill:#3068B7,color:#fff",
|
|
3570
|
+
yup: "fill:#32CD32,color:#000",
|
|
3571
|
+
joi: "fill:#FF6347,color:#fff",
|
|
3572
|
+
"io-ts": "fill:#9370DB,color:#fff",
|
|
3573
|
+
valibot: "fill:#FF8C00,color:#000",
|
|
3574
|
+
arktype: "fill:#20B2AA,color:#fff",
|
|
3575
|
+
superstruct: "fill:#DAA520,color:#000",
|
|
3576
|
+
effect: "fill:#6A5ACD,color:#fff"
|
|
3577
|
+
};
|
|
3578
|
+
var GraphExporter = class {
|
|
3579
|
+
/**
|
|
3580
|
+
* Export dependency graph as DOT format for Graphviz.
|
|
3581
|
+
*/
|
|
3582
|
+
exportDot(graph, options = {}) {
|
|
3583
|
+
const lines = [];
|
|
3584
|
+
lines.push("digraph SchemaShiftDependencies {");
|
|
3585
|
+
lines.push(" rankdir=LR;");
|
|
3586
|
+
lines.push(' node [shape=box, style=filled, fontname="monospace"];');
|
|
3587
|
+
lines.push(' edge [color="#666666"];');
|
|
3588
|
+
lines.push("");
|
|
3589
|
+
const circularFiles = /* @__PURE__ */ new Set();
|
|
3590
|
+
if (options.highlightCircular && graph.circularWarnings.length > 0) {
|
|
3591
|
+
for (const warning of graph.circularWarnings) {
|
|
3592
|
+
const match = warning.match(/Circular dependency: (.+)/);
|
|
3593
|
+
if (match?.[1]) {
|
|
3594
|
+
for (const part of match[1].split(" -> ")) {
|
|
3595
|
+
for (const file of graph.sortedFiles) {
|
|
3596
|
+
if (file.endsWith(part.trim()) || this.shortenPath(file) === part.trim()) {
|
|
3597
|
+
circularFiles.add(file);
|
|
3598
|
+
}
|
|
3599
|
+
}
|
|
3600
|
+
}
|
|
3601
|
+
}
|
|
3602
|
+
}
|
|
3603
|
+
}
|
|
3604
|
+
for (const filePath of graph.sortedFiles) {
|
|
3605
|
+
const meta = options.nodeMetadata?.get(filePath);
|
|
3606
|
+
const library = meta?.library;
|
|
3607
|
+
if (options.filterLibrary && library !== options.filterLibrary) continue;
|
|
3608
|
+
const shortPath = this.shortenPath(filePath);
|
|
3609
|
+
const nodeId = this.toNodeId(filePath);
|
|
3610
|
+
const attrs = [];
|
|
3611
|
+
attrs.push(`label="${shortPath}"`);
|
|
3612
|
+
if (circularFiles.has(filePath)) {
|
|
3613
|
+
attrs.push('color="#FF0000"');
|
|
3614
|
+
attrs.push("penwidth=2");
|
|
3615
|
+
}
|
|
3616
|
+
if (options.colorByLibrary && library && LIBRARY_COLORS[library]) {
|
|
3617
|
+
attrs.push(`fillcolor="${LIBRARY_COLORS[library]}"`);
|
|
3618
|
+
attrs.push('fontcolor="white"');
|
|
3619
|
+
} else {
|
|
3620
|
+
attrs.push('fillcolor="#E8E8E8"');
|
|
3621
|
+
}
|
|
3622
|
+
if (meta?.schemaCount) {
|
|
3623
|
+
attrs.push(`tooltip="${meta.schemaCount} schema(s)"`);
|
|
3624
|
+
}
|
|
3625
|
+
lines.push(` ${nodeId} [${attrs.join(", ")}];`);
|
|
3626
|
+
}
|
|
3627
|
+
lines.push("");
|
|
3628
|
+
const filterSet = options.filterLibrary ? new Set(
|
|
3629
|
+
graph.sortedFiles.filter((f) => {
|
|
3630
|
+
const meta = options.nodeMetadata?.get(f);
|
|
3631
|
+
return meta?.library === options.filterLibrary;
|
|
3632
|
+
})
|
|
3633
|
+
) : void 0;
|
|
3634
|
+
for (const [file, deps] of graph.dependencies) {
|
|
3635
|
+
if (filterSet && !filterSet.has(file)) continue;
|
|
3636
|
+
const fromId = this.toNodeId(file);
|
|
3637
|
+
for (const dep of deps) {
|
|
3638
|
+
if (filterSet && !filterSet.has(dep)) continue;
|
|
3639
|
+
const toId = this.toNodeId(dep);
|
|
3640
|
+
const edgeAttrs = [];
|
|
3641
|
+
if (options.highlightCircular && circularFiles.has(file) && circularFiles.has(dep)) {
|
|
3642
|
+
edgeAttrs.push('color="#FF0000"');
|
|
3643
|
+
edgeAttrs.push("penwidth=2");
|
|
3644
|
+
}
|
|
3645
|
+
lines.push(
|
|
3646
|
+
` ${fromId} -> ${toId}${edgeAttrs.length > 0 ? ` [${edgeAttrs.join(", ")}]` : ""};`
|
|
3647
|
+
);
|
|
3648
|
+
}
|
|
3649
|
+
}
|
|
3650
|
+
lines.push("}");
|
|
3651
|
+
return lines.join("\n");
|
|
3652
|
+
}
|
|
3653
|
+
/**
|
|
3654
|
+
* Export dependency graph as Mermaid diagram syntax.
|
|
3655
|
+
*/
|
|
3656
|
+
exportMermaid(graph, options = {}) {
|
|
3657
|
+
const lines = [];
|
|
3658
|
+
lines.push("graph LR");
|
|
3659
|
+
const styledNodes = /* @__PURE__ */ new Map();
|
|
3660
|
+
for (const [file, deps] of graph.dependencies) {
|
|
3661
|
+
const meta = options.nodeMetadata?.get(file);
|
|
3662
|
+
if (options.filterLibrary && meta?.library !== options.filterLibrary) continue;
|
|
3663
|
+
const fromId = this.toMermaidId(file);
|
|
3664
|
+
const fromLabel = this.shortenPath(file);
|
|
3665
|
+
if (meta?.library) {
|
|
3666
|
+
styledNodes.set(fromId, meta.library);
|
|
3667
|
+
}
|
|
3668
|
+
if (deps.length === 0) {
|
|
3669
|
+
lines.push(` ${fromId}["${fromLabel}"]`);
|
|
3670
|
+
}
|
|
3671
|
+
for (const dep of deps) {
|
|
3672
|
+
const depMeta = options.nodeMetadata?.get(dep);
|
|
3673
|
+
if (options.filterLibrary && depMeta?.library !== options.filterLibrary) continue;
|
|
3674
|
+
const toId = this.toMermaidId(dep);
|
|
3675
|
+
const toLabel = this.shortenPath(dep);
|
|
3676
|
+
if (depMeta?.library) {
|
|
3677
|
+
styledNodes.set(toId, depMeta.library);
|
|
3678
|
+
}
|
|
3679
|
+
lines.push(` ${fromId}["${fromLabel}"] --> ${toId}["${toLabel}"]`);
|
|
3680
|
+
}
|
|
3681
|
+
}
|
|
3682
|
+
for (const file of graph.sortedFiles) {
|
|
3683
|
+
const meta = options.nodeMetadata?.get(file);
|
|
3684
|
+
if (options.filterLibrary && meta?.library !== options.filterLibrary) continue;
|
|
3685
|
+
const id = this.toMermaidId(file);
|
|
3686
|
+
if (!lines.some((l) => l.includes(id))) {
|
|
3687
|
+
lines.push(` ${id}["${this.shortenPath(file)}"]`);
|
|
3688
|
+
if (meta?.library) {
|
|
3689
|
+
styledNodes.set(id, meta.library);
|
|
3690
|
+
}
|
|
3691
|
+
}
|
|
3692
|
+
}
|
|
3693
|
+
if (options.colorByLibrary && styledNodes.size > 0) {
|
|
3694
|
+
lines.push("");
|
|
3695
|
+
const libraryGroups = /* @__PURE__ */ new Map();
|
|
3696
|
+
for (const [nodeId, library] of styledNodes) {
|
|
3697
|
+
const group = libraryGroups.get(library) ?? [];
|
|
3698
|
+
group.push(nodeId);
|
|
3699
|
+
libraryGroups.set(library, group);
|
|
3700
|
+
}
|
|
3701
|
+
for (const [library, nodeIds] of libraryGroups) {
|
|
3702
|
+
const style = LIBRARY_MERMAID_STYLES[library];
|
|
3703
|
+
if (style) {
|
|
3704
|
+
for (const nodeId of nodeIds) {
|
|
3705
|
+
lines.push(` style ${nodeId} ${style}`);
|
|
3706
|
+
}
|
|
3707
|
+
}
|
|
3708
|
+
}
|
|
3709
|
+
}
|
|
3710
|
+
return lines.join("\n");
|
|
3711
|
+
}
|
|
3712
|
+
shortenPath(filePath) {
|
|
3713
|
+
const parts = filePath.split("/");
|
|
3714
|
+
return parts.slice(-2).join("/");
|
|
3715
|
+
}
|
|
3716
|
+
toNodeId(filePath) {
|
|
3717
|
+
return filePath.replace(/[^a-zA-Z0-9]/g, "_").replace(/^_+/, "").replace(/_+$/, "");
|
|
3718
|
+
}
|
|
3719
|
+
toMermaidId(filePath) {
|
|
3720
|
+
return filePath.replace(/[^a-zA-Z0-9]/g, "_").replace(/^_+/, "n_").replace(/_+$/, "");
|
|
3721
|
+
}
|
|
3722
|
+
};
|
|
3723
|
+
|
|
2906
3724
|
// src/incremental.ts
|
|
2907
|
-
var
|
|
2908
|
-
var
|
|
3725
|
+
var import_node_fs8 = require("fs");
|
|
3726
|
+
var import_node_path8 = require("path");
|
|
2909
3727
|
var STATE_DIR = ".schemashift";
|
|
2910
3728
|
var STATE_FILE = "incremental.json";
|
|
2911
3729
|
var IncrementalTracker = class {
|
|
2912
3730
|
stateDir;
|
|
2913
3731
|
statePath;
|
|
2914
3732
|
constructor(projectPath) {
|
|
2915
|
-
this.stateDir = (0,
|
|
2916
|
-
this.statePath = (0,
|
|
3733
|
+
this.stateDir = (0, import_node_path8.join)(projectPath, STATE_DIR);
|
|
3734
|
+
this.statePath = (0, import_node_path8.join)(this.stateDir, STATE_FILE);
|
|
2917
3735
|
}
|
|
2918
3736
|
start(files, from, to) {
|
|
2919
3737
|
const state = {
|
|
@@ -2948,9 +3766,9 @@ var IncrementalTracker = class {
|
|
|
2948
3766
|
this.saveState(state);
|
|
2949
3767
|
}
|
|
2950
3768
|
getState() {
|
|
2951
|
-
if (!(0,
|
|
3769
|
+
if (!(0, import_node_fs8.existsSync)(this.statePath)) return null;
|
|
2952
3770
|
try {
|
|
2953
|
-
return JSON.parse((0,
|
|
3771
|
+
return JSON.parse((0, import_node_fs8.readFileSync)(this.statePath, "utf-8"));
|
|
2954
3772
|
} catch {
|
|
2955
3773
|
return null;
|
|
2956
3774
|
}
|
|
@@ -2976,22 +3794,416 @@ var IncrementalTracker = class {
|
|
|
2976
3794
|
percent
|
|
2977
3795
|
};
|
|
2978
3796
|
}
|
|
3797
|
+
/**
|
|
3798
|
+
* Get a canary batch — a percentage of remaining files, sorted simplest first.
|
|
3799
|
+
* Used for phased rollouts where you migrate a small batch, verify, then continue.
|
|
3800
|
+
*/
|
|
3801
|
+
getCanaryBatch(percent, fileSizes) {
|
|
3802
|
+
const state = this.getState();
|
|
3803
|
+
if (!state) return [];
|
|
3804
|
+
const count = Math.max(1, Math.ceil(state.remainingFiles.length * (percent / 100)));
|
|
3805
|
+
if (fileSizes) {
|
|
3806
|
+
const sorted = [...state.remainingFiles].sort((a, b) => {
|
|
3807
|
+
return (fileSizes.get(a) ?? 0) - (fileSizes.get(b) ?? 0);
|
|
3808
|
+
});
|
|
3809
|
+
return sorted.slice(0, count);
|
|
3810
|
+
}
|
|
3811
|
+
return state.remainingFiles.slice(0, count);
|
|
3812
|
+
}
|
|
2979
3813
|
clear() {
|
|
2980
|
-
if ((0,
|
|
2981
|
-
(0,
|
|
3814
|
+
if ((0, import_node_fs8.existsSync)(this.statePath)) {
|
|
3815
|
+
(0, import_node_fs8.unlinkSync)(this.statePath);
|
|
2982
3816
|
}
|
|
2983
3817
|
}
|
|
2984
3818
|
saveState(state) {
|
|
2985
|
-
if (!(0,
|
|
2986
|
-
(0,
|
|
3819
|
+
if (!(0, import_node_fs8.existsSync)(this.stateDir)) {
|
|
3820
|
+
(0, import_node_fs8.mkdirSync)(this.stateDir, { recursive: true });
|
|
3821
|
+
}
|
|
3822
|
+
(0, import_node_fs8.writeFileSync)(this.statePath, JSON.stringify(state, null, 2));
|
|
3823
|
+
}
|
|
3824
|
+
};
|
|
3825
|
+
|
|
3826
|
+
// src/migration-templates.ts
|
|
3827
|
+
var BUILT_IN_TEMPLATES = [
|
|
3828
|
+
{
|
|
3829
|
+
name: "react-hook-form-yup-to-zod",
|
|
3830
|
+
description: "Migrate React Hook Form project from Yup to Zod validation",
|
|
3831
|
+
category: "form-migration",
|
|
3832
|
+
migrationSteps: [{ from: "yup", to: "zod", description: "Convert Yup schemas to Zod schemas" }],
|
|
3833
|
+
preChecks: [
|
|
3834
|
+
{ description: "Ensure @hookform/resolvers is installed" },
|
|
3835
|
+
{ description: "Check for .when() conditional validations that need manual review" }
|
|
3836
|
+
],
|
|
3837
|
+
postSteps: [
|
|
3838
|
+
{
|
|
3839
|
+
description: "Update resolver imports: yupResolver \u2192 zodResolver",
|
|
3840
|
+
command: void 0
|
|
3841
|
+
},
|
|
3842
|
+
{
|
|
3843
|
+
description: "Run tests to verify form validation behavior",
|
|
3844
|
+
command: "npm test"
|
|
3845
|
+
},
|
|
3846
|
+
{
|
|
3847
|
+
description: "Remove Yup dependency if no longer used",
|
|
3848
|
+
command: "npm uninstall yup"
|
|
3849
|
+
}
|
|
3850
|
+
],
|
|
3851
|
+
packageChanges: [
|
|
3852
|
+
{ action: "install", package: "zod", version: "^3.24.0" },
|
|
3853
|
+
{ action: "upgrade", package: "@hookform/resolvers", version: "latest" }
|
|
3854
|
+
],
|
|
3855
|
+
recommendedFlags: ["--cross-file", "--scaffold-tests", "--verbose"],
|
|
3856
|
+
estimatedEffort: "moderate"
|
|
3857
|
+
},
|
|
3858
|
+
{
|
|
3859
|
+
name: "trpc-zod-v3-to-v4",
|
|
3860
|
+
description: "Upgrade tRPC project from Zod v3 to Zod v4",
|
|
3861
|
+
category: "framework-upgrade",
|
|
3862
|
+
migrationSteps: [
|
|
3863
|
+
{ from: "zod-v3", to: "v4", description: "Upgrade Zod v3 schemas to v4 syntax" }
|
|
3864
|
+
],
|
|
3865
|
+
preChecks: [
|
|
3866
|
+
{ description: "Check tRPC version \u2014 v11+ required for Zod v4 compatibility" },
|
|
3867
|
+
{ description: "Check zod-validation-error version \u2014 v5.0.0+ required" },
|
|
3868
|
+
{ description: "Run existing test suite to establish baseline", command: "npm test" }
|
|
3869
|
+
],
|
|
3870
|
+
postSteps: [
|
|
3871
|
+
{
|
|
3872
|
+
description: "Update tRPC to v11 if not already",
|
|
3873
|
+
command: "npm install @trpc/server@latest @trpc/client@latest"
|
|
3874
|
+
},
|
|
3875
|
+
{
|
|
3876
|
+
description: "Update zod-validation-error if used",
|
|
3877
|
+
command: "npm install zod-validation-error@^5.0.0"
|
|
3878
|
+
},
|
|
3879
|
+
{ description: "Review TODO(schemashift) comments for manual fixes" },
|
|
3880
|
+
{ description: "Run tests to verify tRPC router behavior", command: "npm test" }
|
|
3881
|
+
],
|
|
3882
|
+
packageChanges: [
|
|
3883
|
+
{ action: "upgrade", package: "zod", version: "^3.25.0" },
|
|
3884
|
+
{ action: "upgrade", package: "@trpc/server", version: "^11.0.0" }
|
|
3885
|
+
],
|
|
3886
|
+
recommendedFlags: ["--compat-check", "--scaffold-tests", "--verbose"],
|
|
3887
|
+
estimatedEffort: "high"
|
|
3888
|
+
},
|
|
3889
|
+
{
|
|
3890
|
+
name: "express-joi-to-zod",
|
|
3891
|
+
description: "Migrate Express.js API validators from Joi to Zod",
|
|
3892
|
+
category: "library-switch",
|
|
3893
|
+
migrationSteps: [{ from: "joi", to: "zod", description: "Convert Joi schemas to Zod schemas" }],
|
|
3894
|
+
preChecks: [
|
|
3895
|
+
{ description: "Identify middleware using Joi validation" },
|
|
3896
|
+
{ description: "Check for Joi.extend() custom validators that need manual migration" }
|
|
3897
|
+
],
|
|
3898
|
+
postSteps: [
|
|
3899
|
+
{ description: "Update Express middleware to use Zod schemas" },
|
|
3900
|
+
{ description: "Replace celebrate/express-validation with custom Zod middleware" },
|
|
3901
|
+
{ description: "Run API integration tests", command: "npm test" },
|
|
3902
|
+
{ description: "Remove Joi dependency", command: "npm uninstall joi" }
|
|
3903
|
+
],
|
|
3904
|
+
packageChanges: [
|
|
3905
|
+
{ action: "install", package: "zod", version: "^3.24.0" },
|
|
3906
|
+
{ action: "remove", package: "celebrate" }
|
|
3907
|
+
],
|
|
3908
|
+
recommendedFlags: ["--cross-file", "--verbose"],
|
|
3909
|
+
estimatedEffort: "moderate"
|
|
3910
|
+
},
|
|
3911
|
+
{
|
|
3912
|
+
name: "nextjs-form-migration",
|
|
3913
|
+
description: "Migrate Next.js form validation from Yup/Formik to Zod/React Hook Form",
|
|
3914
|
+
category: "form-migration",
|
|
3915
|
+
migrationSteps: [{ from: "yup", to: "zod", description: "Convert Yup schemas to Zod schemas" }],
|
|
3916
|
+
preChecks: [
|
|
3917
|
+
{ description: "Identify all Formik form components" },
|
|
3918
|
+
{ description: "Check for server-side validation using Yup" },
|
|
3919
|
+
{ description: "Run existing tests to establish baseline", command: "npm test" }
|
|
3920
|
+
],
|
|
3921
|
+
postSteps: [
|
|
3922
|
+
{ description: "Replace Formik with React Hook Form + zodResolver" },
|
|
3923
|
+
{ description: "Update server actions to use Zod for validation" },
|
|
3924
|
+
{
|
|
3925
|
+
description: "Install next-safe-action if using server actions",
|
|
3926
|
+
command: "npm install next-safe-action"
|
|
3927
|
+
},
|
|
3928
|
+
{ description: "Run full test suite", command: "npm test" }
|
|
3929
|
+
],
|
|
3930
|
+
packageChanges: [
|
|
3931
|
+
{ action: "install", package: "zod", version: "^3.24.0" },
|
|
3932
|
+
{ action: "install", package: "react-hook-form", version: "^7.0.0" },
|
|
3933
|
+
{ action: "install", package: "@hookform/resolvers", version: "latest" }
|
|
3934
|
+
],
|
|
3935
|
+
recommendedFlags: ["--cross-file", "--scaffold-tests"],
|
|
3936
|
+
estimatedEffort: "high"
|
|
3937
|
+
},
|
|
3938
|
+
{
|
|
3939
|
+
name: "monorepo-staged-migration",
|
|
3940
|
+
description: "Phased monorepo migration with incremental tracking",
|
|
3941
|
+
category: "monorepo",
|
|
3942
|
+
migrationSteps: [
|
|
3943
|
+
{ from: "yup", to: "zod", description: "Convert shared packages first, then applications" }
|
|
3944
|
+
],
|
|
3945
|
+
preChecks: [
|
|
3946
|
+
{ description: "Analyze monorepo workspace structure" },
|
|
3947
|
+
{ description: "Identify shared schema packages used by multiple apps" },
|
|
3948
|
+
{ description: "Ensure all packages build successfully", command: "npm run build" }
|
|
3949
|
+
],
|
|
3950
|
+
postSteps: [
|
|
3951
|
+
{ description: "Run incremental migration starting with leaf packages" },
|
|
3952
|
+
{ description: "Build all packages after each batch", command: "npm run build" },
|
|
3953
|
+
{ description: "Run full test suite", command: "npm test" },
|
|
3954
|
+
{ description: "Review cross-package type compatibility" }
|
|
3955
|
+
],
|
|
3956
|
+
packageChanges: [],
|
|
3957
|
+
recommendedFlags: ["--cross-file", "--incremental", "--compat-check", "--audit"],
|
|
3958
|
+
estimatedEffort: "high"
|
|
3959
|
+
}
|
|
3960
|
+
];
|
|
3961
|
+
function getMigrationTemplate(name) {
|
|
3962
|
+
return BUILT_IN_TEMPLATES.find((t) => t.name === name);
|
|
3963
|
+
}
|
|
3964
|
+
function getMigrationTemplateNames() {
|
|
3965
|
+
return BUILT_IN_TEMPLATES.map((t) => t.name);
|
|
3966
|
+
}
|
|
3967
|
+
function getMigrationTemplatesByCategory(category) {
|
|
3968
|
+
return BUILT_IN_TEMPLATES.filter((t) => t.category === category);
|
|
3969
|
+
}
|
|
3970
|
+
function getAllMigrationTemplates() {
|
|
3971
|
+
return [...BUILT_IN_TEMPLATES];
|
|
3972
|
+
}
|
|
3973
|
+
function validateMigrationTemplate(template) {
|
|
3974
|
+
const errors = [];
|
|
3975
|
+
if (!template.name || template.name.trim().length === 0) {
|
|
3976
|
+
errors.push("Template name is required");
|
|
3977
|
+
}
|
|
3978
|
+
if (!template.description || template.description.trim().length === 0) {
|
|
3979
|
+
errors.push("Template description is required");
|
|
3980
|
+
}
|
|
3981
|
+
if (!template.migrationSteps || template.migrationSteps.length === 0) {
|
|
3982
|
+
errors.push("At least one migration step is required");
|
|
3983
|
+
}
|
|
3984
|
+
for (const step of template.migrationSteps ?? []) {
|
|
3985
|
+
if (!step.from || !step.to) {
|
|
3986
|
+
errors.push(`Migration step must have from and to: ${JSON.stringify(step)}`);
|
|
3987
|
+
}
|
|
3988
|
+
}
|
|
3989
|
+
return { valid: errors.length === 0, errors };
|
|
3990
|
+
}
|
|
3991
|
+
|
|
3992
|
+
// src/notifications.ts
|
|
3993
|
+
async function computeSignature(payload, secret) {
|
|
3994
|
+
const { createHmac } = await import("crypto");
|
|
3995
|
+
return createHmac("sha256", secret).update(payload).digest("hex");
|
|
3996
|
+
}
|
|
3997
|
+
var WebhookNotifier = class {
|
|
3998
|
+
webhooks;
|
|
3999
|
+
constructor(webhooks) {
|
|
4000
|
+
this.webhooks = webhooks;
|
|
4001
|
+
}
|
|
4002
|
+
/**
|
|
4003
|
+
* Create a migration event with current timestamp.
|
|
4004
|
+
*/
|
|
4005
|
+
createEvent(type, details, project) {
|
|
4006
|
+
return {
|
|
4007
|
+
type,
|
|
4008
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
4009
|
+
project,
|
|
4010
|
+
details
|
|
4011
|
+
};
|
|
4012
|
+
}
|
|
4013
|
+
/**
|
|
4014
|
+
* Send an event to all matching webhooks.
|
|
4015
|
+
*/
|
|
4016
|
+
async send(event) {
|
|
4017
|
+
const results = [];
|
|
4018
|
+
for (const webhook of this.webhooks) {
|
|
4019
|
+
if (webhook.events && !webhook.events.includes(event.type)) {
|
|
4020
|
+
continue;
|
|
4021
|
+
}
|
|
4022
|
+
const result = await this.sendToWebhook(webhook, event);
|
|
4023
|
+
results.push(result);
|
|
4024
|
+
}
|
|
4025
|
+
return results;
|
|
4026
|
+
}
|
|
4027
|
+
/**
|
|
4028
|
+
* Format event as Slack Block Kit message.
|
|
4029
|
+
*/
|
|
4030
|
+
formatSlackPayload(event) {
|
|
4031
|
+
const emoji = this.getEventEmoji(event.type);
|
|
4032
|
+
const title = this.getEventTitle(event.type);
|
|
4033
|
+
const details = event.details;
|
|
4034
|
+
const blocks = [
|
|
4035
|
+
{
|
|
4036
|
+
type: "header",
|
|
4037
|
+
text: { type: "plain_text", text: `${emoji} ${title}`, emoji: true }
|
|
4038
|
+
},
|
|
4039
|
+
{
|
|
4040
|
+
type: "section",
|
|
4041
|
+
fields: Object.entries(details).map(([key, value]) => ({
|
|
4042
|
+
type: "mrkdwn",
|
|
4043
|
+
text: `*${key}:* ${String(value)}`
|
|
4044
|
+
}))
|
|
4045
|
+
},
|
|
4046
|
+
{
|
|
4047
|
+
type: "context",
|
|
4048
|
+
elements: [
|
|
4049
|
+
{
|
|
4050
|
+
type: "mrkdwn",
|
|
4051
|
+
text: `SchemaShift | ${event.timestamp}${event.project ? ` | ${event.project}` : ""}`
|
|
4052
|
+
}
|
|
4053
|
+
]
|
|
4054
|
+
}
|
|
4055
|
+
];
|
|
4056
|
+
return { blocks };
|
|
4057
|
+
}
|
|
4058
|
+
/**
|
|
4059
|
+
* Format event as Microsoft Teams Adaptive Card.
|
|
4060
|
+
*/
|
|
4061
|
+
formatTeamsPayload(event) {
|
|
4062
|
+
const title = this.getEventTitle(event.type);
|
|
4063
|
+
const details = event.details;
|
|
4064
|
+
const facts = Object.entries(details).map(([key, value]) => ({
|
|
4065
|
+
title: key,
|
|
4066
|
+
value: String(value)
|
|
4067
|
+
}));
|
|
4068
|
+
return {
|
|
4069
|
+
type: "message",
|
|
4070
|
+
attachments: [
|
|
4071
|
+
{
|
|
4072
|
+
contentType: "application/vnd.microsoft.card.adaptive",
|
|
4073
|
+
content: {
|
|
4074
|
+
$schema: "http://adaptivecards.io/schemas/adaptive-card.json",
|
|
4075
|
+
type: "AdaptiveCard",
|
|
4076
|
+
version: "1.4",
|
|
4077
|
+
body: [
|
|
4078
|
+
{
|
|
4079
|
+
type: "TextBlock",
|
|
4080
|
+
text: title,
|
|
4081
|
+
weight: "Bolder",
|
|
4082
|
+
size: "Medium"
|
|
4083
|
+
},
|
|
4084
|
+
{
|
|
4085
|
+
type: "FactSet",
|
|
4086
|
+
facts
|
|
4087
|
+
},
|
|
4088
|
+
{
|
|
4089
|
+
type: "TextBlock",
|
|
4090
|
+
text: `SchemaShift | ${event.timestamp}`,
|
|
4091
|
+
isSubtle: true,
|
|
4092
|
+
size: "Small"
|
|
4093
|
+
}
|
|
4094
|
+
]
|
|
4095
|
+
}
|
|
4096
|
+
}
|
|
4097
|
+
]
|
|
4098
|
+
};
|
|
4099
|
+
}
|
|
4100
|
+
getEventEmoji(type) {
|
|
4101
|
+
const emojis = {
|
|
4102
|
+
migration_started: "\u{1F504}",
|
|
4103
|
+
migration_completed: "\u2705",
|
|
4104
|
+
migration_failed: "\u274C",
|
|
4105
|
+
governance_violation: "\u26A0\uFE0F",
|
|
4106
|
+
drift_detected: "\u{1F50D}"
|
|
4107
|
+
};
|
|
4108
|
+
return emojis[type];
|
|
4109
|
+
}
|
|
4110
|
+
getEventTitle(type) {
|
|
4111
|
+
const titles = {
|
|
4112
|
+
migration_started: "Migration Started",
|
|
4113
|
+
migration_completed: "Migration Completed",
|
|
4114
|
+
migration_failed: "Migration Failed",
|
|
4115
|
+
governance_violation: "Governance Violation",
|
|
4116
|
+
drift_detected: "Schema Drift Detected"
|
|
4117
|
+
};
|
|
4118
|
+
return titles[type];
|
|
4119
|
+
}
|
|
4120
|
+
/**
|
|
4121
|
+
* Send event to a single webhook endpoint.
|
|
4122
|
+
*/
|
|
4123
|
+
async sendToWebhook(webhook, event) {
|
|
4124
|
+
let payload;
|
|
4125
|
+
if (webhook.type === "slack") {
|
|
4126
|
+
payload = JSON.stringify(this.formatSlackPayload(event));
|
|
4127
|
+
} else if (webhook.type === "teams") {
|
|
4128
|
+
payload = JSON.stringify(this.formatTeamsPayload(event));
|
|
4129
|
+
} else {
|
|
4130
|
+
payload = JSON.stringify(event);
|
|
4131
|
+
}
|
|
4132
|
+
const headers = {
|
|
4133
|
+
"Content-Type": "application/json",
|
|
4134
|
+
"User-Agent": "SchemaShift-Webhook/1.0",
|
|
4135
|
+
...webhook.headers
|
|
4136
|
+
};
|
|
4137
|
+
if (webhook.secret) {
|
|
4138
|
+
const signature = await computeSignature(payload, webhook.secret);
|
|
4139
|
+
headers["X-SchemaShift-Signature"] = `sha256=${signature}`;
|
|
4140
|
+
}
|
|
4141
|
+
try {
|
|
4142
|
+
const response = await fetch(webhook.url, {
|
|
4143
|
+
method: "POST",
|
|
4144
|
+
headers,
|
|
4145
|
+
body: payload
|
|
4146
|
+
});
|
|
4147
|
+
return {
|
|
4148
|
+
success: response.ok,
|
|
4149
|
+
statusCode: response.status,
|
|
4150
|
+
error: response.ok ? void 0 : `HTTP ${response.status}: ${response.statusText}`
|
|
4151
|
+
};
|
|
4152
|
+
} catch (err) {
|
|
4153
|
+
return {
|
|
4154
|
+
success: false,
|
|
4155
|
+
error: err instanceof Error ? err.message : String(err)
|
|
4156
|
+
};
|
|
2987
4157
|
}
|
|
2988
|
-
|
|
4158
|
+
}
|
|
4159
|
+
/**
|
|
4160
|
+
* Convenience: send a migration_started event.
|
|
4161
|
+
*/
|
|
4162
|
+
async notifyMigrationStarted(from, to, fileCount, project) {
|
|
4163
|
+
const event = this.createEvent("migration_started", { from, to, fileCount }, project);
|
|
4164
|
+
return this.send(event);
|
|
4165
|
+
}
|
|
4166
|
+
/**
|
|
4167
|
+
* Convenience: send a migration_completed event.
|
|
4168
|
+
*/
|
|
4169
|
+
async notifyMigrationCompleted(from, to, fileCount, warningCount, project) {
|
|
4170
|
+
const event = this.createEvent(
|
|
4171
|
+
"migration_completed",
|
|
4172
|
+
{ from, to, fileCount, warningCount },
|
|
4173
|
+
project
|
|
4174
|
+
);
|
|
4175
|
+
return this.send(event);
|
|
4176
|
+
}
|
|
4177
|
+
/**
|
|
4178
|
+
* Convenience: send a migration_failed event.
|
|
4179
|
+
*/
|
|
4180
|
+
async notifyMigrationFailed(from, to, error, project) {
|
|
4181
|
+
const event = this.createEvent("migration_failed", { from, to, error }, project);
|
|
4182
|
+
return this.send(event);
|
|
4183
|
+
}
|
|
4184
|
+
/**
|
|
4185
|
+
* Convenience: send a governance_violation event.
|
|
4186
|
+
*/
|
|
4187
|
+
async notifyGovernanceViolation(violationCount, rules, project) {
|
|
4188
|
+
const event = this.createEvent("governance_violation", { violationCount, rules }, project);
|
|
4189
|
+
return this.send(event);
|
|
4190
|
+
}
|
|
4191
|
+
/**
|
|
4192
|
+
* Convenience: send a drift_detected event.
|
|
4193
|
+
*/
|
|
4194
|
+
async notifyDriftDetected(modifiedFiles, addedFiles, removedFiles, project) {
|
|
4195
|
+
const event = this.createEvent(
|
|
4196
|
+
"drift_detected",
|
|
4197
|
+
{ modifiedFiles, addedFiles, removedFiles },
|
|
4198
|
+
project
|
|
4199
|
+
);
|
|
4200
|
+
return this.send(event);
|
|
2989
4201
|
}
|
|
2990
4202
|
};
|
|
2991
4203
|
|
|
2992
4204
|
// src/package-updater.ts
|
|
2993
|
-
var
|
|
2994
|
-
var
|
|
4205
|
+
var import_node_fs9 = require("fs");
|
|
4206
|
+
var import_node_path9 = require("path");
|
|
2995
4207
|
var TARGET_VERSIONS = {
|
|
2996
4208
|
"yup->zod": { zod: "^3.24.0" },
|
|
2997
4209
|
"joi->zod": { zod: "^3.24.0" },
|
|
@@ -3012,14 +4224,14 @@ var PackageUpdater = class {
|
|
|
3012
4224
|
const add = {};
|
|
3013
4225
|
const remove = [];
|
|
3014
4226
|
const warnings = [];
|
|
3015
|
-
const pkgPath = (0,
|
|
3016
|
-
if (!(0,
|
|
4227
|
+
const pkgPath = (0, import_node_path9.join)(projectPath, "package.json");
|
|
4228
|
+
if (!(0, import_node_fs9.existsSync)(pkgPath)) {
|
|
3017
4229
|
warnings.push("No package.json found. Cannot plan dependency updates.");
|
|
3018
4230
|
return { add, remove, warnings };
|
|
3019
4231
|
}
|
|
3020
4232
|
let pkg;
|
|
3021
4233
|
try {
|
|
3022
|
-
pkg = JSON.parse((0,
|
|
4234
|
+
pkg = JSON.parse((0, import_node_fs9.readFileSync)(pkgPath, "utf-8"));
|
|
3023
4235
|
} catch {
|
|
3024
4236
|
warnings.push("Could not parse package.json.");
|
|
3025
4237
|
return { add, remove, warnings };
|
|
@@ -3049,9 +4261,9 @@ var PackageUpdater = class {
|
|
|
3049
4261
|
return { add, remove, warnings };
|
|
3050
4262
|
}
|
|
3051
4263
|
apply(projectPath, plan) {
|
|
3052
|
-
const pkgPath = (0,
|
|
3053
|
-
if (!(0,
|
|
3054
|
-
const pkgText = (0,
|
|
4264
|
+
const pkgPath = (0, import_node_path9.join)(projectPath, "package.json");
|
|
4265
|
+
if (!(0, import_node_fs9.existsSync)(pkgPath)) return;
|
|
4266
|
+
const pkgText = (0, import_node_fs9.readFileSync)(pkgPath, "utf-8");
|
|
3055
4267
|
const pkg = JSON.parse(pkgText);
|
|
3056
4268
|
if (!pkg.dependencies) pkg.dependencies = {};
|
|
3057
4269
|
for (const [name, version] of Object.entries(plan.add)) {
|
|
@@ -3061,7 +4273,7 @@ var PackageUpdater = class {
|
|
|
3061
4273
|
pkg.dependencies[name] = version;
|
|
3062
4274
|
}
|
|
3063
4275
|
}
|
|
3064
|
-
(0,
|
|
4276
|
+
(0, import_node_fs9.writeFileSync)(pkgPath, `${JSON.stringify(pkg, null, 2)}
|
|
3065
4277
|
`);
|
|
3066
4278
|
}
|
|
3067
4279
|
};
|
|
@@ -3232,9 +4444,164 @@ var PluginLoader = class {
|
|
|
3232
4444
|
}
|
|
3233
4445
|
};
|
|
3234
4446
|
|
|
4447
|
+
// src/schema-verifier.ts
|
|
4448
|
+
var PRIMITIVE_SAMPLES = {
|
|
4449
|
+
string: [
|
|
4450
|
+
{ name: "empty string", input: "", expectedValid: true },
|
|
4451
|
+
{ name: "normal string", input: "hello world", expectedValid: true },
|
|
4452
|
+
{ name: "number as string", input: "12345", expectedValid: true },
|
|
4453
|
+
{ name: "null input", input: null, expectedValid: false },
|
|
4454
|
+
{ name: "number input", input: 42, expectedValid: false },
|
|
4455
|
+
{ name: "boolean input", input: true, expectedValid: false },
|
|
4456
|
+
{ name: "undefined input", input: void 0, expectedValid: false }
|
|
4457
|
+
],
|
|
4458
|
+
number: [
|
|
4459
|
+
{ name: "zero", input: 0, expectedValid: true },
|
|
4460
|
+
{ name: "positive int", input: 42, expectedValid: true },
|
|
4461
|
+
{ name: "negative int", input: -1, expectedValid: true },
|
|
4462
|
+
{ name: "float", input: 3.14, expectedValid: true },
|
|
4463
|
+
{ name: "string input", input: "hello", expectedValid: false },
|
|
4464
|
+
{ name: "null input", input: null, expectedValid: false },
|
|
4465
|
+
{ name: "NaN input", input: Number.NaN, expectedValid: false }
|
|
4466
|
+
],
|
|
4467
|
+
boolean: [
|
|
4468
|
+
{ name: "true", input: true, expectedValid: true },
|
|
4469
|
+
{ name: "false", input: false, expectedValid: true },
|
|
4470
|
+
{ name: "string input", input: "true", expectedValid: false },
|
|
4471
|
+
{ name: "number input", input: 1, expectedValid: false },
|
|
4472
|
+
{ name: "null input", input: null, expectedValid: false }
|
|
4473
|
+
],
|
|
4474
|
+
date: [
|
|
4475
|
+
{ name: "valid date", input: /* @__PURE__ */ new Date("2024-01-01"), expectedValid: true },
|
|
4476
|
+
{ name: "string input", input: "2024-01-01", expectedValid: false },
|
|
4477
|
+
{ name: "null input", input: null, expectedValid: false }
|
|
4478
|
+
]
|
|
4479
|
+
};
|
|
4480
|
+
var EMAIL_SAMPLES = [
|
|
4481
|
+
{ name: "valid email", input: "test@example.com", expectedValid: true },
|
|
4482
|
+
{ name: "invalid email", input: "not-an-email", expectedValid: false },
|
|
4483
|
+
{ name: "empty string", input: "", expectedValid: false }
|
|
4484
|
+
];
|
|
4485
|
+
var URL_SAMPLES = [
|
|
4486
|
+
{ name: "valid url", input: "https://example.com", expectedValid: true },
|
|
4487
|
+
{ name: "invalid url", input: "not a url", expectedValid: false }
|
|
4488
|
+
];
|
|
4489
|
+
var UUID_SAMPLES = [
|
|
4490
|
+
{ name: "valid uuid", input: "550e8400-e29b-41d4-a716-446655440000", expectedValid: true },
|
|
4491
|
+
{ name: "invalid uuid", input: "not-a-uuid", expectedValid: false }
|
|
4492
|
+
];
|
|
4493
|
+
function extractSchemaNames(sourceText) {
|
|
4494
|
+
const schemas = [];
|
|
4495
|
+
const patterns = [
|
|
4496
|
+
/(?:const|let|var)\s+(\w+)\s*=\s*(?:z\.|yup\.|Joi\.|v\.|t\.|S\.|type\(|object\(|string\()/g,
|
|
4497
|
+
/export\s+(?:const|let|var)\s+(\w+)\s*=\s*(?:z\.|yup\.|Joi\.|v\.|t\.|S\.|type\(|object\(|string\()/g
|
|
4498
|
+
];
|
|
4499
|
+
for (const pattern of patterns) {
|
|
4500
|
+
for (const match of sourceText.matchAll(pattern)) {
|
|
4501
|
+
const name = match[1];
|
|
4502
|
+
if (name && !schemas.includes(name)) {
|
|
4503
|
+
schemas.push(name);
|
|
4504
|
+
}
|
|
4505
|
+
}
|
|
4506
|
+
}
|
|
4507
|
+
return schemas;
|
|
4508
|
+
}
|
|
4509
|
+
function generateSamples(sourceText, schemaName, maxSamples) {
|
|
4510
|
+
const samples = [];
|
|
4511
|
+
const schemaBlock = extractSchemaBlock(sourceText, schemaName);
|
|
4512
|
+
if (!schemaBlock) return PRIMITIVE_SAMPLES.string?.slice(0, maxSamples) ?? [];
|
|
4513
|
+
if (/\.email\s*\(/.test(schemaBlock)) {
|
|
4514
|
+
samples.push(...EMAIL_SAMPLES);
|
|
4515
|
+
}
|
|
4516
|
+
if (/\.url\s*\(/.test(schemaBlock)) {
|
|
4517
|
+
samples.push(...URL_SAMPLES);
|
|
4518
|
+
}
|
|
4519
|
+
if (/\.uuid\s*\(/.test(schemaBlock)) {
|
|
4520
|
+
samples.push(...UUID_SAMPLES);
|
|
4521
|
+
}
|
|
4522
|
+
if (/string\s*\(/.test(schemaBlock)) {
|
|
4523
|
+
samples.push(...PRIMITIVE_SAMPLES.string ?? []);
|
|
4524
|
+
}
|
|
4525
|
+
if (/number\s*\(/.test(schemaBlock) || /\.int\s*\(/.test(schemaBlock)) {
|
|
4526
|
+
samples.push(...PRIMITIVE_SAMPLES.number ?? []);
|
|
4527
|
+
}
|
|
4528
|
+
if (/boolean\s*\(/.test(schemaBlock)) {
|
|
4529
|
+
samples.push(...PRIMITIVE_SAMPLES.boolean ?? []);
|
|
4530
|
+
}
|
|
4531
|
+
if (/date\s*\(/.test(schemaBlock)) {
|
|
4532
|
+
samples.push(...PRIMITIVE_SAMPLES.date ?? []);
|
|
4533
|
+
}
|
|
4534
|
+
if (/\.optional\s*\(/.test(schemaBlock) || /optional\s*\(/.test(schemaBlock)) {
|
|
4535
|
+
samples.push({ name: "undefined (optional)", input: void 0, expectedValid: true });
|
|
4536
|
+
}
|
|
4537
|
+
if (/\.nullable\s*\(/.test(schemaBlock) || /nullable\s*\(/.test(schemaBlock)) {
|
|
4538
|
+
samples.push({ name: "null (nullable)", input: null, expectedValid: true });
|
|
4539
|
+
}
|
|
4540
|
+
if (/\.min\s*\(\s*(\d+)/.test(schemaBlock)) {
|
|
4541
|
+
const minMatch = schemaBlock.match(/\.min\s*\(\s*(\d+)/);
|
|
4542
|
+
const minVal = minMatch ? Number.parseInt(minMatch[1] ?? "0", 10) : 0;
|
|
4543
|
+
samples.push({
|
|
4544
|
+
name: `below min (${minVal})`,
|
|
4545
|
+
input: minVal > 0 ? "a".repeat(minVal - 1) : "",
|
|
4546
|
+
expectedValid: false
|
|
4547
|
+
});
|
|
4548
|
+
}
|
|
4549
|
+
const seen = /* @__PURE__ */ new Set();
|
|
4550
|
+
const unique = [];
|
|
4551
|
+
for (const s of samples) {
|
|
4552
|
+
if (!seen.has(s.name)) {
|
|
4553
|
+
seen.add(s.name);
|
|
4554
|
+
unique.push(s);
|
|
4555
|
+
}
|
|
4556
|
+
}
|
|
4557
|
+
return unique.slice(0, maxSamples);
|
|
4558
|
+
}
|
|
4559
|
+
function extractSchemaBlock(sourceText, schemaName) {
|
|
4560
|
+
const escapedName = schemaName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
4561
|
+
const pattern = new RegExp(
|
|
4562
|
+
`(?:const|let|var|export\\s+const)\\s+${escapedName}\\s*=\\s*([\\s\\S]*?)(?:;\\s*$|;\\s*(?:const|let|var|export|function|class|type|interface))`,
|
|
4563
|
+
"m"
|
|
4564
|
+
);
|
|
4565
|
+
const match = sourceText.match(pattern);
|
|
4566
|
+
return match?.[1] ?? null;
|
|
4567
|
+
}
|
|
4568
|
+
function createVerificationReport(from, to, results) {
|
|
4569
|
+
const totalSchemas = results.length;
|
|
4570
|
+
const overallParityScore = totalSchemas > 0 ? results.reduce((sum, r) => sum + r.parityScore, 0) / totalSchemas : 100;
|
|
4571
|
+
return {
|
|
4572
|
+
from,
|
|
4573
|
+
to,
|
|
4574
|
+
totalSchemas,
|
|
4575
|
+
results,
|
|
4576
|
+
overallParityScore: Math.round(overallParityScore * 100) / 100,
|
|
4577
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
4578
|
+
};
|
|
4579
|
+
}
|
|
4580
|
+
function formatVerificationReport(report) {
|
|
4581
|
+
const lines = [];
|
|
4582
|
+
lines.push(`
|
|
4583
|
+
Schema Verification Report: ${report.from} \u2192 ${report.to}`);
|
|
4584
|
+
lines.push("\u2500".repeat(50));
|
|
4585
|
+
for (const result of report.results) {
|
|
4586
|
+
const icon = result.parityScore === 100 ? "\u2713" : result.parityScore >= 80 ? "\u26A0" : "\u2717";
|
|
4587
|
+
lines.push(
|
|
4588
|
+
` ${icon} ${result.schemaName} \u2014 ${result.parityScore}% parity (${result.matchingSamples}/${result.totalSamples} samples)`
|
|
4589
|
+
);
|
|
4590
|
+
for (const mismatch of result.mismatches) {
|
|
4591
|
+
lines.push(
|
|
4592
|
+
` \u2514\u2500 ${mismatch.sampleName}: source=${mismatch.sourceResult.valid ? "valid" : "invalid"}, target=${mismatch.targetResult.valid ? "valid" : "invalid"}`
|
|
4593
|
+
);
|
|
4594
|
+
}
|
|
4595
|
+
}
|
|
4596
|
+
lines.push("\u2500".repeat(50));
|
|
4597
|
+
lines.push(`Overall Parity: ${report.overallParityScore}%`);
|
|
4598
|
+
lines.push("");
|
|
4599
|
+
return lines.join("\n");
|
|
4600
|
+
}
|
|
4601
|
+
|
|
3235
4602
|
// src/standard-schema.ts
|
|
3236
|
-
var
|
|
3237
|
-
var
|
|
4603
|
+
var import_node_fs10 = require("fs");
|
|
4604
|
+
var import_node_path10 = require("path");
|
|
3238
4605
|
var STANDARD_SCHEMA_LIBRARIES = {
|
|
3239
4606
|
zod: { minMajor: 3, minMinor: 23 },
|
|
3240
4607
|
// Zod v3.23+ and v4+
|
|
@@ -3263,13 +4630,13 @@ function isVersionCompatible(version, minMajor, minMinor) {
|
|
|
3263
4630
|
return false;
|
|
3264
4631
|
}
|
|
3265
4632
|
function detectStandardSchema(projectPath) {
|
|
3266
|
-
const pkgPath = (0,
|
|
3267
|
-
if (!(0,
|
|
4633
|
+
const pkgPath = (0, import_node_path10.join)(projectPath, "package.json");
|
|
4634
|
+
if (!(0, import_node_fs10.existsSync)(pkgPath)) {
|
|
3268
4635
|
return { detected: false, compatibleLibraries: [], recommendation: "", interopTools: [] };
|
|
3269
4636
|
}
|
|
3270
4637
|
let allDeps = {};
|
|
3271
4638
|
try {
|
|
3272
|
-
const pkg = JSON.parse((0,
|
|
4639
|
+
const pkg = JSON.parse((0, import_node_fs10.readFileSync)(pkgPath, "utf-8"));
|
|
3273
4640
|
allDeps = { ...pkg.dependencies, ...pkg.devDependencies };
|
|
3274
4641
|
} catch {
|
|
3275
4642
|
return { detected: false, compatibleLibraries: [], recommendation: "", interopTools: [] };
|
|
@@ -3308,6 +4675,105 @@ function detectStandardSchema(projectPath) {
|
|
|
3308
4675
|
return { detected, compatibleLibraries, recommendation, adoptionPath, interopTools };
|
|
3309
4676
|
}
|
|
3310
4677
|
|
|
4678
|
+
// src/standard-schema-advisor.ts
|
|
4679
|
+
var STANDARD_SCHEMA_LIBS = /* @__PURE__ */ new Set(["zod", "valibot", "arktype"]);
|
|
4680
|
+
var StandardSchemaAdvisor = class {
|
|
4681
|
+
/**
|
|
4682
|
+
* Check if a schema library supports Standard Schema.
|
|
4683
|
+
*/
|
|
4684
|
+
supportsStandardSchema(library) {
|
|
4685
|
+
return STANDARD_SCHEMA_LIBS.has(library);
|
|
4686
|
+
}
|
|
4687
|
+
/**
|
|
4688
|
+
* Generate advisory for a given migration path.
|
|
4689
|
+
*/
|
|
4690
|
+
advise(from, to) {
|
|
4691
|
+
const fromSupports = this.supportsStandardSchema(from);
|
|
4692
|
+
const toSupports = this.supportsStandardSchema(to);
|
|
4693
|
+
if (!fromSupports && !toSupports) {
|
|
4694
|
+
return {
|
|
4695
|
+
shouldConsiderAdapter: false,
|
|
4696
|
+
reason: `Neither ${from} nor ${to} supports Standard Schema. Full migration is recommended.`,
|
|
4697
|
+
migrationAdvantages: [
|
|
4698
|
+
"Complete type safety with target library",
|
|
4699
|
+
"Access to target library ecosystem",
|
|
4700
|
+
"No runtime adapter overhead"
|
|
4701
|
+
],
|
|
4702
|
+
adapterAdvantages: [],
|
|
4703
|
+
recommendation: "migrate"
|
|
4704
|
+
};
|
|
4705
|
+
}
|
|
4706
|
+
if (fromSupports && toSupports) {
|
|
4707
|
+
return {
|
|
4708
|
+
shouldConsiderAdapter: true,
|
|
4709
|
+
reason: `Both ${from} and ${to} support Standard Schema 1.0. You may be able to use adapters for ecosystem tools (tRPC, TanStack Form, etc.) instead of migrating all schemas.`,
|
|
4710
|
+
adapterExample: this.generateAdapterExample(from, to),
|
|
4711
|
+
migrationAdvantages: [
|
|
4712
|
+
"Full target library API and ergonomics",
|
|
4713
|
+
"Consistent codebase (single library)",
|
|
4714
|
+
"Better IDE support for one library",
|
|
4715
|
+
"Smaller bundle (avoid loading two libraries)"
|
|
4716
|
+
],
|
|
4717
|
+
adapterAdvantages: [
|
|
4718
|
+
"No code changes needed for existing schemas",
|
|
4719
|
+
"Gradual migration possible",
|
|
4720
|
+
"Ecosystem tools work with both libraries via Standard Schema",
|
|
4721
|
+
"Lower risk \u2014 existing validation behavior preserved"
|
|
4722
|
+
],
|
|
4723
|
+
recommendation: "either"
|
|
4724
|
+
};
|
|
4725
|
+
}
|
|
4726
|
+
if (toSupports && !fromSupports) {
|
|
4727
|
+
return {
|
|
4728
|
+
shouldConsiderAdapter: false,
|
|
4729
|
+
reason: `${from} does not support Standard Schema, but ${to} does. Migrating to ${to} gives you Standard Schema interoperability.`,
|
|
4730
|
+
migrationAdvantages: [
|
|
4731
|
+
"Standard Schema interoperability with ecosystem tools",
|
|
4732
|
+
"Future-proof validation layer",
|
|
4733
|
+
`Access to ${to} API and type inference`
|
|
4734
|
+
],
|
|
4735
|
+
adapterAdvantages: [],
|
|
4736
|
+
recommendation: "migrate"
|
|
4737
|
+
};
|
|
4738
|
+
}
|
|
4739
|
+
return {
|
|
4740
|
+
shouldConsiderAdapter: false,
|
|
4741
|
+
reason: `${from} supports Standard Schema but ${to} does not. Consider if you need the specific features of ${to} that justify losing Standard Schema interoperability.`,
|
|
4742
|
+
migrationAdvantages: [`Access to ${to}-specific features`],
|
|
4743
|
+
adapterAdvantages: [`Keeping ${from} preserves Standard Schema interoperability`],
|
|
4744
|
+
recommendation: "migrate"
|
|
4745
|
+
};
|
|
4746
|
+
}
|
|
4747
|
+
/**
|
|
4748
|
+
* Analyze a project and provide advisory based on detected libraries.
|
|
4749
|
+
*/
|
|
4750
|
+
adviseFromProject(projectPath, from, to) {
|
|
4751
|
+
const projectInfo = detectStandardSchema(projectPath);
|
|
4752
|
+
const advisory = this.advise(from, to);
|
|
4753
|
+
return { ...advisory, projectInfo };
|
|
4754
|
+
}
|
|
4755
|
+
generateAdapterExample(from, to) {
|
|
4756
|
+
return [
|
|
4757
|
+
`// Instead of migrating all ${from} schemas to ${to},`,
|
|
4758
|
+
`// you can use Standard Schema adapters for ecosystem tools:`,
|
|
4759
|
+
`//`,
|
|
4760
|
+
`// Example with tRPC (v11+):`,
|
|
4761
|
+
`// tRPC accepts any Standard Schema-compatible schema.`,
|
|
4762
|
+
`// Both ${from} and ${to} schemas work without conversion:`,
|
|
4763
|
+
`//`,
|
|
4764
|
+
`// import { ${from}Schema } from './existing-${from}-schemas';`,
|
|
4765
|
+
`// import { ${to}Schema } from './new-${to}-schemas';`,
|
|
4766
|
+
`//`,
|
|
4767
|
+
`// const router = t.router({`,
|
|
4768
|
+
`// // Works with ${from} schema (Standard Schema compatible)`,
|
|
4769
|
+
`// getUser: t.procedure.input(${from}Schema).query(...)`,
|
|
4770
|
+
`// // Also works with ${to} schema`,
|
|
4771
|
+
`// createUser: t.procedure.input(${to}Schema).mutation(...)`,
|
|
4772
|
+
`// });`
|
|
4773
|
+
].join("\n");
|
|
4774
|
+
}
|
|
4775
|
+
};
|
|
4776
|
+
|
|
3311
4777
|
// src/test-scaffolder.ts
|
|
3312
4778
|
var TestScaffolder = class {
|
|
3313
4779
|
scaffold(sourceFiles, from, to) {
|
|
@@ -3602,6 +5068,7 @@ var TypeDedupDetector = class {
|
|
|
3602
5068
|
};
|
|
3603
5069
|
// Annotate the CommonJS export names for ESM import in node:
|
|
3604
5070
|
0 && (module.exports = {
|
|
5071
|
+
ApprovalManager,
|
|
3605
5072
|
BehavioralWarningAnalyzer,
|
|
3606
5073
|
BundleEstimator,
|
|
3607
5074
|
CompatibilityAnalyzer,
|
|
@@ -3612,6 +5079,8 @@ var TypeDedupDetector = class {
|
|
|
3612
5079
|
FormResolverMigrator,
|
|
3613
5080
|
GOVERNANCE_TEMPLATES,
|
|
3614
5081
|
GovernanceEngine,
|
|
5082
|
+
GovernanceFixer,
|
|
5083
|
+
GraphExporter,
|
|
3615
5084
|
IncrementalTracker,
|
|
3616
5085
|
MigrationAuditLog,
|
|
3617
5086
|
MigrationChain,
|
|
@@ -3621,24 +5090,41 @@ var TypeDedupDetector = class {
|
|
|
3621
5090
|
PluginLoader,
|
|
3622
5091
|
SchemaAnalyzer,
|
|
3623
5092
|
SchemaDependencyResolver,
|
|
5093
|
+
StandardSchemaAdvisor,
|
|
3624
5094
|
TestScaffolder,
|
|
3625
5095
|
TransformEngine,
|
|
3626
5096
|
TypeDedupDetector,
|
|
5097
|
+
WebhookNotifier,
|
|
3627
5098
|
buildCallChain,
|
|
3628
5099
|
computeParallelBatches,
|
|
5100
|
+
conditionalValidation,
|
|
5101
|
+
createVerificationReport,
|
|
5102
|
+
dependentFields,
|
|
3629
5103
|
detectFormLibraries,
|
|
3630
5104
|
detectSchemaLibrary,
|
|
3631
5105
|
detectStandardSchema,
|
|
5106
|
+
extractSchemaNames,
|
|
5107
|
+
formatVerificationReport,
|
|
5108
|
+
generateSamples,
|
|
5109
|
+
getAllMigrationTemplates,
|
|
3632
5110
|
getGovernanceTemplate,
|
|
3633
5111
|
getGovernanceTemplateNames,
|
|
3634
5112
|
getGovernanceTemplatesByCategory,
|
|
5113
|
+
getMigrationTemplate,
|
|
5114
|
+
getMigrationTemplateNames,
|
|
5115
|
+
getMigrationTemplatesByCategory,
|
|
3635
5116
|
isInsideComment,
|
|
3636
5117
|
isInsideStringLiteral,
|
|
3637
5118
|
loadConfig,
|
|
5119
|
+
mutuallyExclusive,
|
|
3638
5120
|
parseCallChain,
|
|
5121
|
+
requireIf,
|
|
5122
|
+
requireOneOf,
|
|
3639
5123
|
shouldSuppressWarning,
|
|
3640
5124
|
startsWithBase,
|
|
5125
|
+
suggestCrossFieldPattern,
|
|
3641
5126
|
transformMethodChain,
|
|
3642
|
-
validateConfig
|
|
5127
|
+
validateConfig,
|
|
5128
|
+
validateMigrationTemplate
|
|
3643
5129
|
});
|
|
3644
5130
|
//# sourceMappingURL=index.cjs.map
|