@schemashift/core 0.3.0 → 0.7.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.js CHANGED
@@ -23,6 +23,27 @@ function detectSchemaLibrary(moduleSpecifier) {
23
23
  }
24
24
  return "unknown";
25
25
  }
26
+ var FORM_LIBRARY_PATTERNS = {
27
+ "react-hook-form": [/^react-hook-form$/, /^@hookform\/resolvers/],
28
+ formik: [/^formik$/],
29
+ "mantine-form": [/^@mantine\/form$/]
30
+ };
31
+ function detectFormLibraries(sourceFile) {
32
+ const detections = [];
33
+ for (const imp of sourceFile.getImportDeclarations()) {
34
+ const moduleSpecifier = imp.getModuleSpecifierValue();
35
+ for (const [library, patterns] of Object.entries(FORM_LIBRARY_PATTERNS)) {
36
+ if (patterns.some((pattern) => pattern.test(moduleSpecifier))) {
37
+ detections.push({
38
+ library,
39
+ importPath: moduleSpecifier,
40
+ lineNumber: imp.getStartLineNumber()
41
+ });
42
+ }
43
+ }
44
+ }
45
+ return detections;
46
+ }
26
47
 
27
48
  // src/analyzer.ts
28
49
  var SchemaAnalyzer = class {
@@ -109,8 +130,560 @@ var SchemaAnalyzer = class {
109
130
  }
110
131
  };
111
132
 
133
+ // src/ast-utils.ts
134
+ import { Node as NodeUtils } from "ts-morph";
135
+ function parseCallChain(node) {
136
+ const methods = [];
137
+ let current = node;
138
+ while (true) {
139
+ const expression = current.getExpression();
140
+ if (NodeUtils.isPropertyAccessExpression(expression)) {
141
+ const methodName = expression.getName();
142
+ const args = current.getArguments().map((a) => a.getText());
143
+ const inner = expression.getExpression();
144
+ if (NodeUtils.isCallExpression(inner)) {
145
+ methods.unshift({ name: methodName, args, node: current });
146
+ current = inner;
147
+ continue;
148
+ }
149
+ if (NodeUtils.isPropertyAccessExpression(inner)) {
150
+ const factoryName = inner.getName();
151
+ const baseExpr = inner.getExpression();
152
+ if (NodeUtils.isIdentifier(baseExpr)) {
153
+ methods.unshift({ name: methodName, args, node: current });
154
+ return {
155
+ base: baseExpr.getText(),
156
+ factoryMethod: factoryName,
157
+ factoryArgs: current.getArguments().map((a) => a.getText()),
158
+ methods,
159
+ rootNode: node
160
+ };
161
+ }
162
+ methods.unshift({ name: methodName, args, node: current });
163
+ current = inner;
164
+ if (!NodeUtils.isCallExpression(current)) {
165
+ break;
166
+ }
167
+ continue;
168
+ }
169
+ if (NodeUtils.isIdentifier(inner)) {
170
+ return {
171
+ base: inner.getText(),
172
+ factoryMethod: methodName,
173
+ factoryArgs: current.getArguments().map((a) => a.getText()),
174
+ methods,
175
+ rootNode: node
176
+ };
177
+ }
178
+ }
179
+ break;
180
+ }
181
+ return void 0;
182
+ }
183
+ function buildCallChain(base, factoryMethod, factoryArgs, methods) {
184
+ let result = `${base}.${factoryMethod}(${factoryArgs.join(", ")})`;
185
+ for (const method of methods) {
186
+ result += `.${method.name}(${method.args.join(", ")})`;
187
+ }
188
+ return result;
189
+ }
190
+ function isInsideStringLiteral(node) {
191
+ let current = node.getParent();
192
+ while (current) {
193
+ if (NodeUtils.isStringLiteral(current) || NodeUtils.isNoSubstitutionTemplateLiteral(current) || NodeUtils.isTemplateExpression(current)) {
194
+ return true;
195
+ }
196
+ current = current.getParent();
197
+ }
198
+ return false;
199
+ }
200
+ function isInsideComment(node, sourceFile) {
201
+ const start = node.getStart();
202
+ const fullText = sourceFile.getFullText();
203
+ let i = 0;
204
+ while (i < fullText.length && i < start) {
205
+ if (fullText[i] === "/" && fullText[i + 1] === "/") {
206
+ const endOfLine = fullText.indexOf("\n", i);
207
+ const end = endOfLine === -1 ? fullText.length : endOfLine;
208
+ if (start >= i && start < end) return true;
209
+ i = end + 1;
210
+ continue;
211
+ }
212
+ if (fullText[i] === "/" && fullText[i + 1] === "*") {
213
+ const end = fullText.indexOf("*/", i + 2);
214
+ const commentEnd = end === -1 ? fullText.length : end + 2;
215
+ if (start >= i && start < commentEnd) return true;
216
+ i = commentEnd;
217
+ continue;
218
+ }
219
+ if (fullText[i] === "'" || fullText[i] === '"' || fullText[i] === "`") {
220
+ const quote = fullText[i];
221
+ i++;
222
+ while (i < fullText.length) {
223
+ if (fullText[i] === "\\") {
224
+ i += 2;
225
+ continue;
226
+ }
227
+ if (fullText[i] === quote) {
228
+ i++;
229
+ break;
230
+ }
231
+ i++;
232
+ }
233
+ continue;
234
+ }
235
+ i++;
236
+ }
237
+ return false;
238
+ }
239
+ function startsWithBase(node, ...bases) {
240
+ const chain = parseCallChain(node);
241
+ if (!chain) return false;
242
+ return bases.includes(chain.base);
243
+ }
244
+ function transformMethodChain(chain, newBase, factoryMapper, methodMapper) {
245
+ const mappedFactory = factoryMapper(chain.factoryMethod, chain.factoryArgs);
246
+ const factory = mappedFactory || {
247
+ name: chain.factoryMethod,
248
+ args: chain.factoryArgs
249
+ };
250
+ const mappedMethods = [];
251
+ for (const method of chain.methods) {
252
+ const mapped = methodMapper(method);
253
+ if (mapped === null) {
254
+ continue;
255
+ }
256
+ if (mapped === void 0) {
257
+ mappedMethods.push({ name: method.name, args: method.args });
258
+ } else if (Array.isArray(mapped)) {
259
+ mappedMethods.push(...mapped);
260
+ } else {
261
+ mappedMethods.push(mapped);
262
+ }
263
+ }
264
+ return buildCallChain(newBase, factory.name, factory.args, mappedMethods);
265
+ }
266
+
267
+ // src/chain.ts
268
+ import { Project as Project2 } from "ts-morph";
269
+ var MigrationChain = class {
270
+ parseChain(chain) {
271
+ const parts = chain.split("->");
272
+ if (parts.length < 2) return [];
273
+ const steps = [];
274
+ for (let i = 0; i < parts.length - 1; i++) {
275
+ const from = parts[i];
276
+ const to = parts[i + 1];
277
+ if (!from || !to) continue;
278
+ steps.push({
279
+ from,
280
+ to
281
+ });
282
+ }
283
+ return steps;
284
+ }
285
+ validateChain(steps, engine) {
286
+ const errors = [];
287
+ if (steps.length === 0) {
288
+ errors.push("No migration steps specified");
289
+ return { valid: false, errors, steps };
290
+ }
291
+ if (steps.length < 2) {
292
+ errors.push("Chain migration requires at least 2 steps. Use --from/--to for single step.");
293
+ return { valid: false, errors, steps };
294
+ }
295
+ for (const step of steps) {
296
+ if (!engine.hasHandler(step.from, step.to)) {
297
+ errors.push(`No handler for step ${step.from}->${step.to}`);
298
+ }
299
+ }
300
+ return { valid: errors.length === 0, errors, steps };
301
+ }
302
+ executeChain(sourceCode, filePath, steps, engine) {
303
+ const stepResults = [];
304
+ const errors = [];
305
+ let currentCode = sourceCode;
306
+ for (const step of steps) {
307
+ const project = new Project2({ useInMemoryFileSystem: true });
308
+ const sourceFile = project.createSourceFile(filePath, currentCode);
309
+ const result = engine.transform(sourceFile, step.from, step.to, {
310
+ from: step.from,
311
+ to: step.to,
312
+ preserveComments: true
313
+ });
314
+ stepResults.push({ step, result });
315
+ if (!result.success) {
316
+ errors.push(`Step ${step.from}->${step.to} failed: ${result.errors[0]?.message}`);
317
+ return { success: false, steps: stepResults, errors };
318
+ }
319
+ if (result.transformedCode) {
320
+ currentCode = result.transformedCode;
321
+ }
322
+ }
323
+ return {
324
+ success: true,
325
+ steps: stepResults,
326
+ finalCode: currentCode,
327
+ errors
328
+ };
329
+ }
330
+ };
331
+
332
+ // src/compatibility.ts
333
+ import { existsSync as existsSync2, readFileSync as readFileSync2 } from "fs";
334
+ import { join as join2 } from "path";
335
+
336
+ // src/ecosystem.ts
337
+ import { existsSync, readFileSync } from "fs";
338
+ import { join } from "path";
339
+ var ECOSYSTEM_RULES = [
340
+ // ORM integrations
341
+ {
342
+ package: "drizzle-zod",
343
+ category: "orm",
344
+ migrations: ["zod-v3->v4"],
345
+ check: () => ({
346
+ issue: "drizzle-zod may not support Zod v4. Check for a compatible version before upgrading.",
347
+ suggestion: "Upgrade drizzle-zod to the latest version that supports Zod v4, or use --legacy-peer-deps.",
348
+ severity: "warning"
349
+ })
350
+ },
351
+ {
352
+ package: "zod-prisma",
353
+ category: "orm",
354
+ migrations: ["zod-v3->v4"],
355
+ check: () => ({
356
+ issue: "zod-prisma generates Zod v3 schemas. Generated files will need regeneration after upgrading to Zod v4.",
357
+ suggestion: "Upgrade zod-prisma to a v4-compatible version and regenerate schemas.",
358
+ severity: "warning"
359
+ })
360
+ },
361
+ {
362
+ package: "zod-prisma-types",
363
+ category: "orm",
364
+ migrations: ["zod-v3->v4"],
365
+ check: () => ({
366
+ issue: "zod-prisma-types generates Zod v3 schemas. Generated files will need regeneration.",
367
+ suggestion: "Check for a Zod v4-compatible version of zod-prisma-types.",
368
+ severity: "warning"
369
+ })
370
+ },
371
+ // API framework integrations
372
+ {
373
+ package: "@trpc/server",
374
+ category: "api",
375
+ migrations: ["zod-v3->v4"],
376
+ check: (version) => {
377
+ const majorMatch = version.match(/(\d+)/);
378
+ const major = majorMatch?.[1] ? Number.parseInt(majorMatch[1], 10) : 0;
379
+ if (major < 11) {
380
+ return {
381
+ issue: `tRPC v${major} expects Zod v3 types. A v3 ZodType is not assignable to a v4 ZodType.`,
382
+ suggestion: "Upgrade to tRPC v11+ which supports Zod v4 via Standard Schema.",
383
+ severity: "error"
384
+ };
385
+ }
386
+ return {
387
+ issue: "tRPC v11+ supports Zod v4 via Standard Schema.",
388
+ suggestion: "No action required \u2014 tRPC v11 is compatible with Zod v4.",
389
+ severity: "info"
390
+ };
391
+ }
392
+ },
393
+ {
394
+ package: "trpc-ui",
395
+ category: "ui",
396
+ migrations: ["zod-v3->v4"],
397
+ check: () => ({
398
+ issue: "trpc-ui breaks entirely with Zod v4 schemas.",
399
+ suggestion: "Check for a Zod v4-compatible version of trpc-ui before upgrading.",
400
+ severity: "error"
401
+ })
402
+ },
403
+ // Validation utilities
404
+ {
405
+ package: "zod-validation-error",
406
+ category: "validation-util",
407
+ migrations: ["zod-v3->v4"],
408
+ check: (version) => {
409
+ const majorMatch = version.match(/(\d+)/);
410
+ const major = majorMatch?.[1] ? Number.parseInt(majorMatch[1], 10) : 0;
411
+ if (major < 4) {
412
+ return {
413
+ issue: `zod-validation-error v${major} is not compatible with Zod v4.`,
414
+ suggestion: "Upgrade zod-validation-error to v5.0.0+ for Zod v4 support.",
415
+ severity: "error"
416
+ };
417
+ }
418
+ return null;
419
+ }
420
+ },
421
+ // Form library integrations
422
+ {
423
+ package: "@hookform/resolvers",
424
+ category: "form",
425
+ migrations: ["yup->zod", "joi->zod", "io-ts->zod", "zod-v3->v4"],
426
+ check: (_version, migration) => {
427
+ if (migration === "zod-v3->v4") {
428
+ return {
429
+ issue: "@hookform/resolvers zodResolver may need updating for Zod v4.",
430
+ suggestion: "Upgrade @hookform/resolvers to the latest version with Zod v4 support.",
431
+ severity: "warning"
432
+ };
433
+ }
434
+ return {
435
+ issue: "@hookform/resolvers will need its resolver import updated for the new schema library.",
436
+ suggestion: "Switch from the old resolver (e.g., yupResolver) to zodResolver from @hookform/resolvers/zod.",
437
+ severity: "warning"
438
+ };
439
+ }
440
+ },
441
+ {
442
+ package: "formik",
443
+ category: "form",
444
+ migrations: ["yup->zod", "joi->zod"],
445
+ check: () => ({
446
+ issue: "Formik has native Yup integration. Migrating to Zod requires a validation adapter or switching to React Hook Form.",
447
+ suggestion: "Consider switching from Formik to React Hook Form with zodResolver, as Formik is no longer actively maintained.",
448
+ severity: "warning"
449
+ })
450
+ },
451
+ {
452
+ package: "@mantine/form",
453
+ category: "form",
454
+ migrations: ["yup->zod", "joi->zod"],
455
+ check: () => ({
456
+ issue: "@mantine/form schema adapter will need updating after migration.",
457
+ suggestion: "Update @mantine/form validation to use the Zod adapter instead of the old library adapter.",
458
+ severity: "warning"
459
+ })
460
+ },
461
+ // OpenAPI integrations
462
+ {
463
+ package: "zod-openapi",
464
+ category: "openapi",
465
+ migrations: ["zod-v3->v4"],
466
+ check: () => ({
467
+ issue: "zod-openapi may not support Zod v4 yet.",
468
+ suggestion: "Check for a Zod v4-compatible version of zod-openapi.",
469
+ severity: "warning"
470
+ })
471
+ },
472
+ {
473
+ package: "@asteasolutions/zod-to-openapi",
474
+ category: "openapi",
475
+ migrations: ["zod-v3->v4"],
476
+ check: () => ({
477
+ issue: "@asteasolutions/zod-to-openapi may not support Zod v4 yet.",
478
+ suggestion: "Check for a Zod v4-compatible version of @asteasolutions/zod-to-openapi.",
479
+ severity: "warning"
480
+ })
481
+ }
482
+ ];
483
+ var EcosystemAnalyzer = class {
484
+ analyze(projectPath, from, to) {
485
+ const migration = `${from}->${to}`;
486
+ const dependencies = [];
487
+ const warnings = [];
488
+ const blockers = [];
489
+ const pkgPath = join(projectPath, "package.json");
490
+ if (!existsSync(pkgPath)) {
491
+ return { dependencies, warnings, blockers };
492
+ }
493
+ let allDeps = {};
494
+ try {
495
+ const pkg = JSON.parse(readFileSync(pkgPath, "utf-8"));
496
+ allDeps = { ...pkg.dependencies, ...pkg.devDependencies };
497
+ } catch {
498
+ return { dependencies, warnings, blockers };
499
+ }
500
+ for (const rule of ECOSYSTEM_RULES) {
501
+ if (!rule.migrations.includes(migration)) continue;
502
+ const installedVersion = allDeps[rule.package];
503
+ if (!installedVersion) continue;
504
+ const result = rule.check(installedVersion, migration);
505
+ if (!result) continue;
506
+ const issue = {
507
+ package: rule.package,
508
+ installedVersion,
509
+ migration,
510
+ issue: result.issue,
511
+ suggestion: result.suggestion,
512
+ severity: result.severity,
513
+ category: rule.category
514
+ };
515
+ dependencies.push(issue);
516
+ if (result.severity === "error") {
517
+ blockers.push(`${rule.package}@${installedVersion}: ${result.issue}`);
518
+ } else if (result.severity === "warning") {
519
+ warnings.push(`${rule.package}@${installedVersion}: ${result.issue}`);
520
+ }
521
+ }
522
+ return { dependencies, warnings, blockers };
523
+ }
524
+ };
525
+
526
+ // src/compatibility.ts
527
+ var KNOWN_ISSUES = [
528
+ // Zod v3 -> v4 issues
529
+ {
530
+ library: "zod",
531
+ versionPattern: /^[~^]?3\./,
532
+ issue: "Zod v3 uses .errors property which is renamed to .issues in v4",
533
+ suggestion: "Use zod-v3->v4 migration path to handle API changes",
534
+ severity: "warning"
535
+ },
536
+ {
537
+ library: "zod",
538
+ versionPattern: /^[~^]?3\./,
539
+ issue: "z.record() key type behavior changed in v4",
540
+ suggestion: "Ensure z.record() calls explicitly specify key type",
541
+ severity: "info"
542
+ },
543
+ {
544
+ library: "zod",
545
+ versionPattern: /^[~^]?4\./,
546
+ issue: "Zod v4 detected \u2014 already on latest major version",
547
+ suggestion: "No migration needed for Zod version upgrades",
548
+ severity: "info"
549
+ },
550
+ // Yup issues
551
+ {
552
+ library: "yup",
553
+ versionPattern: /^[~^]?0\./,
554
+ issue: "Yup v0.x has different .transform() and .cast() behavior from v1.x",
555
+ suggestion: "Consider upgrading Yup to v1.x before migrating to Zod",
556
+ severity: "warning"
557
+ },
558
+ {
559
+ library: "yup",
560
+ versionPattern: /^[~^]?1\./,
561
+ issue: "Yup v1.x removed .transform() on mixed() and changed .cast() behavior",
562
+ suggestion: "Check for .transform() usage on mixed schemas \u2014 manual conversion may be needed",
563
+ severity: "info"
564
+ },
565
+ // Joi issues
566
+ {
567
+ library: "joi",
568
+ versionPattern: /^[~^]?17\./,
569
+ issue: "Joi v17 changed .pattern() semantics and deprecated .allow('')",
570
+ suggestion: "Review .pattern() and .allow() usages for v17-specific behavior",
571
+ severity: "info"
572
+ },
573
+ {
574
+ library: "joi",
575
+ versionPattern: /^[~^]?(1[0-6])\./,
576
+ issue: "Joi < v17 has significant API differences from modern versions",
577
+ suggestion: "Consider upgrading Joi before migrating to Zod",
578
+ severity: "warning"
579
+ },
580
+ // io-ts issues
581
+ {
582
+ library: "io-ts",
583
+ versionPattern: /^[~^]?2\./,
584
+ issue: "io-ts v2.x uses Either monad patterns requiring manual conversion",
585
+ suggestion: "fp-ts Either handling (isRight/isLeft) needs conversion to try/catch with Zod",
586
+ severity: "warning"
587
+ },
588
+ // Valibot issues
589
+ {
590
+ library: "valibot",
591
+ versionPattern: /^[~^]?0\./,
592
+ issue: "Valibot v0.x API may have breaking changes in v1.0",
593
+ suggestion: "Pin your Valibot version during migration to avoid API drift",
594
+ severity: "info"
595
+ }
596
+ ];
597
+ var CompatibilityAnalyzer = class {
598
+ ecosystemAnalyzer = new EcosystemAnalyzer();
599
+ detectVersions(projectPath) {
600
+ const versions = [];
601
+ const pkgPath = join2(projectPath, "package.json");
602
+ if (!existsSync2(pkgPath)) return versions;
603
+ try {
604
+ const pkg = JSON.parse(readFileSync2(pkgPath, "utf-8"));
605
+ const knownLibs = ["zod", "yup", "joi", "io-ts", "valibot"];
606
+ const allDeps = { ...pkg.dependencies, ...pkg.devDependencies };
607
+ for (const lib of knownLibs) {
608
+ const ver = allDeps[lib];
609
+ if (ver) {
610
+ versions.push({ library: lib, version: ver });
611
+ }
612
+ }
613
+ } catch {
614
+ }
615
+ return versions;
616
+ }
617
+ checkCompatibility(projectPath, from, to) {
618
+ const detectedVersions = this.detectVersions(projectPath);
619
+ const issues = [];
620
+ for (const detected of detectedVersions) {
621
+ for (const known of KNOWN_ISSUES) {
622
+ if (detected.library === known.library && known.versionPattern.test(detected.version)) {
623
+ issues.push({
624
+ library: detected.library,
625
+ detectedVersion: detected.version,
626
+ issue: known.issue,
627
+ suggestion: known.suggestion,
628
+ severity: known.severity
629
+ });
630
+ }
631
+ }
632
+ }
633
+ let ecosystemIssues = [];
634
+ if (from && to) {
635
+ const ecosystemReport = this.ecosystemAnalyzer.analyze(projectPath, from, to);
636
+ ecosystemIssues = ecosystemReport.dependencies;
637
+ }
638
+ const allIssues = [...issues.map((i) => i.severity), ...ecosystemIssues.map((i) => i.severity)];
639
+ const errorCount = allIssues.filter((s) => s === "error").length;
640
+ const warningCount = allIssues.filter((s) => s === "warning").length;
641
+ const overallScore = Math.max(0, 100 - errorCount * 30 - warningCount * 10);
642
+ return {
643
+ detectedVersions,
644
+ issues,
645
+ ecosystemIssues,
646
+ overallScore
647
+ };
648
+ }
649
+ };
650
+
112
651
  // src/config.ts
113
652
  import { cosmiconfig } from "cosmiconfig";
653
+ function validateConfig(config) {
654
+ const errors = [];
655
+ if (config.include && !Array.isArray(config.include)) {
656
+ errors.push('"include" must be an array of glob patterns');
657
+ }
658
+ if (config.exclude && !Array.isArray(config.exclude)) {
659
+ errors.push('"exclude" must be an array of glob patterns');
660
+ }
661
+ if (config.customRules) {
662
+ for (const [i, rule] of config.customRules.entries()) {
663
+ if (!rule.name) errors.push(`customRules[${i}]: "name" is required`);
664
+ if (!rule.match?.library) errors.push(`customRules[${i}]: "match.library" is required`);
665
+ if (!rule.match?.method) errors.push(`customRules[${i}]: "match.method" is required`);
666
+ if (!rule.transform?.method) errors.push(`customRules[${i}]: "transform.method" is required`);
667
+ }
668
+ }
669
+ if (config.suppressWarnings) {
670
+ for (const [i, rule] of config.suppressWarnings.entries()) {
671
+ if (!rule.code) errors.push(`suppressWarnings[${i}]: "code" is required`);
672
+ if (rule.files && !Array.isArray(rule.files)) {
673
+ errors.push(`suppressWarnings[${i}]: "files" must be an array`);
674
+ }
675
+ }
676
+ }
677
+ return { valid: errors.length === 0, errors };
678
+ }
679
+ function shouldSuppressWarning(warning, filePath, rules) {
680
+ for (const rule of rules) {
681
+ if (!warning.includes(rule.code)) continue;
682
+ if (!rule.files || rule.files.length === 0) return true;
683
+ if (rule.files.some((pattern) => filePath.includes(pattern))) return true;
684
+ }
685
+ return false;
686
+ }
114
687
  async function loadConfig(configPath) {
115
688
  const explorer = cosmiconfig("schemashift", {
116
689
  searchPlaces: [
@@ -135,6 +708,725 @@ async function loadConfig(configPath) {
135
708
  };
136
709
  }
137
710
 
711
+ // src/dependency-graph.ts
712
+ var SchemaDependencyResolver = class {
713
+ resolve(project, filePaths) {
714
+ const fileSet = new Set(filePaths);
715
+ const dependencies = /* @__PURE__ */ new Map();
716
+ let crossFileRefs = 0;
717
+ for (const filePath of filePaths) {
718
+ const sourceFile = project.getSourceFile(filePath);
719
+ if (!sourceFile) continue;
720
+ const deps = this.findDependencies(sourceFile, fileSet);
721
+ dependencies.set(filePath, deps);
722
+ crossFileRefs += deps.length;
723
+ }
724
+ const circularWarnings = this.detectCycles(dependencies);
725
+ const sortedFiles = this.topologicalSort(filePaths, dependencies);
726
+ return {
727
+ sortedFiles,
728
+ dependencies,
729
+ circularWarnings,
730
+ crossFileRefs
731
+ };
732
+ }
733
+ findDependencies(sourceFile, fileSet) {
734
+ const deps = [];
735
+ const filePath = sourceFile.getFilePath();
736
+ for (const imp of sourceFile.getImportDeclarations()) {
737
+ const moduleSpecifier = imp.getModuleSpecifierValue();
738
+ if (!moduleSpecifier.startsWith(".") && !moduleSpecifier.startsWith("/")) continue;
739
+ const resolvedSourceFile = imp.getModuleSpecifierSourceFile();
740
+ if (!resolvedSourceFile) continue;
741
+ const resolvedPath = resolvedSourceFile.getFilePath();
742
+ if (resolvedPath === filePath) continue;
743
+ if (!fileSet.has(resolvedPath)) continue;
744
+ if (!deps.includes(resolvedPath)) {
745
+ deps.push(resolvedPath);
746
+ }
747
+ }
748
+ return deps;
749
+ }
750
+ detectCycles(dependencies) {
751
+ const warnings = [];
752
+ const visited = /* @__PURE__ */ new Set();
753
+ const inStack = /* @__PURE__ */ new Set();
754
+ const dfs = (node, path) => {
755
+ if (inStack.has(node)) {
756
+ const cycleStart = path.indexOf(node);
757
+ const cycle = path.slice(cycleStart).concat(node);
758
+ warnings.push(`Circular dependency: ${cycle.map(this.shortenPath).join(" -> ")}`);
759
+ return;
760
+ }
761
+ if (visited.has(node)) return;
762
+ visited.add(node);
763
+ inStack.add(node);
764
+ const deps = dependencies.get(node) || [];
765
+ for (const dep of deps) {
766
+ dfs(dep, [...path, node]);
767
+ }
768
+ inStack.delete(node);
769
+ };
770
+ for (const node of dependencies.keys()) {
771
+ dfs(node, []);
772
+ }
773
+ return warnings;
774
+ }
775
+ topologicalSort(filePaths, dependencies) {
776
+ const visited = /* @__PURE__ */ new Set();
777
+ const sorted = [];
778
+ const visit = (node) => {
779
+ if (visited.has(node)) return;
780
+ visited.add(node);
781
+ const deps = dependencies.get(node) || [];
782
+ for (const dep of deps) {
783
+ visit(dep);
784
+ }
785
+ sorted.push(node);
786
+ };
787
+ for (const filePath of filePaths) {
788
+ visit(filePath);
789
+ }
790
+ return sorted;
791
+ }
792
+ shortenPath(filePath) {
793
+ const parts = filePath.split("/");
794
+ return parts.slice(-2).join("/");
795
+ }
796
+ };
797
+
798
+ // src/detailed-analyzer.ts
799
+ import { existsSync as existsSync3, readFileSync as readFileSync3 } from "fs";
800
+ import { join as join3 } from "path";
801
+ var COMPLEXITY_CHAIN_WEIGHT = 2;
802
+ var COMPLEXITY_DEPTH_WEIGHT = 3;
803
+ var COMPLEXITY_VALIDATION_WEIGHT = 1;
804
+ var COMPLEXITY_MAX = 100;
805
+ var COMPLEXITY_LOW_THRESHOLD = 25;
806
+ var COMPLEXITY_MEDIUM_THRESHOLD = 50;
807
+ var COMPLEXITY_HIGH_THRESHOLD = 75;
808
+ var DetailedAnalyzer = class {
809
+ analyzeComplexity(analyzer) {
810
+ const complexities = [];
811
+ for (const sourceFile of analyzer.getProject().getSourceFiles()) {
812
+ const library = this.detectFileLibrary(sourceFile);
813
+ if (library === "unknown") continue;
814
+ for (const varDecl of sourceFile.getVariableDeclarations()) {
815
+ const initializer = varDecl.getInitializer();
816
+ if (!initializer) continue;
817
+ if (!this.isSchemaExpression(initializer, library)) continue;
818
+ const complexity = this.computeComplexity(varDecl, sourceFile, library);
819
+ complexities.push(complexity);
820
+ }
821
+ }
822
+ return complexities;
823
+ }
824
+ analyzeReadiness(schemas, from, to, supportedMethods) {
825
+ let supportedSchemas = 0;
826
+ const unsupportedPatterns = [];
827
+ for (const schema of schemas) {
828
+ if (schema.library !== from && schema.library !== "unknown") continue;
829
+ const methods = this.extractMethodNames(schema.code);
830
+ const unsupported = methods.filter((m) => !supportedMethods.has(m));
831
+ if (unsupported.length === 0) {
832
+ supportedSchemas++;
833
+ } else {
834
+ for (const m of unsupported) {
835
+ const pattern = `.${m}()`;
836
+ if (!unsupportedPatterns.includes(pattern)) {
837
+ unsupportedPatterns.push(pattern);
838
+ }
839
+ }
840
+ }
841
+ }
842
+ const totalSchemas = schemas.filter(
843
+ (s) => s.library === from || s.library === "unknown"
844
+ ).length;
845
+ const readinessPercent = totalSchemas > 0 ? Math.round(supportedSchemas / totalSchemas * 100) : 100;
846
+ const estimatedManualFixes = totalSchemas - supportedSchemas;
847
+ let riskLevel;
848
+ if (readinessPercent >= 90) riskLevel = "low";
849
+ else if (readinessPercent >= 70) riskLevel = "medium";
850
+ else if (readinessPercent >= 50) riskLevel = "high";
851
+ else riskLevel = "critical";
852
+ return {
853
+ from,
854
+ to,
855
+ totalSchemas,
856
+ supportedSchemas,
857
+ unsupportedPatterns,
858
+ estimatedManualFixes,
859
+ readinessPercent,
860
+ riskLevel
861
+ };
862
+ }
863
+ detectLibraryVersions(projectPath) {
864
+ const versions = [];
865
+ const pkgPath = join3(projectPath, "package.json");
866
+ if (!existsSync3(pkgPath)) return versions;
867
+ try {
868
+ const pkg = JSON.parse(readFileSync3(pkgPath, "utf-8"));
869
+ const knownLibs = ["zod", "yup", "joi", "io-ts", "valibot"];
870
+ const allDeps = {
871
+ ...pkg.dependencies,
872
+ ...pkg.devDependencies
873
+ };
874
+ for (const lib of knownLibs) {
875
+ const ver = allDeps[lib];
876
+ if (ver) {
877
+ versions.push({ library: lib, version: ver });
878
+ }
879
+ }
880
+ } catch {
881
+ }
882
+ return versions;
883
+ }
884
+ generateDetailedResult(complexities, projectPath, readiness) {
885
+ const scores = complexities.map((c) => c.score);
886
+ const averageComplexity = scores.length > 0 ? Math.round(scores.reduce((a, b) => a + b, 0) / scores.length) : 0;
887
+ const maxComplexity = scores.length > 0 ? Math.max(...scores) : 0;
888
+ return {
889
+ complexities,
890
+ averageComplexity,
891
+ maxComplexity,
892
+ libraryVersions: this.detectLibraryVersions(projectPath),
893
+ readiness
894
+ };
895
+ }
896
+ computeComplexity(varDecl, sourceFile, library) {
897
+ const initializer = varDecl.getInitializer();
898
+ if (!initializer) {
899
+ return {
900
+ schemaName: varDecl.getName(),
901
+ filePath: sourceFile.getFilePath(),
902
+ library,
903
+ lineNumber: varDecl.getStartLineNumber(),
904
+ chainLength: 0,
905
+ nestedDepth: 0,
906
+ validationCount: 0,
907
+ score: 0,
908
+ level: "low"
909
+ };
910
+ }
911
+ const text = initializer.getText();
912
+ const chainLength = this.countChainLength(text);
913
+ const nestedDepth = this.countNestedDepth(text, library);
914
+ const validationCount = this.countValidations(text);
915
+ const rawScore = chainLength * COMPLEXITY_CHAIN_WEIGHT + nestedDepth * COMPLEXITY_DEPTH_WEIGHT + validationCount * COMPLEXITY_VALIDATION_WEIGHT;
916
+ const score = Math.min(COMPLEXITY_MAX, rawScore);
917
+ let level;
918
+ if (score <= COMPLEXITY_LOW_THRESHOLD) level = "low";
919
+ else if (score <= COMPLEXITY_MEDIUM_THRESHOLD) level = "medium";
920
+ else if (score <= COMPLEXITY_HIGH_THRESHOLD) level = "high";
921
+ else level = "critical";
922
+ return {
923
+ schemaName: varDecl.getName(),
924
+ filePath: sourceFile.getFilePath(),
925
+ library,
926
+ lineNumber: varDecl.getStartLineNumber(),
927
+ chainLength,
928
+ nestedDepth,
929
+ validationCount,
930
+ score,
931
+ level
932
+ };
933
+ }
934
+ countChainLength(text) {
935
+ const methodPattern = /\.\w+\(/g;
936
+ const matches = text.match(methodPattern);
937
+ return matches ? matches.length : 0;
938
+ }
939
+ countNestedDepth(text, library) {
940
+ const prefix = this.getLibraryPrefix(library);
941
+ if (!prefix) return 0;
942
+ const pattern = new RegExp(`\\b${this.escapeRegex(prefix)}\\.`, "g");
943
+ const matches = text.match(pattern);
944
+ return matches ? Math.max(0, matches.length - 1) : 0;
945
+ }
946
+ countValidations(text) {
947
+ const validationMethods = [
948
+ "min",
949
+ "max",
950
+ "email",
951
+ "url",
952
+ "uuid",
953
+ "regex",
954
+ "trim",
955
+ "length",
956
+ "positive",
957
+ "negative",
958
+ "int",
959
+ "integer",
960
+ "finite",
961
+ "nonnegative",
962
+ "nonpositive",
963
+ "required",
964
+ "optional",
965
+ "nullable",
966
+ "nullish",
967
+ "refine",
968
+ "superRefine",
969
+ "transform",
970
+ "nonempty",
971
+ "ip",
972
+ "cuid",
973
+ "startsWith",
974
+ "endsWith",
975
+ "includes",
976
+ "datetime",
977
+ "date"
978
+ ];
979
+ let count = 0;
980
+ for (const method of validationMethods) {
981
+ const pattern = new RegExp(`\\.${method}\\(`, "g");
982
+ const matches = text.match(pattern);
983
+ if (matches) count += matches.length;
984
+ }
985
+ return count;
986
+ }
987
+ extractMethodNames(code) {
988
+ const methodPattern = /\.(\w+)\(/g;
989
+ const methods = [];
990
+ for (const match of code.matchAll(methodPattern)) {
991
+ if (match[1]) methods.push(match[1]);
992
+ }
993
+ return methods;
994
+ }
995
+ getLibraryPrefix(library) {
996
+ switch (library) {
997
+ case "zod":
998
+ case "zod-v3":
999
+ return "z";
1000
+ case "yup":
1001
+ return "yup";
1002
+ case "joi":
1003
+ return "Joi";
1004
+ case "io-ts":
1005
+ return "t";
1006
+ case "valibot":
1007
+ return "v";
1008
+ default:
1009
+ return "";
1010
+ }
1011
+ }
1012
+ escapeRegex(str) {
1013
+ return str.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
1014
+ }
1015
+ detectFileLibrary(sourceFile) {
1016
+ for (const imp of sourceFile.getImportDeclarations()) {
1017
+ const lib = detectSchemaLibrary(imp.getModuleSpecifierValue());
1018
+ if (lib !== "unknown") return lib;
1019
+ }
1020
+ return "unknown";
1021
+ }
1022
+ isSchemaExpression(node, library) {
1023
+ const text = node.getText();
1024
+ switch (library) {
1025
+ case "zod":
1026
+ return text.includes("z.") || text.includes("zod.");
1027
+ case "yup":
1028
+ return text.includes("yup.") || text.includes("Yup.");
1029
+ case "joi":
1030
+ return text.includes("Joi.") || text.includes("joi.");
1031
+ case "io-ts":
1032
+ return text.includes("t.") && node.getSourceFile().getText().includes("from 'io-ts'");
1033
+ case "valibot":
1034
+ return text.includes("v.") || text.includes("valibot.");
1035
+ default:
1036
+ return false;
1037
+ }
1038
+ }
1039
+ };
1040
+
1041
+ // src/governance.ts
1042
+ var GovernanceEngine = class {
1043
+ rules = /* @__PURE__ */ new Map();
1044
+ configure(rules) {
1045
+ this.rules.clear();
1046
+ for (const [name, config] of Object.entries(rules)) {
1047
+ if (config.enabled !== false) {
1048
+ this.rules.set(name, config);
1049
+ }
1050
+ }
1051
+ }
1052
+ analyze(project) {
1053
+ const violations = [];
1054
+ let schemasChecked = 0;
1055
+ let filesScanned = 0;
1056
+ for (const sourceFile of project.getSourceFiles()) {
1057
+ const library = this.detectFileLibrary(sourceFile);
1058
+ if (library === "unknown") continue;
1059
+ filesScanned++;
1060
+ for (const varDecl of sourceFile.getVariableDeclarations()) {
1061
+ const initializer = varDecl.getInitializer();
1062
+ if (!initializer) continue;
1063
+ const text = initializer.getText();
1064
+ if (!this.isSchemaExpression(text, library)) continue;
1065
+ schemasChecked++;
1066
+ const filePath = sourceFile.getFilePath();
1067
+ const lineNumber = varDecl.getStartLineNumber();
1068
+ const schemaName = varDecl.getName();
1069
+ if (this.rules.has("naming-convention")) {
1070
+ const config = this.rules.get("naming-convention") ?? {};
1071
+ const pattern = config.pattern || ".*Schema$";
1072
+ if (!new RegExp(pattern).test(schemaName)) {
1073
+ violations.push({
1074
+ rule: "naming-convention",
1075
+ message: `Schema "${schemaName}" does not match naming pattern: ${pattern}`,
1076
+ filePath,
1077
+ lineNumber,
1078
+ schemaName,
1079
+ severity: "warning",
1080
+ fixable: true
1081
+ });
1082
+ }
1083
+ }
1084
+ if (this.rules.has("max-complexity")) {
1085
+ const config = this.rules.get("max-complexity") ?? {};
1086
+ const threshold = config.threshold ?? 80;
1087
+ const chainLength = (text.match(/\.\w+\(/g) || []).length;
1088
+ const complexity = chainLength * 5;
1089
+ if (complexity > threshold) {
1090
+ violations.push({
1091
+ rule: "max-complexity",
1092
+ message: `Schema "${schemaName}" exceeds complexity threshold (${complexity} > ${threshold})`,
1093
+ filePath,
1094
+ lineNumber,
1095
+ schemaName,
1096
+ severity: "warning",
1097
+ fixable: false
1098
+ });
1099
+ }
1100
+ }
1101
+ if (this.rules.has("no-any")) {
1102
+ if (text.includes(".any()")) {
1103
+ violations.push({
1104
+ rule: "no-any",
1105
+ message: `Schema "${schemaName}" uses .any() \u2014 consider using a specific type`,
1106
+ filePath,
1107
+ lineNumber,
1108
+ schemaName,
1109
+ severity: "error",
1110
+ fixable: false
1111
+ });
1112
+ }
1113
+ }
1114
+ if (this.rules.has("required-validations")) {
1115
+ if (text.includes(".string()") && !text.includes(".max(")) {
1116
+ violations.push({
1117
+ rule: "required-validations",
1118
+ message: `Schema "${schemaName}" has string() without .max() \u2014 consider adding a max length`,
1119
+ filePath,
1120
+ lineNumber,
1121
+ schemaName,
1122
+ severity: "warning",
1123
+ fixable: true
1124
+ });
1125
+ }
1126
+ }
1127
+ }
1128
+ }
1129
+ return {
1130
+ violations,
1131
+ filesScanned,
1132
+ schemasChecked,
1133
+ passed: violations.filter((v) => v.severity === "error").length === 0
1134
+ };
1135
+ }
1136
+ detectFileLibrary(sourceFile) {
1137
+ for (const imp of sourceFile.getImportDeclarations()) {
1138
+ const lib = detectSchemaLibrary(imp.getModuleSpecifierValue());
1139
+ if (lib !== "unknown") return lib;
1140
+ }
1141
+ return "unknown";
1142
+ }
1143
+ isSchemaExpression(text, library) {
1144
+ switch (library) {
1145
+ case "zod":
1146
+ return text.includes("z.") || text.includes("zod.");
1147
+ case "yup":
1148
+ return text.includes("yup.") || text.includes("Yup.");
1149
+ case "joi":
1150
+ return text.includes("Joi.") || text.includes("joi.");
1151
+ case "io-ts":
1152
+ return text.includes("t.");
1153
+ case "valibot":
1154
+ return text.includes("v.") || text.includes("valibot.");
1155
+ default:
1156
+ return false;
1157
+ }
1158
+ }
1159
+ };
1160
+
1161
+ // src/incremental.ts
1162
+ import { existsSync as existsSync4, mkdirSync, readFileSync as readFileSync4, writeFileSync } from "fs";
1163
+ import { join as join4 } from "path";
1164
+ var STATE_DIR = ".schemashift";
1165
+ var STATE_FILE = "incremental.json";
1166
+ var IncrementalTracker = class {
1167
+ stateDir;
1168
+ statePath;
1169
+ constructor(projectPath) {
1170
+ this.stateDir = join4(projectPath, STATE_DIR);
1171
+ this.statePath = join4(this.stateDir, STATE_FILE);
1172
+ }
1173
+ start(files, from, to) {
1174
+ const state = {
1175
+ migrationId: `incremental-${Date.now()}`,
1176
+ from,
1177
+ to,
1178
+ startedAt: (/* @__PURE__ */ new Date()).toISOString(),
1179
+ completedFiles: [],
1180
+ remainingFiles: [...files],
1181
+ failedFiles: []
1182
+ };
1183
+ this.saveState(state);
1184
+ return state;
1185
+ }
1186
+ markComplete(filePath) {
1187
+ const state = this.getState();
1188
+ if (!state) return;
1189
+ state.remainingFiles = state.remainingFiles.filter((f) => f !== filePath);
1190
+ state.failedFiles = state.failedFiles.filter((f) => f !== filePath);
1191
+ if (!state.completedFiles.includes(filePath)) {
1192
+ state.completedFiles.push(filePath);
1193
+ }
1194
+ this.saveState(state);
1195
+ }
1196
+ markFailed(filePath) {
1197
+ const state = this.getState();
1198
+ if (!state) return;
1199
+ state.remainingFiles = state.remainingFiles.filter((f) => f !== filePath);
1200
+ if (!state.failedFiles.includes(filePath)) {
1201
+ state.failedFiles.push(filePath);
1202
+ }
1203
+ this.saveState(state);
1204
+ }
1205
+ getState() {
1206
+ if (!existsSync4(this.statePath)) return null;
1207
+ try {
1208
+ return JSON.parse(readFileSync4(this.statePath, "utf-8"));
1209
+ } catch {
1210
+ return null;
1211
+ }
1212
+ }
1213
+ resume() {
1214
+ return this.getState();
1215
+ }
1216
+ getNextBatch(batchSize) {
1217
+ const state = this.getState();
1218
+ if (!state) return [];
1219
+ return state.remainingFiles.slice(0, batchSize);
1220
+ }
1221
+ getProgress() {
1222
+ const state = this.getState();
1223
+ if (!state) return null;
1224
+ const total = state.completedFiles.length + state.remainingFiles.length + state.failedFiles.length;
1225
+ const percent = total > 0 ? Math.round(state.completedFiles.length / total * 100) : 0;
1226
+ return {
1227
+ completed: state.completedFiles.length,
1228
+ remaining: state.remainingFiles.length,
1229
+ failed: state.failedFiles.length,
1230
+ total,
1231
+ percent
1232
+ };
1233
+ }
1234
+ clear() {
1235
+ if (existsSync4(this.statePath)) {
1236
+ writeFileSync(this.statePath, "");
1237
+ }
1238
+ }
1239
+ saveState(state) {
1240
+ if (!existsSync4(this.stateDir)) {
1241
+ mkdirSync(this.stateDir, { recursive: true });
1242
+ }
1243
+ writeFileSync(this.statePath, JSON.stringify(state, null, 2));
1244
+ }
1245
+ };
1246
+
1247
+ // src/package-updater.ts
1248
+ import { existsSync as existsSync5, readFileSync as readFileSync5, writeFileSync as writeFileSync2 } from "fs";
1249
+ import { join as join5 } from "path";
1250
+ var TARGET_VERSIONS = {
1251
+ "yup->zod": { zod: "^3.24.0" },
1252
+ "joi->zod": { zod: "^3.24.0" },
1253
+ "io-ts->zod": { zod: "^3.24.0" },
1254
+ "zod-v3->v4": { zod: "^4.0.0" },
1255
+ "zod->valibot": { valibot: "^1.0.0" }
1256
+ };
1257
+ var SOURCE_PACKAGES = {
1258
+ "yup->zod": ["yup"],
1259
+ "joi->zod": ["joi", "@hapi/joi"],
1260
+ "io-ts->zod": ["io-ts", "fp-ts"],
1261
+ "zod->valibot": []
1262
+ // zod might still be used elsewhere
1263
+ };
1264
+ var PackageUpdater = class {
1265
+ plan(projectPath, from, to) {
1266
+ const migration = `${from}->${to}`;
1267
+ const add = {};
1268
+ const remove = [];
1269
+ const warnings = [];
1270
+ const pkgPath = join5(projectPath, "package.json");
1271
+ if (!existsSync5(pkgPath)) {
1272
+ warnings.push("No package.json found. Cannot plan dependency updates.");
1273
+ return { add, remove, warnings };
1274
+ }
1275
+ let pkg;
1276
+ try {
1277
+ pkg = JSON.parse(readFileSync5(pkgPath, "utf-8"));
1278
+ } catch {
1279
+ warnings.push("Could not parse package.json.");
1280
+ return { add, remove, warnings };
1281
+ }
1282
+ const allDeps = { ...pkg.dependencies, ...pkg.devDependencies };
1283
+ const targets = TARGET_VERSIONS[migration];
1284
+ if (targets) {
1285
+ for (const [name, version] of Object.entries(targets)) {
1286
+ const existing = allDeps[name];
1287
+ if (!existing) {
1288
+ add[name] = version;
1289
+ } else if (migration === "zod-v3->v4") {
1290
+ add[name] = version;
1291
+ warnings.push(`zod will be updated from ${existing} to ${version}.`);
1292
+ }
1293
+ }
1294
+ }
1295
+ const sources = SOURCE_PACKAGES[migration];
1296
+ if (sources) {
1297
+ for (const src of sources) {
1298
+ if (allDeps[src]) {
1299
+ remove.push(src);
1300
+ warnings.push(`${src} can be removed after verifying all schemas have been migrated.`);
1301
+ }
1302
+ }
1303
+ }
1304
+ return { add, remove, warnings };
1305
+ }
1306
+ apply(projectPath, plan) {
1307
+ const pkgPath = join5(projectPath, "package.json");
1308
+ if (!existsSync5(pkgPath)) return;
1309
+ const pkgText = readFileSync5(pkgPath, "utf-8");
1310
+ const pkg = JSON.parse(pkgText);
1311
+ if (!pkg.dependencies) pkg.dependencies = {};
1312
+ for (const [name, version] of Object.entries(plan.add)) {
1313
+ if (pkg.devDependencies?.[name]) {
1314
+ pkg.devDependencies[name] = version;
1315
+ } else {
1316
+ pkg.dependencies[name] = version;
1317
+ }
1318
+ }
1319
+ writeFileSync2(pkgPath, `${JSON.stringify(pkg, null, 2)}
1320
+ `);
1321
+ }
1322
+ };
1323
+
1324
+ // src/plugin-loader.ts
1325
+ var PluginLoader = class {
1326
+ async loadPlugins(pluginPaths) {
1327
+ const loaded = [];
1328
+ const errors = [];
1329
+ for (const pluginPath of pluginPaths) {
1330
+ try {
1331
+ const mod = await import(pluginPath);
1332
+ const plugin = mod.default || mod;
1333
+ const validationError = this.validatePlugin(plugin, pluginPath);
1334
+ if (validationError) {
1335
+ errors.push(validationError);
1336
+ continue;
1337
+ }
1338
+ loaded.push(plugin);
1339
+ } catch (err) {
1340
+ errors.push(
1341
+ `Failed to load plugin ${pluginPath}: ${err instanceof Error ? err.message : "Unknown error"}`
1342
+ );
1343
+ }
1344
+ }
1345
+ return { loaded, errors };
1346
+ }
1347
+ validatePlugin(plugin, path) {
1348
+ if (!plugin || typeof plugin !== "object") {
1349
+ return `Plugin ${path}: must export an object`;
1350
+ }
1351
+ const p = plugin;
1352
+ if (typeof p.name !== "string" || !p.name) {
1353
+ return `Plugin ${path}: missing "name" property`;
1354
+ }
1355
+ if (typeof p.version !== "string" || !p.version) {
1356
+ return `Plugin ${path}: missing "version" property`;
1357
+ }
1358
+ if (p.handlers && !Array.isArray(p.handlers)) {
1359
+ return `Plugin ${path}: "handlers" must be an array`;
1360
+ }
1361
+ if (p.rules && !Array.isArray(p.rules)) {
1362
+ return `Plugin ${path}: "rules" must be an array`;
1363
+ }
1364
+ return void 0;
1365
+ }
1366
+ };
1367
+
1368
+ // src/standard-schema.ts
1369
+ import { existsSync as existsSync6, readFileSync as readFileSync6 } from "fs";
1370
+ import { join as join6 } from "path";
1371
+ var STANDARD_SCHEMA_LIBRARIES = {
1372
+ zod: { minMajor: 3, minMinor: 23 },
1373
+ // Zod v3.23+ and v4+
1374
+ valibot: { minMajor: 1, minMinor: 0 },
1375
+ // Valibot v1.0+
1376
+ arktype: { minMajor: 2, minMinor: 0 },
1377
+ // ArkType v2.0+
1378
+ "@effect/schema": { minMajor: 0, minMinor: 0 },
1379
+ // Effect Schema (any version)
1380
+ typebox: { minMajor: 0, minMinor: 34 }
1381
+ // TypeBox v0.34+
1382
+ };
1383
+ function parseVersion(version) {
1384
+ const match = version.match(/(\d+)\.(\d+)/);
1385
+ if (!match?.[1] || !match[2]) return null;
1386
+ return {
1387
+ major: Number.parseInt(match[1], 10),
1388
+ minor: Number.parseInt(match[2], 10)
1389
+ };
1390
+ }
1391
+ function isVersionCompatible(version, minMajor, minMinor) {
1392
+ const parsed = parseVersion(version);
1393
+ if (!parsed) return false;
1394
+ if (parsed.major > minMajor) return true;
1395
+ if (parsed.major === minMajor && parsed.minor >= minMinor) return true;
1396
+ return false;
1397
+ }
1398
+ function detectStandardSchema(projectPath) {
1399
+ const pkgPath = join6(projectPath, "package.json");
1400
+ if (!existsSync6(pkgPath)) {
1401
+ return { detected: false, compatibleLibraries: [], recommendation: "" };
1402
+ }
1403
+ let allDeps = {};
1404
+ try {
1405
+ const pkg = JSON.parse(readFileSync6(pkgPath, "utf-8"));
1406
+ allDeps = { ...pkg.dependencies, ...pkg.devDependencies };
1407
+ } catch {
1408
+ return { detected: false, compatibleLibraries: [], recommendation: "" };
1409
+ }
1410
+ const hasExplicitStandardSchema = "@standard-schema/spec" in allDeps;
1411
+ const compatibleLibraries = [];
1412
+ for (const [lib, { minMajor, minMinor }] of Object.entries(STANDARD_SCHEMA_LIBRARIES)) {
1413
+ const version = allDeps[lib];
1414
+ if (version && isVersionCompatible(version, minMajor, minMinor)) {
1415
+ compatibleLibraries.push({ name: lib, version });
1416
+ }
1417
+ }
1418
+ const detected = hasExplicitStandardSchema || compatibleLibraries.length > 0;
1419
+ let recommendation = "";
1420
+ if (detected && compatibleLibraries.length > 1) {
1421
+ recommendation = "Multiple Standard Schema-compatible libraries detected. These can interoperate through the Standard Schema interface, reducing the need for library-specific adapters in tools like tRPC, TanStack Form, and TanStack Router.";
1422
+ } else if (detected && compatibleLibraries.length === 1) {
1423
+ recommendation = `${compatibleLibraries[0]?.name} supports Standard Schema, enabling interoperability with ecosystem tools that adopt the Standard Schema interface (tRPC, TanStack Form, etc.).`;
1424
+ } else if (hasExplicitStandardSchema) {
1425
+ recommendation = "Standard Schema spec detected. Ensure your validation library supports Standard Schema for maximum interoperability.";
1426
+ }
1427
+ return { detected, compatibleLibraries, recommendation };
1428
+ }
1429
+
138
1430
  // src/transform.ts
139
1431
  var TransformEngine = class {
140
1432
  handlers = /* @__PURE__ */ new Map();
@@ -168,9 +1460,28 @@ var TransformEngine = class {
168
1460
  }
169
1461
  };
170
1462
  export {
1463
+ CompatibilityAnalyzer,
1464
+ DetailedAnalyzer,
1465
+ EcosystemAnalyzer,
1466
+ GovernanceEngine,
1467
+ IncrementalTracker,
1468
+ MigrationChain,
1469
+ PackageUpdater,
1470
+ PluginLoader,
171
1471
  SchemaAnalyzer,
1472
+ SchemaDependencyResolver,
172
1473
  TransformEngine,
1474
+ buildCallChain,
1475
+ detectFormLibraries,
173
1476
  detectSchemaLibrary,
174
- loadConfig
1477
+ detectStandardSchema,
1478
+ isInsideComment,
1479
+ isInsideStringLiteral,
1480
+ loadConfig,
1481
+ parseCallChain,
1482
+ shouldSuppressWarning,
1483
+ startsWithBase,
1484
+ transformMethodChain,
1485
+ validateConfig
175
1486
  };
176
1487
  //# sourceMappingURL=index.js.map