@schemashift/core 0.9.0 → 0.10.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 CHANGED
@@ -25,8 +25,10 @@ __export(index_exports, {
25
25
  CompatibilityAnalyzer: () => CompatibilityAnalyzer,
26
26
  ComplexityEstimator: () => ComplexityEstimator,
27
27
  DetailedAnalyzer: () => DetailedAnalyzer,
28
+ DriftDetector: () => DriftDetector,
28
29
  EcosystemAnalyzer: () => EcosystemAnalyzer,
29
30
  FormResolverMigrator: () => FormResolverMigrator,
31
+ GOVERNANCE_TEMPLATES: () => GOVERNANCE_TEMPLATES,
30
32
  GovernanceEngine: () => GovernanceEngine,
31
33
  IncrementalTracker: () => IncrementalTracker,
32
34
  MigrationAuditLog: () => MigrationAuditLog,
@@ -45,6 +47,9 @@ __export(index_exports, {
45
47
  detectFormLibraries: () => detectFormLibraries,
46
48
  detectSchemaLibrary: () => detectSchemaLibrary,
47
49
  detectStandardSchema: () => detectStandardSchema,
50
+ getGovernanceTemplate: () => getGovernanceTemplate,
51
+ getGovernanceTemplateNames: () => getGovernanceTemplateNames,
52
+ getGovernanceTemplatesByCategory: () => getGovernanceTemplatesByCategory,
48
53
  isInsideComment: () => isInsideComment,
49
54
  isInsideStringLiteral: () => isInsideStringLiteral,
50
55
  loadConfig: () => loadConfig,
@@ -68,6 +73,9 @@ var LIBRARY_PATTERNS = {
68
73
  joi: [/^joi$/, /^@hapi\/joi$/],
69
74
  "io-ts": [/^io-ts$/, /^io-ts\//],
70
75
  valibot: [/^valibot$/],
76
+ arktype: [/^arktype$/],
77
+ superstruct: [/^superstruct$/],
78
+ effect: [/^@effect\/schema$/],
71
79
  v4: [],
72
80
  // Target version, not detectable from imports
73
81
  unknown: []
@@ -362,7 +370,8 @@ var MigrationAuditLog = class {
362
370
  errorCount: params.errorCount,
363
371
  riskScore: params.riskScore,
364
372
  duration: params.duration,
365
- user: this.getCurrentUser()
373
+ user: this.getCurrentUser(),
374
+ metadata: params.metadata || this.collectMetadata()
366
375
  };
367
376
  }
368
377
  /**
@@ -404,12 +413,67 @@ var MigrationAuditLog = class {
404
413
  migrationPaths
405
414
  };
406
415
  }
416
+ /**
417
+ * Export audit log as JSON string.
418
+ */
419
+ exportJson() {
420
+ const log = this.read();
421
+ return JSON.stringify(log, null, 2);
422
+ }
423
+ /**
424
+ * Export audit log as CSV string.
425
+ */
426
+ exportCsv() {
427
+ const log = this.read();
428
+ const headers = [
429
+ "timestamp",
430
+ "migrationId",
431
+ "filePath",
432
+ "action",
433
+ "from",
434
+ "to",
435
+ "success",
436
+ "warningCount",
437
+ "errorCount",
438
+ "riskScore",
439
+ "user",
440
+ "duration"
441
+ ];
442
+ const rows = log.entries.map(
443
+ (e) => headers.map((h) => {
444
+ const val = e[h];
445
+ if (val === void 0 || val === null) return "";
446
+ return String(val).includes(",") ? `"${String(val)}"` : String(val);
447
+ }).join(",")
448
+ );
449
+ return [headers.join(","), ...rows].join("\n");
450
+ }
451
+ /**
452
+ * Get entries filtered by date range.
453
+ */
454
+ getByDateRange(start, end) {
455
+ const log = this.read();
456
+ return log.entries.filter((e) => {
457
+ const ts = new Date(e.timestamp);
458
+ return ts >= start && ts <= end;
459
+ });
460
+ }
407
461
  /**
408
462
  * Clear the audit log.
409
463
  */
410
464
  clear() {
411
465
  this.write({ version: AUDIT_VERSION, entries: [] });
412
466
  }
467
+ collectMetadata() {
468
+ return {
469
+ hostname: process.env.HOSTNAME || void 0,
470
+ nodeVersion: process.version,
471
+ ciJobId: process.env.CI_JOB_ID || process.env.GITHUB_RUN_ID || void 0,
472
+ ciProvider: process.env.GITHUB_ACTIONS ? "github" : process.env.GITLAB_CI ? "gitlab" : process.env.CIRCLECI ? "circleci" : process.env.JENKINS_URL ? "jenkins" : void 0,
473
+ gitBranch: process.env.GITHUB_REF_NAME || process.env.CI_COMMIT_BRANCH || void 0,
474
+ gitCommit: process.env.GITHUB_SHA || process.env.CI_COMMIT_SHA || void 0
475
+ };
476
+ }
413
477
  write(log) {
414
478
  if (!(0, import_node_fs.existsSync)(this.logDir)) {
415
479
  (0, import_node_fs.mkdirSync)(this.logDir, { recursive: true });
@@ -1028,6 +1092,125 @@ var ECOSYSTEM_RULES = [
1028
1092
  severity: "error"
1029
1093
  })
1030
1094
  },
1095
+ // Zod-based HTTP/API clients
1096
+ {
1097
+ package: "zodios",
1098
+ category: "api",
1099
+ migrations: ["zod-v3->v4"],
1100
+ check: () => ({
1101
+ issue: "Zodios uses Zod schemas for API contract definitions. Zod v4 type changes may break contracts.",
1102
+ suggestion: "Upgrade Zodios to a Zod v4-compatible version and verify all API contracts.",
1103
+ severity: "warning",
1104
+ upgradeCommand: "npm install @zodios/core@latest"
1105
+ })
1106
+ },
1107
+ {
1108
+ package: "@zodios/core",
1109
+ category: "api",
1110
+ migrations: ["zod-v3->v4"],
1111
+ check: () => ({
1112
+ issue: "@zodios/core uses Zod schemas for API contract definitions. Zod v4 type changes may break contracts.",
1113
+ suggestion: "Upgrade @zodios/core to a Zod v4-compatible version and verify all API contracts.",
1114
+ severity: "warning",
1115
+ upgradeCommand: "npm install @zodios/core@latest"
1116
+ })
1117
+ },
1118
+ {
1119
+ package: "@ts-rest/core",
1120
+ category: "api",
1121
+ migrations: ["zod-v3->v4"],
1122
+ check: () => ({
1123
+ issue: "@ts-rest/core uses Zod for contract definitions. Zod v4 type incompatibilities may break runtime validation.",
1124
+ suggestion: "Upgrade @ts-rest/core to a version with Zod v4 support.",
1125
+ severity: "warning",
1126
+ upgradeCommand: "npm install @ts-rest/core@latest"
1127
+ })
1128
+ },
1129
+ {
1130
+ package: "trpc-openapi",
1131
+ category: "openapi",
1132
+ migrations: ["zod-v3->v4"],
1133
+ check: () => ({
1134
+ issue: "trpc-openapi needs a v4-compatible version for Zod v4.",
1135
+ suggestion: "Check for a Zod v4-compatible version of trpc-openapi before upgrading.",
1136
+ severity: "warning",
1137
+ upgradeCommand: "npm install trpc-openapi@latest"
1138
+ })
1139
+ },
1140
+ // Form data and URL state libraries
1141
+ {
1142
+ package: "zod-form-data",
1143
+ category: "form",
1144
+ migrations: ["zod-v3->v4"],
1145
+ check: () => ({
1146
+ issue: "zod-form-data relies on Zod v3 internals (_def) which moved to _zod.def in v4.",
1147
+ suggestion: "Upgrade zod-form-data to a Zod v4-compatible version.",
1148
+ severity: "error",
1149
+ upgradeCommand: "npm install zod-form-data@latest"
1150
+ })
1151
+ },
1152
+ {
1153
+ package: "@conform-to/zod",
1154
+ category: "form",
1155
+ migrations: ["zod-v3->v4"],
1156
+ check: () => ({
1157
+ issue: "@conform-to/zod may have Zod v4 compatibility issues.",
1158
+ suggestion: "Upgrade @conform-to/zod to the latest version with Zod v4 support.",
1159
+ severity: "warning",
1160
+ upgradeCommand: "npm install @conform-to/zod@latest"
1161
+ })
1162
+ },
1163
+ {
1164
+ package: "nuqs",
1165
+ category: "validation-util",
1166
+ migrations: ["zod-v3->v4"],
1167
+ check: () => ({
1168
+ issue: "nuqs uses Zod for URL state parsing. Zod v4 changes may affect URL parameter validation.",
1169
+ suggestion: "Upgrade nuqs to a version with Zod v4 support.",
1170
+ severity: "warning",
1171
+ upgradeCommand: "npm install nuqs@latest"
1172
+ })
1173
+ },
1174
+ // Schema library detection for cross-library migrations
1175
+ {
1176
+ package: "@effect/schema",
1177
+ category: "validation-util",
1178
+ migrations: ["io-ts->zod"],
1179
+ check: () => ({
1180
+ issue: "@effect/schema detected \u2014 this is the successor to io-ts/fp-ts. Consider migrating to Effect Schema instead of Zod if you prefer FP patterns.",
1181
+ suggestion: "If using fp-ts patterns heavily, consider Effect Schema as the migration target instead of Zod.",
1182
+ severity: "info"
1183
+ })
1184
+ },
1185
+ {
1186
+ package: "arktype",
1187
+ category: "validation-util",
1188
+ migrations: ["zod->valibot", "zod-v3->v4"],
1189
+ check: (_version, migration) => {
1190
+ if (migration === "zod->valibot") {
1191
+ return {
1192
+ issue: "ArkType detected alongside Zod. Consider ArkType as a migration target \u2014 it offers 100x faster validation and Standard Schema support.",
1193
+ suggestion: "Consider migrating to ArkType for performance-critical paths, or keep Zod for ecosystem compatibility.",
1194
+ severity: "info"
1195
+ };
1196
+ }
1197
+ return {
1198
+ issue: "ArkType detected alongside Zod. ArkType supports Standard Schema, making it interoperable with Zod v4.",
1199
+ suggestion: "No action needed \u2014 ArkType and Zod v4 can coexist via Standard Schema.",
1200
+ severity: "info"
1201
+ };
1202
+ }
1203
+ },
1204
+ {
1205
+ package: "superstruct",
1206
+ category: "validation-util",
1207
+ migrations: ["yup->zod", "joi->zod"],
1208
+ check: () => ({
1209
+ issue: "Superstruct detected in the project. Consider migrating Superstruct schemas to Zod as well for a unified validation approach.",
1210
+ suggestion: "Use SchemaShift to migrate Superstruct schemas alongside Yup/Joi schemas.",
1211
+ severity: "info"
1212
+ })
1213
+ },
1031
1214
  // Additional validation utilities
1032
1215
  {
1033
1216
  package: "zod-to-json-schema",
@@ -1952,6 +2135,165 @@ var DetailedAnalyzer = class {
1952
2135
  }
1953
2136
  };
1954
2137
 
2138
+ // src/drift-detector.ts
2139
+ var import_node_crypto2 = require("crypto");
2140
+ var import_node_fs6 = require("fs");
2141
+ var import_node_path6 = require("path");
2142
+ var SNAPSHOT_DIR = ".schemashift";
2143
+ var SNAPSHOT_FILE = "schema-snapshot.json";
2144
+ var SNAPSHOT_VERSION = 1;
2145
+ var DriftDetector = class {
2146
+ snapshotDir;
2147
+ snapshotPath;
2148
+ constructor(projectPath) {
2149
+ this.snapshotDir = (0, import_node_path6.join)(projectPath, SNAPSHOT_DIR);
2150
+ this.snapshotPath = (0, import_node_path6.join)(this.snapshotDir, SNAPSHOT_FILE);
2151
+ }
2152
+ /**
2153
+ * Take a snapshot of the current schema state
2154
+ */
2155
+ snapshot(files, projectPath) {
2156
+ const schemas = [];
2157
+ for (const filePath of files) {
2158
+ if (!(0, import_node_fs6.existsSync)(filePath)) continue;
2159
+ const content = (0, import_node_fs6.readFileSync)(filePath, "utf-8");
2160
+ const library = this.detectLibraryFromContent(content);
2161
+ if (library === "unknown") continue;
2162
+ const schemaNames = this.extractSchemaNames(content);
2163
+ schemas.push({
2164
+ filePath: (0, import_node_path6.relative)(projectPath, filePath),
2165
+ library,
2166
+ contentHash: this.hashContent(content),
2167
+ schemaCount: schemaNames.length,
2168
+ schemaNames
2169
+ });
2170
+ }
2171
+ const snapshot = {
2172
+ version: SNAPSHOT_VERSION,
2173
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
2174
+ projectPath,
2175
+ schemas
2176
+ };
2177
+ return snapshot;
2178
+ }
2179
+ /**
2180
+ * Save a snapshot to disk
2181
+ */
2182
+ saveSnapshot(snapshot) {
2183
+ if (!(0, import_node_fs6.existsSync)(this.snapshotDir)) {
2184
+ (0, import_node_fs6.mkdirSync)(this.snapshotDir, { recursive: true });
2185
+ }
2186
+ (0, import_node_fs6.writeFileSync)(this.snapshotPath, JSON.stringify(snapshot, null, 2));
2187
+ }
2188
+ /**
2189
+ * Load saved snapshot from disk
2190
+ */
2191
+ loadSnapshot() {
2192
+ if (!(0, import_node_fs6.existsSync)(this.snapshotPath)) {
2193
+ return null;
2194
+ }
2195
+ try {
2196
+ const content = (0, import_node_fs6.readFileSync)(this.snapshotPath, "utf-8");
2197
+ return JSON.parse(content);
2198
+ } catch {
2199
+ return null;
2200
+ }
2201
+ }
2202
+ /**
2203
+ * Compare current state against saved snapshot
2204
+ */
2205
+ detect(currentFiles, projectPath) {
2206
+ const saved = this.loadSnapshot();
2207
+ if (!saved) {
2208
+ return {
2209
+ hasDrift: false,
2210
+ added: [],
2211
+ removed: [],
2212
+ modified: [],
2213
+ unchanged: 0,
2214
+ totalFiles: 0,
2215
+ snapshotTimestamp: ""
2216
+ };
2217
+ }
2218
+ const current = this.snapshot(currentFiles, projectPath);
2219
+ return this.compareSnapshots(saved, current);
2220
+ }
2221
+ /**
2222
+ * Compare two snapshots and return drift results
2223
+ */
2224
+ compareSnapshots(baseline, current) {
2225
+ const baselineMap = new Map(baseline.schemas.map((s) => [s.filePath, s]));
2226
+ const currentMap = new Map(current.schemas.map((s) => [s.filePath, s]));
2227
+ const added = [];
2228
+ const removed = [];
2229
+ const modified = [];
2230
+ let unchanged = 0;
2231
+ for (const [path, currentFile] of currentMap) {
2232
+ const baselineFile = baselineMap.get(path);
2233
+ if (!baselineFile) {
2234
+ added.push(currentFile);
2235
+ } else if (currentFile.contentHash !== baselineFile.contentHash) {
2236
+ const addedSchemas = currentFile.schemaNames.filter(
2237
+ (n) => !baselineFile.schemaNames.includes(n)
2238
+ );
2239
+ const removedSchemas = baselineFile.schemaNames.filter(
2240
+ (n) => !currentFile.schemaNames.includes(n)
2241
+ );
2242
+ modified.push({
2243
+ filePath: path,
2244
+ library: currentFile.library,
2245
+ previousHash: baselineFile.contentHash,
2246
+ currentHash: currentFile.contentHash,
2247
+ previousSchemaCount: baselineFile.schemaCount,
2248
+ currentSchemaCount: currentFile.schemaCount,
2249
+ addedSchemas,
2250
+ removedSchemas
2251
+ });
2252
+ } else {
2253
+ unchanged++;
2254
+ }
2255
+ }
2256
+ for (const [path, baselineFile] of baselineMap) {
2257
+ if (!currentMap.has(path)) {
2258
+ removed.push(baselineFile);
2259
+ }
2260
+ }
2261
+ return {
2262
+ hasDrift: added.length > 0 || removed.length > 0 || modified.length > 0,
2263
+ added,
2264
+ removed,
2265
+ modified,
2266
+ unchanged,
2267
+ totalFiles: currentMap.size,
2268
+ snapshotTimestamp: baseline.timestamp
2269
+ };
2270
+ }
2271
+ extractSchemaNames(content) {
2272
+ const names = [];
2273
+ const pattern = /(?:const|let|var)\s+(\w+)\s*=\s*(?:z\.|yup\.|Joi\.|t\.|v\.|type\(|object\(|string\(|S\.)/g;
2274
+ for (const match of content.matchAll(pattern)) {
2275
+ if (match[1]) names.push(match[1]);
2276
+ }
2277
+ return names;
2278
+ }
2279
+ detectLibraryFromContent(content) {
2280
+ if (/from\s*['"]zod['"]/.test(content) || /\bz\./.test(content)) return "zod";
2281
+ if (/from\s*['"]yup['"]/.test(content) || /\byup\./.test(content)) return "yup";
2282
+ if (/from\s*['"]joi['"]/.test(content) || /\bJoi\./.test(content)) return "joi";
2283
+ if (/from\s*['"]io-ts['"]/.test(content) || /\bt\./.test(content) && /from\s*['"]io-ts/.test(content))
2284
+ return "io-ts";
2285
+ if (/from\s*['"]valibot['"]/.test(content) || /\bv\./.test(content) && /from\s*['"]valibot/.test(content))
2286
+ return "valibot";
2287
+ if (/from\s*['"]arktype['"]/.test(content)) return "arktype";
2288
+ if (/from\s*['"]superstruct['"]/.test(content)) return "superstruct";
2289
+ if (/from\s*['"]@effect\/schema['"]/.test(content)) return "effect";
2290
+ return "unknown";
2291
+ }
2292
+ hashContent(content) {
2293
+ return (0, import_node_crypto2.createHash)("sha256").update(content).digest("hex").substring(0, 16);
2294
+ }
2295
+ };
2296
+
1955
2297
  // src/form-resolver-migrator.ts
1956
2298
  var RESOLVER_MAPPINGS = {
1957
2299
  "yup->zod": [
@@ -2313,17 +2655,265 @@ var GovernanceEngine = class {
2313
2655
  }
2314
2656
  };
2315
2657
 
2658
+ // src/governance-templates.ts
2659
+ var GOVERNANCE_TEMPLATES = [
2660
+ {
2661
+ name: "no-any-schemas",
2662
+ description: "Disallow z.any(), yup.mixed() without constraints, and similar unrestricted types",
2663
+ category: "security",
2664
+ rule: (sourceFile, _config) => {
2665
+ const violations = [];
2666
+ const text = sourceFile.getFullText();
2667
+ const filePath = sourceFile.getFilePath();
2668
+ const lines = text.split("\n");
2669
+ const anyPatterns = [
2670
+ /\bz\.any\(\)/,
2671
+ /\byup\.mixed\(\)/,
2672
+ /\bt\.any\b/,
2673
+ /\bv\.any\(\)/,
2674
+ /\bunknown\(\)/
2675
+ ];
2676
+ for (let i = 0; i < lines.length; i++) {
2677
+ const line = lines[i] ?? "";
2678
+ for (const pattern of anyPatterns) {
2679
+ if (pattern.test(line)) {
2680
+ violations.push({
2681
+ rule: "no-any-schemas",
2682
+ message: "Unrestricted type (any/mixed/unknown) found. Use a specific type with constraints.",
2683
+ filePath,
2684
+ lineNumber: i + 1,
2685
+ schemaName: "",
2686
+ severity: "error",
2687
+ fixable: false
2688
+ });
2689
+ }
2690
+ }
2691
+ }
2692
+ return violations;
2693
+ }
2694
+ },
2695
+ {
2696
+ name: "require-descriptions",
2697
+ description: "All exported schemas must have .describe() for documentation",
2698
+ category: "quality",
2699
+ rule: (sourceFile, _config) => {
2700
+ const violations = [];
2701
+ const text = sourceFile.getFullText();
2702
+ const filePath = sourceFile.getFilePath();
2703
+ const lines = text.split("\n");
2704
+ for (let i = 0; i < lines.length; i++) {
2705
+ const line = lines[i] ?? "";
2706
+ if (/export\s+(const|let)\s+\w+.*=\s*(z\.|yup\.)/.test(line)) {
2707
+ let fullStatement = line;
2708
+ let j = i + 1;
2709
+ while (j < lines.length && !lines[j]?.includes(";") && j < i + 10) {
2710
+ fullStatement += lines[j] ?? "";
2711
+ j++;
2712
+ }
2713
+ if (j < lines.length) fullStatement += lines[j] ?? "";
2714
+ if (!fullStatement.includes(".describe(")) {
2715
+ const nameMatch = line.match(/(?:const|let)\s+(\w+)/);
2716
+ violations.push({
2717
+ rule: "require-descriptions",
2718
+ message: `Exported schema ${nameMatch?.[1] || "unknown"} should include .describe() for documentation.`,
2719
+ filePath,
2720
+ lineNumber: i + 1,
2721
+ schemaName: nameMatch?.[1] || "",
2722
+ severity: "warning",
2723
+ fixable: true
2724
+ });
2725
+ }
2726
+ }
2727
+ }
2728
+ return violations;
2729
+ }
2730
+ },
2731
+ {
2732
+ name: "max-nesting-depth",
2733
+ description: "Limit schema nesting depth to prevent TypeScript performance issues",
2734
+ category: "performance",
2735
+ rule: (sourceFile, config) => {
2736
+ const violations = [];
2737
+ const text = sourceFile.getFullText();
2738
+ const filePath = sourceFile.getFilePath();
2739
+ const maxDepth = config.threshold || 5;
2740
+ const lines = text.split("\n");
2741
+ let currentDepth = 0;
2742
+ let maxFoundDepth = 0;
2743
+ let deepestLine = 0;
2744
+ for (let i = 0; i < lines.length; i++) {
2745
+ const line = lines[i] ?? "";
2746
+ for (const char of line) {
2747
+ if (char === "(" || char === "{" || char === "[") {
2748
+ currentDepth++;
2749
+ if (currentDepth > maxFoundDepth) {
2750
+ maxFoundDepth = currentDepth;
2751
+ deepestLine = i + 1;
2752
+ }
2753
+ }
2754
+ if (char === ")" || char === "}" || char === "]") {
2755
+ currentDepth = Math.max(0, currentDepth - 1);
2756
+ }
2757
+ }
2758
+ }
2759
+ if (maxFoundDepth > maxDepth) {
2760
+ violations.push({
2761
+ rule: "max-nesting-depth",
2762
+ message: `Schema nesting depth ${maxFoundDepth} exceeds maximum of ${maxDepth}. Consider breaking into smaller schemas.`,
2763
+ filePath,
2764
+ lineNumber: deepestLine,
2765
+ schemaName: "",
2766
+ severity: "warning",
2767
+ fixable: false
2768
+ });
2769
+ }
2770
+ return violations;
2771
+ }
2772
+ },
2773
+ {
2774
+ name: "no-deprecated-methods",
2775
+ description: "Flag usage of deprecated schema methods",
2776
+ category: "quality",
2777
+ rule: (sourceFile, _config) => {
2778
+ const violations = [];
2779
+ const text = sourceFile.getFullText();
2780
+ const filePath = sourceFile.getFilePath();
2781
+ const lines = text.split("\n");
2782
+ const deprecatedPatterns = [
2783
+ {
2784
+ pattern: /\.deepPartial\(\)/,
2785
+ message: ".deepPartial() is removed in Zod v4. Use recursive .partial() instead."
2786
+ },
2787
+ {
2788
+ pattern: /\.strip\(\)/,
2789
+ message: ".strip() is deprecated. Use z.strictObject() or explicit stripping."
2790
+ },
2791
+ {
2792
+ pattern: /z\.promise\(/,
2793
+ message: "z.promise() is deprecated in Zod v4. Use native Promise types."
2794
+ },
2795
+ {
2796
+ pattern: /z\.ostring\(\)/,
2797
+ message: "z.ostring() is removed in Zod v4. Use z.string().optional()."
2798
+ },
2799
+ {
2800
+ pattern: /z\.onumber\(\)/,
2801
+ message: "z.onumber() is removed in Zod v4. Use z.number().optional()."
2802
+ },
2803
+ {
2804
+ pattern: /z\.oboolean\(\)/,
2805
+ message: "z.oboolean() is removed in Zod v4. Use z.boolean().optional()."
2806
+ },
2807
+ {
2808
+ pattern: /z\.preprocess\(/,
2809
+ message: "z.preprocess() is removed in Zod v4. Use z.coerce.* instead."
2810
+ }
2811
+ ];
2812
+ for (let i = 0; i < lines.length; i++) {
2813
+ const line = lines[i] ?? "";
2814
+ for (const { pattern, message } of deprecatedPatterns) {
2815
+ if (pattern.test(line)) {
2816
+ violations.push({
2817
+ rule: "no-deprecated-methods",
2818
+ message,
2819
+ filePath,
2820
+ lineNumber: i + 1,
2821
+ schemaName: "",
2822
+ severity: "warning",
2823
+ fixable: false
2824
+ });
2825
+ }
2826
+ }
2827
+ }
2828
+ return violations;
2829
+ }
2830
+ },
2831
+ {
2832
+ name: "naming-convention",
2833
+ description: "Enforce schema naming pattern (e.g., must end with Schema)",
2834
+ category: "quality",
2835
+ rule: (sourceFile, config) => {
2836
+ const violations = [];
2837
+ const text = sourceFile.getFullText();
2838
+ const filePath = sourceFile.getFilePath();
2839
+ const lines = text.split("\n");
2840
+ const pattern = new RegExp(config.pattern || ".*Schema$");
2841
+ for (let i = 0; i < lines.length; i++) {
2842
+ const line = lines[i] ?? "";
2843
+ const match = line.match(
2844
+ /(?:const|let)\s+(\w+)\s*=\s*(?:z\.|yup\.|Joi\.|t\.|v\.|type\(|object\(|string\()/
2845
+ );
2846
+ if (match?.[1] && !pattern.test(match[1])) {
2847
+ violations.push({
2848
+ rule: "naming-convention",
2849
+ message: `Schema "${match[1]}" does not match naming pattern ${pattern.source}.`,
2850
+ filePath,
2851
+ lineNumber: i + 1,
2852
+ schemaName: match[1],
2853
+ severity: "warning",
2854
+ fixable: false
2855
+ });
2856
+ }
2857
+ }
2858
+ return violations;
2859
+ }
2860
+ },
2861
+ {
2862
+ name: "require-max-length",
2863
+ description: "String schemas must have .max() to prevent DoS via unbounded input",
2864
+ category: "security",
2865
+ rule: (sourceFile, _config) => {
2866
+ const violations = [];
2867
+ const text = sourceFile.getFullText();
2868
+ const filePath = sourceFile.getFilePath();
2869
+ const lines = text.split("\n");
2870
+ for (let i = 0; i < lines.length; i++) {
2871
+ const line = lines[i] ?? "";
2872
+ if (/z\.string\(\)/.test(line) && !line.includes(".max(") && !line.includes(".length(")) {
2873
+ let fullChain = line;
2874
+ let j = i + 1;
2875
+ while (j < lines.length && j < i + 5 && /^\s*\./.test(lines[j] ?? "")) {
2876
+ fullChain += lines[j] ?? "";
2877
+ j++;
2878
+ }
2879
+ if (!fullChain.includes(".max(") && !fullChain.includes(".length(")) {
2880
+ violations.push({
2881
+ rule: "require-max-length",
2882
+ message: "String schema should have .max() to prevent unbounded input (DoS protection).",
2883
+ filePath,
2884
+ lineNumber: i + 1,
2885
+ schemaName: "",
2886
+ severity: "warning",
2887
+ fixable: true
2888
+ });
2889
+ }
2890
+ }
2891
+ }
2892
+ return violations;
2893
+ }
2894
+ }
2895
+ ];
2896
+ function getGovernanceTemplate(name) {
2897
+ return GOVERNANCE_TEMPLATES.find((t) => t.name === name);
2898
+ }
2899
+ function getGovernanceTemplatesByCategory(category) {
2900
+ return GOVERNANCE_TEMPLATES.filter((t) => t.category === category);
2901
+ }
2902
+ function getGovernanceTemplateNames() {
2903
+ return GOVERNANCE_TEMPLATES.map((t) => t.name);
2904
+ }
2905
+
2316
2906
  // src/incremental.ts
2317
- var import_node_fs6 = require("fs");
2318
- var import_node_path6 = require("path");
2907
+ var import_node_fs7 = require("fs");
2908
+ var import_node_path7 = require("path");
2319
2909
  var STATE_DIR = ".schemashift";
2320
2910
  var STATE_FILE = "incremental.json";
2321
2911
  var IncrementalTracker = class {
2322
2912
  stateDir;
2323
2913
  statePath;
2324
2914
  constructor(projectPath) {
2325
- this.stateDir = (0, import_node_path6.join)(projectPath, STATE_DIR);
2326
- this.statePath = (0, import_node_path6.join)(this.stateDir, STATE_FILE);
2915
+ this.stateDir = (0, import_node_path7.join)(projectPath, STATE_DIR);
2916
+ this.statePath = (0, import_node_path7.join)(this.stateDir, STATE_FILE);
2327
2917
  }
2328
2918
  start(files, from, to) {
2329
2919
  const state = {
@@ -2358,9 +2948,9 @@ var IncrementalTracker = class {
2358
2948
  this.saveState(state);
2359
2949
  }
2360
2950
  getState() {
2361
- if (!(0, import_node_fs6.existsSync)(this.statePath)) return null;
2951
+ if (!(0, import_node_fs7.existsSync)(this.statePath)) return null;
2362
2952
  try {
2363
- return JSON.parse((0, import_node_fs6.readFileSync)(this.statePath, "utf-8"));
2953
+ return JSON.parse((0, import_node_fs7.readFileSync)(this.statePath, "utf-8"));
2364
2954
  } catch {
2365
2955
  return null;
2366
2956
  }
@@ -2387,21 +2977,21 @@ var IncrementalTracker = class {
2387
2977
  };
2388
2978
  }
2389
2979
  clear() {
2390
- if ((0, import_node_fs6.existsSync)(this.statePath)) {
2391
- (0, import_node_fs6.unlinkSync)(this.statePath);
2980
+ if ((0, import_node_fs7.existsSync)(this.statePath)) {
2981
+ (0, import_node_fs7.unlinkSync)(this.statePath);
2392
2982
  }
2393
2983
  }
2394
2984
  saveState(state) {
2395
- if (!(0, import_node_fs6.existsSync)(this.stateDir)) {
2396
- (0, import_node_fs6.mkdirSync)(this.stateDir, { recursive: true });
2985
+ if (!(0, import_node_fs7.existsSync)(this.stateDir)) {
2986
+ (0, import_node_fs7.mkdirSync)(this.stateDir, { recursive: true });
2397
2987
  }
2398
- (0, import_node_fs6.writeFileSync)(this.statePath, JSON.stringify(state, null, 2));
2988
+ (0, import_node_fs7.writeFileSync)(this.statePath, JSON.stringify(state, null, 2));
2399
2989
  }
2400
2990
  };
2401
2991
 
2402
2992
  // src/package-updater.ts
2403
- var import_node_fs7 = require("fs");
2404
- var import_node_path7 = require("path");
2993
+ var import_node_fs8 = require("fs");
2994
+ var import_node_path8 = require("path");
2405
2995
  var TARGET_VERSIONS = {
2406
2996
  "yup->zod": { zod: "^3.24.0" },
2407
2997
  "joi->zod": { zod: "^3.24.0" },
@@ -2422,14 +3012,14 @@ var PackageUpdater = class {
2422
3012
  const add = {};
2423
3013
  const remove = [];
2424
3014
  const warnings = [];
2425
- const pkgPath = (0, import_node_path7.join)(projectPath, "package.json");
2426
- if (!(0, import_node_fs7.existsSync)(pkgPath)) {
3015
+ const pkgPath = (0, import_node_path8.join)(projectPath, "package.json");
3016
+ if (!(0, import_node_fs8.existsSync)(pkgPath)) {
2427
3017
  warnings.push("No package.json found. Cannot plan dependency updates.");
2428
3018
  return { add, remove, warnings };
2429
3019
  }
2430
3020
  let pkg;
2431
3021
  try {
2432
- pkg = JSON.parse((0, import_node_fs7.readFileSync)(pkgPath, "utf-8"));
3022
+ pkg = JSON.parse((0, import_node_fs8.readFileSync)(pkgPath, "utf-8"));
2433
3023
  } catch {
2434
3024
  warnings.push("Could not parse package.json.");
2435
3025
  return { add, remove, warnings };
@@ -2459,9 +3049,9 @@ var PackageUpdater = class {
2459
3049
  return { add, remove, warnings };
2460
3050
  }
2461
3051
  apply(projectPath, plan) {
2462
- const pkgPath = (0, import_node_path7.join)(projectPath, "package.json");
2463
- if (!(0, import_node_fs7.existsSync)(pkgPath)) return;
2464
- const pkgText = (0, import_node_fs7.readFileSync)(pkgPath, "utf-8");
3052
+ const pkgPath = (0, import_node_path8.join)(projectPath, "package.json");
3053
+ if (!(0, import_node_fs8.existsSync)(pkgPath)) return;
3054
+ const pkgText = (0, import_node_fs8.readFileSync)(pkgPath, "utf-8");
2465
3055
  const pkg = JSON.parse(pkgText);
2466
3056
  if (!pkg.dependencies) pkg.dependencies = {};
2467
3057
  for (const [name, version] of Object.entries(plan.add)) {
@@ -2471,7 +3061,7 @@ var PackageUpdater = class {
2471
3061
  pkg.dependencies[name] = version;
2472
3062
  }
2473
3063
  }
2474
- (0, import_node_fs7.writeFileSync)(pkgPath, `${JSON.stringify(pkg, null, 2)}
3064
+ (0, import_node_fs8.writeFileSync)(pkgPath, `${JSON.stringify(pkg, null, 2)}
2475
3065
  `);
2476
3066
  }
2477
3067
  };
@@ -2643,8 +3233,8 @@ var PluginLoader = class {
2643
3233
  };
2644
3234
 
2645
3235
  // src/standard-schema.ts
2646
- var import_node_fs8 = require("fs");
2647
- var import_node_path8 = require("path");
3236
+ var import_node_fs9 = require("fs");
3237
+ var import_node_path9 = require("path");
2648
3238
  var STANDARD_SCHEMA_LIBRARIES = {
2649
3239
  zod: { minMajor: 3, minMinor: 23 },
2650
3240
  // Zod v3.23+ and v4+
@@ -2673,13 +3263,13 @@ function isVersionCompatible(version, minMajor, minMinor) {
2673
3263
  return false;
2674
3264
  }
2675
3265
  function detectStandardSchema(projectPath) {
2676
- const pkgPath = (0, import_node_path8.join)(projectPath, "package.json");
2677
- if (!(0, import_node_fs8.existsSync)(pkgPath)) {
3266
+ const pkgPath = (0, import_node_path9.join)(projectPath, "package.json");
3267
+ if (!(0, import_node_fs9.existsSync)(pkgPath)) {
2678
3268
  return { detected: false, compatibleLibraries: [], recommendation: "", interopTools: [] };
2679
3269
  }
2680
3270
  let allDeps = {};
2681
3271
  try {
2682
- const pkg = JSON.parse((0, import_node_fs8.readFileSync)(pkgPath, "utf-8"));
3272
+ const pkg = JSON.parse((0, import_node_fs9.readFileSync)(pkgPath, "utf-8"));
2683
3273
  allDeps = { ...pkg.dependencies, ...pkg.devDependencies };
2684
3274
  } catch {
2685
3275
  return { detected: false, compatibleLibraries: [], recommendation: "", interopTools: [] };
@@ -3017,8 +3607,10 @@ var TypeDedupDetector = class {
3017
3607
  CompatibilityAnalyzer,
3018
3608
  ComplexityEstimator,
3019
3609
  DetailedAnalyzer,
3610
+ DriftDetector,
3020
3611
  EcosystemAnalyzer,
3021
3612
  FormResolverMigrator,
3613
+ GOVERNANCE_TEMPLATES,
3022
3614
  GovernanceEngine,
3023
3615
  IncrementalTracker,
3024
3616
  MigrationAuditLog,
@@ -3037,6 +3629,9 @@ var TypeDedupDetector = class {
3037
3629
  detectFormLibraries,
3038
3630
  detectSchemaLibrary,
3039
3631
  detectStandardSchema,
3632
+ getGovernanceTemplate,
3633
+ getGovernanceTemplateNames,
3634
+ getGovernanceTemplatesByCategory,
3040
3635
  isInsideComment,
3041
3636
  isInsideStringLiteral,
3042
3637
  loadConfig,