Module: LcpRuby::Generators::FeatureRegistry
- Extended by:
- FeatureRegistry
- Included in:
- FeatureRegistry
- Defined in:
- lib/lcp_ruby/generators/feature_registry.rb
Overview
Single source of truth for feature generator metadata.
Three consumers read this:
-
CLI (‘lcp new`) — preset expansion + wizard feature picker. Pure-Ruby, no Rails boot — must be require-able from `bin/lcp` before `rails new` runs.
-
Generator runtime (via ‘Prerequisites` concern) — `requires:` is the contract checked in `check_lcp_prerequisites!`.
-
‘lcp_ruby:doctor` rake task — feature state walk.
Per-entry shape:
label: — wizard-visible string
group: — wizard grouping (Authentication, Data Management, …)
requires: — feature names that must already be installed
provides_models: — model file names dropped under
`config/lcp_ruby/models/<name>.{rb,yml}`. Used by
`installed?` as a file-presence marker. Empty
list means "no canonical model marker" (rare).
Drift between this hash and actual generator output is caught by contract specs that run each generator against a tmpdir and inspect the resulting ‘config/lcp_ruby/models/` directory.
Defined Under Namespace
Classes: CyclicDependency, UnknownFeature
Constant Summary collapse
- MODELS_CONFIG_DIR =
Path constants used by ‘installed?`, the `lcp_ruby:doctor` rake task, and `lcp new` manifest plumbing. `app_template.rb` cannot reuse these — it runs in the `rails new` scaffold context before Bundler loads the gem — so those literals are mirrored inline there and pinned by the registry-vs-template drift test.
"config/lcp_ruby/models"- INSTALL_MANIFEST_PATH =
"tmp/lcp_ruby/install_manifest.json"- FEATURES =
{ "install_auth" => { label: "Built-in authentication (Devise)", group: "Authentication", requires: [], provides_models: %w[user] }, "custom_fields" => { label: "Custom fields", group: "Data Management", requires: [], provides_models: %w[custom_field_definition] }, "export" => { label: "Export", group: "Data Management", requires: [], provides_models: %w[export_log export_profile] }, "import" => { label: "Import", group: "Data Management", requires: %w[background_jobs], provides_models: %w[import_row import_profile] }, "batch_operations" => { label: "Batch operations", group: "Data Management", requires: [], provides_models: %w[batch_operation batch_operation_item] }, "role_model" => { label: "Database-backed roles", group: "Access Control", requires: [], provides_models: %w[role] }, "permission_source" => { label: "Dynamic permissions", group: "Access Control", requires: [], provides_models: %w[permission_config] }, "groups" => { label: "Groups", group: "Access Control", requires: [], provides_models: %w[group group_membership group_role_mapping] }, "auditing" => { label: "Auditing", group: "Audit & Monitoring", requires: [], provides_models: %w[audit_log] }, "monitoring" => { label: "Monitoring dashboard", group: "Audit & Monitoring", requires: [], provides_models: %w[lcp_error_log] }, "saved_filters" => { label: "Saved filters", group: "Audit & Monitoring", requires: [], provides_models: %w[saved_filter] }, "pages" => { label: "Database-backed pages", group: "Pages & Jobs", requires: [], provides_models: %w[page_config] }, "background_jobs" => { label: "Background job tracking", group: "Pages & Jobs", requires: [], provides_models: %w[job_execution] }, "workflow_definition" => { label: "Workflow definitions", group: "Workflows", requires: [], provides_models: %w[workflow_definition] }, "workflow_approvals" => { label: "Workflow approvals", group: "Workflows", requires: [], provides_models: %w[workflow_approval_request workflow_approval_step workflow_approval_task] }, "workflow_audit_log" => { label: "Workflow audit log", group: "Workflows", requires: [], provides_models: %w[workflow_audit_log] }, "gapfree_sequences" => { label: "Gap-free sequences", group: "Other", requires: [], provides_models: %w[gapfree_sequence] }, "api_tokens" => { label: "API tokens", group: "Authentication", requires: %w[install_auth], provides_models: %w[api_token] }, "oidc_role_mappings" => { label: "OIDC role mappings", group: "Authentication", requires: [], provides_models: %w[oidc_role_mapping] } }.freeze
Instance Method Summary collapse
-
#expand_with_dependencies(names) ⇒ Object
Topologically sort feature names so dependencies appear before their dependents.
-
#installed?(feature, destination_root) ⇒ Boolean
File-presence marker.
Instance Method Details
#expand_with_dependencies(names) ⇒ Object
Topologically sort feature names so dependencies appear before their dependents. Stable: order within the input list — and within each ‘requires:` list — is preserved.
Raises ‘UnknownFeature` if any name is not in `FEATURES`, `CyclicDependency` if a cycle is detected.
184 185 186 187 188 189 190 |
# File 'lib/lcp_ruby/generators/feature_registry.rb', line 184 def (names) ordered = [] visited = {} visiting = {} Array(names).each { |n| visit(n.to_s, ordered, visited, visiting) } ordered end |
#installed?(feature, destination_root) ⇒ Boolean
File-presence marker. Returns true when every model in ‘provides_models` exists at `config/lcp_ruby/models/<name>.rb,yml` under destination_root. Empty `provides_models` collapses to `true` — features without a canonical marker are reported as “installed” if their entry exists at all.
167 168 169 170 171 172 173 174 175 176 |
# File 'lib/lcp_ruby/generators/feature_registry.rb', line 167 def installed?(feature, destination_root) = FEATURES[feature.to_s] return false unless models = [:provides_models] return true if models.empty? models_dir = File.join(destination_root, MODELS_CONFIG_DIR) models.all? do |name| %w[rb yml].any? { |ext| File.exist?(File.join(models_dir, "#{name}.#{ext}")) } end end |