schemashift-cli 0.8.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";
@@ -35827,7 +35834,13 @@ var TIER_FEATURES = {
35827
35834
  maxRiskScoreThreshold: false,
35828
35835
  formResolverMigration: false,
35829
35836
  complexityEstimator: false,
35830
- monorepoAware: false
35837
+ monorepoAware: false,
35838
+ behavioralWarnings: true,
35839
+ bundleEstimator: false,
35840
+ performanceAnalyzer: false,
35841
+ typeDeduplication: false,
35842
+ testScaffolding: false,
35843
+ auditLogging: false
35831
35844
  },
35832
35845
  [
35833
35846
  "individual"
@@ -35854,7 +35867,13 @@ var TIER_FEATURES = {
35854
35867
  maxRiskScoreThreshold: false,
35855
35868
  formResolverMigration: true,
35856
35869
  complexityEstimator: true,
35857
- monorepoAware: false
35870
+ monorepoAware: false,
35871
+ behavioralWarnings: true,
35872
+ bundleEstimator: true,
35873
+ performanceAnalyzer: true,
35874
+ typeDeduplication: true,
35875
+ testScaffolding: false,
35876
+ auditLogging: false
35858
35877
  },
35859
35878
  [
35860
35879
  "pro"
@@ -35867,7 +35886,9 @@ var TIER_FEATURES = {
35867
35886
  "zod-v3->v4",
35868
35887
  "io-ts->zod",
35869
35888
  "zod->valibot",
35870
- "any->valibot"
35889
+ "any->valibot",
35890
+ "zod->yup",
35891
+ "valibot->zod"
35871
35892
  ],
35872
35893
  devices: 4,
35873
35894
  ciSupport: true,
@@ -35888,7 +35909,13 @@ var TIER_FEATURES = {
35888
35909
  maxRiskScoreThreshold: false,
35889
35910
  formResolverMigration: true,
35890
35911
  complexityEstimator: true,
35891
- monorepoAware: true
35912
+ monorepoAware: true,
35913
+ behavioralWarnings: true,
35914
+ bundleEstimator: true,
35915
+ performanceAnalyzer: true,
35916
+ typeDeduplication: true,
35917
+ testScaffolding: true,
35918
+ auditLogging: false
35892
35919
  },
35893
35920
  [
35894
35921
  "team"
@@ -35901,7 +35928,9 @@ var TIER_FEATURES = {
35901
35928
  "zod-v3->v4",
35902
35929
  "io-ts->zod",
35903
35930
  "any->valibot",
35904
- "zod->valibot"
35931
+ "zod->valibot",
35932
+ "zod->yup",
35933
+ "valibot->zod"
35905
35934
  ],
35906
35935
  devices: Infinity,
35907
35936
  ciSupport: true,
@@ -35922,7 +35951,13 @@ var TIER_FEATURES = {
35922
35951
  maxRiskScoreThreshold: true,
35923
35952
  formResolverMigration: true,
35924
35953
  complexityEstimator: true,
35925
- monorepoAware: true
35954
+ monorepoAware: true,
35955
+ behavioralWarnings: true,
35956
+ bundleEstimator: true,
35957
+ performanceAnalyzer: true,
35958
+ typeDeduplication: true,
35959
+ testScaffolding: true,
35960
+ auditLogging: true
35926
35961
  }
35927
35962
  };
35928
35963
  function canUseMigration(tier, from, to) {
@@ -36199,9 +36234,9 @@ var LicenseManager = class {
36199
36234
  };
36200
36235
 
36201
36236
  // src/cli.ts
36202
- import { createYupToZodHandler } from "@schemashift/yup-zod";
36237
+ import { createYupToZodHandler, createZodToYupHandler } from "@schemashift/yup-zod";
36203
36238
  import { createZodV3ToV4Handler } from "@schemashift/zod-v3-v4";
36204
- import { createZodToValibotHandler } from "@schemashift/zod-valibot";
36239
+ import { createValibotToZodHandler, createZodToValibotHandler } from "@schemashift/zod-valibot";
36205
36240
  import { Command } from "commander";
36206
36241
  import { glob as glob2 } from "glob";
36207
36242
  import { Listr } from "listr2";
@@ -36883,6 +36918,12 @@ var WatchMode = class {
36883
36918
  console.log(pc3.cyan(`
36884
36919
  Watching ${files.length} files for changes...
36885
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
+ }
36886
36927
  console.log(pc3.dim("Press Ctrl+C to stop\n"));
36887
36928
  const directories = new Set(files.map((f) => f.split("/").slice(0, -1).join("/")));
36888
36929
  for (const dir of directories) {
@@ -36903,8 +36944,26 @@ Watching ${files.length} files for changes...
36903
36944
  Changed: ${relative2(process.cwd(), fullPath)}`));
36904
36945
  try {
36905
36946
  await options.onTransform(fullPath);
36906
- console.log(pc3.green(`Transformed successfully
36907
- `));
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("");
36908
36967
  } catch (error) {
36909
36968
  console.error(
36910
36969
  pc3.red(
@@ -36930,6 +36989,21 @@ Changed: ${relative2(process.cwd(), fullPath)}`));
36930
36989
  }
36931
36990
  this.debounceTimers.clear();
36932
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
+ }
36933
37007
  matchesPatterns(file, include, exclude) {
36934
37008
  const isIncluded = include.some((p) => minimatch(file, p));
36935
37009
  const isExcluded = exclude.some((p) => minimatch(file, p));
@@ -36948,6 +37022,8 @@ engine.registerHandler("joi", "zod", createJoiToZodHandler());
36948
37022
  engine.registerHandler("io-ts", "zod", createIoTsToZodHandler());
36949
37023
  engine.registerHandler("zod-v3", "v4", createZodV3ToV4Handler());
36950
37024
  engine.registerHandler("zod", "valibot", createZodToValibotHandler());
37025
+ engine.registerHandler("zod", "yup", createZodToYupHandler());
37026
+ engine.registerHandler("valibot", "zod", createValibotToZodHandler());
36951
37027
  var POLAR_URL = "https://schemashift.qwady.app";
36952
37028
  program.name("schemashift").version(pkg.version).description("TypeScript schema migration CLI");
36953
37029
  program.command("init").description("Create .schemashiftrc.json config file").option("-f, --force", "Overwrite existing config").action((options) => {
@@ -36977,7 +37053,10 @@ program.command("init").description("Create .schemashiftrc.json config file").op
36977
37053
  console.log(pc4.green("Created .schemashiftrc.json"));
36978
37054
  console.log(pc4.dim("Edit the file to customize your migration settings."));
36979
37055
  });
36980
- 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) => {
36981
37060
  const analyzer = new SchemaAnalyzer();
36982
37061
  let files;
36983
37062
  if (existsSync4(targetPath) && statSync(targetPath).isFile()) {
@@ -37158,12 +37237,140 @@ Migration Readiness: ${from} -> ${to}
37158
37237
  }
37159
37238
  }
37160
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
+ }
37161
37368
  });
37162
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(
37163
37370
  "--max-risk-score <score>",
37164
37371
  "Exit 1 if any file exceeds risk score (Team+)",
37165
37372
  Number.parseInt
37166
- ).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) => {
37167
37374
  const startTime = Date.now();
37168
37375
  if (options.maxRiskScore !== void 0 && Number.isNaN(options.maxRiskScore)) {
37169
37376
  console.error(pc4.red("--max-risk-score must be a valid number."));
@@ -37318,6 +37525,41 @@ Compatibility score: ${pc4.cyan(compatResult.overallScore.toString())}%`);
37318
37525
  console.log(pc4.yellow("No files found matching patterns."));
37319
37526
  process.exit(0);
37320
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
+ }
37321
37563
  if (files.length > features.maxFiles) {
37322
37564
  console.error(
37323
37565
  pc4.red(`File limit exceeded. Found ${files.length}, max ${features.maxFiles}.`)
@@ -37452,6 +37694,30 @@ Migrating ${displayPath}
37452
37694
  }
37453
37695
  }
37454
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
+ }
37455
37721
  if (options.gitCommit && git.isAvailable() && !options.dryRun) {
37456
37722
  const successFiles = results.filter((r) => r.success).map((r) => r.filePath);
37457
37723
  if (successFiles.length > 0) {
@@ -37464,9 +37730,11 @@ Migrating ${displayPath}
37464
37730
  if (options.report === "html" && !features.htmlReports) {
37465
37731
  console.error(pc4.red("HTML reports require Individual tier or higher."));
37466
37732
  console.log(`Upgrade at: ${pc4.cyan(POLAR_URL)}`);
37733
+ process.exit(1);
37467
37734
  } else if (options.report === "csv" && !features.csvExport) {
37468
37735
  console.error(pc4.red("CSV export requires Individual tier or higher."));
37469
37736
  console.log(`Upgrade at: ${pc4.cyan(POLAR_URL)}`);
37737
+ process.exit(1);
37470
37738
  } else {
37471
37739
  const reporter = new ReportGenerator();
37472
37740
  const includeRisk = features.riskScoring;
@@ -37489,6 +37757,58 @@ Migrating ${displayPath}
37489
37757
  console.log(pc4.green(`Report saved: ${outputPath}`));
37490
37758
  }
37491
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
+ }
37492
37812
  if (options.dryRun) {
37493
37813
  const diffOutput = generateDiffPreview(results);
37494
37814
  if (diffOutput) {
@@ -37571,15 +37891,15 @@ program.command("watch <path>").description("Watch files and migrate on change")
37571
37891
  };
37572
37892
  }
37573
37893
  const watchMode = new WatchMode();
37574
- const analyzer = new SchemaAnalyzer();
37575
37894
  await watchMode.start({
37576
37895
  patterns: config2.include.map((p) => join3(targetPath, p)),
37577
37896
  exclude: config2.exclude,
37578
37897
  from: options.from,
37579
37898
  to: options.to,
37580
37899
  onTransform: async (file) => {
37581
- analyzer.addSourceFiles([file]);
37582
- const sourceFile = analyzer.getProject().getSourceFileOrThrow(resolve2(file));
37900
+ const fileAnalyzer = new SchemaAnalyzer();
37901
+ fileAnalyzer.addSourceFiles([file]);
37902
+ const sourceFile = fileAnalyzer.getProject().getSourceFileOrThrow(resolve2(file));
37583
37903
  const result = engine.transform(sourceFile, options.from, options.to, {
37584
37904
  from: options.from,
37585
37905
  to: options.to,
package/dist/index.cjs CHANGED
@@ -33791,7 +33791,13 @@ var TIER_FEATURES = {
33791
33791
  maxRiskScoreThreshold: false,
33792
33792
  formResolverMigration: false,
33793
33793
  complexityEstimator: false,
33794
- monorepoAware: false
33794
+ monorepoAware: false,
33795
+ behavioralWarnings: true,
33796
+ bundleEstimator: false,
33797
+ performanceAnalyzer: false,
33798
+ typeDeduplication: false,
33799
+ testScaffolding: false,
33800
+ auditLogging: false
33795
33801
  },
33796
33802
  [
33797
33803
  "individual"
@@ -33818,7 +33824,13 @@ var TIER_FEATURES = {
33818
33824
  maxRiskScoreThreshold: false,
33819
33825
  formResolverMigration: true,
33820
33826
  complexityEstimator: true,
33821
- monorepoAware: false
33827
+ monorepoAware: false,
33828
+ behavioralWarnings: true,
33829
+ bundleEstimator: true,
33830
+ performanceAnalyzer: true,
33831
+ typeDeduplication: true,
33832
+ testScaffolding: false,
33833
+ auditLogging: false
33822
33834
  },
33823
33835
  [
33824
33836
  "pro"
@@ -33831,7 +33843,9 @@ var TIER_FEATURES = {
33831
33843
  "zod-v3->v4",
33832
33844
  "io-ts->zod",
33833
33845
  "zod->valibot",
33834
- "any->valibot"
33846
+ "any->valibot",
33847
+ "zod->yup",
33848
+ "valibot->zod"
33835
33849
  ],
33836
33850
  devices: 4,
33837
33851
  ciSupport: true,
@@ -33852,7 +33866,13 @@ var TIER_FEATURES = {
33852
33866
  maxRiskScoreThreshold: false,
33853
33867
  formResolverMigration: true,
33854
33868
  complexityEstimator: true,
33855
- monorepoAware: true
33869
+ monorepoAware: true,
33870
+ behavioralWarnings: true,
33871
+ bundleEstimator: true,
33872
+ performanceAnalyzer: true,
33873
+ typeDeduplication: true,
33874
+ testScaffolding: true,
33875
+ auditLogging: false
33856
33876
  },
33857
33877
  [
33858
33878
  "team"
@@ -33865,7 +33885,9 @@ var TIER_FEATURES = {
33865
33885
  "zod-v3->v4",
33866
33886
  "io-ts->zod",
33867
33887
  "any->valibot",
33868
- "zod->valibot"
33888
+ "zod->valibot",
33889
+ "zod->yup",
33890
+ "valibot->zod"
33869
33891
  ],
33870
33892
  devices: Infinity,
33871
33893
  ciSupport: true,
@@ -33886,7 +33908,13 @@ var TIER_FEATURES = {
33886
33908
  maxRiskScoreThreshold: true,
33887
33909
  formResolverMigration: true,
33888
33910
  complexityEstimator: true,
33889
- monorepoAware: true
33911
+ monorepoAware: true,
33912
+ behavioralWarnings: true,
33913
+ bundleEstimator: true,
33914
+ performanceAnalyzer: true,
33915
+ typeDeduplication: true,
33916
+ testScaffolding: true,
33917
+ auditLogging: true
33890
33918
  }
33891
33919
  };
33892
33920
  var POLAR_ORG_ID = process.env.POLAR_ORG_ID || "79bbe935-1836-4b9e-9ca8-4c7a94217f5e";
@@ -34641,6 +34669,12 @@ var WatchMode = class {
34641
34669
  console.log(import_picocolors.default.cyan(`
34642
34670
  Watching ${files.length} files for changes...
34643
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
+ }
34644
34678
  console.log(import_picocolors.default.dim("Press Ctrl+C to stop\n"));
34645
34679
  const directories = new Set(files.map((f) => f.split("/").slice(0, -1).join("/")));
34646
34680
  for (const dir of directories) {
@@ -34661,8 +34695,26 @@ Watching ${files.length} files for changes...
34661
34695
  Changed: ${(0, import_node_path2.relative)(process.cwd(), fullPath)}`));
34662
34696
  try {
34663
34697
  await options.onTransform(fullPath);
34664
- console.log(import_picocolors.default.green(`Transformed successfully
34665
- `));
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("");
34666
34718
  } catch (error) {
34667
34719
  console.error(
34668
34720
  import_picocolors.default.red(
@@ -34688,6 +34740,21 @@ Changed: ${(0, import_node_path2.relative)(process.cwd(), fullPath)}`));
34688
34740
  }
34689
34741
  this.debounceTimers.clear();
34690
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
+ }
34691
34758
  matchesPatterns(file, include, exclude) {
34692
34759
  const isIncluded = include.some((p) => (0, import_minimatch.minimatch)(file, p));
34693
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
@@ -33750,7 +33750,13 @@ var TIER_FEATURES = {
33750
33750
  maxRiskScoreThreshold: false,
33751
33751
  formResolverMigration: false,
33752
33752
  complexityEstimator: false,
33753
- monorepoAware: false
33753
+ monorepoAware: false,
33754
+ behavioralWarnings: true,
33755
+ bundleEstimator: false,
33756
+ performanceAnalyzer: false,
33757
+ typeDeduplication: false,
33758
+ testScaffolding: false,
33759
+ auditLogging: false
33754
33760
  },
33755
33761
  [
33756
33762
  "individual"
@@ -33777,7 +33783,13 @@ var TIER_FEATURES = {
33777
33783
  maxRiskScoreThreshold: false,
33778
33784
  formResolverMigration: true,
33779
33785
  complexityEstimator: true,
33780
- monorepoAware: false
33786
+ monorepoAware: false,
33787
+ behavioralWarnings: true,
33788
+ bundleEstimator: true,
33789
+ performanceAnalyzer: true,
33790
+ typeDeduplication: true,
33791
+ testScaffolding: false,
33792
+ auditLogging: false
33781
33793
  },
33782
33794
  [
33783
33795
  "pro"
@@ -33790,7 +33802,9 @@ var TIER_FEATURES = {
33790
33802
  "zod-v3->v4",
33791
33803
  "io-ts->zod",
33792
33804
  "zod->valibot",
33793
- "any->valibot"
33805
+ "any->valibot",
33806
+ "zod->yup",
33807
+ "valibot->zod"
33794
33808
  ],
33795
33809
  devices: 4,
33796
33810
  ciSupport: true,
@@ -33811,7 +33825,13 @@ var TIER_FEATURES = {
33811
33825
  maxRiskScoreThreshold: false,
33812
33826
  formResolverMigration: true,
33813
33827
  complexityEstimator: true,
33814
- monorepoAware: true
33828
+ monorepoAware: true,
33829
+ behavioralWarnings: true,
33830
+ bundleEstimator: true,
33831
+ performanceAnalyzer: true,
33832
+ typeDeduplication: true,
33833
+ testScaffolding: true,
33834
+ auditLogging: false
33815
33835
  },
33816
33836
  [
33817
33837
  "team"
@@ -33824,7 +33844,9 @@ var TIER_FEATURES = {
33824
33844
  "zod-v3->v4",
33825
33845
  "io-ts->zod",
33826
33846
  "any->valibot",
33827
- "zod->valibot"
33847
+ "zod->valibot",
33848
+ "zod->yup",
33849
+ "valibot->zod"
33828
33850
  ],
33829
33851
  devices: Infinity,
33830
33852
  ciSupport: true,
@@ -33845,7 +33867,13 @@ var TIER_FEATURES = {
33845
33867
  maxRiskScoreThreshold: true,
33846
33868
  formResolverMigration: true,
33847
33869
  complexityEstimator: true,
33848
- monorepoAware: true
33870
+ monorepoAware: true,
33871
+ behavioralWarnings: true,
33872
+ bundleEstimator: true,
33873
+ performanceAnalyzer: true,
33874
+ typeDeduplication: true,
33875
+ testScaffolding: true,
33876
+ auditLogging: true
33849
33877
  }
33850
33878
  };
33851
33879
  var POLAR_ORG_ID = process.env.POLAR_ORG_ID || "79bbe935-1836-4b9e-9ca8-4c7a94217f5e";
@@ -34608,6 +34636,12 @@ var WatchMode = class {
34608
34636
  console.log(pc.cyan(`
34609
34637
  Watching ${files.length} files for changes...
34610
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
+ }
34611
34645
  console.log(pc.dim("Press Ctrl+C to stop\n"));
34612
34646
  const directories = new Set(files.map((f) => f.split("/").slice(0, -1).join("/")));
34613
34647
  for (const dir of directories) {
@@ -34628,8 +34662,26 @@ Watching ${files.length} files for changes...
34628
34662
  Changed: ${relative2(process.cwd(), fullPath)}`));
34629
34663
  try {
34630
34664
  await options.onTransform(fullPath);
34631
- console.log(pc.green(`Transformed successfully
34632
- `));
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("");
34633
34685
  } catch (error) {
34634
34686
  console.error(
34635
34687
  pc.red(
@@ -34655,6 +34707,21 @@ Changed: ${relative2(process.cwd(), fullPath)}`));
34655
34707
  }
34656
34708
  this.debounceTimers.clear();
34657
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
+ }
34658
34725
  matchesPatterns(file, include, exclude) {
34659
34726
  const isIncluded = include.some((p) => minimatch(file, p));
34660
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.8.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.8.0",
53
- "@schemashift/io-ts-zod": "0.8.0",
54
- "@schemashift/joi-zod": "0.8.0",
55
- "@schemashift/yup-zod": "0.8.0",
56
- "@schemashift/zod-valibot": "0.8.0",
57
- "@schemashift/zod-v3-v4": "0.8.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.5.0"
66
+ "@schemashift/license": "0.9.0"
67
67
  },
68
68
  "publishConfig": {
69
69
  "access": "public"