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.
- checksums.yaml +7 -0
- data/.rspec +4 -0
- data/.rubocop.yml +40 -0
- data/.yardopts +18 -0
- data/ACCESS_DOCS.md +116 -0
- data/ADVANCED_FEATURES_SUMMARY.md +245 -0
- data/CHANGELOG.md +498 -0
- data/CODE_OF_CONDUCT.md +122 -0
- data/CONTRIBUTING.md +307 -0
- data/DOCUMENTATION.md +204 -0
- data/DOCUMENTATION_GUIDE.md +194 -0
- data/LICENSE +22 -0
- data/README.md +1458 -0
- data/Rakefile +128 -0
- data/benchmark/README.md +591 -0
- data/benchmark/batch_generation.rb +63 -0
- data/benchmark/engine_comparison.rb +295 -0
- data/benchmark/single_record.rb +108 -0
- data/benchmark/streaming.rb +74 -0
- data/docs/ACTIVERECORD_INFERENCE.md +524 -0
- data/docs/API_SERVER.md +278 -0
- data/docs/CONFIG_FILE.md +315 -0
- data/docs/DATA_CONTRACTS.md +312 -0
- data/docs/ENHANCED_REPL.md +304 -0
- data/docs/FACTORY_BOT.md +271 -0
- data/docs/GITHUB_ACTION.md +420 -0
- data/docs/GRAPHQL_EXPORT.md +303 -0
- data/docs/GRAPHQL_FEDERATION.md +651 -0
- data/docs/HOW_TO_GENERATE_DOCS.md +252 -0
- data/docs/LSP.md +189 -0
- data/docs/MIGRATION_GENERATOR.md +547 -0
- data/docs/MOCK_SERVER.md +280 -0
- data/docs/NATIVE_ENGINE.md +298 -0
- data/docs/OPENAPI_EXPORT.md +252 -0
- data/docs/PERFORMANCE_MODE.md +228 -0
- data/docs/PERSONAS.md +547 -0
- data/docs/PROPERTY_TESTING.md +284 -0
- data/docs/PROTOBUF_EXPORT.md +276 -0
- data/docs/QUALITY_METRICS.md +555 -0
- data/docs/QUICK_REFERENCE.md +88 -0
- data/docs/RAILS_ENGINE.md +404 -0
- data/docs/RAILS_INTEGRATION.md +454 -0
- data/docs/README.md +175 -0
- data/docs/REPL_QUICK_REFERENCE.md +149 -0
- data/docs/RUST_INTEGRATION_GUIDE.md +1376 -0
- data/docs/SCENARIOS.md +576 -0
- data/docs/SECURITY_TESTING.md +318 -0
- data/docs/SNAPSHOT_TESTING.md +308 -0
- data/docs/STRING_TEXT_TYPES_REFERENCE.md +512 -0
- data/docs/TERRAFORM_EXPORT.md +447 -0
- data/docs/TIME_TRAVEL.md +507 -0
- data/docs/USAGE_GUIDE.md +980 -0
- data/docs/WEBHOOK_SIMULATOR.md +579 -0
- data/docs/advanced_features.md +183 -0
- data/docs/api_reference.md +153 -0
- data/docs/behaviors.md +138 -0
- data/docs/best_practices.md +191 -0
- data/docs/dsl_reference.md +172 -0
- data/docs/examples.md +162 -0
- data/docs/generation_modes.md +125 -0
- data/docs/getting_started.md +92 -0
- data/docs/index.html +177 -0
- data/docs/strategic/CLOUD_REGISTRY_ARCHITECTURE.md +268 -0
- data/docs/strategic/SIMD_OPTIMIZATION_STRATEGY.md +153 -0
- data/docs/troubleshooting.md +215 -0
- data/docs/type_reference.md +224 -0
- data/examples/hostile_mode_demo.rb +64 -0
- data/exe/synthra +6 -0
- data/ext/synthra_native/Cargo.lock +972 -0
- data/ext/synthra_native/Cargo.toml +46 -0
- data/ext/synthra_native/extconf.rb +41 -0
- data/ext/synthra_native/src/generator.rs +397 -0
- data/ext/synthra_native/src/lib.rs +229 -0
- data/ext/synthra_native/src/types.rs +1485 -0
- data/lib/generators/synthra/install_generator.rb +86 -0
- data/lib/generators/synthra/templates/api_response.dsl +15 -0
- data/lib/generators/synthra/templates/fake_data.rake +116 -0
- data/lib/generators/synthra/templates/synthra.yml +31 -0
- data/lib/generators/synthra/templates/synthra_support.rb +19 -0
- data/lib/generators/synthra/templates/user.dsl +11 -0
- data/lib/synthra/activerecord_inference.rb +394 -0
- data/lib/synthra/api.rb +381 -0
- data/lib/synthra/api_server.rb +718 -0
- data/lib/synthra/behaviors/applicator.rb +165 -0
- data/lib/synthra/behaviors/base.rb +155 -0
- data/lib/synthra/behaviors/close_connection.rb +60 -0
- data/lib/synthra/behaviors/deprecated.rb +100 -0
- data/lib/synthra/behaviors/failure.rb +69 -0
- data/lib/synthra/behaviors/latency.rb +103 -0
- data/lib/synthra/behaviors/partial_data.rb +83 -0
- data/lib/synthra/behaviors/randomize_order.rb +74 -0
- data/lib/synthra/behaviors/registry.rb +238 -0
- data/lib/synthra/behaviors/simulate_error.rb +79 -0
- data/lib/synthra/cli/commands/base.rb +105 -0
- data/lib/synthra/cli/commands/diff.rb +227 -0
- data/lib/synthra/cli/commands/docs.rb +67 -0
- data/lib/synthra/cli/commands/export.rb +150 -0
- data/lib/synthra/cli/commands/generate.rb +72 -0
- data/lib/synthra/cli/commands/import.rb +80 -0
- data/lib/synthra/cli/commands/lint.rb +53 -0
- data/lib/synthra/cli/commands/live.rb +57 -0
- data/lib/synthra/cli/commands/seed.rb +99 -0
- data/lib/synthra/cli/commands/validate.rb +28 -0
- data/lib/synthra/cli.rb +2471 -0
- data/lib/synthra/config_file.rb +129 -0
- data/lib/synthra/configuration.rb +281 -0
- data/lib/synthra/contracts_registry.rb +408 -0
- data/lib/synthra/database_seeder.rb +268 -0
- data/lib/synthra/deterministic_ids.rb +218 -0
- data/lib/synthra/documentation_generator.rb +414 -0
- data/lib/synthra/engine.rb +251 -0
- data/lib/synthra/errors.rb +1169 -0
- data/lib/synthra/export/base.rb +85 -0
- data/lib/synthra/export/csv.rb +101 -0
- data/lib/synthra/export/graphql.rb +266 -0
- data/lib/synthra/export/graphql_federation.rb +377 -0
- data/lib/synthra/export/graphviz.rb +258 -0
- data/lib/synthra/export/javascript.rb +327 -0
- data/lib/synthra/export/json_data.rb +61 -0
- data/lib/synthra/export/json_schema.rb +290 -0
- data/lib/synthra/export/openapi.rb +514 -0
- data/lib/synthra/export/protobuf.rb +483 -0
- data/lib/synthra/export/python.rb +560 -0
- data/lib/synthra/export/sql.rb +381 -0
- data/lib/synthra/export/sql_insert.rb +152 -0
- data/lib/synthra/export/terraform.rb +501 -0
- data/lib/synthra/export/type_mapping.rb +389 -0
- data/lib/synthra/export/typescript.rb +300 -0
- data/lib/synthra/export/xml_data.rb +104 -0
- data/lib/synthra/export/yaml_data.rb +56 -0
- data/lib/synthra/export.rb +404 -0
- data/lib/synthra/factory_bot_integration.rb +157 -0
- data/lib/synthra/field.rb +440 -0
- data/lib/synthra/functions/registry.rb +104 -0
- data/lib/synthra/generator/context.rb +372 -0
- data/lib/synthra/generator/engine.rb +336 -0
- data/lib/synthra/generator/faker_adapter.rb +425 -0
- data/lib/synthra/generator/modes.rb +444 -0
- data/lib/synthra/generator/resolver.rb +256 -0
- data/lib/synthra/generator/rng.rb +273 -0
- data/lib/synthra/generator/streamer.rb +63 -0
- data/lib/synthra/generator/uniqueness.rb +118 -0
- data/lib/synthra/initializer.rb +97 -0
- data/lib/synthra/limits.rb +279 -0
- data/lib/synthra/live_preview.rb +518 -0
- data/lib/synthra/loader_config.rb +78 -0
- data/lib/synthra/lsp/server.rb +689 -0
- data/lib/synthra/migration_generator.rb +301 -0
- data/lib/synthra/mixin.rb +174 -0
- data/lib/synthra/mock_server.rb +458 -0
- data/lib/synthra/native_engine.rb +304 -0
- data/lib/synthra/openapi_importer.rb +228 -0
- data/lib/synthra/output/json_formatter.rb +40 -0
- data/lib/synthra/output/ndjson_formatter.rb +55 -0
- data/lib/synthra/parser/ast.rb +1287 -0
- data/lib/synthra/parser/lexer.rb +1152 -0
- data/lib/synthra/parser/parser.rb +1664 -0
- data/lib/synthra/parser/tokens.rb +461 -0
- data/lib/synthra/performance_mode.rb +364 -0
- data/lib/synthra/personas.rb +397 -0
- data/lib/synthra/property_testing.rb +172 -0
- data/lib/synthra/quality_metrics.rb +405 -0
- data/lib/synthra/rails_test_helper.rb +248 -0
- data/lib/synthra/registry.rb +533 -0
- data/lib/synthra/relationships.rb +193 -0
- data/lib/synthra/repl/enhanced_repl.rb +607 -0
- data/lib/synthra/repl/formatter.rb +191 -0
- data/lib/synthra/scenarios.rb +423 -0
- data/lib/synthra/schema.rb +605 -0
- data/lib/synthra/schema_inheritance.rb +104 -0
- data/lib/synthra/schema_versioning.rb +212 -0
- data/lib/synthra/snapshot_testing.rb +199 -0
- data/lib/synthra/time_travel.rb +338 -0
- data/lib/synthra/type_definitions.rb +274 -0
- data/lib/synthra/types/address_location/addresses.rb +125 -0
- data/lib/synthra/types/address_location/airports.rb +200 -0
- data/lib/synthra/types/address_location/banks_hospitals.rb +207 -0
- data/lib/synthra/types/address_location/locations.rb +406 -0
- data/lib/synthra/types/base.rb +48 -0
- data/lib/synthra/types/commerce_products/commerce.rb +119 -0
- data/lib/synthra/types/commerce_products/companies.rb +71 -0
- data/lib/synthra/types/commerce_products/construction.rb +126 -0
- data/lib/synthra/types/commerce_products/products.rb +205 -0
- data/lib/synthra/types/core/collections.rb +513 -0
- data/lib/synthra/types/core/defaults.rb +76 -0
- data/lib/synthra/types/core/enums.rb +102 -0
- data/lib/synthra/types/core/identifiers.rb +445 -0
- data/lib/synthra/types/core/primitives.rb +586 -0
- data/lib/synthra/types/core/references.rb +466 -0
- data/lib/synthra/types/core/sequences.rb +304 -0
- data/lib/synthra/types/crypto/crypto.rb +162 -0
- data/lib/synthra/types/date_time/dates.rb +387 -0
- data/lib/synthra/types/finance_banking/banking.rb +424 -0
- data/lib/synthra/types/finance_banking/credit_cards.rb +24 -0
- data/lib/synthra/types/finance_banking/identifiers.rb +93 -0
- data/lib/synthra/types/formula.rb +184 -0
- data/lib/synthra/types/health_medical/medical.rb +150 -0
- data/lib/synthra/types/hostile_payloads.rb +122 -0
- data/lib/synthra/types/json_array.rb +51 -0
- data/lib/synthra/types/media_entertainment/media.rb +62 -0
- data/lib/synthra/types/naughty_string.rb +30 -0
- data/lib/synthra/types/personal_names/chinese.rb +41 -0
- data/lib/synthra/types/personal_names/identifiers.rb +187 -0
- data/lib/synthra/types/personal_names/names.rb +246 -0
- data/lib/synthra/types/personal_names/national_id.rb +89 -0
- data/lib/synthra/types/personal_names/titles_suffixes.rb +41 -0
- data/lib/synthra/types/regex.rb +248 -0
- data/lib/synthra/types/registry.rb +182 -0
- data/lib/synthra/types/repeating_element.rb +50 -0
- data/lib/synthra/types/scenario.rb +29 -0
- data/lib/synthra/types/technology_internet/apps.rb +67 -0
- data/lib/synthra/types/technology_internet/communication.rb +162 -0
- data/lib/synthra/types/technology_internet/devices.rb +80 -0
- data/lib/synthra/types/technology_internet/formats.rb +139 -0
- data/lib/synthra/types/technology_internet/networking.rb +143 -0
- data/lib/synthra/types/template.rb +92 -0
- data/lib/synthra/types/text_content/business.rb +128 -0
- data/lib/synthra/types/text_content/colors.rb +70 -0
- data/lib/synthra/types/text_content/misc.rb +237 -0
- data/lib/synthra/types/text_content/security.rb +130 -0
- data/lib/synthra/types/text_content/text_generation.rb +532 -0
- data/lib/synthra/types/travel/travel.rb +150 -0
- data/lib/synthra/utils/string_distance.rb +133 -0
- data/lib/synthra/validator/dsl_validator.rb +339 -0
- data/lib/synthra/validator/path_validator.rb +615 -0
- data/lib/synthra/version.rb +35 -0
- data/lib/synthra/webhook_simulator.rb +341 -0
- data/lib/synthra.rb +259 -0
- data/schemas/address.dsl +16 -0
- data/schemas/api_response.dsl +8 -0
- data/schemas/error_payload.dsl +9 -0
- data/schemas/order.dsl +10 -0
- data/schemas/order_item.dsl +8 -0
- data/schemas/payment.dsl +11 -0
- data/schemas/social_post.dsl +13 -0
- data/schemas/user.dsl +10 -0
- data/scripts/batch_fix_all.rb +96 -0
- data/scripts/delete_old_files.rb +32 -0
- data/scripts/fix_all_grouped_files.rb +137 -0
- data/scripts/fix_all_indentation.rb +48 -0
- data/scripts/fix_all_syntax.rb +124 -0
- data/scripts/fix_grouped_files.rb +184 -0
- data/scripts/fix_syntax_errors.rb +108 -0
- data/scripts/group_domain_types.rb +112 -0
- data/scripts/group_domain_types_fixed.rb +150 -0
- data/scripts/merge_domains_to_one_file.rb +68 -0
- data/scripts/move_existing_types.rb +130 -0
- data/scripts/split_grouped_types.rb +142 -0
- data/tech_docs/README.md +134 -0
- data/tech_docs/advanced/streaming.md +405 -0
- data/tech_docs/advanced/thread_safety.md +253 -0
- data/tech_docs/api/overview.md +485 -0
- data/tech_docs/appendices/type_chart.md +193 -0
- data/tech_docs/basic_concepts.md +383 -0
- data/tech_docs/behaviors/overview.md +346 -0
- data/tech_docs/dsl/complex_types.md +790 -0
- data/tech_docs/dsl/core_types.md +464 -0
- data/tech_docs/dsl/datetime_types.md +325 -0
- data/tech_docs/dsl/field_modifiers.md +414 -0
- data/tech_docs/dsl/grammar.md +431 -0
- data/tech_docs/dsl/schema_definition.md +399 -0
- data/tech_docs/export/README.md +276 -0
- data/tech_docs/installation.md +273 -0
- data/tech_docs/integration/ci_cd.md +707 -0
- data/tech_docs/integration/ci_cd_guide.md +579 -0
- data/tech_docs/integration/factory_bot.md +485 -0
- data/tech_docs/integration/rails.md +630 -0
- data/tech_docs/integration/rspec.md +449 -0
- data/tech_docs/modes/overview.md +350 -0
- data/tech_docs/performance/NATIVE_RUST_EXTENSION.md +1270 -0
- data/tech_docs/performance/OPTIMIZATION_GUIDE.md +901 -0
- data/tech_docs/quick_start.md +256 -0
- data/tech_docs/templates/README.md +805 -0
- data/tech_docs/tutorials/advanced.md +371 -0
- data/tech_docs/tutorials/getting_started.md +189 -0
- data/tech_docs/tutorials/intermediate.md +231 -0
- data/tech_docs/tutorials/template_gallery.md +569 -0
- data/tech_example/01_basic_usage.rb +238 -0
- data/tech_example/02_types_demo.rb +336 -0
- data/tech_example/04_cli_usage.md +429 -0
- data/tech_example/05_database_seeding.rb +283 -0
- data/tech_example/07_rspec_integration.rb +359 -0
- data/tech_example/10_custom_types.rb +387 -0
- data/tech_example/12_behaviors.rb +372 -0
- data/tech_example/13_twitter_dm_example.rb +120 -0
- data/tech_example/14_exact_json_structure.rb +253 -0
- data/tech_example/16_lsp_server.rb +56 -0
- data/tech_example/17_property_testing.rb +92 -0
- data/tech_example/18_enhanced_repl.rb +120 -0
- data/tech_example/19_security_fuzzing.rb +145 -0
- data/tech_example/NEW_FEATURES.md +169 -0
- data/tech_example/README.md +118 -0
- data/tech_example/schemas/api_response.dsl +123 -0
- data/tech_example/schemas/ecommerce.dsl +121 -0
- data/tech_example/schemas/twitter_dm.dsl +109 -0
- data/tech_example/schemas/user.dsl +36 -0
- data/vscode-extension/README.md +246 -0
- data/vscode-extension/language-configuration.json +31 -0
- data/vscode-extension/package.json +55 -0
- data/vscode-extension/snippets/fakedatadsl.json +198 -0
- data/vscode-extension/syntaxes/fakedatadsl.tmLanguage.json +128 -0
- 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
|