synthra 0.1.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.
Files changed (302) hide show
  1. checksums.yaml +7 -0
  2. data/.rspec +4 -0
  3. data/.rubocop.yml +40 -0
  4. data/.yardopts +18 -0
  5. data/ACCESS_DOCS.md +116 -0
  6. data/ADVANCED_FEATURES_SUMMARY.md +245 -0
  7. data/CHANGELOG.md +498 -0
  8. data/CODE_OF_CONDUCT.md +122 -0
  9. data/CONTRIBUTING.md +307 -0
  10. data/DOCUMENTATION.md +204 -0
  11. data/DOCUMENTATION_GUIDE.md +194 -0
  12. data/LICENSE +22 -0
  13. data/README.md +1458 -0
  14. data/Rakefile +128 -0
  15. data/benchmark/README.md +591 -0
  16. data/benchmark/batch_generation.rb +63 -0
  17. data/benchmark/engine_comparison.rb +295 -0
  18. data/benchmark/single_record.rb +108 -0
  19. data/benchmark/streaming.rb +74 -0
  20. data/docs/ACTIVERECORD_INFERENCE.md +524 -0
  21. data/docs/API_SERVER.md +278 -0
  22. data/docs/CONFIG_FILE.md +315 -0
  23. data/docs/DATA_CONTRACTS.md +312 -0
  24. data/docs/ENHANCED_REPL.md +304 -0
  25. data/docs/FACTORY_BOT.md +271 -0
  26. data/docs/GITHUB_ACTION.md +420 -0
  27. data/docs/GRAPHQL_EXPORT.md +303 -0
  28. data/docs/GRAPHQL_FEDERATION.md +651 -0
  29. data/docs/HOW_TO_GENERATE_DOCS.md +252 -0
  30. data/docs/LSP.md +189 -0
  31. data/docs/MIGRATION_GENERATOR.md +547 -0
  32. data/docs/MOCK_SERVER.md +280 -0
  33. data/docs/NATIVE_ENGINE.md +298 -0
  34. data/docs/OPENAPI_EXPORT.md +252 -0
  35. data/docs/PERFORMANCE_MODE.md +228 -0
  36. data/docs/PERSONAS.md +547 -0
  37. data/docs/PROPERTY_TESTING.md +284 -0
  38. data/docs/PROTOBUF_EXPORT.md +276 -0
  39. data/docs/QUALITY_METRICS.md +555 -0
  40. data/docs/QUICK_REFERENCE.md +88 -0
  41. data/docs/RAILS_ENGINE.md +404 -0
  42. data/docs/RAILS_INTEGRATION.md +454 -0
  43. data/docs/README.md +175 -0
  44. data/docs/REPL_QUICK_REFERENCE.md +149 -0
  45. data/docs/RUST_INTEGRATION_GUIDE.md +1376 -0
  46. data/docs/SCENARIOS.md +576 -0
  47. data/docs/SECURITY_TESTING.md +318 -0
  48. data/docs/SNAPSHOT_TESTING.md +308 -0
  49. data/docs/STRING_TEXT_TYPES_REFERENCE.md +512 -0
  50. data/docs/TERRAFORM_EXPORT.md +447 -0
  51. data/docs/TIME_TRAVEL.md +507 -0
  52. data/docs/USAGE_GUIDE.md +980 -0
  53. data/docs/WEBHOOK_SIMULATOR.md +579 -0
  54. data/docs/advanced_features.md +183 -0
  55. data/docs/api_reference.md +153 -0
  56. data/docs/behaviors.md +138 -0
  57. data/docs/best_practices.md +191 -0
  58. data/docs/dsl_reference.md +172 -0
  59. data/docs/examples.md +162 -0
  60. data/docs/generation_modes.md +125 -0
  61. data/docs/getting_started.md +92 -0
  62. data/docs/index.html +177 -0
  63. data/docs/strategic/CLOUD_REGISTRY_ARCHITECTURE.md +268 -0
  64. data/docs/strategic/SIMD_OPTIMIZATION_STRATEGY.md +153 -0
  65. data/docs/troubleshooting.md +215 -0
  66. data/docs/type_reference.md +224 -0
  67. data/examples/hostile_mode_demo.rb +64 -0
  68. data/exe/synthra +6 -0
  69. data/ext/synthra_native/Cargo.lock +972 -0
  70. data/ext/synthra_native/Cargo.toml +46 -0
  71. data/ext/synthra_native/extconf.rb +41 -0
  72. data/ext/synthra_native/src/generator.rs +397 -0
  73. data/ext/synthra_native/src/lib.rs +229 -0
  74. data/ext/synthra_native/src/types.rs +1485 -0
  75. data/lib/generators/synthra/install_generator.rb +86 -0
  76. data/lib/generators/synthra/templates/api_response.dsl +15 -0
  77. data/lib/generators/synthra/templates/fake_data.rake +116 -0
  78. data/lib/generators/synthra/templates/synthra.yml +31 -0
  79. data/lib/generators/synthra/templates/synthra_support.rb +19 -0
  80. data/lib/generators/synthra/templates/user.dsl +11 -0
  81. data/lib/synthra/activerecord_inference.rb +394 -0
  82. data/lib/synthra/api.rb +381 -0
  83. data/lib/synthra/api_server.rb +718 -0
  84. data/lib/synthra/behaviors/applicator.rb +165 -0
  85. data/lib/synthra/behaviors/base.rb +155 -0
  86. data/lib/synthra/behaviors/close_connection.rb +60 -0
  87. data/lib/synthra/behaviors/deprecated.rb +100 -0
  88. data/lib/synthra/behaviors/failure.rb +69 -0
  89. data/lib/synthra/behaviors/latency.rb +103 -0
  90. data/lib/synthra/behaviors/partial_data.rb +83 -0
  91. data/lib/synthra/behaviors/randomize_order.rb +74 -0
  92. data/lib/synthra/behaviors/registry.rb +238 -0
  93. data/lib/synthra/behaviors/simulate_error.rb +79 -0
  94. data/lib/synthra/cli/commands/base.rb +105 -0
  95. data/lib/synthra/cli/commands/diff.rb +227 -0
  96. data/lib/synthra/cli/commands/docs.rb +67 -0
  97. data/lib/synthra/cli/commands/export.rb +150 -0
  98. data/lib/synthra/cli/commands/generate.rb +72 -0
  99. data/lib/synthra/cli/commands/import.rb +80 -0
  100. data/lib/synthra/cli/commands/lint.rb +53 -0
  101. data/lib/synthra/cli/commands/live.rb +57 -0
  102. data/lib/synthra/cli/commands/seed.rb +99 -0
  103. data/lib/synthra/cli/commands/validate.rb +28 -0
  104. data/lib/synthra/cli.rb +2471 -0
  105. data/lib/synthra/config_file.rb +129 -0
  106. data/lib/synthra/configuration.rb +281 -0
  107. data/lib/synthra/contracts_registry.rb +408 -0
  108. data/lib/synthra/database_seeder.rb +268 -0
  109. data/lib/synthra/deterministic_ids.rb +218 -0
  110. data/lib/synthra/documentation_generator.rb +414 -0
  111. data/lib/synthra/engine.rb +251 -0
  112. data/lib/synthra/errors.rb +1169 -0
  113. data/lib/synthra/export/base.rb +85 -0
  114. data/lib/synthra/export/csv.rb +101 -0
  115. data/lib/synthra/export/graphql.rb +266 -0
  116. data/lib/synthra/export/graphql_federation.rb +377 -0
  117. data/lib/synthra/export/graphviz.rb +258 -0
  118. data/lib/synthra/export/javascript.rb +327 -0
  119. data/lib/synthra/export/json_data.rb +61 -0
  120. data/lib/synthra/export/json_schema.rb +290 -0
  121. data/lib/synthra/export/openapi.rb +514 -0
  122. data/lib/synthra/export/protobuf.rb +483 -0
  123. data/lib/synthra/export/python.rb +560 -0
  124. data/lib/synthra/export/sql.rb +381 -0
  125. data/lib/synthra/export/sql_insert.rb +152 -0
  126. data/lib/synthra/export/terraform.rb +501 -0
  127. data/lib/synthra/export/type_mapping.rb +389 -0
  128. data/lib/synthra/export/typescript.rb +300 -0
  129. data/lib/synthra/export/xml_data.rb +104 -0
  130. data/lib/synthra/export/yaml_data.rb +56 -0
  131. data/lib/synthra/export.rb +404 -0
  132. data/lib/synthra/factory_bot_integration.rb +157 -0
  133. data/lib/synthra/field.rb +440 -0
  134. data/lib/synthra/functions/registry.rb +104 -0
  135. data/lib/synthra/generator/context.rb +372 -0
  136. data/lib/synthra/generator/engine.rb +336 -0
  137. data/lib/synthra/generator/faker_adapter.rb +425 -0
  138. data/lib/synthra/generator/modes.rb +444 -0
  139. data/lib/synthra/generator/resolver.rb +256 -0
  140. data/lib/synthra/generator/rng.rb +273 -0
  141. data/lib/synthra/generator/streamer.rb +63 -0
  142. data/lib/synthra/generator/uniqueness.rb +118 -0
  143. data/lib/synthra/initializer.rb +97 -0
  144. data/lib/synthra/limits.rb +279 -0
  145. data/lib/synthra/live_preview.rb +518 -0
  146. data/lib/synthra/loader_config.rb +78 -0
  147. data/lib/synthra/lsp/server.rb +689 -0
  148. data/lib/synthra/migration_generator.rb +301 -0
  149. data/lib/synthra/mixin.rb +174 -0
  150. data/lib/synthra/mock_server.rb +458 -0
  151. data/lib/synthra/native_engine.rb +304 -0
  152. data/lib/synthra/openapi_importer.rb +228 -0
  153. data/lib/synthra/output/json_formatter.rb +40 -0
  154. data/lib/synthra/output/ndjson_formatter.rb +55 -0
  155. data/lib/synthra/parser/ast.rb +1287 -0
  156. data/lib/synthra/parser/lexer.rb +1152 -0
  157. data/lib/synthra/parser/parser.rb +1664 -0
  158. data/lib/synthra/parser/tokens.rb +461 -0
  159. data/lib/synthra/performance_mode.rb +364 -0
  160. data/lib/synthra/personas.rb +397 -0
  161. data/lib/synthra/property_testing.rb +172 -0
  162. data/lib/synthra/quality_metrics.rb +405 -0
  163. data/lib/synthra/rails_test_helper.rb +248 -0
  164. data/lib/synthra/registry.rb +533 -0
  165. data/lib/synthra/relationships.rb +193 -0
  166. data/lib/synthra/repl/enhanced_repl.rb +607 -0
  167. data/lib/synthra/repl/formatter.rb +191 -0
  168. data/lib/synthra/scenarios.rb +423 -0
  169. data/lib/synthra/schema.rb +605 -0
  170. data/lib/synthra/schema_inheritance.rb +104 -0
  171. data/lib/synthra/schema_versioning.rb +212 -0
  172. data/lib/synthra/snapshot_testing.rb +199 -0
  173. data/lib/synthra/time_travel.rb +338 -0
  174. data/lib/synthra/type_definitions.rb +274 -0
  175. data/lib/synthra/types/address_location/addresses.rb +125 -0
  176. data/lib/synthra/types/address_location/airports.rb +200 -0
  177. data/lib/synthra/types/address_location/banks_hospitals.rb +207 -0
  178. data/lib/synthra/types/address_location/locations.rb +406 -0
  179. data/lib/synthra/types/base.rb +48 -0
  180. data/lib/synthra/types/commerce_products/commerce.rb +119 -0
  181. data/lib/synthra/types/commerce_products/companies.rb +71 -0
  182. data/lib/synthra/types/commerce_products/construction.rb +126 -0
  183. data/lib/synthra/types/commerce_products/products.rb +205 -0
  184. data/lib/synthra/types/core/collections.rb +513 -0
  185. data/lib/synthra/types/core/defaults.rb +76 -0
  186. data/lib/synthra/types/core/enums.rb +102 -0
  187. data/lib/synthra/types/core/identifiers.rb +445 -0
  188. data/lib/synthra/types/core/primitives.rb +586 -0
  189. data/lib/synthra/types/core/references.rb +466 -0
  190. data/lib/synthra/types/core/sequences.rb +304 -0
  191. data/lib/synthra/types/crypto/crypto.rb +162 -0
  192. data/lib/synthra/types/date_time/dates.rb +387 -0
  193. data/lib/synthra/types/finance_banking/banking.rb +424 -0
  194. data/lib/synthra/types/finance_banking/credit_cards.rb +24 -0
  195. data/lib/synthra/types/finance_banking/identifiers.rb +93 -0
  196. data/lib/synthra/types/formula.rb +184 -0
  197. data/lib/synthra/types/health_medical/medical.rb +150 -0
  198. data/lib/synthra/types/hostile_payloads.rb +122 -0
  199. data/lib/synthra/types/json_array.rb +51 -0
  200. data/lib/synthra/types/media_entertainment/media.rb +62 -0
  201. data/lib/synthra/types/naughty_string.rb +30 -0
  202. data/lib/synthra/types/personal_names/chinese.rb +41 -0
  203. data/lib/synthra/types/personal_names/identifiers.rb +187 -0
  204. data/lib/synthra/types/personal_names/names.rb +246 -0
  205. data/lib/synthra/types/personal_names/national_id.rb +89 -0
  206. data/lib/synthra/types/personal_names/titles_suffixes.rb +41 -0
  207. data/lib/synthra/types/regex.rb +248 -0
  208. data/lib/synthra/types/registry.rb +182 -0
  209. data/lib/synthra/types/repeating_element.rb +50 -0
  210. data/lib/synthra/types/scenario.rb +29 -0
  211. data/lib/synthra/types/technology_internet/apps.rb +67 -0
  212. data/lib/synthra/types/technology_internet/communication.rb +162 -0
  213. data/lib/synthra/types/technology_internet/devices.rb +80 -0
  214. data/lib/synthra/types/technology_internet/formats.rb +139 -0
  215. data/lib/synthra/types/technology_internet/networking.rb +143 -0
  216. data/lib/synthra/types/template.rb +92 -0
  217. data/lib/synthra/types/text_content/business.rb +128 -0
  218. data/lib/synthra/types/text_content/colors.rb +70 -0
  219. data/lib/synthra/types/text_content/misc.rb +237 -0
  220. data/lib/synthra/types/text_content/security.rb +130 -0
  221. data/lib/synthra/types/text_content/text_generation.rb +532 -0
  222. data/lib/synthra/types/travel/travel.rb +150 -0
  223. data/lib/synthra/utils/string_distance.rb +133 -0
  224. data/lib/synthra/validator/dsl_validator.rb +339 -0
  225. data/lib/synthra/validator/path_validator.rb +615 -0
  226. data/lib/synthra/version.rb +35 -0
  227. data/lib/synthra/webhook_simulator.rb +341 -0
  228. data/lib/synthra.rb +259 -0
  229. data/schemas/address.dsl +16 -0
  230. data/schemas/api_response.dsl +8 -0
  231. data/schemas/error_payload.dsl +9 -0
  232. data/schemas/order.dsl +10 -0
  233. data/schemas/order_item.dsl +8 -0
  234. data/schemas/payment.dsl +11 -0
  235. data/schemas/social_post.dsl +13 -0
  236. data/schemas/user.dsl +10 -0
  237. data/scripts/batch_fix_all.rb +96 -0
  238. data/scripts/delete_old_files.rb +32 -0
  239. data/scripts/fix_all_grouped_files.rb +137 -0
  240. data/scripts/fix_all_indentation.rb +48 -0
  241. data/scripts/fix_all_syntax.rb +124 -0
  242. data/scripts/fix_grouped_files.rb +184 -0
  243. data/scripts/fix_syntax_errors.rb +108 -0
  244. data/scripts/group_domain_types.rb +112 -0
  245. data/scripts/group_domain_types_fixed.rb +150 -0
  246. data/scripts/merge_domains_to_one_file.rb +68 -0
  247. data/scripts/move_existing_types.rb +130 -0
  248. data/scripts/split_grouped_types.rb +142 -0
  249. data/tech_docs/README.md +134 -0
  250. data/tech_docs/advanced/streaming.md +405 -0
  251. data/tech_docs/advanced/thread_safety.md +253 -0
  252. data/tech_docs/api/overview.md +485 -0
  253. data/tech_docs/appendices/type_chart.md +193 -0
  254. data/tech_docs/basic_concepts.md +383 -0
  255. data/tech_docs/behaviors/overview.md +346 -0
  256. data/tech_docs/dsl/complex_types.md +790 -0
  257. data/tech_docs/dsl/core_types.md +464 -0
  258. data/tech_docs/dsl/datetime_types.md +325 -0
  259. data/tech_docs/dsl/field_modifiers.md +414 -0
  260. data/tech_docs/dsl/grammar.md +431 -0
  261. data/tech_docs/dsl/schema_definition.md +399 -0
  262. data/tech_docs/export/README.md +276 -0
  263. data/tech_docs/installation.md +273 -0
  264. data/tech_docs/integration/ci_cd.md +707 -0
  265. data/tech_docs/integration/ci_cd_guide.md +579 -0
  266. data/tech_docs/integration/factory_bot.md +485 -0
  267. data/tech_docs/integration/rails.md +630 -0
  268. data/tech_docs/integration/rspec.md +449 -0
  269. data/tech_docs/modes/overview.md +350 -0
  270. data/tech_docs/performance/NATIVE_RUST_EXTENSION.md +1270 -0
  271. data/tech_docs/performance/OPTIMIZATION_GUIDE.md +901 -0
  272. data/tech_docs/quick_start.md +256 -0
  273. data/tech_docs/templates/README.md +805 -0
  274. data/tech_docs/tutorials/advanced.md +371 -0
  275. data/tech_docs/tutorials/getting_started.md +189 -0
  276. data/tech_docs/tutorials/intermediate.md +231 -0
  277. data/tech_docs/tutorials/template_gallery.md +569 -0
  278. data/tech_example/01_basic_usage.rb +238 -0
  279. data/tech_example/02_types_demo.rb +336 -0
  280. data/tech_example/04_cli_usage.md +429 -0
  281. data/tech_example/05_database_seeding.rb +283 -0
  282. data/tech_example/07_rspec_integration.rb +359 -0
  283. data/tech_example/10_custom_types.rb +387 -0
  284. data/tech_example/12_behaviors.rb +372 -0
  285. data/tech_example/13_twitter_dm_example.rb +120 -0
  286. data/tech_example/14_exact_json_structure.rb +253 -0
  287. data/tech_example/16_lsp_server.rb +56 -0
  288. data/tech_example/17_property_testing.rb +92 -0
  289. data/tech_example/18_enhanced_repl.rb +120 -0
  290. data/tech_example/19_security_fuzzing.rb +145 -0
  291. data/tech_example/NEW_FEATURES.md +169 -0
  292. data/tech_example/README.md +118 -0
  293. data/tech_example/schemas/api_response.dsl +123 -0
  294. data/tech_example/schemas/ecommerce.dsl +121 -0
  295. data/tech_example/schemas/twitter_dm.dsl +109 -0
  296. data/tech_example/schemas/user.dsl +36 -0
  297. data/vscode-extension/README.md +246 -0
  298. data/vscode-extension/language-configuration.json +31 -0
  299. data/vscode-extension/package.json +55 -0
  300. data/vscode-extension/snippets/fakedatadsl.json +198 -0
  301. data/vscode-extension/syntaxes/fakedatadsl.tmLanguage.json +128 -0
  302. metadata +478 -0
@@ -0,0 +1,227 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Synthra
4
+ class CLI
5
+ module Commands
6
+ # Diff command - compares schema versions
7
+ #
8
+ # @example
9
+ # synthra diff schemas/v1/ schemas/v2/
10
+ # synthra diff schemas/v1/ schemas/v2/ --breaking-only
11
+ #
12
+ class Diff < Base
13
+ def call
14
+ path1 = args.shift
15
+ path2 = args.shift
16
+
17
+ unless path1 && path2
18
+ error "Usage: synthra diff <path1> <path2> [--breaking-only]"
19
+ return EXIT_PARSE_ERROR
20
+ end
21
+
22
+ registry1 = Registry.new
23
+ registry2 = Registry.new
24
+
25
+ load_path(registry1, path1)
26
+ load_path(registry2, path2)
27
+
28
+ changes = compute_diff(registry1, registry2)
29
+
30
+ if options[:breaking_only]
31
+ display_breaking_changes(changes, path1, path2)
32
+ else
33
+ display_full_diff(changes, path1, path2)
34
+ end
35
+ end
36
+
37
+ private
38
+
39
+ # Compute detailed diff between two registries
40
+ #
41
+ # @return [Hash] structured diff results
42
+ #
43
+ def compute_diff(registry1, registry2)
44
+ schemas1 = registry1.schemas
45
+ schemas2 = registry2.schemas
46
+ all_names = (schemas1.keys + schemas2.keys).uniq.sort
47
+
48
+ {
49
+ added: schemas2.keys - schemas1.keys,
50
+ removed: schemas1.keys - schemas2.keys,
51
+ modified: compute_modified(schemas1, schemas2, all_names),
52
+ breaking: compute_breaking_changes(schemas1, schemas2, all_names)
53
+ }
54
+ end
55
+
56
+ def compute_modified(schemas1, schemas2, all_names)
57
+ modified = {}
58
+
59
+ (schemas1.keys & schemas2.keys).each do |name|
60
+ s1 = schemas1[name]
61
+ s2 = schemas2[name]
62
+ changes = compare_schemas(s1, s2)
63
+ modified[name] = changes if changes.any?
64
+ end
65
+
66
+ modified
67
+ end
68
+
69
+ def compute_breaking_changes(schemas1, schemas2, all_names)
70
+ breaking = []
71
+
72
+ # Removed schemas are breaking
73
+ (schemas1.keys - schemas2.keys).each do |name|
74
+ breaking << { schema: name, type: :schema_removed, message: "Schema '#{name}' removed" }
75
+ end
76
+
77
+ # Check field changes
78
+ (schemas1.keys & schemas2.keys).each do |name|
79
+ s1 = schemas1[name]
80
+ s2 = schemas2[name]
81
+
82
+ # Removed fields are breaking
83
+ f1_names = s1.fields.map(&:name)
84
+ f2_names = s2.fields.map(&:name)
85
+
86
+ (f1_names - f2_names).each do |field_name|
87
+ breaking << {
88
+ schema: name,
89
+ type: :field_removed,
90
+ field: field_name,
91
+ message: "Field '#{field_name}' removed from '#{name}'"
92
+ }
93
+ end
94
+
95
+ # Type changes are breaking
96
+ (f1_names & f2_names).each do |field_name|
97
+ field1 = s1.field(field_name)
98
+ field2 = s2.field(field_name)
99
+
100
+ if field1.type_name != field2.type_name
101
+ breaking << {
102
+ schema: name,
103
+ type: :type_changed,
104
+ field: field_name,
105
+ from: field1.type_name,
106
+ to: field2.type_name,
107
+ message: "Field '#{field_name}' type changed: #{field1.type_name} → #{field2.type_name}"
108
+ }
109
+ end
110
+
111
+ # Required → Optional is not breaking, but Optional → Required is
112
+ if !field1.optional? && field2.optional?
113
+ # Not breaking
114
+ elsif field1.optional? && !field2.optional?
115
+ breaking << {
116
+ schema: name,
117
+ type: :required_added,
118
+ field: field_name,
119
+ message: "Field '#{field_name}' changed from optional to required"
120
+ }
121
+ end
122
+ end
123
+ end
124
+
125
+ breaking
126
+ end
127
+
128
+ def compare_schemas(s1, s2)
129
+ diff = []
130
+
131
+ # Compare versions
132
+ if s1.version != s2.version
133
+ diff << "version: #{s1.version || 'nil'} → #{s2.version || 'nil'}"
134
+ end
135
+
136
+ # Compare deprecation
137
+ if s1.deprecated? != s2.deprecated?
138
+ diff << "deprecated: #{s1.deprecated?} → #{s2.deprecated?}"
139
+ end
140
+
141
+ # Compare fields
142
+ f1_names = s1.fields.map(&:name)
143
+ f2_names = s2.fields.map(&:name)
144
+
145
+ (f2_names - f1_names).each { |f| diff << "+ field: #{f}" }
146
+ (f1_names - f2_names).each { |f| diff << "- field: #{f}" }
147
+
148
+ # Compare field types
149
+ (f1_names & f2_names).each do |name|
150
+ field1 = s1.field(name)
151
+ field2 = s2.field(name)
152
+ if field1.type_name != field2.type_name
153
+ diff << "~ #{name}: #{field1.type_name} → #{field2.type_name}"
154
+ end
155
+ end
156
+
157
+ diff
158
+ end
159
+
160
+ def display_breaking_changes(changes, path1, path2)
161
+ breaking = changes[:breaking]
162
+
163
+ if breaking.empty?
164
+ success "No breaking changes detected"
165
+ return EXIT_SUCCESS
166
+ end
167
+
168
+ puts "=" * 60
169
+ puts "BREAKING CHANGES DETECTED"
170
+ puts "=" * 60
171
+ puts
172
+ puts "Comparing: #{path1} → #{path2}"
173
+ puts
174
+
175
+ breaking.each do |change|
176
+ puts " ✗ #{change[:message]}"
177
+ end
178
+
179
+ puts
180
+ error "#{breaking.length} breaking change(s) found"
181
+ EXIT_PARSE_ERROR
182
+ end
183
+
184
+ def display_full_diff(changes, path1, path2)
185
+ puts "=" * 60
186
+ puts "Schema Diff"
187
+ puts "=" * 60
188
+ puts "Comparing: #{path1} → #{path2}"
189
+ puts
190
+
191
+ has_diff = false
192
+
193
+ # Added schemas
194
+ changes[:added].each do |name|
195
+ puts "➕ #{name} (added)"
196
+ has_diff = true
197
+ end
198
+
199
+ # Removed schemas
200
+ changes[:removed].each do |name|
201
+ puts "➖ #{name} (removed)"
202
+ has_diff = true
203
+ end
204
+
205
+ # Modified schemas
206
+ changes[:modified].each do |name, diffs|
207
+ puts "📝 #{name} (modified)"
208
+ diffs.each { |d| puts " #{d}" }
209
+ has_diff = true
210
+ end
211
+
212
+ unless has_diff
213
+ success "No differences found"
214
+ end
215
+
216
+ # Show breaking changes summary if any
217
+ if changes[:breaking].any?
218
+ puts
219
+ puts "⚠️ Breaking changes: #{changes[:breaking].length}"
220
+ end
221
+
222
+ EXIT_SUCCESS
223
+ end
224
+ end
225
+ end
226
+ end
227
+ end
@@ -0,0 +1,67 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Synthra
4
+ class CLI
5
+ module Commands
6
+ # CLI command for generating schema documentation
7
+ #
8
+ # @example Generate docs
9
+ # $ synthra docs schemas/ --output docs/
10
+ #
11
+ class Docs
12
+ EXIT_SUCCESS = 0
13
+ EXIT_ERROR = 1
14
+
15
+ def initialize
16
+ # No args needed for this command
17
+ end
18
+
19
+ def call(schema_dir, options)
20
+ unless File.directory?(schema_dir)
21
+ $stderr.puts "✗ Schema directory not found: #{schema_dir}"
22
+ return EXIT_ERROR
23
+ end
24
+
25
+ output_dir = options[:output] || "docs"
26
+ title = options[:title] || "Synthra Schema Documentation"
27
+
28
+ generate_documentation(schema_dir, output_dir, title)
29
+ end
30
+
31
+ private
32
+
33
+ def generate_documentation(schema_dir, output_dir, title)
34
+ require_relative "../../documentation_generator"
35
+
36
+ puts "📚 Generating documentation..."
37
+ puts " Source: #{schema_dir}"
38
+ puts " Output: #{output_dir}"
39
+ puts ""
40
+
41
+ # Load schemas
42
+ registry = Registry.new
43
+ registry.load_dir(schema_dir)
44
+
45
+ if registry.names.empty?
46
+ $stderr.puts "✗ No schemas found in #{schema_dir}"
47
+ return EXIT_ERROR
48
+ end
49
+
50
+ # Generate docs
51
+ generator = DocumentationGenerator.new(registry, title: title)
52
+ files = generator.generate_html(output_dir)
53
+
54
+ puts "✅ Generated #{files.count} documentation files:"
55
+ files.each { |path| puts " #{path}" }
56
+ puts ""
57
+ puts " Open #{output_dir}/index.html in your browser"
58
+
59
+ EXIT_SUCCESS
60
+ rescue StandardError => e
61
+ $stderr.puts "✗ Documentation generation failed: #{e.message}"
62
+ EXIT_ERROR
63
+ end
64
+ end
65
+ end
66
+ end
67
+ end
@@ -0,0 +1,150 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "fileutils"
4
+
5
+ module Synthra
6
+ class CLI
7
+ module Commands
8
+ # Export command - exports schemas to various formats
9
+ #
10
+ # @example
11
+ # synthra export User -d schemas -f typescript -o user.ts
12
+ # synthra export --all -d schemas -f graphql -o schema.graphql
13
+ #
14
+ class Export < Base
15
+ def call
16
+ schema_name = args.shift
17
+
18
+ registry = Registry.new
19
+ load_path(registry, options[:dir], validate: false)
20
+
21
+ format = options[:export_format]
22
+
23
+ output = if options[:all]
24
+ export_all(registry, format)
25
+ else
26
+ unless schema_name
27
+ error "Usage: synthra export <SchemaName> [options]"
28
+ error " synthra export --all [options]"
29
+ return EXIT_PARSE_ERROR
30
+ end
31
+ export_single(registry, schema_name, format)
32
+ end
33
+
34
+ return EXIT_PARSE_ERROR unless output
35
+
36
+ write_output(output, schema_name)
37
+ EXIT_SUCCESS
38
+ rescue KeyError
39
+ error "Unknown schema: #{schema_name}"
40
+ $stderr.puts "Available schemas: #{registry.names.join(", ")}"
41
+ EXIT_PARSE_ERROR
42
+ end
43
+
44
+ private
45
+
46
+ def export_all(registry, format)
47
+ case format
48
+ when "typescript", "ts"
49
+ Synthra::Export::Typescript.export_all(registry)
50
+ when "javascript", "js"
51
+ Synthra::Export::Javascript.export_all(registry)
52
+ when "python", "py", "pydantic"
53
+ Synthra::Export::Python.export_all(registry, style: options[:python_style])
54
+ when "dataclass"
55
+ Synthra::Export::Python.export_all(registry, style: :dataclass)
56
+ when "sql", "sql-ddl"
57
+ Synthra::Export::Sql.export_all(registry, dialect: options[:sql_dialect])
58
+ when "graphviz", "dot"
59
+ Synthra::Export::Graphviz.new(nil, registry: registry).export
60
+ when "graphql"
61
+ Synthra::Export::Graphql.export_all(registry)
62
+ else
63
+ error "Bulk export not supported for format: #{format}"
64
+ nil
65
+ end
66
+ end
67
+
68
+ def export_single(registry, schema_name, format)
69
+ schema = registry.schema(schema_name)
70
+ export_opts = build_export_options(registry)
71
+
72
+ case format
73
+ # Schema export formats
74
+ when "json-schema"
75
+ Synthra::Export::JsonSchema.new(schema, **export_opts).export
76
+ when "typescript", "ts"
77
+ Synthra::Export::Typescript.new(schema, **export_opts).export
78
+ when "javascript", "js"
79
+ Synthra::Export::Javascript.new(schema, **export_opts).export
80
+ when "python", "py", "pydantic"
81
+ Synthra::Export::Python.new(schema, **export_opts.merge(style: options[:python_style])).export
82
+ when "dataclass"
83
+ Synthra::Export::Python.new(schema, **export_opts.merge(style: :dataclass)).export
84
+ when "sql", "sql-ddl"
85
+ Synthra::Export::Sql.new(schema, registry: registry, dialect: options[:sql_dialect]).export
86
+ when "graphviz", "dot"
87
+ Synthra::Export::Graphviz.new(schema, **export_opts).export
88
+ when "graphql"
89
+ Synthra::Export::Graphql.new(schema, **export_opts).export
90
+
91
+ # Data export formats
92
+ when "json", "json-data"
93
+ Synthra::Export::JsonData.new(schema, **export_opts.merge(pretty: options[:pretty])).export
94
+ when "csv"
95
+ Synthra::Export::Csv.new(schema, **export_opts).export
96
+ when "sql-insert", "insert"
97
+ Synthra::Export::SqlInsert.new(schema, **export_opts.merge(
98
+ dialect: options[:sql_dialect],
99
+ batch_insert: options[:batch_insert]
100
+ )).export
101
+ when "yaml", "yml"
102
+ Synthra::Export::YamlData.new(schema, **export_opts).export
103
+ when "xml"
104
+ Synthra::Export::XmlData.new(schema, **export_opts.merge(
105
+ root: options[:root],
106
+ item: options[:item]
107
+ )).export
108
+ else
109
+ error "Unknown export format: #{format}"
110
+ nil
111
+ end
112
+ end
113
+
114
+ def build_export_options(registry)
115
+ {
116
+ registry: registry,
117
+ count: options[:count],
118
+ seed: options[:seed],
119
+ mode: options[:mode] || :random
120
+ }.compact
121
+ end
122
+
123
+ def write_output(output, schema_name)
124
+ output_path = determine_output_path(schema_name)
125
+
126
+ if output_path
127
+ FileUtils.mkdir_p(File.dirname(output_path)) if File.dirname(output_path) != "."
128
+ File.write(output_path, output)
129
+ success "Exported to #{output_path}"
130
+ else
131
+ puts output
132
+ end
133
+ end
134
+
135
+ def determine_output_path(schema_name)
136
+ if options[:output]
137
+ options[:output]
138
+ elsif options[:auto_file]
139
+ output_dir = options[:output_dir] || "."
140
+ filename = Synthra::Export.auto_filename(
141
+ options[:all] ? "all_schemas" : schema_name,
142
+ options[:export_format]
143
+ )
144
+ File.join(output_dir, filename)
145
+ end
146
+ end
147
+ end
148
+ end
149
+ end
150
+ end
@@ -0,0 +1,72 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Synthra
4
+ class CLI
5
+ module Commands
6
+ # Generate command - generates fake data from a schema
7
+ #
8
+ # @example
9
+ # synthra generate User --dir schemas --count 10 --pretty
10
+ #
11
+ class Generate < Base
12
+ def call
13
+ schema_name = args.shift
14
+
15
+ unless schema_name
16
+ error "Usage: synthra generate <SchemaName> [options]"
17
+ return EXIT_PARSE_ERROR
18
+ end
19
+
20
+ registry = Registry.new
21
+ load_schemas_from_dir(registry, options[:dir])
22
+
23
+ schema = registry.schema(schema_name)
24
+ generate_output(schema, registry)
25
+ EXIT_SUCCESS
26
+ rescue KeyError
27
+ error "Unknown schema: #{schema_name}"
28
+ $stderr.puts "Available schemas: #{registry.schemas.keys.join(", ")}"
29
+ EXIT_PARSE_ERROR
30
+ end
31
+
32
+ private
33
+
34
+ def generate_output(schema, registry)
35
+ gen_options = {
36
+ seed: options[:seed],
37
+ mode: options[:mode],
38
+ registry: registry
39
+ }.compact
40
+
41
+ if options[:format] == :ndjson
42
+ schema.generate_stream(count: options[:count], **gen_options).each do |record|
43
+ puts JSON.generate(sanitize_for_json(record))
44
+ end
45
+ else
46
+ records = schema.generate_many(options[:count], **gen_options)
47
+ output = options[:count] == 1 ? records.first : records
48
+
49
+ if options[:pretty]
50
+ puts JSON.pretty_generate(sanitize_for_json(output))
51
+ else
52
+ puts JSON.generate(sanitize_for_json(output))
53
+ end
54
+ end
55
+ end
56
+
57
+ def sanitize_for_json(value)
58
+ case value
59
+ when Float
60
+ (value.infinite? || value.nan?) ? nil : value
61
+ when Hash
62
+ value.transform_values { |v| sanitize_for_json(v) }
63
+ when Array
64
+ value.map { |v| sanitize_for_json(v) }
65
+ else
66
+ value
67
+ end
68
+ end
69
+ end
70
+ end
71
+ end
72
+ end
@@ -0,0 +1,80 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Synthra
4
+ class CLI
5
+ module Commands
6
+ # CLI command for importing schemas from external formats
7
+ #
8
+ # @example Import from OpenAPI
9
+ # $ synthra import openapi.yaml --output schemas/
10
+ #
11
+ class Import
12
+ EXIT_SUCCESS = 0
13
+ EXIT_ERROR = 1
14
+
15
+ def initialize
16
+ # No args needed for this command
17
+ end
18
+
19
+ def call(input_path, options)
20
+ unless File.exist?(input_path)
21
+ $stderr.puts "✗ Input file not found: #{input_path}"
22
+ return EXIT_ERROR
23
+ end
24
+
25
+ output_dir = options[:output] || "schemas"
26
+ format = detect_format(input_path)
27
+
28
+ case format
29
+ when :openapi
30
+ import_openapi(input_path, output_dir)
31
+ when :json_schema
32
+ import_json_schema(input_path, output_dir)
33
+ else
34
+ $stderr.puts "✗ Unsupported format: #{format}"
35
+ EXIT_ERROR
36
+ end
37
+ end
38
+
39
+ private
40
+
41
+ def detect_format(path)
42
+ content = File.read(path)
43
+
44
+ if content.include?("openapi:") || content.include?('"openapi"')
45
+ :openapi
46
+ elsif content.include?("swagger:") || content.include?('"swagger"')
47
+ :openapi
48
+ elsif content.include?('"$schema"') || content.include?("$schema:")
49
+ :json_schema
50
+ else
51
+ :unknown
52
+ end
53
+ end
54
+
55
+ def import_openapi(input_path, output_dir)
56
+ require_relative "../../openapi_importer"
57
+
58
+ puts "📥 Importing from OpenAPI: #{input_path}"
59
+
60
+ importer = OpenAPIImporter.new(input_path)
61
+ created = importer.import_to_directory(output_dir)
62
+
63
+ puts ""
64
+ puts "✅ Successfully imported #{created.count} schemas:"
65
+ created.each { |path| puts " #{path}" }
66
+
67
+ EXIT_SUCCESS
68
+ end
69
+
70
+ def import_json_schema(input_path, output_dir)
71
+ # TODO: Implement JSON Schema import
72
+ puts "📥 JSON Schema import not yet implemented"
73
+ puts " Use OpenAPI format for now"
74
+
75
+ EXIT_ERROR
76
+ end
77
+ end
78
+ end
79
+ end
80
+ end
@@ -0,0 +1,53 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Synthra
4
+ class CLI
5
+ module Commands
6
+ # Lint command - checks schemas for semantic errors
7
+ #
8
+ # @example
9
+ # synthra lint schemas/ --strict
10
+ #
11
+ class Lint < Base
12
+ def call
13
+ path = args.shift
14
+
15
+ unless path
16
+ error "Usage: synthra lint <path|directory>"
17
+ return EXIT_PARSE_ERROR
18
+ end
19
+
20
+ registry = Registry.new
21
+ load_path(registry, path, validate: false)
22
+
23
+ errors = registry.validate_paths
24
+
25
+ if errors.empty?
26
+ success "No lint errors found in #{registry.size} schema(s)"
27
+ if options[:verbose]
28
+ puts "\nSchemas checked:"
29
+ registry.names.sort.each { |name| puts " - #{name}" }
30
+ end
31
+ EXIT_SUCCESS
32
+ else
33
+ error "Found #{errors.length} lint error(s):\n"
34
+
35
+ errors.each_with_index do |err, i|
36
+ puts "#{i + 1}. #{err.message}"
37
+ if err.respond_to?(:suggestions) && err.suggestions&.any?
38
+ puts " Did you mean: #{err.suggestions.first(3).join(", ")}?"
39
+ end
40
+ puts
41
+ end
42
+
43
+ if options[:strict]
44
+ $stderr.puts "Strict mode: #{errors.length} error(s) treated as failures"
45
+ end
46
+
47
+ EXIT_PARSE_ERROR
48
+ end
49
+ end
50
+ end
51
+ end
52
+ end
53
+ end
@@ -0,0 +1,57 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Synthra
4
+ class CLI
5
+ module Commands
6
+ # CLI command for live preview server
7
+ #
8
+ # @example Start live server
9
+ # $ synthra live schemas/ --port 4567
10
+ #
11
+ class Live
12
+ DEFAULT_PORT = 4567
13
+ EXIT_SUCCESS = 0
14
+ EXIT_ERROR = 1
15
+
16
+ def initialize
17
+ # No args needed for this command
18
+ end
19
+
20
+ def call(schema_dir, options)
21
+ unless File.directory?(schema_dir)
22
+ $stderr.puts "✗ Schema directory not found: #{schema_dir}"
23
+ return EXIT_ERROR
24
+ end
25
+
26
+ port = options[:port] || DEFAULT_PORT
27
+
28
+ start_live_server(schema_dir, port)
29
+ end
30
+
31
+ private
32
+
33
+ def start_live_server(schema_dir, port)
34
+ require_relative "../../live_preview"
35
+
36
+ puts "🚀 Starting Synthra Live Preview..."
37
+ puts ""
38
+ puts " Schema directory: #{schema_dir}"
39
+ puts " URL: http://localhost:#{port}"
40
+ puts ""
41
+ puts " Press Ctrl+C to stop"
42
+ puts ""
43
+
44
+ LivePreview.start(schema_dir: schema_dir, port: port)
45
+
46
+ EXIT_SUCCESS
47
+ rescue Errno::EADDRINUSE
48
+ $stderr.puts "✗ Port #{port} is already in use. Try a different port with --port"
49
+ EXIT_ERROR
50
+ rescue StandardError => e
51
+ $stderr.puts "✗ Failed to start server: #{e.message}"
52
+ EXIT_ERROR
53
+ end
54
+ end
55
+ end
56
+ end
57
+ end