seams 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/CHANGELOG.md +335 -0
- data/LICENSE +21 -0
- data/README.md +104 -0
- data/lib/generators/seams/accounts/accounts_generator.rb +272 -0
- data/lib/generators/seams/accounts/templates/README.md.tt +219 -0
- data/lib/generators/seams/accounts/templates/app/models/account.rb.tt +124 -0
- data/lib/generators/seams/accounts/templates/app/models/application_record.rb.tt +7 -0
- data/lib/generators/seams/accounts/templates/app/models/current.rb.tt +38 -0
- data/lib/generators/seams/accounts/templates/app/models/membership.rb.tt +114 -0
- data/lib/generators/seams/accounts/templates/config/routes.rb.tt +8 -0
- data/lib/generators/seams/accounts/templates/db/migrate/create_accounts.rb.tt +29 -0
- data/lib/generators/seams/accounts/templates/db/migrate/create_accounts_memberships.rb.tt +49 -0
- data/lib/generators/seams/accounts/templates/lib/accounts.rb.tt +21 -0
- data/lib/generators/seams/accounts/templates/lib/concerns/account_scoped.rb.tt +97 -0
- data/lib/generators/seams/accounts/templates/lib/concerns/authorization.rb.tt +105 -0
- data/lib/generators/seams/accounts/templates/lib/configuration.rb.tt +26 -0
- data/lib/generators/seams/accounts/templates/lib/engine.rb.tt +55 -0
- data/lib/generators/seams/accounts/templates/spec/factories/accounts.rb.tt +49 -0
- data/lib/generators/seams/accounts/templates/spec/models/accounts/account_spec.rb.tt +64 -0
- data/lib/generators/seams/accounts/templates/spec/models/accounts/membership_spec.rb.tt +99 -0
- data/lib/generators/seams/accounts/templates/spec/runtime/accounts_boot_spec.rb.tt +181 -0
- data/lib/generators/seams/admin/admin_generator.rb +852 -0
- data/lib/generators/seams/admin/templates/README.md.tt +266 -0
- data/lib/generators/seams/admin/templates/app/controllers/admin/accounts_controller.rb.tt +16 -0
- data/lib/generators/seams/admin/templates/app/controllers/admin/accounts_memberships_controller.rb.tt +16 -0
- data/lib/generators/seams/admin/templates/app/controllers/admin/application_controller.rb.tt +282 -0
- data/lib/generators/seams/admin/templates/app/controllers/admin/identities_controller.rb.tt +26 -0
- data/lib/generators/seams/admin/templates/app/controllers/admin/invitations_controller.rb.tt +14 -0
- data/lib/generators/seams/admin/templates/app/controllers/admin/invoices_controller.rb.tt +14 -0
- data/lib/generators/seams/admin/templates/app/controllers/admin/lifetime_passes_controller.rb.tt +14 -0
- data/lib/generators/seams/admin/templates/app/controllers/admin/notification_preferences_controller.rb.tt +15 -0
- data/lib/generators/seams/admin/templates/app/controllers/admin/notifications_controller.rb.tt +18 -0
- data/lib/generators/seams/admin/templates/app/controllers/admin/plans_controller.rb.tt +14 -0
- data/lib/generators/seams/admin/templates/app/controllers/admin/subscriptions_controller.rb.tt +14 -0
- data/lib/generators/seams/admin/templates/app/controllers/admin/teams_controller.rb.tt +14 -0
- data/lib/generators/seams/admin/templates/app/controllers/admin/teams_memberships_controller.rb.tt +16 -0
- data/lib/generators/seams/admin/templates/app/dashboards/admin/account_dashboard.rb.tt +50 -0
- data/lib/generators/seams/admin/templates/app/dashboards/admin/accounts_membership_dashboard.rb.tt +58 -0
- data/lib/generators/seams/admin/templates/app/dashboards/admin/identity_dashboard.rb.tt +48 -0
- data/lib/generators/seams/admin/templates/app/dashboards/admin/invitation_dashboard.rb.tt +51 -0
- data/lib/generators/seams/admin/templates/app/dashboards/admin/invoice_dashboard.rb.tt +67 -0
- data/lib/generators/seams/admin/templates/app/dashboards/admin/lifetime_pass_dashboard.rb.tt +65 -0
- data/lib/generators/seams/admin/templates/app/dashboards/admin/notification_dashboard.rb.tt +58 -0
- data/lib/generators/seams/admin/templates/app/dashboards/admin/notification_preference_dashboard.rb.tt +43 -0
- data/lib/generators/seams/admin/templates/app/dashboards/admin/plan_dashboard.rb.tt +72 -0
- data/lib/generators/seams/admin/templates/app/dashboards/admin/subscription_dashboard.rb.tt +59 -0
- data/lib/generators/seams/admin/templates/app/dashboards/admin/team_dashboard.rb.tt +39 -0
- data/lib/generators/seams/admin/templates/app/dashboards/admin/teams_membership_dashboard.rb.tt +43 -0
- data/lib/generators/seams/admin/templates/app/policies/admin/platform/account_policy.rb.tt +10 -0
- data/lib/generators/seams/admin/templates/app/policies/admin/platform/accounts_membership_policy.rb.tt +10 -0
- data/lib/generators/seams/admin/templates/app/policies/admin/platform/application_policy.rb.tt +85 -0
- data/lib/generators/seams/admin/templates/app/policies/admin/platform/identity_policy.rb.tt +18 -0
- data/lib/generators/seams/admin/templates/app/policies/admin/platform/invitation_policy.rb.tt +9 -0
- data/lib/generators/seams/admin/templates/app/policies/admin/platform/invoice_policy.rb.tt +9 -0
- data/lib/generators/seams/admin/templates/app/policies/admin/platform/lifetime_pass_policy.rb.tt +9 -0
- data/lib/generators/seams/admin/templates/app/policies/admin/platform/notification_policy.rb.tt +9 -0
- data/lib/generators/seams/admin/templates/app/policies/admin/platform/notification_preference_policy.rb.tt +9 -0
- data/lib/generators/seams/admin/templates/app/policies/admin/platform/plan_policy.rb.tt +11 -0
- data/lib/generators/seams/admin/templates/app/policies/admin/platform/subscription_policy.rb.tt +9 -0
- data/lib/generators/seams/admin/templates/app/policies/admin/platform/team_policy.rb.tt +9 -0
- data/lib/generators/seams/admin/templates/app/policies/admin/platform/teams_membership_policy.rb.tt +9 -0
- data/lib/generators/seams/admin/templates/app/policies/admin/tenant/account_policy.rb.tt +33 -0
- data/lib/generators/seams/admin/templates/app/policies/admin/tenant/accounts_membership_policy.rb.tt +24 -0
- data/lib/generators/seams/admin/templates/app/policies/admin/tenant/application_policy.rb.tt +169 -0
- data/lib/generators/seams/admin/templates/app/policies/admin/tenant/identity_policy.rb.tt +67 -0
- data/lib/generators/seams/admin/templates/app/policies/admin/tenant/invitation_policy.rb.tt +24 -0
- data/lib/generators/seams/admin/templates/app/policies/admin/tenant/invoice_policy.rb.tt +21 -0
- data/lib/generators/seams/admin/templates/app/policies/admin/tenant/lifetime_pass_policy.rb.tt +21 -0
- data/lib/generators/seams/admin/templates/app/policies/admin/tenant/notification_policy.rb.tt +25 -0
- data/lib/generators/seams/admin/templates/app/policies/admin/tenant/notification_preference_policy.rb.tt +23 -0
- data/lib/generators/seams/admin/templates/app/policies/admin/tenant/plan_policy.rb.tt +47 -0
- data/lib/generators/seams/admin/templates/app/policies/admin/tenant/subscription_policy.rb.tt +22 -0
- data/lib/generators/seams/admin/templates/app/policies/admin/tenant/team_policy.rb.tt +28 -0
- data/lib/generators/seams/admin/templates/app/policies/admin/tenant/teams_membership_policy.rb.tt +24 -0
- data/lib/generators/seams/admin/templates/config/routes.rb.tt +38 -0
- data/lib/generators/seams/admin/templates/lib/admin.rb.tt +36 -0
- data/lib/generators/seams/admin/templates/lib/concerns/authenticator.rb.tt +66 -0
- data/lib/generators/seams/admin/templates/lib/configuration.rb.tt +90 -0
- data/lib/generators/seams/admin/templates/lib/context.rb.tt +44 -0
- data/lib/generators/seams/admin/templates/lib/engine.rb.tt +68 -0
- data/lib/generators/seams/admin/templates/spec/factories/admin.rb.tt +10 -0
- data/lib/generators/seams/admin/templates/spec/runtime/admin_boot_spec.rb.tt +604 -0
- data/lib/generators/seams/auth/add_oauth_provider/add_oauth_provider_generator.rb +157 -0
- data/lib/generators/seams/auth/add_oauth_provider/templates/adapter.rb.tt +95 -0
- data/lib/generators/seams/auth/add_oauth_provider/templates/adapter_spec.rb.tt +58 -0
- data/lib/generators/seams/auth/auth_generator.rb +311 -0
- data/lib/generators/seams/auth/templates/README.md.tt +289 -0
- data/lib/generators/seams/auth/templates/app/controllers/oauth/callbacks_controller.rb.tt +80 -0
- data/lib/generators/seams/auth/templates/app/controllers/password_resets_controller.rb.tt +44 -0
- data/lib/generators/seams/auth/templates/app/controllers/registrations_controller.rb.tt +34 -0
- data/lib/generators/seams/auth/templates/app/controllers/sessions_controller.rb.tt +49 -0
- data/lib/generators/seams/auth/templates/app/jobs/application_job.rb.tt +7 -0
- data/lib/generators/seams/auth/templates/app/jobs/cleanup_expired_sessions_job.rb.tt +30 -0
- data/lib/generators/seams/auth/templates/app/mailers/passwords_mailer.rb.tt +15 -0
- data/lib/generators/seams/auth/templates/app/models/api_token.rb.tt +62 -0
- data/lib/generators/seams/auth/templates/app/models/application_record.rb.tt +7 -0
- data/lib/generators/seams/auth/templates/app/models/current.rb.tt +15 -0
- data/lib/generators/seams/auth/templates/app/models/identity.rb.tt +74 -0
- data/lib/generators/seams/auth/templates/app/models/oauth/provider.rb.tt +48 -0
- data/lib/generators/seams/auth/templates/app/models/session.rb.tt +28 -0
- data/lib/generators/seams/auth/templates/app/services/authenticate_identity.rb.tt +31 -0
- data/lib/generators/seams/auth/templates/app/services/generate_api_token.rb.tt +35 -0
- data/lib/generators/seams/auth/templates/app/services/oauth/authenticator.rb.tt +94 -0
- data/lib/generators/seams/auth/templates/app/services/register_identity.rb.tt +57 -0
- data/lib/generators/seams/auth/templates/app/services/reset_password.rb.tt +41 -0
- data/lib/generators/seams/auth/templates/app/services/revoke_api_token.rb.tt +38 -0
- data/lib/generators/seams/auth/templates/app/views/password_resets/edit.html.erb.tt +12 -0
- data/lib/generators/seams/auth/templates/app/views/password_resets/new.html.erb.tt +11 -0
- data/lib/generators/seams/auth/templates/app/views/passwords_mailer/reset_email.html.erb.tt +7 -0
- data/lib/generators/seams/auth/templates/app/views/registrations/new.html.erb.tt +26 -0
- data/lib/generators/seams/auth/templates/app/views/sessions/_oauth_buttons.html.erb.tt +18 -0
- data/lib/generators/seams/auth/templates/app/views/sessions/new.html.erb.tt +17 -0
- data/lib/generators/seams/auth/templates/config/routes.rb.tt +21 -0
- data/lib/generators/seams/auth/templates/db/migrate/create_auth_api_tokens.rb.tt +26 -0
- data/lib/generators/seams/auth/templates/db/migrate/create_auth_identities.rb.tt +29 -0
- data/lib/generators/seams/auth/templates/db/migrate/create_auth_oauth_providers.rb.tt +35 -0
- data/lib/generators/seams/auth/templates/db/migrate/create_auth_sessions.rb.tt +19 -0
- data/lib/generators/seams/auth/templates/lib/auth.rb.tt +39 -0
- data/lib/generators/seams/auth/templates/lib/concerns/api_authenticatable.rb.tt +58 -0
- data/lib/generators/seams/auth/templates/lib/concerns/authenticatable.rb.tt +32 -0
- data/lib/generators/seams/auth/templates/lib/concerns/authentication.rb.tt +60 -0
- data/lib/generators/seams/auth/templates/lib/configuration.rb.tt +45 -0
- data/lib/generators/seams/auth/templates/lib/engine.rb.tt +46 -0
- data/lib/generators/seams/auth/templates/lib/oauth/abstract.rb.tt +87 -0
- data/lib/generators/seams/auth/templates/lib/oauth/github.rb.tt +112 -0
- data/lib/generators/seams/auth/templates/lib/oauth/google.rb.tt +78 -0
- data/lib/generators/seams/auth/templates/lib/tasks/auth_pii.rake.tt +68 -0
- data/lib/generators/seams/auth/templates/spec/factories/auth.rb.tt +38 -0
- data/lib/generators/seams/auth/templates/spec/mailers/passwords_mailer_spec.rb.tt +37 -0
- data/lib/generators/seams/auth/templates/spec/models/api_token_spec.rb.tt +84 -0
- data/lib/generators/seams/auth/templates/spec/models/identity_spec.rb.tt +56 -0
- data/lib/generators/seams/auth/templates/spec/models/oauth/provider_spec.rb.tt +64 -0
- data/lib/generators/seams/auth/templates/spec/models/session_spec.rb.tt +34 -0
- data/lib/generators/seams/auth/templates/spec/runtime/boot_spec.rb.tt +30 -0
- data/lib/generators/seams/auth/templates/spec/runtime/event_payload_spec.rb.tt +29 -0
- data/lib/generators/seams/auth/templates/spec/runtime/login_flow_spec.rb.tt +45 -0
- data/lib/generators/seams/billing/billing_generator.rb +476 -0
- data/lib/generators/seams/billing/templates/README.md.tt +355 -0
- data/lib/generators/seams/billing/templates/app/controllers/admin/lifetime_passes_controller.rb.tt +84 -0
- data/lib/generators/seams/billing/templates/app/controllers/checkout_controller.rb.tt +92 -0
- data/lib/generators/seams/billing/templates/app/controllers/invoices_controller.rb.tt +63 -0
- data/lib/generators/seams/billing/templates/app/controllers/plans_controller.rb.tt +14 -0
- data/lib/generators/seams/billing/templates/app/controllers/portal_controller.rb.tt +45 -0
- data/lib/generators/seams/billing/templates/app/controllers/subscriptions_controller.rb.tt +119 -0
- data/lib/generators/seams/billing/templates/app/controllers/webhooks_controller.rb.tt +98 -0
- data/lib/generators/seams/billing/templates/app/helpers/currency_helper.rb.tt +44 -0
- data/lib/generators/seams/billing/templates/app/jobs/application_job.rb.tt +6 -0
- data/lib/generators/seams/billing/templates/app/jobs/cancel_subscription_job.rb.tt +39 -0
- data/lib/generators/seams/billing/templates/app/jobs/start_subscription_job.rb.tt +32 -0
- data/lib/generators/seams/billing/templates/app/jobs/webhooks/process_event_job.rb.tt +37 -0
- data/lib/generators/seams/billing/templates/app/models/application_record.rb.tt +7 -0
- data/lib/generators/seams/billing/templates/app/models/invoice.rb.tt +35 -0
- data/lib/generators/seams/billing/templates/app/models/lifetime_pass.rb.tt +60 -0
- data/lib/generators/seams/billing/templates/app/models/plan.rb.tt +95 -0
- data/lib/generators/seams/billing/templates/app/models/subscription.rb.tt +31 -0
- data/lib/generators/seams/billing/templates/app/models/webhook_event.rb.tt +13 -0
- data/lib/generators/seams/billing/templates/app/services/checkout_session_service.rb.tt +25 -0
- data/lib/generators/seams/billing/templates/app/services/customers/find_or_create_service.rb.tt +73 -0
- data/lib/generators/seams/billing/templates/app/services/invoices/sync_service.rb.tt +50 -0
- data/lib/generators/seams/billing/templates/app/services/lifetime/create_lifetime_session_service.rb.tt +82 -0
- data/lib/generators/seams/billing/templates/app/services/lifetime/create_pass_from_checkout_service.rb.tt +88 -0
- data/lib/generators/seams/billing/templates/app/services/lifetime/grant_pass_service.rb.tt +80 -0
- data/lib/generators/seams/billing/templates/app/services/lifetime/revoke_pass_service.rb.tt +59 -0
- data/lib/generators/seams/billing/templates/app/services/portal_session_service.rb.tt +23 -0
- data/lib/generators/seams/billing/templates/app/services/service_result.rb.tt +38 -0
- data/lib/generators/seams/billing/templates/app/services/stripe_service.rb.tt +67 -0
- data/lib/generators/seams/billing/templates/app/services/subscriptions/cancel_service.rb.tt +42 -0
- data/lib/generators/seams/billing/templates/app/services/subscriptions/change_plan_service.rb.tt +48 -0
- data/lib/generators/seams/billing/templates/app/services/subscriptions/reactivate_service.rb.tt +28 -0
- data/lib/generators/seams/billing/templates/app/services/webhooks/event_router.rb.tt +54 -0
- data/lib/generators/seams/billing/templates/app/services/webhooks/handler.rb.tt +93 -0
- data/lib/generators/seams/billing/templates/app/services/webhooks/handlers/charge_refunded_handler.rb.tt +18 -0
- data/lib/generators/seams/billing/templates/app/services/webhooks/handlers/checkout_session_completed_handler.rb.tt +58 -0
- data/lib/generators/seams/billing/templates/app/services/webhooks/handlers/invoice_created_handler.rb.tt +16 -0
- data/lib/generators/seams/billing/templates/app/services/webhooks/handlers/invoice_finalized_handler.rb.tt +14 -0
- data/lib/generators/seams/billing/templates/app/services/webhooks/handlers/invoice_handler_base.rb.tt +80 -0
- data/lib/generators/seams/billing/templates/app/services/webhooks/handlers/invoice_paid_handler.rb.tt +12 -0
- data/lib/generators/seams/billing/templates/app/services/webhooks/handlers/invoice_payment_failed_handler.rb.tt +12 -0
- data/lib/generators/seams/billing/templates/app/services/webhooks/handlers/invoice_voided_handler.rb.tt +12 -0
- data/lib/generators/seams/billing/templates/app/services/webhooks/handlers/payment_failed_handler.rb.tt +15 -0
- data/lib/generators/seams/billing/templates/app/services/webhooks/handlers/payment_succeeded_handler.rb.tt +19 -0
- data/lib/generators/seams/billing/templates/app/services/webhooks/handlers/subscription_created_handler.rb.tt +11 -0
- data/lib/generators/seams/billing/templates/app/services/webhooks/handlers/subscription_deleted_handler.rb.tt +15 -0
- data/lib/generators/seams/billing/templates/app/services/webhooks/handlers/subscription_handler_base.rb.tt +92 -0
- data/lib/generators/seams/billing/templates/app/services/webhooks/handlers/subscription_trial_will_end_handler.rb.tt +15 -0
- data/lib/generators/seams/billing/templates/app/services/webhooks/handlers/subscription_updated_handler.rb.tt +11 -0
- data/lib/generators/seams/billing/templates/app/views/admin/lifetime_passes/index.html.erb.tt +36 -0
- data/lib/generators/seams/billing/templates/app/views/admin/lifetime_passes/new.html.erb.tt +37 -0
- data/lib/generators/seams/billing/templates/app/views/checkout/success.html.erb.tt +5 -0
- data/lib/generators/seams/billing/templates/app/views/invoices/index.html.erb.tt +22 -0
- data/lib/generators/seams/billing/templates/app/views/invoices/show.html.erb.tt +14 -0
- data/lib/generators/seams/billing/templates/app/views/plans/index.html.erb.tt +51 -0
- data/lib/generators/seams/billing/templates/app/views/subscriptions/index.html.erb.tt +16 -0
- data/lib/generators/seams/billing/templates/app/views/subscriptions/show.html.erb.tt +25 -0
- data/lib/generators/seams/billing/templates/config/routes.rb.tt +39 -0
- data/lib/generators/seams/billing/templates/db/migrate/create_billing_invoices.rb.tt +32 -0
- data/lib/generators/seams/billing/templates/db/migrate/create_billing_lifetime_passes.rb.tt +43 -0
- data/lib/generators/seams/billing/templates/db/migrate/create_billing_plans.rb.tt +31 -0
- data/lib/generators/seams/billing/templates/db/migrate/create_billing_subscriptions.rb.tt +33 -0
- data/lib/generators/seams/billing/templates/db/migrate/create_billing_webhook_events.rb.tt +24 -0
- data/lib/generators/seams/billing/templates/lib/billing.rb.tt +34 -0
- data/lib/generators/seams/billing/templates/lib/concerns/billable.rb.tt +100 -0
- data/lib/generators/seams/billing/templates/lib/configuration.rb.tt +52 -0
- data/lib/generators/seams/billing/templates/lib/engine.rb.tt +72 -0
- data/lib/generators/seams/billing/templates/lib/gateways/abstract.rb.tt +65 -0
- data/lib/generators/seams/billing/templates/lib/gateways/adyen.rb.tt +16 -0
- data/lib/generators/seams/billing/templates/lib/gateways/paddle.rb.tt +22 -0
- data/lib/generators/seams/billing/templates/lib/gateways/stripe.rb.tt +155 -0
- data/lib/generators/seams/billing/templates/lib/stripe/client.rb.tt +101 -0
- data/lib/generators/seams/billing/templates/lib/stripe/webhook_signature.rb.tt +43 -0
- data/lib/generators/seams/billing/templates/lib/tasks/billing_check.rake.tt +34 -0
- data/lib/generators/seams/billing/templates/spec/factories/billing.rb.tt +65 -0
- data/lib/generators/seams/billing/templates/spec/fixtures/stripe/charge_refunded.json.tt +19 -0
- data/lib/generators/seams/billing/templates/spec/fixtures/stripe/checkout_session_completed.json.tt +17 -0
- data/lib/generators/seams/billing/templates/spec/fixtures/stripe/customer_subscription_created.json.tt +25 -0
- data/lib/generators/seams/billing/templates/spec/fixtures/stripe/customer_subscription_deleted.json.tt +17 -0
- data/lib/generators/seams/billing/templates/spec/fixtures/stripe/customer_subscription_trial_will_end.json.tt +17 -0
- data/lib/generators/seams/billing/templates/spec/fixtures/stripe/customer_subscription_updated.json.tt +28 -0
- data/lib/generators/seams/billing/templates/spec/fixtures/stripe/invoice_created.json.tt +18 -0
- data/lib/generators/seams/billing/templates/spec/fixtures/stripe/invoice_finalized.json.tt +18 -0
- data/lib/generators/seams/billing/templates/spec/fixtures/stripe/invoice_paid.json.tt +19 -0
- data/lib/generators/seams/billing/templates/spec/fixtures/stripe/invoice_payment_failed.json.tt +20 -0
- data/lib/generators/seams/billing/templates/spec/fixtures/stripe/invoice_voided.json.tt +18 -0
- data/lib/generators/seams/billing/templates/spec/fixtures/stripe/payment_intent_payment_failed.json.tt +21 -0
- data/lib/generators/seams/billing/templates/spec/fixtures/stripe/payment_intent_succeeded.json.tt +17 -0
- data/lib/generators/seams/billing/templates/spec/gateways/contract_spec.rb.tt +11 -0
- data/lib/generators/seams/billing/templates/spec/gateways/stripe_spec.rb.tt +53 -0
- data/lib/generators/seams/billing/templates/spec/models/plan_spec.rb.tt +81 -0
- data/lib/generators/seams/billing/templates/spec/models/subscription_spec.rb.tt +43 -0
- data/lib/generators/seams/billing/templates/spec/runtime/boot_spec.rb.tt +38 -0
- data/lib/generators/seams/billing/templates/spec/runtime/webhook_handlers_spec.rb.tt +382 -0
- data/lib/generators/seams/billing/templates/spec/support/shared_examples/a_billing_gateway.rb.tt +100 -0
- data/lib/generators/seams/billing/templates/spec/support/stripe_helpers.rb.tt +59 -0
- data/lib/generators/seams/core/core_generator.rb +191 -0
- data/lib/generators/seams/core/templates/README.md.tt +45 -0
- data/lib/generators/seams/core/templates/app/controllers/concerns/has_current_attributes.rb.tt +71 -0
- data/lib/generators/seams/core/templates/app/models/application_record.rb.tt +7 -0
- data/lib/generators/seams/core/templates/app/models/audit_log.rb.tt +19 -0
- data/lib/generators/seams/core/templates/app/models/concerns/auditable.rb.tt +64 -0
- data/lib/generators/seams/core/templates/app/models/concerns/sluggable.rb.tt +53 -0
- data/lib/generators/seams/core/templates/app/models/concerns/soft_deletable.rb.tt +37 -0
- data/lib/generators/seams/core/templates/app/models/concerns/tenant_scoped.rb.tt +39 -0
- data/lib/generators/seams/core/templates/app/models/current.rb.tt +16 -0
- data/lib/generators/seams/core/templates/app/services/event_publisher.rb.tt +23 -0
- data/lib/generators/seams/core/templates/app/validators/email_format_validator.rb.tt +21 -0
- data/lib/generators/seams/core/templates/db/migrate/create_core_audit_logs.rb.tt +29 -0
- data/lib/generators/seams/core/templates/lib/core.rb.tt +8 -0
- data/lib/generators/seams/core/templates/lib/engine.rb.tt +28 -0
- data/lib/generators/seams/core/templates/spec/concerns/auditable_spec.rb.tt +39 -0
- data/lib/generators/seams/core/templates/spec/concerns/sluggable_spec.rb.tt +29 -0
- data/lib/generators/seams/core/templates/spec/models/audit_log_spec.rb.tt +22 -0
- data/lib/generators/seams/core/templates/spec/runtime/boot_spec.rb.tt +25 -0
- data/lib/generators/seams/core/templates/spec/validators/email_format_validator_spec.rb.tt +29 -0
- data/lib/generators/seams/engine/engine_generator.rb +165 -0
- data/lib/generators/seams/engine/templates/Gemfile.tt +19 -0
- data/lib/generators/seams/engine/templates/LICENSE.tt +21 -0
- data/lib/generators/seams/engine/templates/README.md.tt +40 -0
- data/lib/generators/seams/engine/templates/Rakefile.tt +14 -0
- data/lib/generators/seams/engine/templates/app/application_controller.rb.tt +6 -0
- data/lib/generators/seams/engine/templates/app/application_record.rb.tt +16 -0
- data/lib/generators/seams/engine/templates/config/locales/en.yml.tt +14 -0
- data/lib/generators/seams/engine/templates/config/routes.rb.tt +4 -0
- data/lib/generators/seams/engine/templates/gemspec.tt +20 -0
- data/lib/generators/seams/engine/templates/host_initializer.rb.tt +13 -0
- data/lib/generators/seams/engine/templates/lib/engine.rb.tt +27 -0
- data/lib/generators/seams/engine/templates/lib/root.rb.tt +7 -0
- data/lib/generators/seams/engine/templates/lib/version.rb.tt +5 -0
- data/lib/generators/seams/engine/templates/rubocop.yml.tt +55 -0
- data/lib/generators/seams/engine/templates/spec/example_spec.rb.tt +16 -0
- data/lib/generators/seams/engine/templates/spec/spec_helper.rb.tt +23 -0
- data/lib/generators/seams/install/install_generator.rb +211 -0
- data/lib/generators/seams/install/templates/Dockerfile.tt +52 -0
- data/lib/generators/seams/install/templates/Procfile.tt +14 -0
- data/lib/generators/seams/install/templates/bin_seams.tt +107 -0
- data/lib/generators/seams/install/templates/ci.yml.tt +123 -0
- data/lib/generators/seams/install/templates/deploy.yml.tt +63 -0
- data/lib/generators/seams/install/templates/doc/ARCHITECTURE.md.tt +86 -0
- data/lib/generators/seams/install/templates/docker-entrypoint.tt +27 -0
- data/lib/generators/seams/install/templates/rubocop.yml.tt +33 -0
- data/lib/generators/seams/install/templates/ruby-version.tt +1 -0
- data/lib/generators/seams/install/templates/script/collate_coverage.rb.tt +33 -0
- data/lib/generators/seams/install/templates/script/run_affected_tests.sh.tt +64 -0
- data/lib/generators/seams/install/templates/seams.rake.tt +65 -0
- data/lib/generators/seams/install/templates/seams.rb.tt +9 -0
- data/lib/generators/seams/install/templates/seams_engines.rb.tt +15 -0
- data/lib/generators/seams/notifications/notifications_generator.rb +395 -0
- data/lib/generators/seams/notifications/templates/README.md.tt +269 -0
- data/lib/generators/seams/notifications/templates/app/channels/notification_channel.rb.tt +36 -0
- data/lib/generators/seams/notifications/templates/app/controllers/notifications_controller.rb.tt +58 -0
- data/lib/generators/seams/notifications/templates/app/controllers/preferences_controller.rb.tt +54 -0
- data/lib/generators/seams/notifications/templates/app/javascript/controllers/notification_bell_controller.js.tt +34 -0
- data/lib/generators/seams/notifications/templates/app/jobs/application_job.rb.tt +6 -0
- data/lib/generators/seams/notifications/templates/app/jobs/create_notification_job.rb.tt +31 -0
- data/lib/generators/seams/notifications/templates/app/jobs/send_due_notifications_job.rb.tt +22 -0
- data/lib/generators/seams/notifications/templates/app/jobs/send_notification_job.rb.tt +13 -0
- data/lib/generators/seams/notifications/templates/app/mailers/application_mailer.rb.tt +12 -0
- data/lib/generators/seams/notifications/templates/app/mailers/notification_mailer.rb.tt +23 -0
- data/lib/generators/seams/notifications/templates/app/models/application_record.rb.tt +7 -0
- data/lib/generators/seams/notifications/templates/app/models/delivery.rb.tt +13 -0
- data/lib/generators/seams/notifications/templates/app/models/notification.rb.tt +218 -0
- data/lib/generators/seams/notifications/templates/app/models/notification_preference.rb.tt +29 -0
- data/lib/generators/seams/notifications/templates/app/models/strategies/email.rb.tt +38 -0
- data/lib/generators/seams/notifications/templates/app/models/strategies/in_app.rb.tt +26 -0
- data/lib/generators/seams/notifications/templates/app/models/strategies/sms.rb.tt +33 -0
- data/lib/generators/seams/notifications/templates/app/subscribers/auth_subscriber.rb.tt +71 -0
- data/lib/generators/seams/notifications/templates/app/subscribers/billing_subscriber.rb.tt +127 -0
- data/lib/generators/seams/notifications/templates/app/views/layouts/notifications/mailer.html.erb.tt +22 -0
- data/lib/generators/seams/notifications/templates/app/views/layouts/notifications/mailer.text.erb.tt +4 -0
- data/lib/generators/seams/notifications/templates/app/views/notifications/_bell.html.erb.tt +15 -0
- data/lib/generators/seams/notifications/templates/app/views/notifications/index.html.erb.tt +15 -0
- data/lib/generators/seams/notifications/templates/app/views/templates/billing/invoice_failed.html.erb.tt +4 -0
- data/lib/generators/seams/notifications/templates/app/views/templates/billing/invoice_failed.text.erb.tt +4 -0
- data/lib/generators/seams/notifications/templates/app/views/templates/billing/invoice_paid.html.erb.tt +3 -0
- data/lib/generators/seams/notifications/templates/app/views/templates/billing/invoice_paid.text.erb.tt +3 -0
- data/lib/generators/seams/notifications/templates/app/views/templates/billing/lifetime_granted.html.erb.tt +5 -0
- data/lib/generators/seams/notifications/templates/app/views/templates/billing/lifetime_granted.text.erb.tt +5 -0
- data/lib/generators/seams/notifications/templates/app/views/templates/billing/lifetime_purchased.html.erb.tt +5 -0
- data/lib/generators/seams/notifications/templates/app/views/templates/billing/lifetime_purchased.text.erb.tt +5 -0
- data/lib/generators/seams/notifications/templates/app/views/templates/billing/subscription_canceled.html.erb.tt +4 -0
- data/lib/generators/seams/notifications/templates/app/views/templates/billing/subscription_canceled.text.erb.tt +4 -0
- data/lib/generators/seams/notifications/templates/app/views/templates/billing/subscription_started.html.erb.tt +4 -0
- data/lib/generators/seams/notifications/templates/app/views/templates/billing/subscription_started.text.erb.tt +5 -0
- data/lib/generators/seams/notifications/templates/app/views/templates/billing/subscription_updated.html.erb.tt +3 -0
- data/lib/generators/seams/notifications/templates/app/views/templates/billing/subscription_updated.text.erb.tt +3 -0
- data/lib/generators/seams/notifications/templates/app/views/templates/default.html.erb.tt +10 -0
- data/lib/generators/seams/notifications/templates/app/views/templates/default.text.erb.tt +11 -0
- data/lib/generators/seams/notifications/templates/app/views/templates/welcome.html.erb.tt +6 -0
- data/lib/generators/seams/notifications/templates/app/views/templates/welcome.text.erb.tt +6 -0
- data/lib/generators/seams/notifications/templates/config/initializers/notifications.rb.tt +58 -0
- data/lib/generators/seams/notifications/templates/config/routes.rb.tt +17 -0
- data/lib/generators/seams/notifications/templates/db/migrate/create_notification_deliveries.rb.tt +16 -0
- data/lib/generators/seams/notifications/templates/db/migrate/create_notification_preferences.rb.tt +25 -0
- data/lib/generators/seams/notifications/templates/db/migrate/create_notifications.rb.tt +35 -0
- data/lib/generators/seams/notifications/templates/lib/adapters/abstract.rb.tt +20 -0
- data/lib/generators/seams/notifications/templates/lib/adapters/action_mailer.rb.tt +17 -0
- data/lib/generators/seams/notifications/templates/lib/adapters/null_sms.rb.tt +23 -0
- data/lib/generators/seams/notifications/templates/lib/concerns/notifiable.rb.tt +135 -0
- data/lib/generators/seams/notifications/templates/lib/configuration.rb.tt +24 -0
- data/lib/generators/seams/notifications/templates/lib/engine.rb.tt +35 -0
- data/lib/generators/seams/notifications/templates/lib/notifications.rb.tt +75 -0
- data/lib/generators/seams/notifications/templates/lib/type_registry.rb.tt +74 -0
- data/lib/generators/seams/notifications/templates/spec/factories/notifications.rb.tt +53 -0
- data/lib/generators/seams/notifications/templates/spec/models/delivery_spec.rb.tt +28 -0
- data/lib/generators/seams/notifications/templates/spec/models/notification_preference_spec.rb.tt +46 -0
- data/lib/generators/seams/notifications/templates/spec/models/notification_spec.rb.tt +60 -0
- data/lib/generators/seams/notifications/templates/spec/runtime/bell_broadcast_spec.rb.tt +59 -0
- data/lib/generators/seams/notifications/templates/spec/runtime/billing_subscriber_skip_spec.rb.tt +87 -0
- data/lib/generators/seams/notifications/templates/spec/runtime/boot_spec.rb.tt +39 -0
- data/lib/generators/seams/notifications/templates/spec/runtime/schedule_round_trip_spec.rb.tt +55 -0
- data/lib/generators/seams/remove/remove_generator.rb +259 -0
- data/lib/generators/seams/teams/teams_generator.rb +298 -0
- data/lib/generators/seams/teams/templates/README.md.tt +88 -0
- data/lib/generators/seams/teams/templates/app/controllers/invitations_controller.rb.tt +102 -0
- data/lib/generators/seams/teams/templates/app/controllers/memberships_controller.rb.tt +54 -0
- data/lib/generators/seams/teams/templates/app/controllers/teams_controller.rb.tt +68 -0
- data/lib/generators/seams/teams/templates/app/jobs/application_job.rb.tt +6 -0
- data/lib/generators/seams/teams/templates/app/mailers/invitation_mailer.rb.tt +34 -0
- data/lib/generators/seams/teams/templates/app/models/application_record.rb.tt +7 -0
- data/lib/generators/seams/teams/templates/app/models/current.rb.tt +30 -0
- data/lib/generators/seams/teams/templates/app/models/invitation.rb.tt +36 -0
- data/lib/generators/seams/teams/templates/app/models/membership.rb.tt +36 -0
- data/lib/generators/seams/teams/templates/app/models/team.rb.tt +32 -0
- data/lib/generators/seams/teams/templates/app/subscribers/invitation_subscriber.rb.tt +36 -0
- data/lib/generators/seams/teams/templates/app/views/invitation_mailer/invite.text.erb.tt +8 -0
- data/lib/generators/seams/teams/templates/app/views/invitations/index.html.erb.tt +44 -0
- data/lib/generators/seams/teams/templates/app/views/memberships/index.html.erb.tt +32 -0
- data/lib/generators/seams/teams/templates/app/views/teams/edit.html.erb.tt +28 -0
- data/lib/generators/seams/teams/templates/app/views/teams/index.html.erb.tt +15 -0
- data/lib/generators/seams/teams/templates/app/views/teams/new.html.erb.tt +24 -0
- data/lib/generators/seams/teams/templates/app/views/teams/show.html.erb.tt +17 -0
- data/lib/generators/seams/teams/templates/config/routes.rb.tt +19 -0
- data/lib/generators/seams/teams/templates/db/migrate/create_team_invitations.rb.tt +24 -0
- data/lib/generators/seams/teams/templates/db/migrate/create_team_memberships.rb.tt +25 -0
- data/lib/generators/seams/teams/templates/db/migrate/create_teams.rb.tt +18 -0
- data/lib/generators/seams/teams/templates/lib/concerns/account_scoped.rb.tt +79 -0
- data/lib/generators/seams/teams/templates/lib/concerns/authorization.rb.tt +55 -0
- data/lib/generators/seams/teams/templates/lib/configuration.rb.tt +45 -0
- data/lib/generators/seams/teams/templates/lib/engine.rb.tt +51 -0
- data/lib/generators/seams/teams/templates/lib/teams.rb.tt +22 -0
- data/lib/generators/seams/teams/templates/spec/factories/teams.rb.tt +47 -0
- data/lib/generators/seams/teams/templates/spec/models/invitation_spec.rb.tt +25 -0
- data/lib/generators/seams/teams/templates/spec/models/membership_spec.rb.tt +29 -0
- data/lib/generators/seams/teams/templates/spec/models/team_spec.rb.tt +23 -0
- data/lib/generators/seams/teams/templates/spec/runtime/boot_spec.rb.tt +32 -0
- data/lib/seams/cli/list.rb +111 -0
- data/lib/seams/cli/quality.rb +99 -0
- data/lib/seams/cli/resolve.rb +276 -0
- data/lib/seams/cli/test_changed.rb +116 -0
- data/lib/seams/cli.rb +48 -0
- data/lib/seams/configuration.rb +19 -0
- data/lib/seams/cops/known_queue_names.rb +42 -0
- data/lib/seams/cops/migration_comments.rb +68 -0
- data/lib/seams/cops/no_cross_engine_dependency.rb +58 -0
- data/lib/seams/cops/no_cross_engine_model_access.rb +153 -0
- data/lib/seams/cops.rb +18 -0
- data/lib/seams/event_registry.rb +49 -0
- data/lib/seams/events/adapter.rb +24 -0
- data/lib/seams/events/adapters/active_support.rb +31 -0
- data/lib/seams/events/publisher.rb +178 -0
- data/lib/seams/events.rb +39 -0
- data/lib/seams/generators/dummy_app_writer.rb +424 -0
- data/lib/seams/generators/eject_aware.rb +102 -0
- data/lib/seams/generators/follow_up_generator.rb +148 -0
- data/lib/seams/generators/host_injector.rb +124 -0
- data/lib/seams/generators/sibling_rubocop_writer.rb +77 -0
- data/lib/seams/generators/splicer.rb +217 -0
- data/lib/seams/observability/adapter.rb +33 -0
- data/lib/seams/observability/adapters/rails_logger.rb +59 -0
- data/lib/seams/observability.rb +34 -0
- data/lib/seams/runtime.rb +23 -0
- data/lib/seams/version.rb +5 -0
- data/lib/seams.rb +23 -0
- metadata +493 -0
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "fileutils"
|
|
4
|
+
|
|
5
|
+
module Seams
|
|
6
|
+
module Generators
|
|
7
|
+
# Idempotent host-file edits used by every canonical generator.
|
|
8
|
+
# Mix into a Rails::Generators::Base subclass; the methods below
|
|
9
|
+
# delegate to Thor's inject_into_file / append_to_file primitives
|
|
10
|
+
# but skip the edit if the host already contains the snippet.
|
|
11
|
+
#
|
|
12
|
+
# Every method is safe to call when the target file is missing —
|
|
13
|
+
# it prints a yellow `skip` line so the user knows to do the edit
|
|
14
|
+
# themselves later.
|
|
15
|
+
module HostInjector
|
|
16
|
+
def host_inject_gem(name, *args, group: nil)
|
|
17
|
+
gemfile = host_path("Gemfile")
|
|
18
|
+
return host_skip("Gemfile not found — add `gem #{name.inspect}` yourself") unless File.exist?(gemfile)
|
|
19
|
+
|
|
20
|
+
return if File.read(gemfile).match?(/^\s*gem\s+["']#{Regexp.escape(name)}["']/)
|
|
21
|
+
|
|
22
|
+
line = build_gem_line(name, args, group)
|
|
23
|
+
say " inject Gemfile (gem \"#{name}\")", :green
|
|
24
|
+
append_to_file(gemfile, "\n#{line}\n")
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def host_inject_mount(engine_class:, at:)
|
|
28
|
+
routes = host_path("config/routes.rb")
|
|
29
|
+
unless File.exist?(routes)
|
|
30
|
+
return host_skip("config/routes.rb not found — add `mount #{engine_class}, at: \"#{at}\"` yourself")
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
# Word-boundary match on the engine class name so a sibling
|
|
34
|
+
# `mount Auth::EngineExtras` doesn't trick us into thinking
|
|
35
|
+
# `Auth::Engine` is already mounted. The boundary is "anything
|
|
36
|
+
# that isn't a constant-name character" (`[\w:]` excluded).
|
|
37
|
+
return if File.read(routes).match?(/\bmount\s+#{Regexp.escape(engine_class)}(?![\w:])/)
|
|
38
|
+
|
|
39
|
+
say " inject config/routes.rb (mount #{engine_class})", :green
|
|
40
|
+
inject_into_file(routes, after: routes_draw_anchor) do
|
|
41
|
+
" mount #{engine_class}, at: \"#{at}\"\n"
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
# Matches the common `Rails.application.routes.draw do` forms:
|
|
46
|
+
# plain `do`, `do |routes|` block-arg, `do # comment`, and the
|
|
47
|
+
# rare `Rails::Application.routes.draw`.
|
|
48
|
+
def routes_draw_anchor
|
|
49
|
+
/Rails(?:\.application|::Application)\.routes\.draw\s+do(?:\s*\|[^|]+\|)?[^\n]*\n/
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def host_inject_include_in_user(concern_name)
|
|
53
|
+
host_inject_include("app/models/user.rb", "User", concern_name, label: "User model")
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def host_inject_include_in_application_controller(concern_name)
|
|
57
|
+
host_inject_include(
|
|
58
|
+
"app/controllers/application_controller.rb", "ApplicationController",
|
|
59
|
+
concern_name, label: "ApplicationController"
|
|
60
|
+
)
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
# Reverse of host_inject_gem — used by the remove generator.
|
|
64
|
+
# Removes the `gem "<name>"` line if present.
|
|
65
|
+
def host_uninject_gem(name)
|
|
66
|
+
gemfile = host_path("Gemfile")
|
|
67
|
+
return unless File.exist?(gemfile)
|
|
68
|
+
|
|
69
|
+
new_content = File.read(gemfile).gsub(/^\s*gem\s+["']#{Regexp.escape(name)}["'][^\n]*\n/, "")
|
|
70
|
+
File.write(gemfile, new_content)
|
|
71
|
+
say " remove Gemfile (gem \"#{name}\")", :red
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
def host_uninject_mount(engine_class:)
|
|
75
|
+
routes = host_path("config/routes.rb")
|
|
76
|
+
return unless File.exist?(routes)
|
|
77
|
+
|
|
78
|
+
# Word-boundary match — see host_inject_mount. Without it, an
|
|
79
|
+
# unrelated `mount Auth::EngineExtras` would match a remove of
|
|
80
|
+
# `mount Auth::Engine` and silently delete the wrong line.
|
|
81
|
+
pattern = /^\s*mount\s+#{Regexp.escape(engine_class)}(?![\w:])[^\n]*\n/
|
|
82
|
+
new_content = File.read(routes).gsub(pattern, "")
|
|
83
|
+
File.write(routes, new_content)
|
|
84
|
+
say " remove config/routes.rb (mount #{engine_class})", :red
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
def host_uninject_include(file_relative, concern_name)
|
|
88
|
+
full = host_path(file_relative)
|
|
89
|
+
return unless File.exist?(full)
|
|
90
|
+
|
|
91
|
+
new_content = File.read(full).gsub(/^\s*include\s+#{Regexp.escape(concern_name)}\s*\n/, "")
|
|
92
|
+
File.write(full, new_content)
|
|
93
|
+
say " remove #{file_relative} (include #{concern_name})", :red
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
private
|
|
97
|
+
|
|
98
|
+
def host_path(relative)
|
|
99
|
+
File.join(destination_root, relative)
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
def host_skip(message)
|
|
103
|
+
say " skip #{message}", :yellow
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
def host_inject_include(file_relative, class_name, concern_name, label:)
|
|
107
|
+
full = host_path(file_relative)
|
|
108
|
+
return host_skip("#{label} not found — add `include #{concern_name}` yourself") unless File.exist?(full)
|
|
109
|
+
|
|
110
|
+
return if File.read(full).match?(/^\s*include\s+#{Regexp.escape(concern_name)}\s*$/)
|
|
111
|
+
|
|
112
|
+
say " inject #{file_relative} (include #{concern_name})", :green
|
|
113
|
+
inject_into_class(full, class_name, " include #{concern_name}\n")
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
def build_gem_line(name, args, group)
|
|
117
|
+
version_part = args.first.is_a?(String) ? %(, "#{args.first}") : ""
|
|
118
|
+
gem_line = %(gem "#{name}"#{version_part})
|
|
119
|
+
gem_line = "group #{group.inspect} do\n #{gem_line}\nend" if group
|
|
120
|
+
gem_line
|
|
121
|
+
end
|
|
122
|
+
end
|
|
123
|
+
end
|
|
124
|
+
end
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "fileutils"
|
|
4
|
+
|
|
5
|
+
module Seams
|
|
6
|
+
module Generators
|
|
7
|
+
# Rewrites the OtherEngines lists inside every engine's .rubocop.yml
|
|
8
|
+
# so each engine is configured with every OTHER engine as a
|
|
9
|
+
# boundary. Used by both the seams:engine and seams:remove
|
|
10
|
+
# generators — generation adds the new engine, removal prunes the
|
|
11
|
+
# gone one.
|
|
12
|
+
#
|
|
13
|
+
# The replacement is scoped tightly to the OtherEngines key so we
|
|
14
|
+
# never clobber the surrounding ExposedConcerns / Enabled / OwnEngine
|
|
15
|
+
# values. The previous version of this code used a too-greedy
|
|
16
|
+
# lookahead that ate the next sibling key.
|
|
17
|
+
module SiblingRubocopWriter
|
|
18
|
+
module_function
|
|
19
|
+
|
|
20
|
+
MODULE_ACCESS_KEY = "Seams/NoCrossEngineModelAccess"
|
|
21
|
+
DEPENDENCY_KEY = "Seams/NoCrossEngineDependency"
|
|
22
|
+
|
|
23
|
+
# @param engines_root [String] absolute or relative path to engines/
|
|
24
|
+
# @param dirs [Array<String>] directory names of the engines
|
|
25
|
+
# that currently exist on disk
|
|
26
|
+
def rewrite!(engines_root:, dirs:)
|
|
27
|
+
dirs.each do |engine_dir|
|
|
28
|
+
others = (dirs - [engine_dir]).sort
|
|
29
|
+
others_module = others.map { |d| camelcase(d) }
|
|
30
|
+
rubocop_path = File.join(engines_root, engine_dir, ".rubocop.yml")
|
|
31
|
+
next unless File.exist?(rubocop_path)
|
|
32
|
+
|
|
33
|
+
content = File.read(rubocop_path)
|
|
34
|
+
content = replace_other_engines(content, MODULE_ACCESS_KEY, others_module)
|
|
35
|
+
content = replace_other_engines(content, DEPENDENCY_KEY, others)
|
|
36
|
+
File.write(rubocop_path, content)
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def camelcase(name)
|
|
41
|
+
name.split("_").map(&:capitalize).join
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
# Matches `<cop_key>:` and the very next `OtherEngines:` line under
|
|
45
|
+
# it, replacing only the value of OtherEngines. We don't try to
|
|
46
|
+
# span multiple following keys — the regex stops as soon as it has
|
|
47
|
+
# consumed either the inline `[]` form or the indented list form.
|
|
48
|
+
def replace_other_engines(content, cop_key, values)
|
|
49
|
+
regex = other_engines_regex(cop_key)
|
|
50
|
+
formatted = formatted_block(values)
|
|
51
|
+
|
|
52
|
+
unless regex.match?(content)
|
|
53
|
+
raise ArgumentError,
|
|
54
|
+
"SiblingRubocopWriter could not find a writable `OtherEngines:` " \
|
|
55
|
+
"value under `#{cop_key}`. Hand-edited or flow-style YAML " \
|
|
56
|
+
"(`{ OtherEngines: [Foo] }`) is not supported — restore the " \
|
|
57
|
+
"block-style template and retry."
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
content.sub(regex, "\\1#{formatted}\n")
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def formatted_block(values)
|
|
64
|
+
return " OtherEngines: []" if values.empty?
|
|
65
|
+
|
|
66
|
+
" OtherEngines:\n#{values.map { |v| " - #{v}" }.join("\n")}"
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
def other_engines_regex(cop_key)
|
|
70
|
+
prefix = "#{Regexp.escape(cop_key)}:[^\\n]*\\n(?:[ \\t]+[^\\n]+\\n)*?"
|
|
71
|
+
empty_form = "[ \\t]*\\[\\][ \\t]*\\n"
|
|
72
|
+
list_form = "[ \\t]*\\n(?: -[ \\t]+[^\\n]+\\n)*"
|
|
73
|
+
Regexp.new("(#{prefix}) OtherEngines:(?:#{empty_form}|#{list_form})")
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
end
|
|
@@ -0,0 +1,217 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "fileutils"
|
|
4
|
+
|
|
5
|
+
module Seams
|
|
6
|
+
module Generators
|
|
7
|
+
# Idempotent splice operations against files containing
|
|
8
|
+
# `# seams:insertion-point <name>` markers. Wave 10 introduces
|
|
9
|
+
# follow-up generators (e.g. `seams:auth:add_oauth_provider`) that
|
|
10
|
+
# need to extend already-generated engines without re-templating
|
|
11
|
+
# the whole file. The Splicer is the shared primitive: every
|
|
12
|
+
# follow-up generator funnels through these methods.
|
|
13
|
+
#
|
|
14
|
+
# See doc/INSERTION_POINTS.md for the marker format spec and
|
|
15
|
+
# doc/INSERTION_POINTS_CATALOGUE.md for the canonical list of
|
|
16
|
+
# markers each engine ships.
|
|
17
|
+
#
|
|
18
|
+
# Example:
|
|
19
|
+
#
|
|
20
|
+
# Seams::Generators::Splicer.splice_after_marker(
|
|
21
|
+
# file_path: "engines/auth/lib/auth/engine.rb",
|
|
22
|
+
# marker: "auth.engine.events",
|
|
23
|
+
# content: " Seams::EventRegistry.register(\"identity.passkey_added.auth\", emitted_by: \"Auth\")\n"
|
|
24
|
+
# )
|
|
25
|
+
#
|
|
26
|
+
# The Splicer owns four design choices worth surfacing:
|
|
27
|
+
#
|
|
28
|
+
# 1. Markers are looked up by NAME, never by line number. A
|
|
29
|
+
# follow-up generator written today must keep working after
|
|
30
|
+
# the host adds twenty unrelated lines above the marker.
|
|
31
|
+
# 2. Idempotency is checked by string-matching the splice content
|
|
32
|
+
# inside a 50-line window after the marker. Re-running the
|
|
33
|
+
# same splice is a no-op rather than an error.
|
|
34
|
+
# 3. Indentation is auto-detected from the marker line itself.
|
|
35
|
+
# Follow-up generators don't have to know whether the marker
|
|
36
|
+
# sits at column 0, column 4, or column 6.
|
|
37
|
+
# 4. The Splicer is pure file I/O — no Rails dep, no Thor — so
|
|
38
|
+
# it can be tested in isolation and reused outside the
|
|
39
|
+
# generator stack (e.g. by `bin/seams resolve --eject`).
|
|
40
|
+
module Splicer
|
|
41
|
+
module_function
|
|
42
|
+
|
|
43
|
+
# Pattern matched by every Splicer method. The character class
|
|
44
|
+
# for the marker name allows lowercase letters, digits, dot, and
|
|
45
|
+
# underscore — see INSERTION_POINTS.md naming rules.
|
|
46
|
+
MARKER_PREFIX = "# seams:insertion-point"
|
|
47
|
+
MARKER_NAME_RE = /[a-z0-9_.]+/
|
|
48
|
+
MARKER_LINE_RE = /^(\s*)#{Regexp.escape(MARKER_PREFIX)}\s+(#{MARKER_NAME_RE})\s*$/
|
|
49
|
+
IDEMPOTENCY_WINDOW = 50
|
|
50
|
+
|
|
51
|
+
# Result struct returned by every splice operation.
|
|
52
|
+
# `ok?` is the only required predicate; `error` is populated
|
|
53
|
+
# only when ok? is false; `lines_added` is 0 when ok? is true
|
|
54
|
+
# but the splice was a no-op (idempotency hit).
|
|
55
|
+
Result = Struct.new(:ok?, :lines_added, :error, keyword_init: true) do
|
|
56
|
+
def to_s
|
|
57
|
+
ok? ? "Splicer::Result(ok, +#{lines_added})" : "Splicer::Result(error: #{error})"
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
# Splice `content` immediately after the line containing
|
|
62
|
+
# `# seams:insertion-point <marker>`.
|
|
63
|
+
#
|
|
64
|
+
# @param file_path [String] absolute or working-dir-relative path
|
|
65
|
+
# @param marker [String] the marker name, e.g. "auth.engine.events"
|
|
66
|
+
# @param content [String] the snippet to insert. Must end in a newline.
|
|
67
|
+
# @param indent [String, nil] override auto-detected indentation. Pass
|
|
68
|
+
# `""` to insert verbatim, or a string of spaces to
|
|
69
|
+
# prepend to every line of `content`.
|
|
70
|
+
# @return [Result]
|
|
71
|
+
def splice_after_marker(file_path:, marker:, content:, indent: nil)
|
|
72
|
+
splice(file_path: file_path, marker: marker, content: content, indent: indent, position: :after)
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
# Splice `content` immediately before the line containing the
|
|
76
|
+
# marker. Same semantics as +splice_after_marker+ otherwise.
|
|
77
|
+
#
|
|
78
|
+
# @return [Result]
|
|
79
|
+
def splice_before_marker(file_path:, marker:, content:, indent: nil)
|
|
80
|
+
splice(file_path: file_path, marker: marker, content: content, indent: indent, position: :before)
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
# Locate a marker without modifying the file. Useful for follow-up
|
|
84
|
+
# generators that want to verify multiple markers exist before
|
|
85
|
+
# they start writing.
|
|
86
|
+
#
|
|
87
|
+
# @return [Hash, nil] { line_number: 1-indexed, indent: " ", marker: "auth.engine.events" }
|
|
88
|
+
# or nil if the marker isn't present.
|
|
89
|
+
def find_marker(file_path:, marker:)
|
|
90
|
+
return nil unless File.exist?(file_path)
|
|
91
|
+
|
|
92
|
+
lines = File.read(file_path).lines
|
|
93
|
+
lines.each_with_index do |line, index|
|
|
94
|
+
match = line.match(MARKER_LINE_RE)
|
|
95
|
+
next unless match
|
|
96
|
+
next unless match[2] == marker
|
|
97
|
+
|
|
98
|
+
return { line_number: index + 1, indent: match[1], marker: marker }
|
|
99
|
+
end
|
|
100
|
+
nil
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
# Enumerate every `seams:insertion-point` marker in a file in
|
|
104
|
+
# source order. Used by the eject CLI's `--list-markers` flag
|
|
105
|
+
# and by `bin/seams resolve` for human-readable diagnostics.
|
|
106
|
+
#
|
|
107
|
+
# @return [Array<Hash>] each entry shaped like +find_marker+ returns.
|
|
108
|
+
def list_markers(file_path:)
|
|
109
|
+
return [] unless File.exist?(file_path)
|
|
110
|
+
|
|
111
|
+
result = []
|
|
112
|
+
File.read(file_path).lines.each_with_index do |line, index|
|
|
113
|
+
match = line.match(MARKER_LINE_RE)
|
|
114
|
+
next unless match
|
|
115
|
+
|
|
116
|
+
result << { line_number: index + 1, indent: match[1], marker: match[2] }
|
|
117
|
+
end
|
|
118
|
+
result
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
# Internal: shared body for splice_{after,before}_marker.
|
|
122
|
+
def splice(file_path:, marker:, content:, indent:, position:)
|
|
123
|
+
return file_not_found_result(file_path) unless File.exist?(file_path)
|
|
124
|
+
|
|
125
|
+
lines = File.read(file_path).lines
|
|
126
|
+
marker_index = locate_marker_index(lines, marker)
|
|
127
|
+
return marker_not_found_result(marker, file_path) unless marker_index
|
|
128
|
+
|
|
129
|
+
prepared = apply_indent(content, indent || detect_indent(lines[marker_index]))
|
|
130
|
+
write_splice(file_path, lines, prepared, marker_index, position)
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
def write_splice(file_path, lines, prepared, marker_index, position)
|
|
134
|
+
return Result.new(ok?: true, lines_added: 0, error: nil) if already_present?(lines, prepared, marker_index,
|
|
135
|
+
position)
|
|
136
|
+
|
|
137
|
+
File.write(file_path, insert_at(lines, prepared, marker_index, position).join)
|
|
138
|
+
Result.new(ok?: true, lines_added: prepared.lines.size, error: nil)
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
def file_not_found_result(file_path)
|
|
142
|
+
Result.new(ok?: false, lines_added: 0, error: "file not found: #{file_path}")
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
def marker_not_found_result(marker, file_path)
|
|
146
|
+
Result.new(ok?: false, lines_added: 0,
|
|
147
|
+
error: "marker '#{marker}' not found in #{file_path}")
|
|
148
|
+
end
|
|
149
|
+
|
|
150
|
+
# The marker line plus every line in the idempotency window
|
|
151
|
+
# following (or preceding for :before) is inspected. The check
|
|
152
|
+
# looks for the FULL prepared content as a contiguous block,
|
|
153
|
+
# not for individual lines — a partial overlap is treated as
|
|
154
|
+
# "not yet spliced" and re-splices, which is the safer default
|
|
155
|
+
# for a tool that prefers re-runs to silent partials.
|
|
156
|
+
#
|
|
157
|
+
# The window grows to accommodate snippets larger than
|
|
158
|
+
# IDEMPOTENCY_WINDOW lines: a 60-line follow-up splice would
|
|
159
|
+
# never round-trip with a fixed 50-line window because the
|
|
160
|
+
# haystack would be smaller than the needle. The effective
|
|
161
|
+
# window is `max(IDEMPOTENCY_WINDOW, prepared.lines.size)`,
|
|
162
|
+
# which guarantees idempotency regardless of snippet size while
|
|
163
|
+
# keeping the small-splice fast path identical.
|
|
164
|
+
def already_present?(lines, prepared, marker_index, position)
|
|
165
|
+
haystack = haystack_for(lines, marker_index, position, prepared)
|
|
166
|
+
haystack.include?(prepared)
|
|
167
|
+
end
|
|
168
|
+
|
|
169
|
+
def haystack_for(lines, marker_index, position, prepared)
|
|
170
|
+
window = [IDEMPOTENCY_WINDOW, prepared.lines.size].max
|
|
171
|
+
if position == :after
|
|
172
|
+
slice_end = [marker_index + window, lines.size - 1].min
|
|
173
|
+
lines[(marker_index + 1)..slice_end].to_a.join
|
|
174
|
+
else
|
|
175
|
+
slice_start = [marker_index - window, 0].max
|
|
176
|
+
lines[slice_start...marker_index].to_a.join
|
|
177
|
+
end
|
|
178
|
+
end
|
|
179
|
+
|
|
180
|
+
def insert_at(lines, prepared, marker_index, position)
|
|
181
|
+
prepared_lines = prepared.lines
|
|
182
|
+
if position == :after
|
|
183
|
+
lines[0..marker_index] + prepared_lines + (lines[(marker_index + 1)..] || [])
|
|
184
|
+
else
|
|
185
|
+
lines[0...marker_index] + prepared_lines + lines[marker_index..]
|
|
186
|
+
end
|
|
187
|
+
end
|
|
188
|
+
|
|
189
|
+
def locate_marker_index(lines, marker)
|
|
190
|
+
lines.each_with_index do |line, index|
|
|
191
|
+
match = line.match(MARKER_LINE_RE)
|
|
192
|
+
return index if match && match[2] == marker
|
|
193
|
+
end
|
|
194
|
+
nil
|
|
195
|
+
end
|
|
196
|
+
|
|
197
|
+
def detect_indent(marker_line)
|
|
198
|
+
marker_line[/^\s*/].to_s
|
|
199
|
+
end
|
|
200
|
+
|
|
201
|
+
# Apply `indent` to every non-blank line of `content`. Blank
|
|
202
|
+
# lines stay blank — re-indenting them would leave trailing
|
|
203
|
+
# whitespace that some linters (and our own RuboCop config) flag.
|
|
204
|
+
def apply_indent(content, indent)
|
|
205
|
+
return content if indent.empty?
|
|
206
|
+
|
|
207
|
+
content.lines.map do |line|
|
|
208
|
+
if line.strip.empty?
|
|
209
|
+
line
|
|
210
|
+
else
|
|
211
|
+
"#{indent}#{line}"
|
|
212
|
+
end
|
|
213
|
+
end.join
|
|
214
|
+
end
|
|
215
|
+
end
|
|
216
|
+
end
|
|
217
|
+
end
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "seams/observability"
|
|
4
|
+
|
|
5
|
+
module Seams
|
|
6
|
+
module Observability
|
|
7
|
+
# Abstract base class for observability adapters. Concrete adapters
|
|
8
|
+
# bridge to the host application's logger / APM (Rails.logger,
|
|
9
|
+
# Datadog, OpenTelemetry, etc.) — engines never speak to those
|
|
10
|
+
# libraries directly.
|
|
11
|
+
class Adapter
|
|
12
|
+
def debug(_message, **_context)
|
|
13
|
+
raise NotImplementedError, "#{self.class} must implement #debug"
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def info(_message, **_context)
|
|
17
|
+
raise NotImplementedError, "#{self.class} must implement #info"
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def warn(_message, **_context)
|
|
21
|
+
raise NotImplementedError, "#{self.class} must implement #warn"
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def error(_message, **_context)
|
|
25
|
+
raise NotImplementedError, "#{self.class} must implement #error"
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def measure(_operation, **_context, &)
|
|
29
|
+
raise NotImplementedError, "#{self.class} must implement #measure"
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
end
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "logger"
|
|
4
|
+
require "seams/observability/adapter"
|
|
5
|
+
|
|
6
|
+
module Seams
|
|
7
|
+
module Observability
|
|
8
|
+
module Adapters
|
|
9
|
+
# Default observability adapter — wraps Rails.logger (or a stdout
|
|
10
|
+
# Logger when Rails isn't booted) and emits messages tagged with
|
|
11
|
+
# the seams namespace plus structured key=value context.
|
|
12
|
+
class RailsLogger < Seams::Observability::Adapter
|
|
13
|
+
def initialize(logger: nil)
|
|
14
|
+
super()
|
|
15
|
+
@logger = logger || default_logger
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
%i[debug info warn error].each do |level|
|
|
19
|
+
define_method(level) do |message, **context|
|
|
20
|
+
@logger.public_send(level, format_message(message, context))
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def measure(operation, **context)
|
|
25
|
+
start = monotonic_ms
|
|
26
|
+
result = yield
|
|
27
|
+
duration = (monotonic_ms - start).round(2)
|
|
28
|
+
info(operation, **context, duration_ms: duration)
|
|
29
|
+
result
|
|
30
|
+
rescue StandardError => e
|
|
31
|
+
duration = (monotonic_ms - start).round(2)
|
|
32
|
+
error(operation, **context, duration_ms: duration, error: "#{e.class}: #{e.message}")
|
|
33
|
+
raise
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
private
|
|
37
|
+
|
|
38
|
+
def format_message(message, context)
|
|
39
|
+
tags = ["[seams]"]
|
|
40
|
+
tags << "[#{context.delete(:engine)}]" if context[:engine]
|
|
41
|
+
parts = context.map { |k, v| "#{k}=#{v}" }
|
|
42
|
+
([tags.join(" "), message] + parts).join(" ").strip
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def monotonic_ms
|
|
46
|
+
Process.clock_gettime(Process::CLOCK_MONOTONIC) * 1_000.0
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def default_logger
|
|
50
|
+
if defined?(::Rails) && ::Rails.respond_to?(:logger) && ::Rails.logger
|
|
51
|
+
::Rails.logger
|
|
52
|
+
else
|
|
53
|
+
::Logger.new($stdout)
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
end
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Seams
|
|
4
|
+
# Observability module — structured logging and timing primitives that
|
|
5
|
+
# every engine should use instead of calling Rails.logger directly.
|
|
6
|
+
#
|
|
7
|
+
# Engines call Seams::Observability.adapter.info / .warn / .error /
|
|
8
|
+
# .measure with structured context (engine name, actor id, etc.) so that
|
|
9
|
+
# logs can be parsed and filtered uniformly by the host application's
|
|
10
|
+
# log aggregator.
|
|
11
|
+
module Observability
|
|
12
|
+
class Error < Seams::Error; end
|
|
13
|
+
|
|
14
|
+
class << self
|
|
15
|
+
def adapter
|
|
16
|
+
@adapter ||= build_adapter
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def reset!
|
|
20
|
+
@adapter = nil
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
private
|
|
24
|
+
|
|
25
|
+
def build_adapter
|
|
26
|
+
klass_name = Seams.configuration.observability_adapter
|
|
27
|
+
Object.const_get(klass_name).new
|
|
28
|
+
rescue NameError => e
|
|
29
|
+
raise Seams::ConfigurationError,
|
|
30
|
+
"Observability adapter #{klass_name.inspect} could not be loaded: #{e.message}"
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Seams
|
|
4
|
+
# Module-level methods for the public Seams API. Lives in its own
|
|
5
|
+
# file so that lib/seams.rb stays a thin require-only wrapper.
|
|
6
|
+
module Runtime
|
|
7
|
+
def configuration
|
|
8
|
+
@configuration ||= Configuration.new
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def configure
|
|
12
|
+
yield configuration
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def reset_configuration!
|
|
16
|
+
@configuration = Configuration.new
|
|
17
|
+
Events::Publisher.reset! if defined?(Events::Publisher)
|
|
18
|
+
Observability.reset! if defined?(Observability)
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
extend Runtime
|
|
23
|
+
end
|
data/lib/seams.rb
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "seams/version"
|
|
4
|
+
|
|
5
|
+
# Seams — A CLI framework that generates modular Rails engines.
|
|
6
|
+
#
|
|
7
|
+
# See https://github.com/Davidslv/seams for documentation.
|
|
8
|
+
module Seams
|
|
9
|
+
class Error < StandardError; end
|
|
10
|
+
class ConfigurationError < Error; end
|
|
11
|
+
class GeneratorError < Error; end
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
require "seams/configuration"
|
|
15
|
+
require "seams/runtime"
|
|
16
|
+
require "seams/events"
|
|
17
|
+
require "seams/events/adapter"
|
|
18
|
+
require "seams/events/adapters/active_support"
|
|
19
|
+
require "seams/event_registry"
|
|
20
|
+
require "seams/events/publisher"
|
|
21
|
+
require "seams/observability"
|
|
22
|
+
require "seams/observability/adapter"
|
|
23
|
+
require "seams/observability/adapters/rails_logger"
|