@schemashift/core 0.11.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 +381 -1
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +79 -1
- package/dist/index.d.ts +79 -1
- package/dist/index.js +377 -1
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -60,10 +60,14 @@ __export(index_exports, {
|
|
|
60
60
|
buildCallChain: () => buildCallChain,
|
|
61
61
|
computeParallelBatches: () => computeParallelBatches,
|
|
62
62
|
conditionalValidation: () => conditionalValidation,
|
|
63
|
+
createVerificationReport: () => createVerificationReport,
|
|
63
64
|
dependentFields: () => dependentFields,
|
|
64
65
|
detectFormLibraries: () => detectFormLibraries,
|
|
65
66
|
detectSchemaLibrary: () => detectSchemaLibrary,
|
|
66
67
|
detectStandardSchema: () => detectStandardSchema,
|
|
68
|
+
extractSchemaNames: () => extractSchemaNames,
|
|
69
|
+
formatVerificationReport: () => formatVerificationReport,
|
|
70
|
+
generateSamples: () => generateSamples,
|
|
67
71
|
getAllMigrationTemplates: () => getAllMigrationTemplates,
|
|
68
72
|
getGovernanceTemplate: () => getGovernanceTemplate,
|
|
69
73
|
getGovernanceTemplateNames: () => getGovernanceTemplateNames,
|
|
@@ -594,6 +598,88 @@ var MigrationAuditLog = class {
|
|
|
594
598
|
clear() {
|
|
595
599
|
this.write({ version: AUDIT_VERSION, entries: [] });
|
|
596
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
|
+
}
|
|
597
683
|
collectMetadata() {
|
|
598
684
|
return {
|
|
599
685
|
hostname: process.env.HOSTNAME || void 0,
|
|
@@ -1726,6 +1812,25 @@ var ComplexityEstimator = class {
|
|
|
1726
1812
|
riskAreas
|
|
1727
1813
|
};
|
|
1728
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
|
+
}
|
|
1729
1834
|
calculateEffort(totalSchemas, advancedCount, hasDeepDU) {
|
|
1730
1835
|
if (totalSchemas >= 500 && hasDeepDU) return "extreme";
|
|
1731
1836
|
if (totalSchemas >= 200 || advancedCount >= 20) return "high";
|
|
@@ -3689,6 +3794,22 @@ var IncrementalTracker = class {
|
|
|
3689
3794
|
percent
|
|
3690
3795
|
};
|
|
3691
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
|
+
}
|
|
3692
3813
|
clear() {
|
|
3693
3814
|
if ((0, import_node_fs8.existsSync)(this.statePath)) {
|
|
3694
3815
|
(0, import_node_fs8.unlinkSync)(this.statePath);
|
|
@@ -3903,11 +4024,111 @@ var WebhookNotifier = class {
|
|
|
3903
4024
|
}
|
|
3904
4025
|
return results;
|
|
3905
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
|
+
}
|
|
3906
4120
|
/**
|
|
3907
4121
|
* Send event to a single webhook endpoint.
|
|
3908
4122
|
*/
|
|
3909
4123
|
async sendToWebhook(webhook, event) {
|
|
3910
|
-
|
|
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
|
+
}
|
|
3911
4132
|
const headers = {
|
|
3912
4133
|
"Content-Type": "application/json",
|
|
3913
4134
|
"User-Agent": "SchemaShift-Webhook/1.0",
|
|
@@ -4223,6 +4444,161 @@ var PluginLoader = class {
|
|
|
4223
4444
|
}
|
|
4224
4445
|
};
|
|
4225
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
|
+
|
|
4226
4602
|
// src/standard-schema.ts
|
|
4227
4603
|
var import_node_fs10 = require("fs");
|
|
4228
4604
|
var import_node_path10 = require("path");
|
|
@@ -4722,10 +5098,14 @@ var TypeDedupDetector = class {
|
|
|
4722
5098
|
buildCallChain,
|
|
4723
5099
|
computeParallelBatches,
|
|
4724
5100
|
conditionalValidation,
|
|
5101
|
+
createVerificationReport,
|
|
4725
5102
|
dependentFields,
|
|
4726
5103
|
detectFormLibraries,
|
|
4727
5104
|
detectSchemaLibrary,
|
|
4728
5105
|
detectStandardSchema,
|
|
5106
|
+
extractSchemaNames,
|
|
5107
|
+
formatVerificationReport,
|
|
5108
|
+
generateSamples,
|
|
4729
5109
|
getAllMigrationTemplates,
|
|
4730
5110
|
getGovernanceTemplate,
|
|
4731
5111
|
getGovernanceTemplateNames,
|