schemashift-cli 0.7.0 → 0.9.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/README.md CHANGED
@@ -33,6 +33,10 @@ schemashift analyze <path> [options]
33
33
  | `--detailed` | Full stats with complexity scoring | Individual+ |
34
34
  | `--readiness <path>` | Migration readiness report, e.g. `yup->zod` | Individual+ |
35
35
  | `--complexity` | Per-schema complexity scores | Individual+ |
36
+ | `--behavioral <migration>` | Behavioral difference warnings, e.g. `yup->zod` | Free |
37
+ | `--bundle <migration>` | Bundle size estimation, e.g. `zod->valibot` | Individual+ |
38
+ | `--performance <migration>` | Performance impact analysis, e.g. `zod-v3->v4` | Individual+ |
39
+ | `--dedup` | Detect duplicate type definitions | Individual+ |
36
40
 
37
41
  ### `migrate`
38
42
 
@@ -73,6 +77,8 @@ schemashift migrate <path> [options]
73
77
  | `--compat-check` | Run compatibility check before migration | Pro+ |
74
78
  | `--fail-on-warnings` | Exit 1 if any warnings | Team |
75
79
  | `--max-risk-score <n>` | Exit 1 if any file exceeds risk score | Team |
80
+ | `--scaffold-tests` | Generate validation tests after migration | Pro+ |
81
+ | `--audit` | Enable migration audit logging | Team |
76
82
 
77
83
  ### `watch`
78
84
 
@@ -193,6 +199,18 @@ schemashift migrate ./src --chain yup->zod->valibot
193
199
  # CI mode
194
200
  schemashift migrate ./src --from yup --to zod --ci --report json
195
201
 
202
+ # Backward migration: Zod→Yup
203
+ schemashift migrate ./src --from zod --to yup
204
+
205
+ # Backward migration: Valibot→Zod
206
+ schemashift migrate ./src --from valibot --to zod
207
+
208
+ # Analyze with behavioral warnings and bundle estimation
209
+ schemashift analyze ./src --behavioral yup->zod --bundle yup->zod
210
+
211
+ # Migrate with test scaffolding
212
+ schemashift migrate ./src --from yup --to zod --scaffold-tests
213
+
196
214
  # Rollback last migration
197
215
  schemashift rollback
198
216
  ```
package/dist/cli.js CHANGED
@@ -10,16 +10,23 @@ import { existsSync as existsSync4, readFileSync as readFileSync4, statSync, wri
10
10
  import { dirname as dirname2, join as join3, resolve as resolve2 } from "path";
11
11
  import { fileURLToPath } from "url";
12
12
  import {
13
+ BehavioralWarningAnalyzer,
14
+ BundleEstimator,
13
15
  CompatibilityAnalyzer,
14
16
  DetailedAnalyzer,
15
17
  detectFormLibraries,
16
18
  GovernanceEngine,
19
+ IncrementalTracker,
17
20
  loadConfig,
21
+ MigrationAuditLog,
18
22
  MigrationChain,
23
+ PerformanceAnalyzer,
19
24
  PluginLoader,
20
25
  SchemaAnalyzer,
21
26
  SchemaDependencyResolver,
22
- TransformEngine
27
+ TestScaffolder,
28
+ TransformEngine,
29
+ TypeDedupDetector
23
30
  } from "@schemashift/core";
24
31
  import { createIoTsToZodHandler } from "@schemashift/io-ts-zod";
25
32
  import { createJoiToZodHandler } from "@schemashift/joi-zod";
@@ -35824,7 +35831,16 @@ var TIER_FEATURES = {
35824
35831
  customPlugins: false,
35825
35832
  governance: false,
35826
35833
  failOnWarnings: false,
35827
- maxRiskScoreThreshold: false
35834
+ maxRiskScoreThreshold: false,
35835
+ formResolverMigration: false,
35836
+ complexityEstimator: false,
35837
+ monorepoAware: false,
35838
+ behavioralWarnings: true,
35839
+ bundleEstimator: false,
35840
+ performanceAnalyzer: false,
35841
+ typeDeduplication: false,
35842
+ testScaffolding: false,
35843
+ auditLogging: false
35828
35844
  },
35829
35845
  [
35830
35846
  "individual"
@@ -35848,7 +35864,16 @@ var TIER_FEATURES = {
35848
35864
  customPlugins: false,
35849
35865
  governance: false,
35850
35866
  failOnWarnings: false,
35851
- maxRiskScoreThreshold: false
35867
+ maxRiskScoreThreshold: false,
35868
+ formResolverMigration: true,
35869
+ complexityEstimator: true,
35870
+ monorepoAware: false,
35871
+ behavioralWarnings: true,
35872
+ bundleEstimator: true,
35873
+ performanceAnalyzer: true,
35874
+ typeDeduplication: true,
35875
+ testScaffolding: false,
35876
+ auditLogging: false
35852
35877
  },
35853
35878
  [
35854
35879
  "pro"
@@ -35861,7 +35886,9 @@ var TIER_FEATURES = {
35861
35886
  "zod-v3->v4",
35862
35887
  "io-ts->zod",
35863
35888
  "zod->valibot",
35864
- "any->valibot"
35889
+ "any->valibot",
35890
+ "zod->yup",
35891
+ "valibot->zod"
35865
35892
  ],
35866
35893
  devices: 4,
35867
35894
  ciSupport: true,
@@ -35879,7 +35906,16 @@ var TIER_FEATURES = {
35879
35906
  customPlugins: false,
35880
35907
  governance: false,
35881
35908
  failOnWarnings: false,
35882
- maxRiskScoreThreshold: false
35909
+ maxRiskScoreThreshold: false,
35910
+ formResolverMigration: true,
35911
+ complexityEstimator: true,
35912
+ monorepoAware: true,
35913
+ behavioralWarnings: true,
35914
+ bundleEstimator: true,
35915
+ performanceAnalyzer: true,
35916
+ typeDeduplication: true,
35917
+ testScaffolding: true,
35918
+ auditLogging: false
35883
35919
  },
35884
35920
  [
35885
35921
  "team"
@@ -35892,7 +35928,9 @@ var TIER_FEATURES = {
35892
35928
  "zod-v3->v4",
35893
35929
  "io-ts->zod",
35894
35930
  "any->valibot",
35895
- "zod->valibot"
35931
+ "zod->valibot",
35932
+ "zod->yup",
35933
+ "valibot->zod"
35896
35934
  ],
35897
35935
  devices: Infinity,
35898
35936
  ciSupport: true,
@@ -35910,7 +35948,16 @@ var TIER_FEATURES = {
35910
35948
  customPlugins: true,
35911
35949
  governance: true,
35912
35950
  failOnWarnings: true,
35913
- maxRiskScoreThreshold: true
35951
+ maxRiskScoreThreshold: true,
35952
+ formResolverMigration: true,
35953
+ complexityEstimator: true,
35954
+ monorepoAware: true,
35955
+ behavioralWarnings: true,
35956
+ bundleEstimator: true,
35957
+ performanceAnalyzer: true,
35958
+ typeDeduplication: true,
35959
+ testScaffolding: true,
35960
+ auditLogging: true
35914
35961
  }
35915
35962
  };
35916
35963
  function canUseMigration(tier, from, to) {
@@ -36187,9 +36234,9 @@ var LicenseManager = class {
36187
36234
  };
36188
36235
 
36189
36236
  // src/cli.ts
36190
- import { createYupToZodHandler } from "@schemashift/yup-zod";
36237
+ import { createYupToZodHandler, createZodToYupHandler } from "@schemashift/yup-zod";
36191
36238
  import { createZodV3ToV4Handler } from "@schemashift/zod-v3-v4";
36192
- import { createZodToValibotHandler } from "@schemashift/zod-valibot";
36239
+ import { createValibotToZodHandler, createZodToValibotHandler } from "@schemashift/zod-valibot";
36193
36240
  import { Command } from "commander";
36194
36241
  import { glob as glob2 } from "glob";
36195
36242
  import { Listr } from "listr2";
@@ -36871,6 +36918,12 @@ var WatchMode = class {
36871
36918
  console.log(pc3.cyan(`
36872
36919
  Watching ${files.length} files for changes...
36873
36920
  `));
36921
+ if (options.dependents && options.dependents.size > 0) {
36922
+ console.log(
36923
+ pc3.dim(`Dependency-aware mode: ${options.dependents.size} files with dependents
36924
+ `)
36925
+ );
36926
+ }
36874
36927
  console.log(pc3.dim("Press Ctrl+C to stop\n"));
36875
36928
  const directories = new Set(files.map((f) => f.split("/").slice(0, -1).join("/")));
36876
36929
  for (const dir of directories) {
@@ -36891,11 +36944,33 @@ Watching ${files.length} files for changes...
36891
36944
  Changed: ${relative2(process.cwd(), fullPath)}`));
36892
36945
  try {
36893
36946
  await options.onTransform(fullPath);
36894
- console.log(pc3.green(`Transformed successfully
36895
- `));
36947
+ console.log(pc3.green(`Transformed successfully`));
36948
+ const dependentFiles = options.dependents?.get(fullPath);
36949
+ if (dependentFiles && dependentFiles.length > 0) {
36950
+ console.log(
36951
+ pc3.cyan(` Re-transforming ${dependentFiles.length} dependent file(s)...`)
36952
+ );
36953
+ for (const depFile of dependentFiles) {
36954
+ try {
36955
+ await options.onTransform(depFile);
36956
+ console.log(pc3.green(` \u2713 ${relative2(process.cwd(), depFile)}`));
36957
+ } catch (error) {
36958
+ console.error(
36959
+ pc3.red(
36960
+ ` \u2717 ${relative2(process.cwd(), depFile)}: ${error instanceof Error ? error.message : String(error)}`
36961
+ )
36962
+ );
36963
+ }
36964
+ }
36965
+ }
36966
+ console.log("");
36896
36967
  } catch (error) {
36897
- console.error(pc3.red(`Transform failed: ${error}
36898
- `));
36968
+ console.error(
36969
+ pc3.red(
36970
+ `Transform failed: ${error instanceof Error ? error.message : String(error)}
36971
+ `
36972
+ )
36973
+ );
36899
36974
  }
36900
36975
  this.debounceTimers.delete(fullPath);
36901
36976
  }, 100)
@@ -36914,6 +36989,21 @@ Changed: ${relative2(process.cwd(), fullPath)}`));
36914
36989
  }
36915
36990
  this.debounceTimers.clear();
36916
36991
  }
36992
+ /**
36993
+ * Build a reverse dependency map (file -> files that depend on it).
36994
+ * Takes the forward dependency map from SchemaDependencyResolver and inverts it.
36995
+ */
36996
+ static buildDependentsMap(dependencies) {
36997
+ const dependents = /* @__PURE__ */ new Map();
36998
+ for (const [file, deps] of dependencies) {
36999
+ for (const dep of deps) {
37000
+ const existing = dependents.get(dep) ?? [];
37001
+ existing.push(file);
37002
+ dependents.set(dep, existing);
37003
+ }
37004
+ }
37005
+ return dependents;
37006
+ }
36917
37007
  matchesPatterns(file, include, exclude) {
36918
37008
  const isIncluded = include.some((p) => minimatch(file, p));
36919
37009
  const isExcluded = exclude.some((p) => minimatch(file, p));
@@ -36932,6 +37022,8 @@ engine.registerHandler("joi", "zod", createJoiToZodHandler());
36932
37022
  engine.registerHandler("io-ts", "zod", createIoTsToZodHandler());
36933
37023
  engine.registerHandler("zod-v3", "v4", createZodV3ToV4Handler());
36934
37024
  engine.registerHandler("zod", "valibot", createZodToValibotHandler());
37025
+ engine.registerHandler("zod", "yup", createZodToYupHandler());
37026
+ engine.registerHandler("valibot", "zod", createValibotToZodHandler());
36935
37027
  var POLAR_URL = "https://schemashift.qwady.app";
36936
37028
  program.name("schemashift").version(pkg.version).description("TypeScript schema migration CLI");
36937
37029
  program.command("init").description("Create .schemashiftrc.json config file").option("-f, --force", "Overwrite existing config").action((options) => {
@@ -36961,7 +37053,10 @@ program.command("init").description("Create .schemashiftrc.json config file").op
36961
37053
  console.log(pc4.green("Created .schemashiftrc.json"));
36962
37054
  console.log(pc4.dim("Edit the file to customize your migration settings."));
36963
37055
  });
36964
- program.command("analyze <path>").description("Analyze schemas in your project").option("--json", "Output as JSON").option("-v, --verbose", "Show detailed analysis").option("--detailed", "Show complexity analysis (Individual+)").option("--readiness <migration>", "Check migration readiness, e.g. yup->zod (Individual+)").option("--complexity", "Show per-schema complexity scores (Individual+)").action(async (targetPath, options) => {
37056
+ program.command("analyze <path>").description("Analyze schemas in your project").option("--json", "Output as JSON").option("-v, --verbose", "Show detailed analysis").option("--detailed", "Show complexity analysis (Individual+)").option("--readiness <migration>", "Check migration readiness, e.g. yup->zod (Individual+)").option("--complexity", "Show per-schema complexity scores (Individual+)").option("--behavioral <migration>", "Show behavioral difference warnings, e.g. yup->zod").option("--bundle <migration>", "Show bundle size estimation, e.g. zod->valibot (Individual+)").option(
37057
+ "--performance <migration>",
37058
+ "Show performance impact analysis, e.g. zod-v3->v4 (Individual+)"
37059
+ ).option("--dedup", "Detect duplicate type definitions (Individual+)").action(async (targetPath, options) => {
36965
37060
  const analyzer = new SchemaAnalyzer();
36966
37061
  let files;
36967
37062
  if (existsSync4(targetPath) && statSync(targetPath).isFile()) {
@@ -37142,13 +37237,145 @@ Migration Readiness: ${from} -> ${to}
37142
37237
  }
37143
37238
  }
37144
37239
  }
37240
+ if (options.behavioral) {
37241
+ const migParts = options.behavioral.split("->");
37242
+ if (migParts.length !== 2) {
37243
+ console.error(pc4.red("Invalid format. Use: --behavioral yup->zod"));
37244
+ } else {
37245
+ const [bFrom, bTo] = migParts;
37246
+ const behavioralAnalyzer = new BehavioralWarningAnalyzer();
37247
+ const sourceFiles = analyzer.getProject().getSourceFiles();
37248
+ const behavioralResult = behavioralAnalyzer.analyze(
37249
+ sourceFiles,
37250
+ bFrom,
37251
+ bTo
37252
+ );
37253
+ if (behavioralResult.warnings.length > 0) {
37254
+ console.log(pc4.bold("\nBehavioral Difference Warnings\n"));
37255
+ const byCategory = /* @__PURE__ */ new Map();
37256
+ for (const w of behavioralResult.warnings) {
37257
+ const existing = byCategory.get(w.category) ?? [];
37258
+ existing.push(w);
37259
+ byCategory.set(w.category, existing);
37260
+ }
37261
+ for (const [category, catWarnings] of byCategory) {
37262
+ console.log(pc4.yellow(` [${category}] ${catWarnings.length} warning(s)`));
37263
+ for (const w of catWarnings.slice(0, 5)) {
37264
+ console.log(` ${w.message}`);
37265
+ console.log(` ${pc4.dim(w.detail)}`);
37266
+ }
37267
+ if (catWarnings.length > 5) {
37268
+ console.log(pc4.dim(` ... and ${catWarnings.length - 5} more`));
37269
+ }
37270
+ }
37271
+ } else {
37272
+ console.log(pc4.green("\nNo behavioral difference warnings found."));
37273
+ }
37274
+ }
37275
+ }
37276
+ if (options.bundle) {
37277
+ const validation = await licenseManager.validate();
37278
+ const tier = validation.license?.features || TIER_FEATURES[LicenseTier.FREE];
37279
+ if (!tier.bundleEstimator) {
37280
+ console.error(pc4.red("\nBundle estimation requires Individual tier or higher."));
37281
+ console.log(`Upgrade at: ${pc4.cyan(POLAR_URL)}`);
37282
+ } else {
37283
+ const migParts = options.bundle.split("->");
37284
+ if (migParts.length !== 2) {
37285
+ console.error(pc4.red("Invalid format. Use: --bundle zod->valibot"));
37286
+ } else {
37287
+ const [bFrom, bTo] = migParts;
37288
+ const bundleEstimator = new BundleEstimator();
37289
+ const sourceFiles = analyzer.getProject().getSourceFiles();
37290
+ const estimate = bundleEstimator.estimate(
37291
+ sourceFiles,
37292
+ bFrom,
37293
+ bTo
37294
+ );
37295
+ console.log(pc4.bold("\nBundle Size Estimation\n"));
37296
+ console.log(
37297
+ ` ${estimate.from.library}: ~${pc4.cyan(`${estimate.from.minifiedGzipKb}kB`)}`
37298
+ );
37299
+ console.log(` ${estimate.to.library}: ~${pc4.cyan(`${estimate.to.minifiedGzipKb}kB`)}`);
37300
+ console.log(
37301
+ ` Delta: ${pc4.yellow(`${estimate.estimatedDelta > 0 ? "+" : ""}${estimate.estimatedDelta.toFixed(1)}kB (${estimate.deltaPercent.toFixed(0)}%)`)}`
37302
+ );
37303
+ console.log(`
37304
+ ${pc4.dim(estimate.summary)}`);
37305
+ }
37306
+ }
37307
+ }
37308
+ if (options.performance) {
37309
+ const validation = await licenseManager.validate();
37310
+ const tier = validation.license?.features || TIER_FEATURES[LicenseTier.FREE];
37311
+ if (!tier.performanceAnalyzer) {
37312
+ console.error(pc4.red("\nPerformance analysis requires Individual tier or higher."));
37313
+ console.log(`Upgrade at: ${pc4.cyan(POLAR_URL)}`);
37314
+ } else {
37315
+ const migParts = options.performance.split("->");
37316
+ if (migParts.length !== 2) {
37317
+ console.error(pc4.red("Invalid format. Use: --performance zod-v3->v4"));
37318
+ } else {
37319
+ const [pFrom, pTo] = migParts;
37320
+ const perfAnalyzer = new PerformanceAnalyzer();
37321
+ const sourceFiles = analyzer.getProject().getSourceFiles();
37322
+ const perfResult = perfAnalyzer.analyze(
37323
+ sourceFiles,
37324
+ pFrom,
37325
+ pTo
37326
+ );
37327
+ if (perfResult.warnings.length > 0) {
37328
+ console.log(pc4.bold("\nPerformance Impact Analysis\n"));
37329
+ for (const w of perfResult.warnings) {
37330
+ const color = w.severity === "error" ? pc4.red : w.severity === "warning" ? pc4.yellow : pc4.dim;
37331
+ console.log(` ${color(`[${w.severity}]`)} ${w.message}`);
37332
+ console.log(` ${pc4.dim(w.detail)}`);
37333
+ }
37334
+ console.log(`
37335
+ ${pc4.dim(perfResult.recommendation)}`);
37336
+ } else {
37337
+ console.log(pc4.green("\nNo performance concerns detected."));
37338
+ }
37339
+ }
37340
+ }
37341
+ }
37342
+ if (options.dedup) {
37343
+ const validation = await licenseManager.validate();
37344
+ const tier = validation.license?.features || TIER_FEATURES[LicenseTier.FREE];
37345
+ if (!tier.typeDeduplication) {
37346
+ console.error(pc4.red("\nType deduplication detection requires Individual tier or higher."));
37347
+ console.log(`Upgrade at: ${pc4.cyan(POLAR_URL)}`);
37348
+ } else {
37349
+ const dedupDetector = new TypeDedupDetector();
37350
+ const sourceFiles = analyzer.getProject().getSourceFiles();
37351
+ const dedupResult = dedupDetector.detect(sourceFiles);
37352
+ if (dedupResult.candidates.length > 0) {
37353
+ console.log(pc4.bold("\nDuplicate Type Candidates\n"));
37354
+ for (const c of dedupResult.candidates.slice(0, 10)) {
37355
+ console.log(` ${pc4.cyan(c.typeName)} <-> ${pc4.cyan(c.schemaName)}`);
37356
+ console.log(` Type: ${pc4.dim(c.typeFilePath)}:${c.typeLineNumber}`);
37357
+ console.log(` Schema: ${pc4.dim(c.schemaFilePath)}:${c.schemaLineNumber}`);
37358
+ console.log(` Confidence: ${pc4.dim(c.confidence)} | ${pc4.dim(c.suggestion)}`);
37359
+ }
37360
+ if (dedupResult.candidates.length > 10) {
37361
+ console.log(pc4.dim(` ... and ${dedupResult.candidates.length - 10} more`));
37362
+ }
37363
+ } else {
37364
+ console.log(pc4.green("\nNo duplicate type candidates found."));
37365
+ }
37366
+ }
37367
+ }
37145
37368
  });
37146
37369
  program.command("migrate <path>").description("Migrate schemas from one library to another").requiredOption("-f, --from <library>", "Source library (yup, joi, io-ts, zod-v3, zod)").requiredOption("-t, --to <library>", "Target library (zod, v4, valibot)").option("-d, --dry-run", "Preview changes without writing files").option("-v, --verbose", "Show detailed transformation info").option("-c, --config <path>", "Path to config file").option("--report <format>", "Generate report (json, html, csv)").option("--report-output <path>", "Report output path").option("--git-branch", "Create git branch for changes").option("--git-commit", "Auto-commit changes").option("--no-backup", "Skip backup creation").option("--yes", "Skip confirmation prompt").option("--ci", "CI mode (non-interactive, exit code on failure)").option("--cross-file", "Resolve cross-file schema dependencies (Pro+)").option("--chain <path>", "Chain migrations, e.g. yup->zod->valibot (Pro+)").option("--compat-check", "Run compatibility check before migration (Pro+)").option("--fail-on-warnings", "Exit 1 if any warnings (Team+)").option(
37147
37370
  "--max-risk-score <score>",
37148
37371
  "Exit 1 if any file exceeds risk score (Team+)",
37149
37372
  Number.parseInt
37150
- ).action(async (targetPath, options) => {
37373
+ ).option("--incremental", "Enable incremental migration (file-by-file with progress tracking)").option("--resume", "Resume a previously started incremental migration").option("--incremental-status", "Show incremental migration progress").option("--scaffold-tests", "Generate validation tests after migration (Pro+)").option("--audit", "Enable migration audit logging (Team+)").action(async (targetPath, options) => {
37151
37374
  const startTime = Date.now();
37375
+ if (options.maxRiskScore !== void 0 && Number.isNaN(options.maxRiskScore)) {
37376
+ console.error(pc4.red("--max-risk-score must be a valid number."));
37377
+ process.exit(1);
37378
+ }
37152
37379
  let config2;
37153
37380
  try {
37154
37381
  config2 = await loadConfig(options.config);
@@ -37298,6 +37525,41 @@ Compatibility score: ${pc4.cyan(compatResult.overallScore.toString())}%`);
37298
37525
  console.log(pc4.yellow("No files found matching patterns."));
37299
37526
  process.exit(0);
37300
37527
  }
37528
+ const incrementalTracker = new IncrementalTracker(resolve2(targetPath));
37529
+ if (options.incrementalStatus) {
37530
+ const progress = incrementalTracker.getProgress();
37531
+ if (!progress) {
37532
+ console.log(pc4.yellow("No incremental migration in progress."));
37533
+ process.exit(0);
37534
+ }
37535
+ console.log(pc4.bold("\nIncremental Migration Status\n"));
37536
+ console.log(`Completed: ${pc4.green(progress.completed.toString())}`);
37537
+ console.log(`Remaining: ${pc4.cyan(progress.remaining.toString())}`);
37538
+ console.log(`Failed: ${pc4.red(progress.failed.toString())}`);
37539
+ console.log(`Total: ${progress.total}`);
37540
+ console.log(`Progress: ${pc4.cyan(`${progress.percent}%`)}`);
37541
+ process.exit(0);
37542
+ }
37543
+ if (options.resume) {
37544
+ const state = incrementalTracker.resume();
37545
+ if (!state) {
37546
+ console.error(pc4.red("No incremental migration to resume."));
37547
+ process.exit(1);
37548
+ }
37549
+ files = state.remainingFiles;
37550
+ console.log(pc4.dim(`Resuming incremental migration: ${state.migrationId}`));
37551
+ const progress = incrementalTracker.getProgress();
37552
+ if (progress) {
37553
+ console.log(
37554
+ pc4.dim(
37555
+ `Progress: ${progress.completed}/${progress.total} completed (${progress.percent}%)`
37556
+ )
37557
+ );
37558
+ }
37559
+ } else if (options.incremental) {
37560
+ incrementalTracker.start(files, options.from, options.to);
37561
+ console.log(pc4.dim("Started incremental migration."));
37562
+ }
37301
37563
  if (files.length > features.maxFiles) {
37302
37564
  console.error(
37303
37565
  pc4.red(`File limit exceeded. Found ${files.length}, max ${features.maxFiles}.`)
@@ -37432,6 +37694,30 @@ Migrating ${displayPath}
37432
37694
  }
37433
37695
  }
37434
37696
  }
37697
+ if (options.incremental || options.resume) {
37698
+ for (const result of results) {
37699
+ if (result.success) {
37700
+ incrementalTracker.markComplete(result.filePath);
37701
+ } else {
37702
+ incrementalTracker.markFailed(result.filePath);
37703
+ }
37704
+ }
37705
+ const progress = incrementalTracker.getProgress();
37706
+ if (progress) {
37707
+ console.log(
37708
+ pc4.bold(`
37709
+ Incremental: ${progress.completed}/${progress.total} (${progress.percent}%)`)
37710
+ );
37711
+ if (progress.remaining > 0) {
37712
+ console.log(
37713
+ pc4.dim(`Run with --resume to continue. ${progress.remaining} files remaining.`)
37714
+ );
37715
+ }
37716
+ if (progress.failed > 0) {
37717
+ console.log(pc4.yellow(`${progress.failed} file(s) failed. Run with --resume to retry.`));
37718
+ }
37719
+ }
37720
+ }
37435
37721
  if (options.gitCommit && git.isAvailable() && !options.dryRun) {
37436
37722
  const successFiles = results.filter((r) => r.success).map((r) => r.filePath);
37437
37723
  if (successFiles.length > 0) {
@@ -37444,9 +37730,11 @@ Migrating ${displayPath}
37444
37730
  if (options.report === "html" && !features.htmlReports) {
37445
37731
  console.error(pc4.red("HTML reports require Individual tier or higher."));
37446
37732
  console.log(`Upgrade at: ${pc4.cyan(POLAR_URL)}`);
37733
+ process.exit(1);
37447
37734
  } else if (options.report === "csv" && !features.csvExport) {
37448
37735
  console.error(pc4.red("CSV export requires Individual tier or higher."));
37449
37736
  console.log(`Upgrade at: ${pc4.cyan(POLAR_URL)}`);
37737
+ process.exit(1);
37450
37738
  } else {
37451
37739
  const reporter = new ReportGenerator();
37452
37740
  const includeRisk = features.riskScoring;
@@ -37469,6 +37757,58 @@ Migrating ${displayPath}
37469
37757
  console.log(pc4.green(`Report saved: ${outputPath}`));
37470
37758
  }
37471
37759
  }
37760
+ if (options.scaffoldTests) {
37761
+ if (!features.testScaffolding) {
37762
+ console.error(pc4.red("\nTest scaffolding requires Pro tier or higher."));
37763
+ console.log(`Upgrade at: ${pc4.cyan(POLAR_URL)}`);
37764
+ } else {
37765
+ const scaffolder = new TestScaffolder();
37766
+ const successfulFiles = results.filter((r) => r.success).map((r) => r.filePath);
37767
+ if (successfulFiles.length > 0) {
37768
+ const scaffoldAnalyzer = new SchemaAnalyzer();
37769
+ for (const f of successfulFiles) {
37770
+ scaffoldAnalyzer.addSourceFiles([f]);
37771
+ }
37772
+ const sourceFiles = scaffoldAnalyzer.getProject().getSourceFiles();
37773
+ const scaffoldResult = scaffolder.scaffold(sourceFiles, options.from, options.to);
37774
+ console.log(pc4.bold("\nTest Scaffolding\n"));
37775
+ console.log(
37776
+ ` Generated ${pc4.cyan(scaffoldResult.tests.length.toString())} test file(s) for ${scaffoldResult.totalSchemas} schema(s)`
37777
+ );
37778
+ for (const test of scaffoldResult.tests) {
37779
+ console.log(` ${pc4.dim(test.filePath)} (${test.schemaCount} schemas)`);
37780
+ }
37781
+ }
37782
+ }
37783
+ }
37784
+ if (options.audit) {
37785
+ if (!features.auditLogging) {
37786
+ console.error(pc4.red("\nAudit logging requires Team tier."));
37787
+ console.log(`Upgrade at: ${pc4.cyan(POLAR_URL)}`);
37788
+ } else {
37789
+ const projectPath = resolve2(targetPath);
37790
+ const auditLog = new MigrationAuditLog(projectPath);
37791
+ const migrationId = `${options.from}->${options.to}-${Date.now()}`;
37792
+ for (const result of results) {
37793
+ const entry = auditLog.createEntry({
37794
+ migrationId,
37795
+ filePath: result.filePath,
37796
+ from: options.from,
37797
+ to: options.to,
37798
+ originalCode: result.originalCode ?? "",
37799
+ transformedCode: result.transformedCode,
37800
+ success: result.success,
37801
+ warningCount: result.warnings.length,
37802
+ errorCount: result.errors.length
37803
+ });
37804
+ auditLog.append(entry);
37805
+ }
37806
+ console.log(
37807
+ pc4.dim(`
37808
+ Audit log saved to .schemashift/audit-log.json (${results.length} entries)`)
37809
+ );
37810
+ }
37811
+ }
37472
37812
  if (options.dryRun) {
37473
37813
  const diffOutput = generateDiffPreview(results);
37474
37814
  if (diffOutput) {
@@ -37551,15 +37891,15 @@ program.command("watch <path>").description("Watch files and migrate on change")
37551
37891
  };
37552
37892
  }
37553
37893
  const watchMode = new WatchMode();
37554
- const analyzer = new SchemaAnalyzer();
37555
37894
  await watchMode.start({
37556
37895
  patterns: config2.include.map((p) => join3(targetPath, p)),
37557
37896
  exclude: config2.exclude,
37558
37897
  from: options.from,
37559
37898
  to: options.to,
37560
37899
  onTransform: async (file) => {
37561
- analyzer.addSourceFiles([file]);
37562
- const sourceFile = analyzer.getProject().getSourceFileOrThrow(resolve2(file));
37900
+ const fileAnalyzer = new SchemaAnalyzer();
37901
+ fileAnalyzer.addSourceFiles([file]);
37902
+ const sourceFile = fileAnalyzer.getProject().getSourceFileOrThrow(resolve2(file));
37563
37903
  const result = engine.transform(sourceFile, options.from, options.to, {
37564
37904
  from: options.from,
37565
37905
  to: options.to,
package/dist/index.cjs CHANGED
@@ -33788,7 +33788,16 @@ var TIER_FEATURES = {
33788
33788
  customPlugins: false,
33789
33789
  governance: false,
33790
33790
  failOnWarnings: false,
33791
- maxRiskScoreThreshold: false
33791
+ maxRiskScoreThreshold: false,
33792
+ formResolverMigration: false,
33793
+ complexityEstimator: false,
33794
+ monorepoAware: false,
33795
+ behavioralWarnings: true,
33796
+ bundleEstimator: false,
33797
+ performanceAnalyzer: false,
33798
+ typeDeduplication: false,
33799
+ testScaffolding: false,
33800
+ auditLogging: false
33792
33801
  },
33793
33802
  [
33794
33803
  "individual"
@@ -33812,7 +33821,16 @@ var TIER_FEATURES = {
33812
33821
  customPlugins: false,
33813
33822
  governance: false,
33814
33823
  failOnWarnings: false,
33815
- maxRiskScoreThreshold: false
33824
+ maxRiskScoreThreshold: false,
33825
+ formResolverMigration: true,
33826
+ complexityEstimator: true,
33827
+ monorepoAware: false,
33828
+ behavioralWarnings: true,
33829
+ bundleEstimator: true,
33830
+ performanceAnalyzer: true,
33831
+ typeDeduplication: true,
33832
+ testScaffolding: false,
33833
+ auditLogging: false
33816
33834
  },
33817
33835
  [
33818
33836
  "pro"
@@ -33825,7 +33843,9 @@ var TIER_FEATURES = {
33825
33843
  "zod-v3->v4",
33826
33844
  "io-ts->zod",
33827
33845
  "zod->valibot",
33828
- "any->valibot"
33846
+ "any->valibot",
33847
+ "zod->yup",
33848
+ "valibot->zod"
33829
33849
  ],
33830
33850
  devices: 4,
33831
33851
  ciSupport: true,
@@ -33843,7 +33863,16 @@ var TIER_FEATURES = {
33843
33863
  customPlugins: false,
33844
33864
  governance: false,
33845
33865
  failOnWarnings: false,
33846
- maxRiskScoreThreshold: false
33866
+ maxRiskScoreThreshold: false,
33867
+ formResolverMigration: true,
33868
+ complexityEstimator: true,
33869
+ monorepoAware: true,
33870
+ behavioralWarnings: true,
33871
+ bundleEstimator: true,
33872
+ performanceAnalyzer: true,
33873
+ typeDeduplication: true,
33874
+ testScaffolding: true,
33875
+ auditLogging: false
33847
33876
  },
33848
33877
  [
33849
33878
  "team"
@@ -33856,7 +33885,9 @@ var TIER_FEATURES = {
33856
33885
  "zod-v3->v4",
33857
33886
  "io-ts->zod",
33858
33887
  "any->valibot",
33859
- "zod->valibot"
33888
+ "zod->valibot",
33889
+ "zod->yup",
33890
+ "valibot->zod"
33860
33891
  ],
33861
33892
  devices: Infinity,
33862
33893
  ciSupport: true,
@@ -33874,7 +33905,16 @@ var TIER_FEATURES = {
33874
33905
  customPlugins: true,
33875
33906
  governance: true,
33876
33907
  failOnWarnings: true,
33877
- maxRiskScoreThreshold: true
33908
+ maxRiskScoreThreshold: true,
33909
+ formResolverMigration: true,
33910
+ complexityEstimator: true,
33911
+ monorepoAware: true,
33912
+ behavioralWarnings: true,
33913
+ bundleEstimator: true,
33914
+ performanceAnalyzer: true,
33915
+ typeDeduplication: true,
33916
+ testScaffolding: true,
33917
+ auditLogging: true
33878
33918
  }
33879
33919
  };
33880
33920
  var POLAR_ORG_ID = process.env.POLAR_ORG_ID || "79bbe935-1836-4b9e-9ca8-4c7a94217f5e";
@@ -34629,6 +34669,12 @@ var WatchMode = class {
34629
34669
  console.log(import_picocolors.default.cyan(`
34630
34670
  Watching ${files.length} files for changes...
34631
34671
  `));
34672
+ if (options.dependents && options.dependents.size > 0) {
34673
+ console.log(
34674
+ import_picocolors.default.dim(`Dependency-aware mode: ${options.dependents.size} files with dependents
34675
+ `)
34676
+ );
34677
+ }
34632
34678
  console.log(import_picocolors.default.dim("Press Ctrl+C to stop\n"));
34633
34679
  const directories = new Set(files.map((f) => f.split("/").slice(0, -1).join("/")));
34634
34680
  for (const dir of directories) {
@@ -34649,11 +34695,33 @@ Watching ${files.length} files for changes...
34649
34695
  Changed: ${(0, import_node_path2.relative)(process.cwd(), fullPath)}`));
34650
34696
  try {
34651
34697
  await options.onTransform(fullPath);
34652
- console.log(import_picocolors.default.green(`Transformed successfully
34653
- `));
34698
+ console.log(import_picocolors.default.green(`Transformed successfully`));
34699
+ const dependentFiles = options.dependents?.get(fullPath);
34700
+ if (dependentFiles && dependentFiles.length > 0) {
34701
+ console.log(
34702
+ import_picocolors.default.cyan(` Re-transforming ${dependentFiles.length} dependent file(s)...`)
34703
+ );
34704
+ for (const depFile of dependentFiles) {
34705
+ try {
34706
+ await options.onTransform(depFile);
34707
+ console.log(import_picocolors.default.green(` \u2713 ${(0, import_node_path2.relative)(process.cwd(), depFile)}`));
34708
+ } catch (error) {
34709
+ console.error(
34710
+ import_picocolors.default.red(
34711
+ ` \u2717 ${(0, import_node_path2.relative)(process.cwd(), depFile)}: ${error instanceof Error ? error.message : String(error)}`
34712
+ )
34713
+ );
34714
+ }
34715
+ }
34716
+ }
34717
+ console.log("");
34654
34718
  } catch (error) {
34655
- console.error(import_picocolors.default.red(`Transform failed: ${error}
34656
- `));
34719
+ console.error(
34720
+ import_picocolors.default.red(
34721
+ `Transform failed: ${error instanceof Error ? error.message : String(error)}
34722
+ `
34723
+ )
34724
+ );
34657
34725
  }
34658
34726
  this.debounceTimers.delete(fullPath);
34659
34727
  }, 100)
@@ -34672,6 +34740,21 @@ Changed: ${(0, import_node_path2.relative)(process.cwd(), fullPath)}`));
34672
34740
  }
34673
34741
  this.debounceTimers.clear();
34674
34742
  }
34743
+ /**
34744
+ * Build a reverse dependency map (file -> files that depend on it).
34745
+ * Takes the forward dependency map from SchemaDependencyResolver and inverts it.
34746
+ */
34747
+ static buildDependentsMap(dependencies) {
34748
+ const dependents = /* @__PURE__ */ new Map();
34749
+ for (const [file, deps] of dependencies) {
34750
+ for (const dep of deps) {
34751
+ const existing = dependents.get(dep) ?? [];
34752
+ existing.push(file);
34753
+ dependents.set(dep, existing);
34754
+ }
34755
+ }
34756
+ return dependents;
34757
+ }
34675
34758
  matchesPatterns(file, include, exclude) {
34676
34759
  const isIncluded = include.some((p) => (0, import_minimatch.minimatch)(file, p));
34677
34760
  const isExcluded = exclude.some((p) => (0, import_minimatch.minimatch)(file, p));
package/dist/index.d.cts CHANGED
@@ -91,12 +91,19 @@ interface WatchOptions {
91
91
  from: string;
92
92
  to: string;
93
93
  onTransform: (file: string) => Promise<void>;
94
+ /** Optional: dependency map from SchemaDependencyResolver. Keys are file paths, values are lists of files that depend on the key. */
95
+ dependents?: Map<string, string[]>;
94
96
  }
95
97
  declare class WatchMode {
96
98
  private watchers;
97
99
  private debounceTimers;
98
100
  start(options: WatchOptions): Promise<void>;
99
101
  stop(): void;
102
+ /**
103
+ * Build a reverse dependency map (file -> files that depend on it).
104
+ * Takes the forward dependency map from SchemaDependencyResolver and inverts it.
105
+ */
106
+ static buildDependentsMap(dependencies: Map<string, string[]>): Map<string, string[]>;
100
107
  private matchesPatterns;
101
108
  }
102
109
 
package/dist/index.d.ts CHANGED
@@ -91,12 +91,19 @@ interface WatchOptions {
91
91
  from: string;
92
92
  to: string;
93
93
  onTransform: (file: string) => Promise<void>;
94
+ /** Optional: dependency map from SchemaDependencyResolver. Keys are file paths, values are lists of files that depend on the key. */
95
+ dependents?: Map<string, string[]>;
94
96
  }
95
97
  declare class WatchMode {
96
98
  private watchers;
97
99
  private debounceTimers;
98
100
  start(options: WatchOptions): Promise<void>;
99
101
  stop(): void;
102
+ /**
103
+ * Build a reverse dependency map (file -> files that depend on it).
104
+ * Takes the forward dependency map from SchemaDependencyResolver and inverts it.
105
+ */
106
+ static buildDependentsMap(dependencies: Map<string, string[]>): Map<string, string[]>;
100
107
  private matchesPatterns;
101
108
  }
102
109
 
package/dist/index.js CHANGED
@@ -33747,7 +33747,16 @@ var TIER_FEATURES = {
33747
33747
  customPlugins: false,
33748
33748
  governance: false,
33749
33749
  failOnWarnings: false,
33750
- maxRiskScoreThreshold: false
33750
+ maxRiskScoreThreshold: false,
33751
+ formResolverMigration: false,
33752
+ complexityEstimator: false,
33753
+ monorepoAware: false,
33754
+ behavioralWarnings: true,
33755
+ bundleEstimator: false,
33756
+ performanceAnalyzer: false,
33757
+ typeDeduplication: false,
33758
+ testScaffolding: false,
33759
+ auditLogging: false
33751
33760
  },
33752
33761
  [
33753
33762
  "individual"
@@ -33771,7 +33780,16 @@ var TIER_FEATURES = {
33771
33780
  customPlugins: false,
33772
33781
  governance: false,
33773
33782
  failOnWarnings: false,
33774
- maxRiskScoreThreshold: false
33783
+ maxRiskScoreThreshold: false,
33784
+ formResolverMigration: true,
33785
+ complexityEstimator: true,
33786
+ monorepoAware: false,
33787
+ behavioralWarnings: true,
33788
+ bundleEstimator: true,
33789
+ performanceAnalyzer: true,
33790
+ typeDeduplication: true,
33791
+ testScaffolding: false,
33792
+ auditLogging: false
33775
33793
  },
33776
33794
  [
33777
33795
  "pro"
@@ -33784,7 +33802,9 @@ var TIER_FEATURES = {
33784
33802
  "zod-v3->v4",
33785
33803
  "io-ts->zod",
33786
33804
  "zod->valibot",
33787
- "any->valibot"
33805
+ "any->valibot",
33806
+ "zod->yup",
33807
+ "valibot->zod"
33788
33808
  ],
33789
33809
  devices: 4,
33790
33810
  ciSupport: true,
@@ -33802,7 +33822,16 @@ var TIER_FEATURES = {
33802
33822
  customPlugins: false,
33803
33823
  governance: false,
33804
33824
  failOnWarnings: false,
33805
- maxRiskScoreThreshold: false
33825
+ maxRiskScoreThreshold: false,
33826
+ formResolverMigration: true,
33827
+ complexityEstimator: true,
33828
+ monorepoAware: true,
33829
+ behavioralWarnings: true,
33830
+ bundleEstimator: true,
33831
+ performanceAnalyzer: true,
33832
+ typeDeduplication: true,
33833
+ testScaffolding: true,
33834
+ auditLogging: false
33806
33835
  },
33807
33836
  [
33808
33837
  "team"
@@ -33815,7 +33844,9 @@ var TIER_FEATURES = {
33815
33844
  "zod-v3->v4",
33816
33845
  "io-ts->zod",
33817
33846
  "any->valibot",
33818
- "zod->valibot"
33847
+ "zod->valibot",
33848
+ "zod->yup",
33849
+ "valibot->zod"
33819
33850
  ],
33820
33851
  devices: Infinity,
33821
33852
  ciSupport: true,
@@ -33833,7 +33864,16 @@ var TIER_FEATURES = {
33833
33864
  customPlugins: true,
33834
33865
  governance: true,
33835
33866
  failOnWarnings: true,
33836
- maxRiskScoreThreshold: true
33867
+ maxRiskScoreThreshold: true,
33868
+ formResolverMigration: true,
33869
+ complexityEstimator: true,
33870
+ monorepoAware: true,
33871
+ behavioralWarnings: true,
33872
+ bundleEstimator: true,
33873
+ performanceAnalyzer: true,
33874
+ typeDeduplication: true,
33875
+ testScaffolding: true,
33876
+ auditLogging: true
33837
33877
  }
33838
33878
  };
33839
33879
  var POLAR_ORG_ID = process.env.POLAR_ORG_ID || "79bbe935-1836-4b9e-9ca8-4c7a94217f5e";
@@ -34596,6 +34636,12 @@ var WatchMode = class {
34596
34636
  console.log(pc.cyan(`
34597
34637
  Watching ${files.length} files for changes...
34598
34638
  `));
34639
+ if (options.dependents && options.dependents.size > 0) {
34640
+ console.log(
34641
+ pc.dim(`Dependency-aware mode: ${options.dependents.size} files with dependents
34642
+ `)
34643
+ );
34644
+ }
34599
34645
  console.log(pc.dim("Press Ctrl+C to stop\n"));
34600
34646
  const directories = new Set(files.map((f) => f.split("/").slice(0, -1).join("/")));
34601
34647
  for (const dir of directories) {
@@ -34616,11 +34662,33 @@ Watching ${files.length} files for changes...
34616
34662
  Changed: ${relative2(process.cwd(), fullPath)}`));
34617
34663
  try {
34618
34664
  await options.onTransform(fullPath);
34619
- console.log(pc.green(`Transformed successfully
34620
- `));
34665
+ console.log(pc.green(`Transformed successfully`));
34666
+ const dependentFiles = options.dependents?.get(fullPath);
34667
+ if (dependentFiles && dependentFiles.length > 0) {
34668
+ console.log(
34669
+ pc.cyan(` Re-transforming ${dependentFiles.length} dependent file(s)...`)
34670
+ );
34671
+ for (const depFile of dependentFiles) {
34672
+ try {
34673
+ await options.onTransform(depFile);
34674
+ console.log(pc.green(` \u2713 ${relative2(process.cwd(), depFile)}`));
34675
+ } catch (error) {
34676
+ console.error(
34677
+ pc.red(
34678
+ ` \u2717 ${relative2(process.cwd(), depFile)}: ${error instanceof Error ? error.message : String(error)}`
34679
+ )
34680
+ );
34681
+ }
34682
+ }
34683
+ }
34684
+ console.log("");
34621
34685
  } catch (error) {
34622
- console.error(pc.red(`Transform failed: ${error}
34623
- `));
34686
+ console.error(
34687
+ pc.red(
34688
+ `Transform failed: ${error instanceof Error ? error.message : String(error)}
34689
+ `
34690
+ )
34691
+ );
34624
34692
  }
34625
34693
  this.debounceTimers.delete(fullPath);
34626
34694
  }, 100)
@@ -34639,6 +34707,21 @@ Changed: ${relative2(process.cwd(), fullPath)}`));
34639
34707
  }
34640
34708
  this.debounceTimers.clear();
34641
34709
  }
34710
+ /**
34711
+ * Build a reverse dependency map (file -> files that depend on it).
34712
+ * Takes the forward dependency map from SchemaDependencyResolver and inverts it.
34713
+ */
34714
+ static buildDependentsMap(dependencies) {
34715
+ const dependents = /* @__PURE__ */ new Map();
34716
+ for (const [file, deps] of dependencies) {
34717
+ for (const dep of deps) {
34718
+ const existing = dependents.get(dep) ?? [];
34719
+ existing.push(file);
34720
+ dependents.set(dep, existing);
34721
+ }
34722
+ }
34723
+ return dependents;
34724
+ }
34642
34725
  matchesPatterns(file, include, exclude) {
34643
34726
  const isIncluded = include.some((p) => minimatch(file, p));
34644
34727
  const isExcluded = exclude.some((p) => minimatch(file, p));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "schemashift-cli",
3
- "version": "0.7.0",
3
+ "version": "0.9.0",
4
4
  "description": "TypeScript schema migration CLI - migrate between Zod, Yup, Joi, and more",
5
5
  "type": "module",
6
6
  "bin": {
@@ -49,12 +49,12 @@
49
49
  "author": "Joseph May",
50
50
  "license": "MIT",
51
51
  "dependencies": {
52
- "@schemashift/core": "0.7.0",
53
- "@schemashift/io-ts-zod": "0.7.0",
54
- "@schemashift/joi-zod": "0.7.0",
55
- "@schemashift/yup-zod": "0.7.0",
56
- "@schemashift/zod-valibot": "0.7.0",
57
- "@schemashift/zod-v3-v4": "0.7.0",
52
+ "@schemashift/core": "0.9.0",
53
+ "@schemashift/io-ts-zod": "0.9.0",
54
+ "@schemashift/joi-zod": "0.9.0",
55
+ "@schemashift/yup-zod": "0.9.0",
56
+ "@schemashift/zod-valibot": "0.9.0",
57
+ "@schemashift/zod-v3-v4": "0.9.0",
58
58
  "commander": "14.0.2",
59
59
  "cosmiconfig": "9.0.0",
60
60
  "glob": "13.0.0",
@@ -63,7 +63,7 @@
63
63
  "picocolors": "1.1.1"
64
64
  },
65
65
  "devDependencies": {
66
- "@schemashift/license": "0.4.0"
66
+ "@schemashift/license": "0.9.0"
67
67
  },
68
68
  "publishConfig": {
69
69
  "access": "public"