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.
Files changed (414) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +335 -0
  3. data/LICENSE +21 -0
  4. data/README.md +104 -0
  5. data/lib/generators/seams/accounts/accounts_generator.rb +272 -0
  6. data/lib/generators/seams/accounts/templates/README.md.tt +219 -0
  7. data/lib/generators/seams/accounts/templates/app/models/account.rb.tt +124 -0
  8. data/lib/generators/seams/accounts/templates/app/models/application_record.rb.tt +7 -0
  9. data/lib/generators/seams/accounts/templates/app/models/current.rb.tt +38 -0
  10. data/lib/generators/seams/accounts/templates/app/models/membership.rb.tt +114 -0
  11. data/lib/generators/seams/accounts/templates/config/routes.rb.tt +8 -0
  12. data/lib/generators/seams/accounts/templates/db/migrate/create_accounts.rb.tt +29 -0
  13. data/lib/generators/seams/accounts/templates/db/migrate/create_accounts_memberships.rb.tt +49 -0
  14. data/lib/generators/seams/accounts/templates/lib/accounts.rb.tt +21 -0
  15. data/lib/generators/seams/accounts/templates/lib/concerns/account_scoped.rb.tt +97 -0
  16. data/lib/generators/seams/accounts/templates/lib/concerns/authorization.rb.tt +105 -0
  17. data/lib/generators/seams/accounts/templates/lib/configuration.rb.tt +26 -0
  18. data/lib/generators/seams/accounts/templates/lib/engine.rb.tt +55 -0
  19. data/lib/generators/seams/accounts/templates/spec/factories/accounts.rb.tt +49 -0
  20. data/lib/generators/seams/accounts/templates/spec/models/accounts/account_spec.rb.tt +64 -0
  21. data/lib/generators/seams/accounts/templates/spec/models/accounts/membership_spec.rb.tt +99 -0
  22. data/lib/generators/seams/accounts/templates/spec/runtime/accounts_boot_spec.rb.tt +181 -0
  23. data/lib/generators/seams/admin/admin_generator.rb +852 -0
  24. data/lib/generators/seams/admin/templates/README.md.tt +266 -0
  25. data/lib/generators/seams/admin/templates/app/controllers/admin/accounts_controller.rb.tt +16 -0
  26. data/lib/generators/seams/admin/templates/app/controllers/admin/accounts_memberships_controller.rb.tt +16 -0
  27. data/lib/generators/seams/admin/templates/app/controllers/admin/application_controller.rb.tt +282 -0
  28. data/lib/generators/seams/admin/templates/app/controllers/admin/identities_controller.rb.tt +26 -0
  29. data/lib/generators/seams/admin/templates/app/controllers/admin/invitations_controller.rb.tt +14 -0
  30. data/lib/generators/seams/admin/templates/app/controllers/admin/invoices_controller.rb.tt +14 -0
  31. data/lib/generators/seams/admin/templates/app/controllers/admin/lifetime_passes_controller.rb.tt +14 -0
  32. data/lib/generators/seams/admin/templates/app/controllers/admin/notification_preferences_controller.rb.tt +15 -0
  33. data/lib/generators/seams/admin/templates/app/controllers/admin/notifications_controller.rb.tt +18 -0
  34. data/lib/generators/seams/admin/templates/app/controllers/admin/plans_controller.rb.tt +14 -0
  35. data/lib/generators/seams/admin/templates/app/controllers/admin/subscriptions_controller.rb.tt +14 -0
  36. data/lib/generators/seams/admin/templates/app/controllers/admin/teams_controller.rb.tt +14 -0
  37. data/lib/generators/seams/admin/templates/app/controllers/admin/teams_memberships_controller.rb.tt +16 -0
  38. data/lib/generators/seams/admin/templates/app/dashboards/admin/account_dashboard.rb.tt +50 -0
  39. data/lib/generators/seams/admin/templates/app/dashboards/admin/accounts_membership_dashboard.rb.tt +58 -0
  40. data/lib/generators/seams/admin/templates/app/dashboards/admin/identity_dashboard.rb.tt +48 -0
  41. data/lib/generators/seams/admin/templates/app/dashboards/admin/invitation_dashboard.rb.tt +51 -0
  42. data/lib/generators/seams/admin/templates/app/dashboards/admin/invoice_dashboard.rb.tt +67 -0
  43. data/lib/generators/seams/admin/templates/app/dashboards/admin/lifetime_pass_dashboard.rb.tt +65 -0
  44. data/lib/generators/seams/admin/templates/app/dashboards/admin/notification_dashboard.rb.tt +58 -0
  45. data/lib/generators/seams/admin/templates/app/dashboards/admin/notification_preference_dashboard.rb.tt +43 -0
  46. data/lib/generators/seams/admin/templates/app/dashboards/admin/plan_dashboard.rb.tt +72 -0
  47. data/lib/generators/seams/admin/templates/app/dashboards/admin/subscription_dashboard.rb.tt +59 -0
  48. data/lib/generators/seams/admin/templates/app/dashboards/admin/team_dashboard.rb.tt +39 -0
  49. data/lib/generators/seams/admin/templates/app/dashboards/admin/teams_membership_dashboard.rb.tt +43 -0
  50. data/lib/generators/seams/admin/templates/app/policies/admin/platform/account_policy.rb.tt +10 -0
  51. data/lib/generators/seams/admin/templates/app/policies/admin/platform/accounts_membership_policy.rb.tt +10 -0
  52. data/lib/generators/seams/admin/templates/app/policies/admin/platform/application_policy.rb.tt +85 -0
  53. data/lib/generators/seams/admin/templates/app/policies/admin/platform/identity_policy.rb.tt +18 -0
  54. data/lib/generators/seams/admin/templates/app/policies/admin/platform/invitation_policy.rb.tt +9 -0
  55. data/lib/generators/seams/admin/templates/app/policies/admin/platform/invoice_policy.rb.tt +9 -0
  56. data/lib/generators/seams/admin/templates/app/policies/admin/platform/lifetime_pass_policy.rb.tt +9 -0
  57. data/lib/generators/seams/admin/templates/app/policies/admin/platform/notification_policy.rb.tt +9 -0
  58. data/lib/generators/seams/admin/templates/app/policies/admin/platform/notification_preference_policy.rb.tt +9 -0
  59. data/lib/generators/seams/admin/templates/app/policies/admin/platform/plan_policy.rb.tt +11 -0
  60. data/lib/generators/seams/admin/templates/app/policies/admin/platform/subscription_policy.rb.tt +9 -0
  61. data/lib/generators/seams/admin/templates/app/policies/admin/platform/team_policy.rb.tt +9 -0
  62. data/lib/generators/seams/admin/templates/app/policies/admin/platform/teams_membership_policy.rb.tt +9 -0
  63. data/lib/generators/seams/admin/templates/app/policies/admin/tenant/account_policy.rb.tt +33 -0
  64. data/lib/generators/seams/admin/templates/app/policies/admin/tenant/accounts_membership_policy.rb.tt +24 -0
  65. data/lib/generators/seams/admin/templates/app/policies/admin/tenant/application_policy.rb.tt +169 -0
  66. data/lib/generators/seams/admin/templates/app/policies/admin/tenant/identity_policy.rb.tt +67 -0
  67. data/lib/generators/seams/admin/templates/app/policies/admin/tenant/invitation_policy.rb.tt +24 -0
  68. data/lib/generators/seams/admin/templates/app/policies/admin/tenant/invoice_policy.rb.tt +21 -0
  69. data/lib/generators/seams/admin/templates/app/policies/admin/tenant/lifetime_pass_policy.rb.tt +21 -0
  70. data/lib/generators/seams/admin/templates/app/policies/admin/tenant/notification_policy.rb.tt +25 -0
  71. data/lib/generators/seams/admin/templates/app/policies/admin/tenant/notification_preference_policy.rb.tt +23 -0
  72. data/lib/generators/seams/admin/templates/app/policies/admin/tenant/plan_policy.rb.tt +47 -0
  73. data/lib/generators/seams/admin/templates/app/policies/admin/tenant/subscription_policy.rb.tt +22 -0
  74. data/lib/generators/seams/admin/templates/app/policies/admin/tenant/team_policy.rb.tt +28 -0
  75. data/lib/generators/seams/admin/templates/app/policies/admin/tenant/teams_membership_policy.rb.tt +24 -0
  76. data/lib/generators/seams/admin/templates/config/routes.rb.tt +38 -0
  77. data/lib/generators/seams/admin/templates/lib/admin.rb.tt +36 -0
  78. data/lib/generators/seams/admin/templates/lib/concerns/authenticator.rb.tt +66 -0
  79. data/lib/generators/seams/admin/templates/lib/configuration.rb.tt +90 -0
  80. data/lib/generators/seams/admin/templates/lib/context.rb.tt +44 -0
  81. data/lib/generators/seams/admin/templates/lib/engine.rb.tt +68 -0
  82. data/lib/generators/seams/admin/templates/spec/factories/admin.rb.tt +10 -0
  83. data/lib/generators/seams/admin/templates/spec/runtime/admin_boot_spec.rb.tt +604 -0
  84. data/lib/generators/seams/auth/add_oauth_provider/add_oauth_provider_generator.rb +157 -0
  85. data/lib/generators/seams/auth/add_oauth_provider/templates/adapter.rb.tt +95 -0
  86. data/lib/generators/seams/auth/add_oauth_provider/templates/adapter_spec.rb.tt +58 -0
  87. data/lib/generators/seams/auth/auth_generator.rb +311 -0
  88. data/lib/generators/seams/auth/templates/README.md.tt +289 -0
  89. data/lib/generators/seams/auth/templates/app/controllers/oauth/callbacks_controller.rb.tt +80 -0
  90. data/lib/generators/seams/auth/templates/app/controllers/password_resets_controller.rb.tt +44 -0
  91. data/lib/generators/seams/auth/templates/app/controllers/registrations_controller.rb.tt +34 -0
  92. data/lib/generators/seams/auth/templates/app/controllers/sessions_controller.rb.tt +49 -0
  93. data/lib/generators/seams/auth/templates/app/jobs/application_job.rb.tt +7 -0
  94. data/lib/generators/seams/auth/templates/app/jobs/cleanup_expired_sessions_job.rb.tt +30 -0
  95. data/lib/generators/seams/auth/templates/app/mailers/passwords_mailer.rb.tt +15 -0
  96. data/lib/generators/seams/auth/templates/app/models/api_token.rb.tt +62 -0
  97. data/lib/generators/seams/auth/templates/app/models/application_record.rb.tt +7 -0
  98. data/lib/generators/seams/auth/templates/app/models/current.rb.tt +15 -0
  99. data/lib/generators/seams/auth/templates/app/models/identity.rb.tt +74 -0
  100. data/lib/generators/seams/auth/templates/app/models/oauth/provider.rb.tt +48 -0
  101. data/lib/generators/seams/auth/templates/app/models/session.rb.tt +28 -0
  102. data/lib/generators/seams/auth/templates/app/services/authenticate_identity.rb.tt +31 -0
  103. data/lib/generators/seams/auth/templates/app/services/generate_api_token.rb.tt +35 -0
  104. data/lib/generators/seams/auth/templates/app/services/oauth/authenticator.rb.tt +94 -0
  105. data/lib/generators/seams/auth/templates/app/services/register_identity.rb.tt +57 -0
  106. data/lib/generators/seams/auth/templates/app/services/reset_password.rb.tt +41 -0
  107. data/lib/generators/seams/auth/templates/app/services/revoke_api_token.rb.tt +38 -0
  108. data/lib/generators/seams/auth/templates/app/views/password_resets/edit.html.erb.tt +12 -0
  109. data/lib/generators/seams/auth/templates/app/views/password_resets/new.html.erb.tt +11 -0
  110. data/lib/generators/seams/auth/templates/app/views/passwords_mailer/reset_email.html.erb.tt +7 -0
  111. data/lib/generators/seams/auth/templates/app/views/registrations/new.html.erb.tt +26 -0
  112. data/lib/generators/seams/auth/templates/app/views/sessions/_oauth_buttons.html.erb.tt +18 -0
  113. data/lib/generators/seams/auth/templates/app/views/sessions/new.html.erb.tt +17 -0
  114. data/lib/generators/seams/auth/templates/config/routes.rb.tt +21 -0
  115. data/lib/generators/seams/auth/templates/db/migrate/create_auth_api_tokens.rb.tt +26 -0
  116. data/lib/generators/seams/auth/templates/db/migrate/create_auth_identities.rb.tt +29 -0
  117. data/lib/generators/seams/auth/templates/db/migrate/create_auth_oauth_providers.rb.tt +35 -0
  118. data/lib/generators/seams/auth/templates/db/migrate/create_auth_sessions.rb.tt +19 -0
  119. data/lib/generators/seams/auth/templates/lib/auth.rb.tt +39 -0
  120. data/lib/generators/seams/auth/templates/lib/concerns/api_authenticatable.rb.tt +58 -0
  121. data/lib/generators/seams/auth/templates/lib/concerns/authenticatable.rb.tt +32 -0
  122. data/lib/generators/seams/auth/templates/lib/concerns/authentication.rb.tt +60 -0
  123. data/lib/generators/seams/auth/templates/lib/configuration.rb.tt +45 -0
  124. data/lib/generators/seams/auth/templates/lib/engine.rb.tt +46 -0
  125. data/lib/generators/seams/auth/templates/lib/oauth/abstract.rb.tt +87 -0
  126. data/lib/generators/seams/auth/templates/lib/oauth/github.rb.tt +112 -0
  127. data/lib/generators/seams/auth/templates/lib/oauth/google.rb.tt +78 -0
  128. data/lib/generators/seams/auth/templates/lib/tasks/auth_pii.rake.tt +68 -0
  129. data/lib/generators/seams/auth/templates/spec/factories/auth.rb.tt +38 -0
  130. data/lib/generators/seams/auth/templates/spec/mailers/passwords_mailer_spec.rb.tt +37 -0
  131. data/lib/generators/seams/auth/templates/spec/models/api_token_spec.rb.tt +84 -0
  132. data/lib/generators/seams/auth/templates/spec/models/identity_spec.rb.tt +56 -0
  133. data/lib/generators/seams/auth/templates/spec/models/oauth/provider_spec.rb.tt +64 -0
  134. data/lib/generators/seams/auth/templates/spec/models/session_spec.rb.tt +34 -0
  135. data/lib/generators/seams/auth/templates/spec/runtime/boot_spec.rb.tt +30 -0
  136. data/lib/generators/seams/auth/templates/spec/runtime/event_payload_spec.rb.tt +29 -0
  137. data/lib/generators/seams/auth/templates/spec/runtime/login_flow_spec.rb.tt +45 -0
  138. data/lib/generators/seams/billing/billing_generator.rb +476 -0
  139. data/lib/generators/seams/billing/templates/README.md.tt +355 -0
  140. data/lib/generators/seams/billing/templates/app/controllers/admin/lifetime_passes_controller.rb.tt +84 -0
  141. data/lib/generators/seams/billing/templates/app/controllers/checkout_controller.rb.tt +92 -0
  142. data/lib/generators/seams/billing/templates/app/controllers/invoices_controller.rb.tt +63 -0
  143. data/lib/generators/seams/billing/templates/app/controllers/plans_controller.rb.tt +14 -0
  144. data/lib/generators/seams/billing/templates/app/controllers/portal_controller.rb.tt +45 -0
  145. data/lib/generators/seams/billing/templates/app/controllers/subscriptions_controller.rb.tt +119 -0
  146. data/lib/generators/seams/billing/templates/app/controllers/webhooks_controller.rb.tt +98 -0
  147. data/lib/generators/seams/billing/templates/app/helpers/currency_helper.rb.tt +44 -0
  148. data/lib/generators/seams/billing/templates/app/jobs/application_job.rb.tt +6 -0
  149. data/lib/generators/seams/billing/templates/app/jobs/cancel_subscription_job.rb.tt +39 -0
  150. data/lib/generators/seams/billing/templates/app/jobs/start_subscription_job.rb.tt +32 -0
  151. data/lib/generators/seams/billing/templates/app/jobs/webhooks/process_event_job.rb.tt +37 -0
  152. data/lib/generators/seams/billing/templates/app/models/application_record.rb.tt +7 -0
  153. data/lib/generators/seams/billing/templates/app/models/invoice.rb.tt +35 -0
  154. data/lib/generators/seams/billing/templates/app/models/lifetime_pass.rb.tt +60 -0
  155. data/lib/generators/seams/billing/templates/app/models/plan.rb.tt +95 -0
  156. data/lib/generators/seams/billing/templates/app/models/subscription.rb.tt +31 -0
  157. data/lib/generators/seams/billing/templates/app/models/webhook_event.rb.tt +13 -0
  158. data/lib/generators/seams/billing/templates/app/services/checkout_session_service.rb.tt +25 -0
  159. data/lib/generators/seams/billing/templates/app/services/customers/find_or_create_service.rb.tt +73 -0
  160. data/lib/generators/seams/billing/templates/app/services/invoices/sync_service.rb.tt +50 -0
  161. data/lib/generators/seams/billing/templates/app/services/lifetime/create_lifetime_session_service.rb.tt +82 -0
  162. data/lib/generators/seams/billing/templates/app/services/lifetime/create_pass_from_checkout_service.rb.tt +88 -0
  163. data/lib/generators/seams/billing/templates/app/services/lifetime/grant_pass_service.rb.tt +80 -0
  164. data/lib/generators/seams/billing/templates/app/services/lifetime/revoke_pass_service.rb.tt +59 -0
  165. data/lib/generators/seams/billing/templates/app/services/portal_session_service.rb.tt +23 -0
  166. data/lib/generators/seams/billing/templates/app/services/service_result.rb.tt +38 -0
  167. data/lib/generators/seams/billing/templates/app/services/stripe_service.rb.tt +67 -0
  168. data/lib/generators/seams/billing/templates/app/services/subscriptions/cancel_service.rb.tt +42 -0
  169. data/lib/generators/seams/billing/templates/app/services/subscriptions/change_plan_service.rb.tt +48 -0
  170. data/lib/generators/seams/billing/templates/app/services/subscriptions/reactivate_service.rb.tt +28 -0
  171. data/lib/generators/seams/billing/templates/app/services/webhooks/event_router.rb.tt +54 -0
  172. data/lib/generators/seams/billing/templates/app/services/webhooks/handler.rb.tt +93 -0
  173. data/lib/generators/seams/billing/templates/app/services/webhooks/handlers/charge_refunded_handler.rb.tt +18 -0
  174. data/lib/generators/seams/billing/templates/app/services/webhooks/handlers/checkout_session_completed_handler.rb.tt +58 -0
  175. data/lib/generators/seams/billing/templates/app/services/webhooks/handlers/invoice_created_handler.rb.tt +16 -0
  176. data/lib/generators/seams/billing/templates/app/services/webhooks/handlers/invoice_finalized_handler.rb.tt +14 -0
  177. data/lib/generators/seams/billing/templates/app/services/webhooks/handlers/invoice_handler_base.rb.tt +80 -0
  178. data/lib/generators/seams/billing/templates/app/services/webhooks/handlers/invoice_paid_handler.rb.tt +12 -0
  179. data/lib/generators/seams/billing/templates/app/services/webhooks/handlers/invoice_payment_failed_handler.rb.tt +12 -0
  180. data/lib/generators/seams/billing/templates/app/services/webhooks/handlers/invoice_voided_handler.rb.tt +12 -0
  181. data/lib/generators/seams/billing/templates/app/services/webhooks/handlers/payment_failed_handler.rb.tt +15 -0
  182. data/lib/generators/seams/billing/templates/app/services/webhooks/handlers/payment_succeeded_handler.rb.tt +19 -0
  183. data/lib/generators/seams/billing/templates/app/services/webhooks/handlers/subscription_created_handler.rb.tt +11 -0
  184. data/lib/generators/seams/billing/templates/app/services/webhooks/handlers/subscription_deleted_handler.rb.tt +15 -0
  185. data/lib/generators/seams/billing/templates/app/services/webhooks/handlers/subscription_handler_base.rb.tt +92 -0
  186. data/lib/generators/seams/billing/templates/app/services/webhooks/handlers/subscription_trial_will_end_handler.rb.tt +15 -0
  187. data/lib/generators/seams/billing/templates/app/services/webhooks/handlers/subscription_updated_handler.rb.tt +11 -0
  188. data/lib/generators/seams/billing/templates/app/views/admin/lifetime_passes/index.html.erb.tt +36 -0
  189. data/lib/generators/seams/billing/templates/app/views/admin/lifetime_passes/new.html.erb.tt +37 -0
  190. data/lib/generators/seams/billing/templates/app/views/checkout/success.html.erb.tt +5 -0
  191. data/lib/generators/seams/billing/templates/app/views/invoices/index.html.erb.tt +22 -0
  192. data/lib/generators/seams/billing/templates/app/views/invoices/show.html.erb.tt +14 -0
  193. data/lib/generators/seams/billing/templates/app/views/plans/index.html.erb.tt +51 -0
  194. data/lib/generators/seams/billing/templates/app/views/subscriptions/index.html.erb.tt +16 -0
  195. data/lib/generators/seams/billing/templates/app/views/subscriptions/show.html.erb.tt +25 -0
  196. data/lib/generators/seams/billing/templates/config/routes.rb.tt +39 -0
  197. data/lib/generators/seams/billing/templates/db/migrate/create_billing_invoices.rb.tt +32 -0
  198. data/lib/generators/seams/billing/templates/db/migrate/create_billing_lifetime_passes.rb.tt +43 -0
  199. data/lib/generators/seams/billing/templates/db/migrate/create_billing_plans.rb.tt +31 -0
  200. data/lib/generators/seams/billing/templates/db/migrate/create_billing_subscriptions.rb.tt +33 -0
  201. data/lib/generators/seams/billing/templates/db/migrate/create_billing_webhook_events.rb.tt +24 -0
  202. data/lib/generators/seams/billing/templates/lib/billing.rb.tt +34 -0
  203. data/lib/generators/seams/billing/templates/lib/concerns/billable.rb.tt +100 -0
  204. data/lib/generators/seams/billing/templates/lib/configuration.rb.tt +52 -0
  205. data/lib/generators/seams/billing/templates/lib/engine.rb.tt +72 -0
  206. data/lib/generators/seams/billing/templates/lib/gateways/abstract.rb.tt +65 -0
  207. data/lib/generators/seams/billing/templates/lib/gateways/adyen.rb.tt +16 -0
  208. data/lib/generators/seams/billing/templates/lib/gateways/paddle.rb.tt +22 -0
  209. data/lib/generators/seams/billing/templates/lib/gateways/stripe.rb.tt +155 -0
  210. data/lib/generators/seams/billing/templates/lib/stripe/client.rb.tt +101 -0
  211. data/lib/generators/seams/billing/templates/lib/stripe/webhook_signature.rb.tt +43 -0
  212. data/lib/generators/seams/billing/templates/lib/tasks/billing_check.rake.tt +34 -0
  213. data/lib/generators/seams/billing/templates/spec/factories/billing.rb.tt +65 -0
  214. data/lib/generators/seams/billing/templates/spec/fixtures/stripe/charge_refunded.json.tt +19 -0
  215. data/lib/generators/seams/billing/templates/spec/fixtures/stripe/checkout_session_completed.json.tt +17 -0
  216. data/lib/generators/seams/billing/templates/spec/fixtures/stripe/customer_subscription_created.json.tt +25 -0
  217. data/lib/generators/seams/billing/templates/spec/fixtures/stripe/customer_subscription_deleted.json.tt +17 -0
  218. data/lib/generators/seams/billing/templates/spec/fixtures/stripe/customer_subscription_trial_will_end.json.tt +17 -0
  219. data/lib/generators/seams/billing/templates/spec/fixtures/stripe/customer_subscription_updated.json.tt +28 -0
  220. data/lib/generators/seams/billing/templates/spec/fixtures/stripe/invoice_created.json.tt +18 -0
  221. data/lib/generators/seams/billing/templates/spec/fixtures/stripe/invoice_finalized.json.tt +18 -0
  222. data/lib/generators/seams/billing/templates/spec/fixtures/stripe/invoice_paid.json.tt +19 -0
  223. data/lib/generators/seams/billing/templates/spec/fixtures/stripe/invoice_payment_failed.json.tt +20 -0
  224. data/lib/generators/seams/billing/templates/spec/fixtures/stripe/invoice_voided.json.tt +18 -0
  225. data/lib/generators/seams/billing/templates/spec/fixtures/stripe/payment_intent_payment_failed.json.tt +21 -0
  226. data/lib/generators/seams/billing/templates/spec/fixtures/stripe/payment_intent_succeeded.json.tt +17 -0
  227. data/lib/generators/seams/billing/templates/spec/gateways/contract_spec.rb.tt +11 -0
  228. data/lib/generators/seams/billing/templates/spec/gateways/stripe_spec.rb.tt +53 -0
  229. data/lib/generators/seams/billing/templates/spec/models/plan_spec.rb.tt +81 -0
  230. data/lib/generators/seams/billing/templates/spec/models/subscription_spec.rb.tt +43 -0
  231. data/lib/generators/seams/billing/templates/spec/runtime/boot_spec.rb.tt +38 -0
  232. data/lib/generators/seams/billing/templates/spec/runtime/webhook_handlers_spec.rb.tt +382 -0
  233. data/lib/generators/seams/billing/templates/spec/support/shared_examples/a_billing_gateway.rb.tt +100 -0
  234. data/lib/generators/seams/billing/templates/spec/support/stripe_helpers.rb.tt +59 -0
  235. data/lib/generators/seams/core/core_generator.rb +191 -0
  236. data/lib/generators/seams/core/templates/README.md.tt +45 -0
  237. data/lib/generators/seams/core/templates/app/controllers/concerns/has_current_attributes.rb.tt +71 -0
  238. data/lib/generators/seams/core/templates/app/models/application_record.rb.tt +7 -0
  239. data/lib/generators/seams/core/templates/app/models/audit_log.rb.tt +19 -0
  240. data/lib/generators/seams/core/templates/app/models/concerns/auditable.rb.tt +64 -0
  241. data/lib/generators/seams/core/templates/app/models/concerns/sluggable.rb.tt +53 -0
  242. data/lib/generators/seams/core/templates/app/models/concerns/soft_deletable.rb.tt +37 -0
  243. data/lib/generators/seams/core/templates/app/models/concerns/tenant_scoped.rb.tt +39 -0
  244. data/lib/generators/seams/core/templates/app/models/current.rb.tt +16 -0
  245. data/lib/generators/seams/core/templates/app/services/event_publisher.rb.tt +23 -0
  246. data/lib/generators/seams/core/templates/app/validators/email_format_validator.rb.tt +21 -0
  247. data/lib/generators/seams/core/templates/db/migrate/create_core_audit_logs.rb.tt +29 -0
  248. data/lib/generators/seams/core/templates/lib/core.rb.tt +8 -0
  249. data/lib/generators/seams/core/templates/lib/engine.rb.tt +28 -0
  250. data/lib/generators/seams/core/templates/spec/concerns/auditable_spec.rb.tt +39 -0
  251. data/lib/generators/seams/core/templates/spec/concerns/sluggable_spec.rb.tt +29 -0
  252. data/lib/generators/seams/core/templates/spec/models/audit_log_spec.rb.tt +22 -0
  253. data/lib/generators/seams/core/templates/spec/runtime/boot_spec.rb.tt +25 -0
  254. data/lib/generators/seams/core/templates/spec/validators/email_format_validator_spec.rb.tt +29 -0
  255. data/lib/generators/seams/engine/engine_generator.rb +165 -0
  256. data/lib/generators/seams/engine/templates/Gemfile.tt +19 -0
  257. data/lib/generators/seams/engine/templates/LICENSE.tt +21 -0
  258. data/lib/generators/seams/engine/templates/README.md.tt +40 -0
  259. data/lib/generators/seams/engine/templates/Rakefile.tt +14 -0
  260. data/lib/generators/seams/engine/templates/app/application_controller.rb.tt +6 -0
  261. data/lib/generators/seams/engine/templates/app/application_record.rb.tt +16 -0
  262. data/lib/generators/seams/engine/templates/config/locales/en.yml.tt +14 -0
  263. data/lib/generators/seams/engine/templates/config/routes.rb.tt +4 -0
  264. data/lib/generators/seams/engine/templates/gemspec.tt +20 -0
  265. data/lib/generators/seams/engine/templates/host_initializer.rb.tt +13 -0
  266. data/lib/generators/seams/engine/templates/lib/engine.rb.tt +27 -0
  267. data/lib/generators/seams/engine/templates/lib/root.rb.tt +7 -0
  268. data/lib/generators/seams/engine/templates/lib/version.rb.tt +5 -0
  269. data/lib/generators/seams/engine/templates/rubocop.yml.tt +55 -0
  270. data/lib/generators/seams/engine/templates/spec/example_spec.rb.tt +16 -0
  271. data/lib/generators/seams/engine/templates/spec/spec_helper.rb.tt +23 -0
  272. data/lib/generators/seams/install/install_generator.rb +211 -0
  273. data/lib/generators/seams/install/templates/Dockerfile.tt +52 -0
  274. data/lib/generators/seams/install/templates/Procfile.tt +14 -0
  275. data/lib/generators/seams/install/templates/bin_seams.tt +107 -0
  276. data/lib/generators/seams/install/templates/ci.yml.tt +123 -0
  277. data/lib/generators/seams/install/templates/deploy.yml.tt +63 -0
  278. data/lib/generators/seams/install/templates/doc/ARCHITECTURE.md.tt +86 -0
  279. data/lib/generators/seams/install/templates/docker-entrypoint.tt +27 -0
  280. data/lib/generators/seams/install/templates/rubocop.yml.tt +33 -0
  281. data/lib/generators/seams/install/templates/ruby-version.tt +1 -0
  282. data/lib/generators/seams/install/templates/script/collate_coverage.rb.tt +33 -0
  283. data/lib/generators/seams/install/templates/script/run_affected_tests.sh.tt +64 -0
  284. data/lib/generators/seams/install/templates/seams.rake.tt +65 -0
  285. data/lib/generators/seams/install/templates/seams.rb.tt +9 -0
  286. data/lib/generators/seams/install/templates/seams_engines.rb.tt +15 -0
  287. data/lib/generators/seams/notifications/notifications_generator.rb +395 -0
  288. data/lib/generators/seams/notifications/templates/README.md.tt +269 -0
  289. data/lib/generators/seams/notifications/templates/app/channels/notification_channel.rb.tt +36 -0
  290. data/lib/generators/seams/notifications/templates/app/controllers/notifications_controller.rb.tt +58 -0
  291. data/lib/generators/seams/notifications/templates/app/controllers/preferences_controller.rb.tt +54 -0
  292. data/lib/generators/seams/notifications/templates/app/javascript/controllers/notification_bell_controller.js.tt +34 -0
  293. data/lib/generators/seams/notifications/templates/app/jobs/application_job.rb.tt +6 -0
  294. data/lib/generators/seams/notifications/templates/app/jobs/create_notification_job.rb.tt +31 -0
  295. data/lib/generators/seams/notifications/templates/app/jobs/send_due_notifications_job.rb.tt +22 -0
  296. data/lib/generators/seams/notifications/templates/app/jobs/send_notification_job.rb.tt +13 -0
  297. data/lib/generators/seams/notifications/templates/app/mailers/application_mailer.rb.tt +12 -0
  298. data/lib/generators/seams/notifications/templates/app/mailers/notification_mailer.rb.tt +23 -0
  299. data/lib/generators/seams/notifications/templates/app/models/application_record.rb.tt +7 -0
  300. data/lib/generators/seams/notifications/templates/app/models/delivery.rb.tt +13 -0
  301. data/lib/generators/seams/notifications/templates/app/models/notification.rb.tt +218 -0
  302. data/lib/generators/seams/notifications/templates/app/models/notification_preference.rb.tt +29 -0
  303. data/lib/generators/seams/notifications/templates/app/models/strategies/email.rb.tt +38 -0
  304. data/lib/generators/seams/notifications/templates/app/models/strategies/in_app.rb.tt +26 -0
  305. data/lib/generators/seams/notifications/templates/app/models/strategies/sms.rb.tt +33 -0
  306. data/lib/generators/seams/notifications/templates/app/subscribers/auth_subscriber.rb.tt +71 -0
  307. data/lib/generators/seams/notifications/templates/app/subscribers/billing_subscriber.rb.tt +127 -0
  308. data/lib/generators/seams/notifications/templates/app/views/layouts/notifications/mailer.html.erb.tt +22 -0
  309. data/lib/generators/seams/notifications/templates/app/views/layouts/notifications/mailer.text.erb.tt +4 -0
  310. data/lib/generators/seams/notifications/templates/app/views/notifications/_bell.html.erb.tt +15 -0
  311. data/lib/generators/seams/notifications/templates/app/views/notifications/index.html.erb.tt +15 -0
  312. data/lib/generators/seams/notifications/templates/app/views/templates/billing/invoice_failed.html.erb.tt +4 -0
  313. data/lib/generators/seams/notifications/templates/app/views/templates/billing/invoice_failed.text.erb.tt +4 -0
  314. data/lib/generators/seams/notifications/templates/app/views/templates/billing/invoice_paid.html.erb.tt +3 -0
  315. data/lib/generators/seams/notifications/templates/app/views/templates/billing/invoice_paid.text.erb.tt +3 -0
  316. data/lib/generators/seams/notifications/templates/app/views/templates/billing/lifetime_granted.html.erb.tt +5 -0
  317. data/lib/generators/seams/notifications/templates/app/views/templates/billing/lifetime_granted.text.erb.tt +5 -0
  318. data/lib/generators/seams/notifications/templates/app/views/templates/billing/lifetime_purchased.html.erb.tt +5 -0
  319. data/lib/generators/seams/notifications/templates/app/views/templates/billing/lifetime_purchased.text.erb.tt +5 -0
  320. data/lib/generators/seams/notifications/templates/app/views/templates/billing/subscription_canceled.html.erb.tt +4 -0
  321. data/lib/generators/seams/notifications/templates/app/views/templates/billing/subscription_canceled.text.erb.tt +4 -0
  322. data/lib/generators/seams/notifications/templates/app/views/templates/billing/subscription_started.html.erb.tt +4 -0
  323. data/lib/generators/seams/notifications/templates/app/views/templates/billing/subscription_started.text.erb.tt +5 -0
  324. data/lib/generators/seams/notifications/templates/app/views/templates/billing/subscription_updated.html.erb.tt +3 -0
  325. data/lib/generators/seams/notifications/templates/app/views/templates/billing/subscription_updated.text.erb.tt +3 -0
  326. data/lib/generators/seams/notifications/templates/app/views/templates/default.html.erb.tt +10 -0
  327. data/lib/generators/seams/notifications/templates/app/views/templates/default.text.erb.tt +11 -0
  328. data/lib/generators/seams/notifications/templates/app/views/templates/welcome.html.erb.tt +6 -0
  329. data/lib/generators/seams/notifications/templates/app/views/templates/welcome.text.erb.tt +6 -0
  330. data/lib/generators/seams/notifications/templates/config/initializers/notifications.rb.tt +58 -0
  331. data/lib/generators/seams/notifications/templates/config/routes.rb.tt +17 -0
  332. data/lib/generators/seams/notifications/templates/db/migrate/create_notification_deliveries.rb.tt +16 -0
  333. data/lib/generators/seams/notifications/templates/db/migrate/create_notification_preferences.rb.tt +25 -0
  334. data/lib/generators/seams/notifications/templates/db/migrate/create_notifications.rb.tt +35 -0
  335. data/lib/generators/seams/notifications/templates/lib/adapters/abstract.rb.tt +20 -0
  336. data/lib/generators/seams/notifications/templates/lib/adapters/action_mailer.rb.tt +17 -0
  337. data/lib/generators/seams/notifications/templates/lib/adapters/null_sms.rb.tt +23 -0
  338. data/lib/generators/seams/notifications/templates/lib/concerns/notifiable.rb.tt +135 -0
  339. data/lib/generators/seams/notifications/templates/lib/configuration.rb.tt +24 -0
  340. data/lib/generators/seams/notifications/templates/lib/engine.rb.tt +35 -0
  341. data/lib/generators/seams/notifications/templates/lib/notifications.rb.tt +75 -0
  342. data/lib/generators/seams/notifications/templates/lib/type_registry.rb.tt +74 -0
  343. data/lib/generators/seams/notifications/templates/spec/factories/notifications.rb.tt +53 -0
  344. data/lib/generators/seams/notifications/templates/spec/models/delivery_spec.rb.tt +28 -0
  345. data/lib/generators/seams/notifications/templates/spec/models/notification_preference_spec.rb.tt +46 -0
  346. data/lib/generators/seams/notifications/templates/spec/models/notification_spec.rb.tt +60 -0
  347. data/lib/generators/seams/notifications/templates/spec/runtime/bell_broadcast_spec.rb.tt +59 -0
  348. data/lib/generators/seams/notifications/templates/spec/runtime/billing_subscriber_skip_spec.rb.tt +87 -0
  349. data/lib/generators/seams/notifications/templates/spec/runtime/boot_spec.rb.tt +39 -0
  350. data/lib/generators/seams/notifications/templates/spec/runtime/schedule_round_trip_spec.rb.tt +55 -0
  351. data/lib/generators/seams/remove/remove_generator.rb +259 -0
  352. data/lib/generators/seams/teams/teams_generator.rb +298 -0
  353. data/lib/generators/seams/teams/templates/README.md.tt +88 -0
  354. data/lib/generators/seams/teams/templates/app/controllers/invitations_controller.rb.tt +102 -0
  355. data/lib/generators/seams/teams/templates/app/controllers/memberships_controller.rb.tt +54 -0
  356. data/lib/generators/seams/teams/templates/app/controllers/teams_controller.rb.tt +68 -0
  357. data/lib/generators/seams/teams/templates/app/jobs/application_job.rb.tt +6 -0
  358. data/lib/generators/seams/teams/templates/app/mailers/invitation_mailer.rb.tt +34 -0
  359. data/lib/generators/seams/teams/templates/app/models/application_record.rb.tt +7 -0
  360. data/lib/generators/seams/teams/templates/app/models/current.rb.tt +30 -0
  361. data/lib/generators/seams/teams/templates/app/models/invitation.rb.tt +36 -0
  362. data/lib/generators/seams/teams/templates/app/models/membership.rb.tt +36 -0
  363. data/lib/generators/seams/teams/templates/app/models/team.rb.tt +32 -0
  364. data/lib/generators/seams/teams/templates/app/subscribers/invitation_subscriber.rb.tt +36 -0
  365. data/lib/generators/seams/teams/templates/app/views/invitation_mailer/invite.text.erb.tt +8 -0
  366. data/lib/generators/seams/teams/templates/app/views/invitations/index.html.erb.tt +44 -0
  367. data/lib/generators/seams/teams/templates/app/views/memberships/index.html.erb.tt +32 -0
  368. data/lib/generators/seams/teams/templates/app/views/teams/edit.html.erb.tt +28 -0
  369. data/lib/generators/seams/teams/templates/app/views/teams/index.html.erb.tt +15 -0
  370. data/lib/generators/seams/teams/templates/app/views/teams/new.html.erb.tt +24 -0
  371. data/lib/generators/seams/teams/templates/app/views/teams/show.html.erb.tt +17 -0
  372. data/lib/generators/seams/teams/templates/config/routes.rb.tt +19 -0
  373. data/lib/generators/seams/teams/templates/db/migrate/create_team_invitations.rb.tt +24 -0
  374. data/lib/generators/seams/teams/templates/db/migrate/create_team_memberships.rb.tt +25 -0
  375. data/lib/generators/seams/teams/templates/db/migrate/create_teams.rb.tt +18 -0
  376. data/lib/generators/seams/teams/templates/lib/concerns/account_scoped.rb.tt +79 -0
  377. data/lib/generators/seams/teams/templates/lib/concerns/authorization.rb.tt +55 -0
  378. data/lib/generators/seams/teams/templates/lib/configuration.rb.tt +45 -0
  379. data/lib/generators/seams/teams/templates/lib/engine.rb.tt +51 -0
  380. data/lib/generators/seams/teams/templates/lib/teams.rb.tt +22 -0
  381. data/lib/generators/seams/teams/templates/spec/factories/teams.rb.tt +47 -0
  382. data/lib/generators/seams/teams/templates/spec/models/invitation_spec.rb.tt +25 -0
  383. data/lib/generators/seams/teams/templates/spec/models/membership_spec.rb.tt +29 -0
  384. data/lib/generators/seams/teams/templates/spec/models/team_spec.rb.tt +23 -0
  385. data/lib/generators/seams/teams/templates/spec/runtime/boot_spec.rb.tt +32 -0
  386. data/lib/seams/cli/list.rb +111 -0
  387. data/lib/seams/cli/quality.rb +99 -0
  388. data/lib/seams/cli/resolve.rb +276 -0
  389. data/lib/seams/cli/test_changed.rb +116 -0
  390. data/lib/seams/cli.rb +48 -0
  391. data/lib/seams/configuration.rb +19 -0
  392. data/lib/seams/cops/known_queue_names.rb +42 -0
  393. data/lib/seams/cops/migration_comments.rb +68 -0
  394. data/lib/seams/cops/no_cross_engine_dependency.rb +58 -0
  395. data/lib/seams/cops/no_cross_engine_model_access.rb +153 -0
  396. data/lib/seams/cops.rb +18 -0
  397. data/lib/seams/event_registry.rb +49 -0
  398. data/lib/seams/events/adapter.rb +24 -0
  399. data/lib/seams/events/adapters/active_support.rb +31 -0
  400. data/lib/seams/events/publisher.rb +178 -0
  401. data/lib/seams/events.rb +39 -0
  402. data/lib/seams/generators/dummy_app_writer.rb +424 -0
  403. data/lib/seams/generators/eject_aware.rb +102 -0
  404. data/lib/seams/generators/follow_up_generator.rb +148 -0
  405. data/lib/seams/generators/host_injector.rb +124 -0
  406. data/lib/seams/generators/sibling_rubocop_writer.rb +77 -0
  407. data/lib/seams/generators/splicer.rb +217 -0
  408. data/lib/seams/observability/adapter.rb +33 -0
  409. data/lib/seams/observability/adapters/rails_logger.rb +59 -0
  410. data/lib/seams/observability.rb +34 -0
  411. data/lib/seams/runtime.rb +23 -0
  412. data/lib/seams/version.rb +5 -0
  413. data/lib/seams.rb +23 -0
  414. metadata +493 -0
@@ -0,0 +1,33 @@
1
+ # Host application RuboCop config.
2
+ #
3
+ # Engines under engines/<name>/ ship their own self-contained
4
+ # .rubocop.yml — they do NOT inherit from this file (this is
5
+ # intentional so a host using rubocop-rails-omakase doesn't force
6
+ # engine code through application-style rules).
7
+ #
8
+ # Add your own host rules here; the engine boundary cops are loaded
9
+ # per-engine via engines/<name>/.rubocop.yml, not globally.
10
+
11
+ AllCops:
12
+ TargetRubyVersion: 4.0
13
+ NewCops: enable
14
+ SuggestExtensions: false
15
+ Exclude:
16
+ - "bin/**/*"
17
+ - "db/schema.rb"
18
+ - "tmp/**/*"
19
+ - "vendor/**/*"
20
+ - "engines/**/*" # engines lint themselves
21
+ - "lib/tasks/seams.rake" # gem-generated; uses default rubocop array style
22
+
23
+ Style/Documentation:
24
+ Enabled: false
25
+
26
+ Style/StringLiterals:
27
+ EnforcedStyle: double_quotes
28
+
29
+ Layout/LineLength:
30
+ Max: 120
31
+
32
+ Metrics/MethodLength:
33
+ Max: 25
@@ -0,0 +1,33 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Aggregates SimpleCov result files from every engine spec run into
4
+ # one combined report at coverage/.
5
+ #
6
+ # Each engine writes its own coverage/.resultset.json when its specs
7
+ # run. CI runs the engines in parallel (each on its own dummy app), so
8
+ # we end up with N partial reports. SimpleCov.collate merges them.
9
+ #
10
+ # Usage:
11
+ #
12
+ # bin/rails seams:test # writes per-engine coverage/.resultset.json
13
+ # ruby script/collate_coverage.rb
14
+ # open coverage/index.html
15
+ #
16
+ # Idempotent — re-running overwrites the merged report.
17
+ require "simplecov"
18
+
19
+ # Glob every engine's resultset, plus the host's, into one collation.
20
+ result_files = Dir.glob("engines/*/coverage/.resultset.json")
21
+ result_files << "coverage/.resultset.json" if File.exist?("coverage/.resultset.json")
22
+
23
+ if result_files.empty?
24
+ warn "No coverage/.resultset.json files found. Run specs first."
25
+ exit 1
26
+ end
27
+
28
+ puts "Collating #{result_files.size} resultset(s)..."
29
+ SimpleCov.collate(result_files) do
30
+ formatter SimpleCov::Formatter::HTMLFormatter
31
+ end
32
+
33
+ puts "Merged report: coverage/index.html"
@@ -0,0 +1,64 @@
1
+ #!/usr/bin/env bash
2
+ # Detects which engines changed vs the merge base with `main` and runs
3
+ # only those engines' specs. Falls back to running every engine when
4
+ # the diff is empty (a noop branch) or when invoked with --all.
5
+ #
6
+ # Usage:
7
+ #
8
+ # script/run_affected_tests.sh # diff against main
9
+ # script/run_affected_tests.sh --all # run every engine
10
+ # BASE_BRANCH=develop script/run_affected_tests.sh
11
+ #
12
+ # Exit code is the OR of every engine spec run — first failure aborts.
13
+
14
+ set -euo pipefail
15
+
16
+ BASE_BRANCH="${BASE_BRANCH:-main}"
17
+ RUN_ALL=0
18
+
19
+ if [[ "${1:-}" == "--all" ]]; then
20
+ RUN_ALL=1
21
+ fi
22
+
23
+ # Resolve the merge base so we ignore commits already on main.
24
+ merge_base="$(git merge-base "$BASE_BRANCH" HEAD 2>/dev/null || echo "")"
25
+ if [[ -z "$merge_base" ]]; then
26
+ echo "Could not resolve merge base with $BASE_BRANCH — running all engines."
27
+ RUN_ALL=1
28
+ fi
29
+
30
+ if [[ "$RUN_ALL" == "1" ]]; then
31
+ changed_engines=$(ls -1 engines/ 2>/dev/null || true)
32
+ else
33
+ changed_engines=$(git diff --name-only "$merge_base" HEAD -- 'engines/*' \
34
+ | awk -F/ '{print $2}' | sort -u)
35
+ fi
36
+
37
+ if [[ -z "$changed_engines" ]]; then
38
+ echo "No engines changed since $BASE_BRANCH. Nothing to test."
39
+ exit 0
40
+ fi
41
+
42
+ echo "Engines to test:"
43
+ echo "$changed_engines" | sed 's/^/ - /'
44
+ echo
45
+
46
+ failed=()
47
+ for engine in $changed_engines; do
48
+ if [[ ! -d "engines/$engine" ]]; then
49
+ continue
50
+ fi
51
+ echo "=== running engines/$engine specs ==="
52
+ if ! bundle exec rspec "engines/$engine/spec"; then
53
+ failed+=("$engine")
54
+ fi
55
+ done
56
+
57
+ if (( ${#failed[@]} > 0 )); then
58
+ echo
59
+ echo "Failed engines: ${failed[*]}"
60
+ exit 1
61
+ fi
62
+
63
+ echo
64
+ echo "All affected engine specs passed."
@@ -0,0 +1,65 @@
1
+ # frozen_string_literal: true
2
+
3
+ namespace :seams do
4
+ ENGINE_NAME_PATTERN = /\A[a-z][a-z0-9_]*\z/
5
+
6
+ desc "List installed engines, the events they emit, and their dependencies"
7
+ task list: :environment do
8
+ require "seams/cli/list"
9
+ Seams::CLI::List.new.call
10
+ end
11
+
12
+ desc "Run RSpec for a single engine: rake seams:test[billing]"
13
+ task :test, [:engine] => :environment do |_t, args|
14
+ name = args[:engine] or abort("Usage: rake seams:test[billing]")
15
+ abort("Invalid engine name: #{name.inspect}") unless ENGINE_NAME_PATTERN.match?(name)
16
+ # Array form: no shell interpolation, no injection.
17
+ sh "bundle", "exec", "rspec", "engines/#{name}/spec"
18
+ end
19
+
20
+ desc "Run RuboCop for a single engine: rake seams:quality[billing]"
21
+ task :quality, [:engine] => :environment do |_t, args|
22
+ name = args[:engine] or abort("Usage: rake seams:quality[billing]")
23
+ abort("Invalid engine name: #{name.inspect}") unless ENGINE_NAME_PATTERN.match?(name)
24
+ sh "bundle", "exec", "rubocop", "engines/#{name}"
25
+ end
26
+
27
+ namespace :test do
28
+ desc "Run specs only for engines changed vs $BASE (default: main)"
29
+ task changed: :environment do
30
+ require "seams/cli"
31
+ base = ENV.fetch("BASE", "main")
32
+ abort("seams:test:changed failed.") unless Seams::CLI.test_changed(base: base)
33
+ end
34
+ end
35
+
36
+ namespace :quality do
37
+ desc "Aggregate RuboCop + Brakeman + bundle-audit + SimpleCov collation across the host"
38
+ task all: :environment do
39
+ require "seams/cli"
40
+ abort("seams:quality:all reported failures.") unless Seams::CLI.quality
41
+ end
42
+ end
43
+
44
+ desc "Pre-push verification: rubocop, host + per-engine specs, brakeman, bundle-audit, orphan-event check"
45
+ task audit: :environment do
46
+ Rake::Task["environment"].invoke
47
+ sh "bundle", "exec", "rubocop", "--parallel"
48
+ sh "bundle", "exec", "rspec", "spec"
49
+ Dir["engines/*"].select { |d| File.directory?(d) }.sort.each do |dir|
50
+ next if Dir.glob("#{dir}/spec/**/*_spec.rb").empty?
51
+
52
+ engine_name = File.basename(dir)
53
+ sh "bundle", "exec", "rspec",
54
+ "--default-path", "engines/#{engine_name}/spec",
55
+ "engines/#{engine_name}/spec"
56
+ end
57
+ sh "bundle", "exec", "brakeman", "--no-pager", "--no-progress", "--quiet"
58
+ sh "bundle", "exec", "bundle-audit", "check", "--update"
59
+
60
+ orphans = Seams::Events::Publisher.orphan_subscriptions
61
+ abort "orphan event subscriptions detected: #{orphans.inspect}" unless orphans.empty?
62
+
63
+ puts "All audit checks passed."
64
+ end
65
+ end
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Seams configuration. Both adapters can be replaced with custom
4
+ # implementations — see https://github.com/Davidslv/seams for details.
5
+ Seams.configure do |config|
6
+ config.event_bus_adapter = "Seams::Events::Adapters::ActiveSupport"
7
+ config.observability_adapter = "Seams::Observability::Adapters::RailsLogger"
8
+ config.host_app_name = (Rails.application.class.module_parent_name rescue nil)
9
+ end
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Load every engine under engines/ as if it were a Gemfile entry. Must
4
+ # run before Rails.application.initialize! so each engine's Railtie
5
+ # registers its initializers + paths (db/migrate, app/*) with the host.
6
+ # Required from config/application.rb after Bundler.require.
7
+ Dir[File.expand_path("../engines/*", __dir__)].sort.each do |engine_path|
8
+ next unless File.directory?(engine_path)
9
+
10
+ $LOAD_PATH.unshift File.join(engine_path, "lib")
11
+
12
+ engine_name = File.basename(engine_path)
13
+ main = File.join(engine_path, "lib", "#{engine_name}.rb")
14
+ require engine_name if File.exist?(main)
15
+ end
@@ -0,0 +1,395 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "fileutils"
4
+ require "rails/generators"
5
+ require "seams"
6
+ require "generators/seams/engine/engine_generator"
7
+ require "seams/generators/host_injector"
8
+ require "seams/generators/eject_aware"
9
+ require "seams/generators/dummy_app_writer"
10
+
11
+ module Seams
12
+ module Generators
13
+ # Generates a canonical Notifications engine on top of the generic
14
+ # engine scaffold.
15
+ #
16
+ # Notifications uses STI: a single +Notifications::Notification+
17
+ # base with three concrete subclasses under +Strategies+ — InApp,
18
+ # Email, Sms — each implementing its own +#dispatch!+. The
19
+ # schedule lives in a jsonb column populated by ice_cube;
20
+ # +next_delivery_at+ is the indexed cache the recurring sweeper
21
+ # reads from.
22
+ #
23
+ # Run with: bin/rails generate seams:notifications
24
+ # rubocop:disable Metrics/ClassLength
25
+ class NotificationsGenerator < Rails::Generators::Base
26
+ include Seams::Generators::HostInjector
27
+ include Seams::Generators::EjectAware
28
+
29
+ source_root File.expand_path("templates", __dir__)
30
+
31
+ ENGINE_NAME = "notifications"
32
+ DEFAULT_CHANNELS = %w[in_app email sms].freeze
33
+ BILLING_TEMPLATES = %w[
34
+ subscription_started
35
+ subscription_updated
36
+ subscription_canceled
37
+ invoice_paid
38
+ invoice_failed
39
+ lifetime_granted
40
+ lifetime_purchased
41
+ ].freeze
42
+
43
+ class_option :channels, type: :string, default: "all",
44
+ desc: "Comma-separated channels to enable: in_app,email,sms (or 'all')"
45
+
46
+ def create_base_engine
47
+ EngineGenerator.start([ENGINE_NAME], destination_root: destination_root)
48
+ end
49
+
50
+ def overwrite_engine_entry_point
51
+ # engine.rb stays framework-managed.
52
+ template "lib/engine.rb.tt", engine_path("lib/notifications/engine.rb"), force: true
53
+ end
54
+
55
+ def overwrite_routes
56
+ # Routes ship with an explicit Engine.routes.draw block — the
57
+ # generic engine scaffold leaves it empty, but notifications
58
+ # needs the canonical /notifications + /preferences surface +
59
+ # the Wave 10 `notifications.routes.after_preferences`
60
+ # insertion-point marker so follow-up generators can splice
61
+ # admin-side digest / opt-out routes.
62
+ template_unless_ejected "config/routes.rb.tt", engine_path("config/routes.rb"), force: true
63
+ end
64
+
65
+ def create_configuration
66
+ template_unless_ejected "lib/configuration.rb.tt",
67
+ engine_path("lib/notifications/configuration.rb")
68
+ template_unless_ejected "lib/type_registry.rb.tt",
69
+ engine_path("lib/notifications/type_registry.rb")
70
+ # lib/notifications.rb stays framework-managed (root require file).
71
+ template "lib/notifications.rb.tt", engine_path("lib/notifications.rb"), force: true
72
+ end
73
+
74
+ def create_adapters
75
+ template_unless_ejected "lib/adapters/abstract.rb.tt",
76
+ engine_path("lib/notifications/adapters/abstract.rb")
77
+ template_unless_ejected "lib/adapters/action_mailer.rb.tt",
78
+ engine_path("lib/notifications/adapters/action_mailer.rb")
79
+ # SMS adapter only ships when the sms channel is enabled.
80
+ return unless channels.include?("sms")
81
+
82
+ template_unless_ejected "lib/adapters/null_sms.rb.tt",
83
+ engine_path("lib/notifications/adapters/null_sms.rb")
84
+ end
85
+
86
+ def create_concern
87
+ template_unless_ejected "lib/concerns/notifiable.rb.tt",
88
+ engine_path("lib/notifications/concerns/notifiable.rb")
89
+ end
90
+
91
+ def create_models
92
+ template_unless_ejected "app/models/application_record.rb.tt",
93
+ engine_path("app/models/notifications/application_record.rb")
94
+ template_unless_ejected "app/models/notification.rb.tt",
95
+ engine_path("app/models/notifications/notification.rb")
96
+ template_unless_ejected "app/models/notification_preference.rb.tt",
97
+ engine_path("app/models/notifications/notification_preference.rb")
98
+ template_unless_ejected "app/models/delivery.rb.tt",
99
+ engine_path("app/models/notifications/delivery.rb")
100
+ create_strategy_models
101
+ end
102
+
103
+ def create_strategy_models
104
+ # STI strategy subclasses ship per --channels selection.
105
+ # STRATEGY_CLASSES in the Notifiable concern is conditionally
106
+ # rendered (in_app/email/sms) to match.
107
+ channels.each do |channel|
108
+ template_unless_ejected "app/models/strategies/#{channel}.rb.tt",
109
+ engine_path("app/models/notifications/strategies/#{channel}.rb")
110
+ end
111
+ end
112
+
113
+ def create_jobs
114
+ template_unless_ejected "app/jobs/application_job.rb.tt",
115
+ engine_path("app/jobs/notifications/application_job.rb")
116
+ template_unless_ejected "app/jobs/send_due_notifications_job.rb.tt",
117
+ engine_path("app/jobs/notifications/send_due_notifications_job.rb")
118
+ template_unless_ejected "app/jobs/send_notification_job.rb.tt",
119
+ engine_path("app/jobs/notifications/send_notification_job.rb")
120
+ template_unless_ejected "app/jobs/create_notification_job.rb.tt",
121
+ engine_path("app/jobs/notifications/create_notification_job.rb")
122
+ end
123
+
124
+ def create_subscriber
125
+ template_unless_ejected "app/subscribers/auth_subscriber.rb.tt",
126
+ engine_path("app/subscribers/notifications/auth_subscriber.rb")
127
+ template_unless_ejected "app/subscribers/billing_subscriber.rb.tt",
128
+ engine_path("app/subscribers/notifications/billing_subscriber.rb")
129
+ end
130
+
131
+ def create_controllers
132
+ template_unless_ejected "app/controllers/notifications_controller.rb.tt",
133
+ engine_path("app/controllers/notifications/notifications_controller.rb")
134
+ template_unless_ejected "app/controllers/preferences_controller.rb.tt",
135
+ engine_path("app/controllers/notifications/preferences_controller.rb")
136
+ end
137
+
138
+ def create_views
139
+ template_unless_ejected "app/views/notifications/_bell.html.erb.tt",
140
+ engine_path("app/views/notifications/notifications/_bell.html.erb")
141
+ template_unless_ejected "app/views/notifications/index.html.erb.tt",
142
+ engine_path("app/views/notifications/notifications/index.html.erb")
143
+ end
144
+
145
+ def create_channel_and_stimulus
146
+ template_unless_ejected "app/channels/notification_channel.rb.tt",
147
+ engine_path("app/channels/notifications/notification_channel.rb")
148
+ template_unless_ejected "app/javascript/controllers/notification_bell_controller.js.tt",
149
+ engine_path("app/javascript/notifications/controllers/notification_bell_controller.js")
150
+ end
151
+
152
+ def create_mailer
153
+ template_unless_ejected "app/mailers/application_mailer.rb.tt",
154
+ engine_path("app/mailers/notifications/application_mailer.rb")
155
+ template_unless_ejected "app/mailers/notification_mailer.rb.tt",
156
+ engine_path("app/mailers/notifications/notification_mailer.rb")
157
+ end
158
+
159
+ def create_default_templates
160
+ # Each notification template ships in two formats — .text.erb
161
+ # for SMS / plain-text email + .html.erb for HTML email and
162
+ # in-app rendering. Hosts override either or both by dropping
163
+ # files at app/views/notifications/templates/<name>.<format>.erb.
164
+ %i[text html].each do |format|
165
+ template_unless_ejected "app/views/templates/default.#{format}.erb.tt",
166
+ engine_path("app/views/notifications/templates/default.#{format}.erb")
167
+ template_unless_ejected "app/views/templates/welcome.#{format}.erb.tt",
168
+ engine_path("app/views/notifications/templates/welcome.#{format}.erb")
169
+ BILLING_TEMPLATES.each do |name|
170
+ template_unless_ejected "app/views/templates/billing/#{name}.#{format}.erb.tt",
171
+ engine_path("app/views/notifications/templates/billing/#{name}.#{format}.erb")
172
+ end
173
+ end
174
+
175
+ # Mailer layout — wraps every notification email so hosts get
176
+ # consistent header/footer chrome without per-template repetition.
177
+ template_unless_ejected "app/views/layouts/notifications/mailer.html.erb.tt",
178
+ engine_path("app/views/layouts/notifications/mailer.html.erb")
179
+ template_unless_ejected "app/views/layouts/notifications/mailer.text.erb.tt",
180
+ engine_path("app/views/layouts/notifications/mailer.text.erb")
181
+ end
182
+
183
+ def create_migrations
184
+ template "db/migrate/create_notifications.rb.tt",
185
+ engine_path("db/migrate/#{timestamp(0)}_create_notifications.rb")
186
+ template "db/migrate/create_notification_preferences.rb.tt",
187
+ engine_path("db/migrate/#{timestamp(1)}_create_notification_preferences.rb")
188
+ template "db/migrate/create_notification_deliveries.rb.tt",
189
+ engine_path("db/migrate/#{timestamp(2)}_create_notification_deliveries.rb")
190
+ end
191
+
192
+ def create_specs
193
+ # Phase 2B finish — coverage for the engine's three core models.
194
+ template_unless_ejected "spec/factories/notifications.rb.tt",
195
+ engine_path("spec/factories/notifications.rb")
196
+ template_unless_ejected "spec/models/notification_spec.rb.tt",
197
+ engine_path("spec/models/notifications/notification_spec.rb")
198
+ template_unless_ejected "spec/models/delivery_spec.rb.tt",
199
+ engine_path("spec/models/notifications/delivery_spec.rb")
200
+ template_unless_ejected "spec/models/notification_preference_spec.rb.tt",
201
+ engine_path("spec/models/notifications/notification_preference_spec.rb")
202
+ end
203
+
204
+ def create_dummy_app
205
+ Seams::Generators::DummyAppWriter.write!(
206
+ engine_path: File.join(destination_root, "engines", ENGINE_NAME),
207
+ engine_module: "Notifications",
208
+ mount_at: "/notifications",
209
+ schema: dummy_schema,
210
+ host_user: dummy_host_user,
211
+ host_user_path: "app/models/auth/identity.rb"
212
+ )
213
+ # Wire the runtime spec templates into the generator output —
214
+ # they were orphaned in templates/ pre-Wave-12 (the integration
215
+ # test silently skipped them).
216
+ template "spec/runtime/boot_spec.rb.tt",
217
+ engine_path("spec/runtime/notifications_boot_spec.rb")
218
+ template "spec/runtime/schedule_round_trip_spec.rb.tt",
219
+ engine_path("spec/runtime/notifications_schedule_round_trip_spec.rb")
220
+ # The BillingSubscriber emits a deliberately-loud
221
+ # `notifications.billing_subscriber.skip` warn line when a
222
+ # billing event arrives without an `account_id` — that's the
223
+ # only signal a host operator gets that notifications are
224
+ # silently failing for that tenant. Spec it.
225
+ template "spec/runtime/billing_subscriber_skip_spec.rb.tt",
226
+ engine_path("spec/runtime/notifications_billing_subscriber_skip_spec.rb")
227
+ # Phase 2B (3/3) — bell + ActionCable broadcast verification.
228
+ return unless channels.include?("in_app")
229
+
230
+ template "spec/runtime/bell_broadcast_spec.rb.tt",
231
+ engine_path("spec/runtime/notifications_bell_broadcast_spec.rb")
232
+ end
233
+
234
+ def overwrite_readme
235
+ template "README.md.tt", engine_path("README.md"), force: true
236
+ end
237
+
238
+ def update_exposed_concerns
239
+ rubocop_path = engine_path(".rubocop.yml")
240
+ return unless File.exist?(rubocop_path)
241
+
242
+ contents = File.read(rubocop_path)
243
+ replacement = " ExposedConcerns:\n - Notifications::Notifiable"
244
+ contents.sub!(/^ ExposedConcerns: \[\]$/, replacement)
245
+ File.write(rubocop_path, contents)
246
+ end
247
+
248
+ def create_initializer
249
+ # Host-side initializer — documents the configure-adapters
250
+ # entry point AND the optional Notifiable concern include
251
+ # patterns. Wave 9 dropped the auto-include into the host
252
+ # User; hosts now opt in by uncommenting one of the patterns
253
+ # below (or leave the concern uninvoked — the polymorphic
254
+ # owner column doesn't require it).
255
+ template "config/initializers/notifications.rb.tt",
256
+ File.join(destination_root, "config/initializers/notifications.rb")
257
+ end
258
+
259
+ def wire_into_host
260
+ host_inject_gem("ice_cube", ">= 0.16")
261
+ # factory_bot_rails powers the engine's spec/factories/*. Lives
262
+ # in the host's test group only.
263
+ host_inject_gem("factory_bot_rails", "~> 6.4", group: :test)
264
+ host_inject_mount(engine_class: "Notifications::Engine", at: "/notifications")
265
+ # Wave 9: no auto-include into a host User. The canonical demo
266
+ # has no host User after Wave 9 (the "human" is Auth::Identity).
267
+ # Hosts wire `Notifications::Notifiable` themselves via the
268
+ # initializer template — see config/initializers/notifications.rb.
269
+ end
270
+
271
+ def report_summary
272
+ say ""
273
+ say " Notifications engine generated at engines/notifications/", :green
274
+ say ""
275
+ say " Next steps:", :yellow
276
+ say " 1. bundle install (picks up ice_cube)"
277
+ say " 2. bin/rails db:migrate"
278
+ say " 3. Schedule the sweeper. With Solid Queue, add to config/recurring.yml:"
279
+ say " notifications_dispatcher:"
280
+ say " class: Notifications::SendDueNotificationsJob"
281
+ say " schedule: every minute"
282
+ say " 4. Configure adapters in config/initializers/notifications.rb"
283
+ say " 5. (optional) Include Notifications::Notifiable on Auth::Identity"
284
+ say " via the same initializer — see the file's comments."
285
+ say ""
286
+ say " Subscribed to: identity.signed_up.auth (creates an InApp + Email Notification)"
287
+ say ""
288
+ end
289
+
290
+ private
291
+
292
+ # Resolved list of channels the host opted into via --channels.
293
+ # "all" (or empty / unrecognised) → all three. Memoised per
294
+ # generator run so conditional template branches stay consistent.
295
+ def channels
296
+ @channels ||= begin
297
+ raw = options[:channels].to_s.downcase.strip
298
+ if raw.empty? || raw == "all"
299
+ DEFAULT_CHANNELS.dup
300
+ else
301
+ requested = raw.split(",").map(&:strip).reject(&:empty?)
302
+ allowed = requested & DEFAULT_CHANNELS
303
+ allowed.empty? ? DEFAULT_CHANNELS.dup : allowed
304
+ end
305
+ end
306
+ end
307
+
308
+ def engine_path(relative)
309
+ File.join(destination_root, "engines", ENGINE_NAME, relative)
310
+ end
311
+
312
+ # Add 100+offset to the packed timestamp so this engine's
313
+ # migrations don't collide with another engine generated in the
314
+ # same second.
315
+ def timestamp(offset = 0)
316
+ base = Time.now.utc.strftime("%Y%m%d%H%M%S").to_i
317
+ (base + 100 + offset).to_s
318
+ end
319
+
320
+ def dummy_schema
321
+ # Wave 9: the dummy ships an `auth_identities` table (not
322
+ # `users`) so spec fixtures can construct a realistic owner
323
+ # for polymorphic Notifications. The dummy doesn't load the
324
+ # auth gem itself — a slim Auth::Identity stub model lives in
325
+ # spec/dummy/app/models/auth/identity.rb (see dummy_host_user
326
+ # below) and includes Notifiable so existing helpers
327
+ # (`unread_in_app_notifications`, etc.) keep working.
328
+ # The schema mirrors the auth + accounts engine shape (text
329
+ # email, password_digest, staff flag + partial index) so
330
+ # cross-engine fixture sharing doesn't drift.
331
+ <<~SCHEMA
332
+ create_table :auth_identities do |t|
333
+ t.text :email, null: false
334
+ t.string :password_digest, null: false, default: ""
335
+ t.boolean :staff, null: false, default: false
336
+ t.timestamps
337
+ end
338
+ add_index :auth_identities, :email, unique: true
339
+ add_index :auth_identities, :staff, where: "staff = true"
340
+
341
+ create_table :notifications do |t|
342
+ t.string :type, null: false
343
+ # Polymorphic owner stored as strings so the column holds
344
+ # bigint Identity IDs and UUID Account IDs simultaneously.
345
+ t.string :owner_type, null: false
346
+ t.string :owner_id, null: false
347
+ t.string :recipient
348
+ t.string :template, null: false
349
+ t.jsonb :schedule_data
350
+ t.datetime :next_delivery_at
351
+ t.datetime :read_at
352
+ t.timestamps
353
+ end
354
+ add_index :notifications, %i[owner_type owner_id]
355
+ add_index :notifications, :next_delivery_at
356
+
357
+ create_table :notification_preferences do |t|
358
+ t.bigint :identity_id, null: false
359
+ t.string :channel, null: false
360
+ t.string :notification_type
361
+ t.boolean :enabled, null: false, default: true
362
+ t.timestamps
363
+ end
364
+ add_index :notification_preferences, %i[identity_id channel notification_type], unique: true,
365
+ name: "index_notification_prefs_unique"
366
+
367
+ create_table :notification_deliveries do |t|
368
+ t.references :notification, null: false, foreign_key: true, index: true
369
+ t.datetime :sent_at, null: false
370
+ t.timestamps
371
+ end
372
+ SCHEMA
373
+ end
374
+
375
+ # Slim Auth::Identity stub for the dummy app. Stands in for the
376
+ # real Auth::Identity (which lives in the auth engine, not loaded
377
+ # by the dummy) so notifications specs can build a polymorphic
378
+ # owner. Includes Notifiable so the bell + #notify helpers
379
+ # exercise the same code path the canonical seams host uses.
380
+ def dummy_host_user
381
+ <<~RB
382
+ # frozen_string_literal: true
383
+
384
+ module Auth
385
+ class Identity < ApplicationRecord
386
+ self.table_name = "auth_identities"
387
+ include Notifications::Notifiable
388
+ end
389
+ end
390
+ RB
391
+ end
392
+ end
393
+ # rubocop:enable Metrics/ClassLength
394
+ end
395
+ end