diff --git a/.rubocop.yml b/.rubocop.yml index 41ea6784f..85ffd82cb 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -308,6 +308,7 @@ Metrics/BlockLength: - 'packages/forest_admin_datasource_toolkit/lib/forest_admin_datasource_toolkit/components/query/filter_factory.rb' - 'packages/forest_admin_datasource_customizer/lib/forest_admin_datasource_customizer/decorators/action/base_action.rb' - 'packages/forest_admin_datasource_mongoid/lib/forest_admin_datasource_mongoid/collection.rb' + - 'packages/forest_admin_rails/lib/tasks/forest_admin_rails_tasks.rake' - 'packages/forest_admin_rpc_agent/lib/forest_admin_rpc_agent/routes/sse.rb' - 'packages/forest_admin_rpc_agent/lib/forest_admin_rpc_agent/http/router.rb' diff --git a/packages/forest_admin_agent/lib/forest_admin_agent/builder/agent_factory.rb b/packages/forest_admin_agent/lib/forest_admin_agent/builder/agent_factory.rb index d26118251..4255653c4 100644 --- a/packages/forest_admin_agent/lib/forest_admin_agent/builder/agent_factory.rb +++ b/packages/forest_admin_agent/lib/forest_admin_agent/builder/agent_factory.rb @@ -11,6 +11,7 @@ class AgentFactory include ForestAdminDatasourceCustomizer::DSL::DatasourceHelpers attr_reader :customizer, :container, :has_env_secret + attr_accessor :schema_only_mode def setup(options) @options = options @@ -55,7 +56,22 @@ def build # Reset route cache to ensure routes are computed with all customizations ForestAdminAgent::Http::Router.reset_cached_routes! - send_schema + if @schema_only_mode + generate_schema_only + else + send_schema + end + end + + def generate_schema_only + datasource = @container.resolve(:datasource) + schema = build_schema(datasource) + schema_path = Facades::Container.cache(:schema_path) + + write_schema_file(schema_path, schema) + @logger.log('Info', "[ForestAdmin] Schema generated successfully at #{schema_path}") + + schema end def reload! diff --git a/packages/forest_admin_agent/spec/lib/forest_admin_agent/builder/agent_factory_spec.rb b/packages/forest_admin_agent/spec/lib/forest_admin_agent/builder/agent_factory_spec.rb index 945cefcf1..ee1cc84f8 100644 --- a/packages/forest_admin_agent/spec/lib/forest_admin_agent/builder/agent_factory_spec.rb +++ b/packages/forest_admin_agent/spec/lib/forest_admin_agent/builder/agent_factory_spec.rb @@ -47,6 +47,83 @@ module Builder expect(described_class.instance.container.resolve(:datasource)) .to eq(described_class.instance.customizer.datasource({})) end + + it 'calls send_schema when schema_only_mode is false' do + instance = described_class.instance + instance.schema_only_mode = false + allow(instance).to receive(:send_schema) + + instance.build + + expect(instance).to have_received(:send_schema) + end + + it 'calls generate_schema_only when schema_only_mode is true' do + instance = described_class.instance + instance.schema_only_mode = true + allow(instance).to receive(:generate_schema_only) + + instance.build + + expect(instance).to have_received(:generate_schema_only) + ensure + instance.schema_only_mode = false + end + end + + describe 'generate_schema_only' do + it 'generates schema and writes to default path' do + instance = described_class.instance + logger = instance_spy(Services::LoggerService) + instance.instance_variable_set(:@logger, logger) + + datasource = instance_double(ForestAdminDatasourceToolkit::Datasource) + instance.container.register(:datasource, datasource) + + allow(Facades::Container).to receive(:cache).with(:schema_path).and_return('/path/to/schema.json') + allow(ForestAdminAgent::Utils::Schema::SchemaEmitter).to receive_messages(generate: [], meta: {}) + allow(File).to receive(:write) + + instance.generate_schema_only + + expect(File).to have_received(:write).with('/path/to/schema.json', anything) + expect(logger).to have_received(:log).with('Info', '[ForestAdmin] Schema generated successfully at /path/to/schema.json') + end + + it 'returns the generated schema' do + instance = described_class.instance + logger = instance_spy(Services::LoggerService) + instance.instance_variable_set(:@logger, logger) + + datasource = instance_double(ForestAdminDatasourceToolkit::Datasource) + instance.container.register(:datasource, datasource) + + allow(Facades::Container).to receive(:cache).with(:schema_path).and_return('/path/to/schema.json') + allow(ForestAdminAgent::Utils::Schema::SchemaEmitter).to receive_messages(generate: [{ name: 'Book' }], meta: { liana: 'forest-rails' }) + allow(File).to receive(:write) + + result = instance.generate_schema_only + + expect(result).to eq({ meta: { liana: 'forest-rails' }, collections: [{ name: 'Book' }] }) + end + + it 'does not call post_schema' do + instance = described_class.instance + logger = instance_spy(Services::LoggerService) + instance.instance_variable_set(:@logger, logger) + + datasource = instance_double(ForestAdminDatasourceToolkit::Datasource) + instance.container.register(:datasource, datasource) + + allow(Facades::Container).to receive(:cache).with(:schema_path).and_return('/path/to/schema.json') + allow(ForestAdminAgent::Utils::Schema::SchemaEmitter).to receive_messages(generate: [], meta: {}) + allow(File).to receive(:write) + allow(instance).to receive(:post_schema) + + instance.generate_schema_only + + expect(instance).not_to have_received(:post_schema) + end end describe 'reload!' do diff --git a/packages/forest_admin_rails/lib/tasks/forest_admin_rails_tasks.rake b/packages/forest_admin_rails/lib/tasks/forest_admin_rails_tasks.rake index e3b89673d..c6f5ec581 100644 --- a/packages/forest_admin_rails/lib/tasks/forest_admin_rails_tasks.rake +++ b/packages/forest_admin_rails/lib/tasks/forest_admin_rails_tasks.rake @@ -1,4 +1,43 @@ -# desc "Explaining what the task does" -# task :forest_admin_rails do -# # Task goes here -# end +namespace :forest_admin do + namespace :schema do + desc 'Generate the Forest Admin schema file without starting the server or sending it to the API' + task generate: :environment do + require 'forest_admin_agent' + + debug_mode = ENV['debug'] == 'true' + + puts '[ForestAdmin] Starting schema generation...' + + # Force eager loading of all models + Rails.application.eager_load! + + # Check if create_agent.rb exists + create_agent_path = Rails.root.join('lib', 'forest_admin_rails', 'create_agent.rb') + unless File.exist?(create_agent_path) + puts '[ForestAdmin] Error: create_agent.rb not found at lib/forest_admin_rails/create_agent.rb' + puts '[ForestAdmin] Run `rails generate forest_admin_rails ` to create it.' + exit 1 + end + + # Setup the agent factory + agent_factory = ForestAdminAgent::Builder::AgentFactory.instance + agent_factory.setup(ForestAdminRails.config) + + # Enable schema-only mode (generates schema without sending to API) + agent_factory.schema_only_mode = true + + # Load and execute the create_agent.rb file + require create_agent_path + + # Call setup! which will generate the schema + begin + ForestAdminRails::CreateAgent.setup! + puts '[ForestAdmin] Schema generation completed!' + rescue StandardError => e + puts "[ForestAdmin] Error during schema generation: #{e.message}" + puts e.backtrace.first(10).join("\n") if debug_mode + exit 1 + end + end + end +end