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,25 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# What: creates the team_memberships join table.
|
|
4
|
+
# Why: many-to-many between teams and Auth::Identity rows, plus a
|
|
5
|
+
# role column (owner / admin / member) for cheap RBAC. Teams
|
|
6
|
+
# are peers to Accounts post-Wave-9, so the join is to Identity
|
|
7
|
+
# directly — not to an Accounts::Membership.
|
|
8
|
+
# Risk: append-mostly. Unique index on (team_id, identity_id) prevents
|
|
9
|
+
# duplicate memberships. No FK to auth_identities because the
|
|
10
|
+
# auth and teams engines may live in different schemas /
|
|
11
|
+
# databases in production — cross-engine integrity is enforced
|
|
12
|
+
# at the application layer.
|
|
13
|
+
class CreateTeamMemberships < ActiveRecord::Migration[7.1]
|
|
14
|
+
def change
|
|
15
|
+
create_table :team_memberships do |t|
|
|
16
|
+
t.references :team, null: false, foreign_key: { to_table: :teams }, index: true
|
|
17
|
+
t.bigint :identity_id, null: false
|
|
18
|
+
t.string :role, null: false, default: "member"
|
|
19
|
+
t.timestamps
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
add_index :team_memberships, %i[team_id identity_id], unique: true
|
|
23
|
+
add_index :team_memberships, :identity_id
|
|
24
|
+
end
|
|
25
|
+
end
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# What: creates the teams table for the Teams engine.
|
|
4
|
+
# Why: the Team is the unit of multi-tenant ownership (billing
|
|
5
|
+
# customer, project scope, RBAC root) — every host that mounts
|
|
6
|
+
# Teams::Engine needs it.
|
|
7
|
+
# Risk: empty on creation, append-mostly thereafter.
|
|
8
|
+
class CreateTeams < ActiveRecord::Migration[7.1]
|
|
9
|
+
def change
|
|
10
|
+
create_table :teams do |t|
|
|
11
|
+
t.string :name, null: false
|
|
12
|
+
t.string :slug, null: false
|
|
13
|
+
t.timestamps
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
add_index :teams, :slug, unique: true
|
|
17
|
+
end
|
|
18
|
+
end
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "active_support/concern"
|
|
4
|
+
|
|
5
|
+
module Teams
|
|
6
|
+
# Mix into any model whose rows belong to a single team. Sets up
|
|
7
|
+
# the belongs_to + a default_scope that filters to
|
|
8
|
+
# `Teams::Current.team`. When `Teams::Current.team` is unset the
|
|
9
|
+
# scope returns `none` (an empty relation) — see "fail-closed"
|
|
10
|
+
# below.
|
|
11
|
+
#
|
|
12
|
+
# class Project < ApplicationRecord
|
|
13
|
+
# include Teams::AccountScoped
|
|
14
|
+
# end
|
|
15
|
+
#
|
|
16
|
+
# Teams::Current.team = team
|
|
17
|
+
# Project.create!(name: "...") # team_id auto-assigned to team.id
|
|
18
|
+
# Project.all # only team's projects
|
|
19
|
+
#
|
|
20
|
+
# Pairs with Core's `TenantScoped` — same shape, different ownership
|
|
21
|
+
# model: TenantScoped binds to the host's tenant abstraction;
|
|
22
|
+
# AccountScoped binds specifically to a Teams::Team.
|
|
23
|
+
#
|
|
24
|
+
# ### Fail-closed default
|
|
25
|
+
#
|
|
26
|
+
# When `Teams::Current.team` is nil, the default_scope returns
|
|
27
|
+
# `none` rather than `all`. Same rationale as Accounts::AccountScoped:
|
|
28
|
+
# the alternative (return all rows across all teams when no team is
|
|
29
|
+
# bound) is the canonical multi-team data-leak bug. Fail-closed
|
|
30
|
+
# surfaces "I have no rows" — a noisy symptom the developer fixes
|
|
31
|
+
# immediately — instead of a silent leak.
|
|
32
|
+
#
|
|
33
|
+
# Opt-out paths (use deliberately):
|
|
34
|
+
# * `.unscoped` — full Active Record bypass.
|
|
35
|
+
# * `.with_no_team_scope` — drops the team_id filter only,
|
|
36
|
+
# preserves any other default_scopes.
|
|
37
|
+
#
|
|
38
|
+
# Background jobs MUST set `Teams::Current.team =` before querying,
|
|
39
|
+
# or call `.with_no_team_scope` explicitly.
|
|
40
|
+
module AccountScoped
|
|
41
|
+
extend ActiveSupport::Concern
|
|
42
|
+
|
|
43
|
+
included do
|
|
44
|
+
belongs_to :team, class_name: "Teams::Team"
|
|
45
|
+
|
|
46
|
+
# The default_scope ALWAYS applies a where(:team_id) clause —
|
|
47
|
+
# either filtered to the current team, or filtered to a
|
|
48
|
+
# never-matching sentinel (`team_id IS NULL`). This shape lets
|
|
49
|
+
# `unscope(where: :team_id)` in `with_no_team_scope` reverse the
|
|
50
|
+
# filter cleanly; using `none` here would create a NullRelation
|
|
51
|
+
# that `unscope` cannot un-do.
|
|
52
|
+
default_scope -> {
|
|
53
|
+
team = Teams::Current.team
|
|
54
|
+
if team
|
|
55
|
+
where(team_id: team.id)
|
|
56
|
+
else
|
|
57
|
+
where(team_id: nil)
|
|
58
|
+
end
|
|
59
|
+
}
|
|
60
|
+
before_validation :assign_current_team, on: :create
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
class_methods do
|
|
64
|
+
# Returns the relation as it would be without the team_id
|
|
65
|
+
# filter, preserving any other default_scopes the model
|
|
66
|
+
# declares. Use deliberately — name-it-loud opt-out for seed
|
|
67
|
+
# scripts, migrations, and platform tooling.
|
|
68
|
+
def with_no_team_scope
|
|
69
|
+
unscope(where: :team_id)
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
private
|
|
74
|
+
|
|
75
|
+
def assign_current_team
|
|
76
|
+
self.team_id ||= Teams::Current.team&.id if Teams::Current.team
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
end
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "active_support/concern"
|
|
4
|
+
|
|
5
|
+
module Teams
|
|
6
|
+
# Helpers for controllers that need to enforce team-membership
|
|
7
|
+
# checks. Mix in by default in TeamsController/MembershipsController/
|
|
8
|
+
# InvitationsController so the engine ships with safe defaults.
|
|
9
|
+
# Hosts can override the predicates by re-opening the concern.
|
|
10
|
+
#
|
|
11
|
+
# Resolves the current Identity via `current_identity_id`, which by
|
|
12
|
+
# default reads `Auth::Current.identity` (the Auth engine's
|
|
13
|
+
# per-request namespace). Hosts can override `current_identity_id`
|
|
14
|
+
# to plug in a different resolver.
|
|
15
|
+
module Authorization
|
|
16
|
+
extend ActiveSupport::Concern
|
|
17
|
+
|
|
18
|
+
private
|
|
19
|
+
|
|
20
|
+
def require_team_member!
|
|
21
|
+
head :forbidden and return false unless team_member?
|
|
22
|
+
true
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def require_team_admin!
|
|
26
|
+
head :forbidden and return false unless team_admin?
|
|
27
|
+
true
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def team_member?
|
|
31
|
+
return false unless current_identity_id && @team
|
|
32
|
+
|
|
33
|
+
Teams::Membership.exists?(team_id: @team.id, identity_id: current_identity_id)
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def team_admin?
|
|
37
|
+
return false unless current_identity_id && @team
|
|
38
|
+
|
|
39
|
+
Teams::Membership.where(team_id: @team.id, identity_id: current_identity_id, role: %w[owner admin]).exists?
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
# Resolves the signed-in human's id from `Auth::Current.identity`
|
|
43
|
+
# (the Auth engine's per-request namespace). Gated on
|
|
44
|
+
# `defined?(Auth::Current)` so the concern remains include-safe in
|
|
45
|
+
# hosts that don't ship the auth engine. Hosts that wire auth
|
|
46
|
+
# differently override this method.
|
|
47
|
+
def current_identity_id
|
|
48
|
+
if defined?(Auth::Current) && Auth::Current.respond_to?(:identity) && Auth::Current.identity
|
|
49
|
+
return Auth::Current.identity.id
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
nil
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
end
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Teams
|
|
4
|
+
# Engine-scoped configuration. Override in
|
|
5
|
+
# config/initializers/teams.rb of the host application.
|
|
6
|
+
class Configuration
|
|
7
|
+
attr_accessor :invitation_ttl, :max_members_per_team
|
|
8
|
+
attr_writer :host_url, :invitation_mailer_from
|
|
9
|
+
# Follow-up generators that add knobs (slug_generator, archive_grace_period) declare their attr_accessor here.
|
|
10
|
+
# seams:insertion-point teams.configuration.attributes
|
|
11
|
+
|
|
12
|
+
def initialize
|
|
13
|
+
@invitation_ttl = 7 * 24 * 60 * 60 # 7 days, in seconds
|
|
14
|
+
@max_members_per_team = nil # nil = unlimited
|
|
15
|
+
@invitation_mailer_from = nil
|
|
16
|
+
@host_url = nil
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
# `host_url` is used to build the invitation accept link. Without
|
|
20
|
+
# it, invitation emails would carry a link to nowhere, so we raise
|
|
21
|
+
# at the first read rather than ship a broken email. Set in
|
|
22
|
+
# config/initializers/teams.rb.
|
|
23
|
+
def host_url
|
|
24
|
+
return @host_url if @host_url
|
|
25
|
+
|
|
26
|
+
raise Teams::ConfigurationError,
|
|
27
|
+
"Teams.configuration.host_url is not set. Add\n" \
|
|
28
|
+
" Teams.configure { |c| c.host_url = \"https://your-app.com\" }\n" \
|
|
29
|
+
"to config/initializers/teams.rb so invitation emails can " \
|
|
30
|
+
"build the accept URL."
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
# `invitation_mailer_from` is the From: header on invitation emails.
|
|
34
|
+
# Same shape as `host_url` — nil-default, raises on read so the
|
|
35
|
+
# host can't accidentally ship mail from "no-reply@example.com".
|
|
36
|
+
def invitation_mailer_from
|
|
37
|
+
return @invitation_mailer_from if @invitation_mailer_from
|
|
38
|
+
|
|
39
|
+
raise Teams::ConfigurationError,
|
|
40
|
+
"Teams.configuration.invitation_mailer_from is not set. Add\n" \
|
|
41
|
+
" Teams.configure { |c| c.invitation_mailer_from = \"team@your-app.com\" }\n" \
|
|
42
|
+
"to config/initializers/teams.rb."
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
end
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Teams
|
|
4
|
+
class Engine < ::Rails::Engine
|
|
5
|
+
isolate_namespace Teams
|
|
6
|
+
|
|
7
|
+
config.generators do |g|
|
|
8
|
+
g.test_framework :rspec
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
initializer "teams.register_events" do
|
|
12
|
+
Seams::EventRegistry.register("team.created.teams", emitted_by: "Teams")
|
|
13
|
+
Seams::EventRegistry.register("team.member_joined.teams", emitted_by: "Teams")
|
|
14
|
+
Seams::EventRegistry.register("team.member_left.teams", emitted_by: "Teams")
|
|
15
|
+
Seams::EventRegistry.register("invitation.sent.teams", emitted_by: "Teams")
|
|
16
|
+
Seams::EventRegistry.register("invitation.accepted.teams", emitted_by: "Teams")
|
|
17
|
+
# Follow-up generators that emit new teams events (team.archived.teams, invitation.revoked.teams) register them here.
|
|
18
|
+
# seams:insertion-point teams.engine.events
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
initializer "teams.append_migrations" do |app|
|
|
22
|
+
unless app.root == root
|
|
23
|
+
config.paths["db/migrate"].expanded.each do |expanded_path|
|
|
24
|
+
app.config.paths["db/migrate"] << expanded_path
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
config.after_initialize do
|
|
30
|
+
# Boot-time dependency assertion. Teams::Membership.identity_id
|
|
31
|
+
# references auth_identities; without auth the team membership
|
|
32
|
+
# rows are dangling. We enforce at boot so the operator gets a
|
|
33
|
+
# clear "install seams:auth" error rather than a NULL identity_id
|
|
34
|
+
# surprise on first query.
|
|
35
|
+
unless defined?(::Auth::Identity)
|
|
36
|
+
raise <<~MSG
|
|
37
|
+
[seams teams] missing required cross-engine dependency:
|
|
38
|
+
Auth::Identity (run: bin/rails generate seams:auth)
|
|
39
|
+
|
|
40
|
+
The teams engine joins identity_id to auth_identities; it
|
|
41
|
+
cannot run without auth. Generate the auth engine, or
|
|
42
|
+
remove teams with `bin/rails generate seams:remove teams --force`.
|
|
43
|
+
MSG
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
Teams::InvitationSubscriber.attach!
|
|
47
|
+
# Follow-up generators that ship subscribers (Teams::AccountSubscriber.attach!, etc.) splice their attach! calls here.
|
|
48
|
+
# seams:insertion-point teams.engine.subscribers
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
end
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "teams/version"
|
|
4
|
+
require "teams/configuration"
|
|
5
|
+
require "teams/engine"
|
|
6
|
+
require "teams/concerns/authorization"
|
|
7
|
+
|
|
8
|
+
module Teams
|
|
9
|
+
class Error < StandardError; end
|
|
10
|
+
class AuthorizationError < Error; end
|
|
11
|
+
class ConfigurationError < Error; end
|
|
12
|
+
|
|
13
|
+
class << self
|
|
14
|
+
def configuration
|
|
15
|
+
@configuration ||= Configuration.new
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def configure
|
|
19
|
+
yield configuration
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# Factories for Teams engine specs. Sequences keep names unique
|
|
4
|
+
# across the spec run so uniqueness validations don't trip.
|
|
5
|
+
#
|
|
6
|
+
# `:team_membership` associates with the auth engine's
|
|
7
|
+
# `:auth_identity` factory — ensure the auth_identities table is
|
|
8
|
+
# present in the engine's dummy schema (it is, see
|
|
9
|
+
# `TeamsGenerator#dummy_schema`).
|
|
10
|
+
FactoryBot.define do
|
|
11
|
+
factory :team, class: "Teams::Team" do
|
|
12
|
+
sequence(:name) { |n| "Team #{n}" }
|
|
13
|
+
sequence(:slug) { |n| "team-#{n}" }
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
factory :team_membership, class: "Teams::Membership" do
|
|
17
|
+
association :team
|
|
18
|
+
role { "member" }
|
|
19
|
+
|
|
20
|
+
# Denormalises identity_id from a built/created :auth_identity
|
|
21
|
+
# rather than relying on belongs_to magic — Membership has no
|
|
22
|
+
# `belongs_to :identity` because Auth::Identity lives in a peer
|
|
23
|
+
# engine (no cross-engine model access at the ActiveRecord level,
|
|
24
|
+
# per the Seams/NoCrossEngineModelAccess cop). Hosts call
|
|
25
|
+
# `create(:team_membership, identity: existing_identity)` or pass
|
|
26
|
+
# `identity_id:` directly.
|
|
27
|
+
transient do
|
|
28
|
+
identity { nil }
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
after(:build) do |membership, evaluator|
|
|
32
|
+
membership.identity_id ||= evaluator.identity&.id || FactoryBot.create(:auth_identity).id
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
factory :team_admin_membership do
|
|
36
|
+
role { "admin" }
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
factory :team_invitation, class: "Teams::Invitation" do
|
|
41
|
+
association :team
|
|
42
|
+
sequence(:email) { |n| "invitee-#{n}@example.com" }
|
|
43
|
+
role { "member" }
|
|
44
|
+
token { SecureRandom.hex(16) }
|
|
45
|
+
expires_at { 7.days.from_now }
|
|
46
|
+
end
|
|
47
|
+
end
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "rails_helper"
|
|
4
|
+
|
|
5
|
+
RSpec.describe Teams::Invitation do
|
|
6
|
+
let(:team) { Teams::Team.create!(name: "Acme") }
|
|
7
|
+
|
|
8
|
+
it "auto-assigns a token and expiry" do
|
|
9
|
+
inv = team.invitations.create!(email: "x@y.com", role: "member")
|
|
10
|
+
expect(inv.token).to be_present
|
|
11
|
+
expect(inv.expires_at).to be > Time.current
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
it "is expired once expires_at passes" do
|
|
15
|
+
inv = team.invitations.create!(email: "x@y.com", role: "member", expires_at: 1.minute.ago)
|
|
16
|
+
expect(inv).to be_expired
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
it "rejects two pending invitations to the same email for the same team" do
|
|
20
|
+
team.invitations.create!(email: "x@y.com", role: "member")
|
|
21
|
+
expect do
|
|
22
|
+
team.invitations.create!(email: "x@y.com", role: "admin")
|
|
23
|
+
end.to raise_error(ActiveRecord::RecordNotUnique)
|
|
24
|
+
end
|
|
25
|
+
end
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "rails_helper"
|
|
4
|
+
|
|
5
|
+
RSpec.describe Teams::Membership do
|
|
6
|
+
let(:team) { Teams::Team.create!(name: "Acme") }
|
|
7
|
+
|
|
8
|
+
it "rejects unknown roles" do
|
|
9
|
+
m = described_class.new(team: team, identity_id: 1, role: "made_up")
|
|
10
|
+
expect(m).not_to be_valid
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
it "is unique on (team_id, identity_id)" do
|
|
14
|
+
described_class.create!(team: team, identity_id: 1, role: "member")
|
|
15
|
+
dup = described_class.new(team: team, identity_id: 1, role: "admin")
|
|
16
|
+
expect(dup).not_to be_valid
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
describe "#admin?" do
|
|
20
|
+
it "is true for owner and admin roles" do
|
|
21
|
+
expect(described_class.new(role: "owner")).to be_admin
|
|
22
|
+
expect(described_class.new(role: "admin")).to be_admin
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
it "is false for member" do
|
|
26
|
+
expect(described_class.new(role: "member")).not_to be_admin
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "rails_helper"
|
|
4
|
+
|
|
5
|
+
RSpec.describe Teams::Team do
|
|
6
|
+
describe "validations" do
|
|
7
|
+
it "requires a name" do
|
|
8
|
+
expect(described_class.new(name: "")).not_to be_valid
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
it "auto-assigns slug from name" do
|
|
12
|
+
team = described_class.new(name: "My Team!")
|
|
13
|
+
team.valid?
|
|
14
|
+
expect(team.slug).to eq("my-team")
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
it "rejects duplicate slugs" do
|
|
18
|
+
described_class.create!(name: "Acme")
|
|
19
|
+
dup = described_class.new(name: "Acme")
|
|
20
|
+
expect(dup).not_to be_valid
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "../rails_helper"
|
|
4
|
+
|
|
5
|
+
RSpec.describe "Teams engine boot", type: :integration do
|
|
6
|
+
it "loads the engine" do
|
|
7
|
+
expect(defined?(Teams::Engine)).to eq("constant")
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
it "registers the five canonical team events" do
|
|
11
|
+
%w[
|
|
12
|
+
team.created.teams
|
|
13
|
+
team.member_joined.teams
|
|
14
|
+
team.member_left.teams
|
|
15
|
+
invitation.sent.teams
|
|
16
|
+
invitation.accepted.teams
|
|
17
|
+
].each do |event|
|
|
18
|
+
expect(Seams::EventRegistry.registered?(event)).to be(true)
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
it "creates the team tables from the dummy schema" do
|
|
23
|
+
%i[teams team_memberships team_invitations].each do |t|
|
|
24
|
+
expect(ActiveRecord::Base.connection.table_exists?(t)).to be(true), "missing #{t}"
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
it "creates a Team and auto-assigns a slug" do
|
|
29
|
+
team = Teams::Team.create!(name: "Acme Corp")
|
|
30
|
+
expect(team.slug).to eq("acme-corp")
|
|
31
|
+
end
|
|
32
|
+
end
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "seams"
|
|
4
|
+
|
|
5
|
+
module Seams
|
|
6
|
+
module CLI
|
|
7
|
+
# Implementation behind `bin/rails seams:list` — discovers every
|
|
8
|
+
# engine under engines/, looks up its registered events in
|
|
9
|
+
# Seams::EventRegistry, and prints a summary.
|
|
10
|
+
class List
|
|
11
|
+
DEFAULT_ENGINES_ROOT = "engines"
|
|
12
|
+
|
|
13
|
+
MODULE_DECLARATION = /\bmodule\s+([A-Z][A-Za-z0-9_]*)\b/
|
|
14
|
+
|
|
15
|
+
def initialize(engines_root: DEFAULT_ENGINES_ROOT, output: $stdout)
|
|
16
|
+
@engines_root = engines_root
|
|
17
|
+
@output = output
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def call
|
|
21
|
+
engines = discover_engines
|
|
22
|
+
@output.puts("seams: #{engines.size} engine(s) installed")
|
|
23
|
+
|
|
24
|
+
if engines.empty?
|
|
25
|
+
@output.puts(" (no engines — generate one with `bin/rails generate seams:engine <name>`)")
|
|
26
|
+
return
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
engines.each { |engine| print_engine(engine) }
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
private
|
|
33
|
+
|
|
34
|
+
def discover_engines
|
|
35
|
+
return [] unless Dir.exist?(@engines_root)
|
|
36
|
+
|
|
37
|
+
Dir.children(@engines_root)
|
|
38
|
+
.select { |child| File.directory?(File.join(@engines_root, child)) }
|
|
39
|
+
.reject { |child| child.start_with?(".") }
|
|
40
|
+
.sort
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def print_engine(name)
|
|
44
|
+
@output.puts(" - #{name}")
|
|
45
|
+
events_for(name).each { |event| @output.puts(" emits: #{event}") }
|
|
46
|
+
subscriptions_for(name).each { |event| @output.puts(" subscribes: #{event}") }
|
|
47
|
+
depends_on(name).each { |dep| @output.puts(" depends on: #{dep}") }
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def events_for(name)
|
|
51
|
+
module_name = module_name_for(name)
|
|
52
|
+
events = Seams::EventRegistry.all.select { |_, owner| owner.to_s == module_name }.keys
|
|
53
|
+
events.empty? ? ["(no events)"] : events
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
# Returns the event names this engine subscribes to. Looks in
|
|
57
|
+
# both the engine.rb itself AND in `app/subscribers/**/*.rb` —
|
|
58
|
+
# the canonical seams convention, where each subscriber class
|
|
59
|
+
# calls `Publisher.attach_class(KEY, "event.name", ...)` (or the
|
|
60
|
+
# legacy `attach_once`) from an `attach!` class method that the
|
|
61
|
+
# engine's `config.after_initialize` block invokes. Without
|
|
62
|
+
# scanning `app/subscribers/` this method silently reported zero
|
|
63
|
+
# subscribers for every generated engine, hiding the
|
|
64
|
+
# cross-engine dependency graph this command exists to surface.
|
|
65
|
+
def subscriptions_for(name)
|
|
66
|
+
subscription_sources_for(name)
|
|
67
|
+
.filter_map { |path| File.read(path) if File.exist?(path) }
|
|
68
|
+
.flat_map do |content|
|
|
69
|
+
content.scan(/Publisher\.(?:subscribe|attach_once|attach_class)\([^"']*["']([^"']+)["']/m)
|
|
70
|
+
.flatten
|
|
71
|
+
end
|
|
72
|
+
.uniq
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
def subscription_sources_for(name)
|
|
76
|
+
engine_rb = File.join(@engines_root, name, "lib", name, "engine.rb")
|
|
77
|
+
subscriber_glob = File.join(@engines_root, name, "app", "subscribers", "**", "*.rb")
|
|
78
|
+
|
|
79
|
+
# Dir.glob is already sorted on every supported Ruby version.
|
|
80
|
+
[engine_rb, *Dir.glob(subscriber_glob)]
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
# Walks the subscribe-list and resolves each event back to the
|
|
84
|
+
# engine that emits it (via the canonical "name.action.<engine>"
|
|
85
|
+
# naming convention). Returns the set of distinct engine names
|
|
86
|
+
# this one depends on.
|
|
87
|
+
def depends_on(name)
|
|
88
|
+
subscriptions_for(name)
|
|
89
|
+
.map { |event| event.split(".").last }
|
|
90
|
+
.reject { |dep| dep.nil? || dep == name }
|
|
91
|
+
.uniq
|
|
92
|
+
.sort
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
# Tries to find the engine's actual Ruby module name by reading
|
|
96
|
+
# its lib/<name>.rb. Falls back to a CamelCase conversion of the
|
|
97
|
+
# directory name (so `oauth2` becomes `Oauth2` rather than
|
|
98
|
+
# crashing) when the file is missing or doesn't declare a module.
|
|
99
|
+
def module_name_for(name)
|
|
100
|
+
root_file = File.join(@engines_root, name, "lib", "#{name}.rb")
|
|
101
|
+
|
|
102
|
+
if File.exist?(root_file)
|
|
103
|
+
match = File.read(root_file).match(MODULE_DECLARATION)
|
|
104
|
+
return match[1] if match
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
name.split("_").map(&:capitalize).join
|
|
108
|
+
end
|
|
109
|
+
end
|
|
110
|
+
end
|
|
111
|
+
end
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "seams"
|
|
4
|
+
|
|
5
|
+
module Seams
|
|
6
|
+
module CLI
|
|
7
|
+
# Runs the four quality gates the host's `bin/audit` script also
|
|
8
|
+
# runs (RuboCop, Brakeman, bundler-audit, SimpleCov collation), in
|
|
9
|
+
# one shot, and reports a unified summary. Each tool that is not
|
|
10
|
+
# installed in the host's bundle is skipped with a note rather
|
|
11
|
+
# than failing — hosts opt in by adding the gem.
|
|
12
|
+
#
|
|
13
|
+
# bin/rails seams:quality:all
|
|
14
|
+
#
|
|
15
|
+
# Returns true when every gate that ran passed; false otherwise.
|
|
16
|
+
class Quality
|
|
17
|
+
DEFAULT_ENGINES_ROOT = "engines"
|
|
18
|
+
|
|
19
|
+
def initialize(engines_root: DEFAULT_ENGINES_ROOT, output: $stdout)
|
|
20
|
+
@engines_root = engines_root
|
|
21
|
+
@output = output
|
|
22
|
+
@results = {}
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def call
|
|
26
|
+
run_rubocop
|
|
27
|
+
run_brakeman
|
|
28
|
+
run_bundler_audit
|
|
29
|
+
run_simplecov_collation
|
|
30
|
+
|
|
31
|
+
print_summary
|
|
32
|
+
@results.values.all? { |status| %i[pass skipped].include?(status) }
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
private
|
|
36
|
+
|
|
37
|
+
def run_rubocop
|
|
38
|
+
if gem_installed?("rubocop")
|
|
39
|
+
@output.puts("=== rubocop --parallel ===")
|
|
40
|
+
@results[:rubocop] = system("bundle", "exec", "rubocop", "--parallel") ? :pass : :fail
|
|
41
|
+
else
|
|
42
|
+
@output.puts("rubocop not installed — skipping.")
|
|
43
|
+
@results[:rubocop] = :skipped
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def run_brakeman
|
|
48
|
+
if gem_installed?("brakeman")
|
|
49
|
+
@output.puts("=== brakeman ===")
|
|
50
|
+
@results[:brakeman] =
|
|
51
|
+
system("bundle", "exec", "brakeman", "--no-pager", "--no-progress", "--quiet") ? :pass : :fail
|
|
52
|
+
else
|
|
53
|
+
@output.puts("brakeman not installed — skipping.")
|
|
54
|
+
@results[:brakeman] = :skipped
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def run_bundler_audit
|
|
59
|
+
if gem_installed?("bundler-audit")
|
|
60
|
+
@output.puts("=== bundle-audit check --update ===")
|
|
61
|
+
@results[:bundler_audit] = system("bundle", "exec", "bundle-audit", "check", "--update") ? :pass : :fail
|
|
62
|
+
else
|
|
63
|
+
@output.puts("bundler-audit not installed — skipping.")
|
|
64
|
+
@results[:bundler_audit] = :skipped
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
# Calls the host's script/collate_coverage.rb (shipped by
|
|
69
|
+
# Phase 1.5). Skipped if the script is not present (older host)
|
|
70
|
+
# or no per-engine resultset files exist yet.
|
|
71
|
+
def run_simplecov_collation
|
|
72
|
+
script = "script/collate_coverage.rb"
|
|
73
|
+
unless File.exist?(script)
|
|
74
|
+
@output.puts("script/collate_coverage.rb not present — skipping coverage collation.")
|
|
75
|
+
@results[:coverage] = :skipped
|
|
76
|
+
return
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
@output.puts("=== ruby script/collate_coverage.rb ===")
|
|
80
|
+
@results[:coverage] = system("ruby", script) ? :pass : :fail
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
def gem_installed?(gem_name)
|
|
84
|
+
Gem::Specification.find_by_name(gem_name)
|
|
85
|
+
true
|
|
86
|
+
rescue Gem::LoadError
|
|
87
|
+
false
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
def print_summary
|
|
91
|
+
@output.puts("")
|
|
92
|
+
@output.puts("seams:quality summary")
|
|
93
|
+
@results.each do |gate, status|
|
|
94
|
+
@output.puts(" #{gate.to_s.ljust(15)} #{status}")
|
|
95
|
+
end
|
|
96
|
+
end
|
|
97
|
+
end
|
|
98
|
+
end
|
|
99
|
+
end
|