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,424 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "fileutils"
4
+
5
+ module Seams
6
+ module Generators
7
+ # Writes a slim spec/dummy/ Rails app inside a generated engine so
8
+ # the engine's specs can boot Rails and run against a real
9
+ # ActiveRecord database without requiring the host application.
10
+ #
11
+ # The boilerplate (application.rb, boot.rb, environment.rb,
12
+ # database.yml, environments/test.rb, application_record.rb,
13
+ # log/.gitkeep, spec_helper.rb, rails_helper.rb) is emitted by
14
+ # this helper. Engines supply the bits that vary: the schema, the
15
+ # optional host User model, and the routes block.
16
+ #
17
+ # Seams::Generators::DummyAppWriter.write!(
18
+ # engine_path: "engines/auth",
19
+ # engine_module: "Auth",
20
+ # mount_at: "/auth",
21
+ # schema: "<schema body>",
22
+ # host_user: "<class User body>", # optional
23
+ # host_user_path: "app/models/user.rb", # optional, defaults to app/models/user.rb
24
+ # )
25
+ #
26
+ # `host_user_path` lets engines whose dummy "user" model lives at a
27
+ # different autoload path (e.g. `app/models/auth/identity.rb`)
28
+ # write the file where Zeitwerk will find it.
29
+ module DummyAppWriter
30
+ module_function
31
+
32
+ def write!(engine_path:, engine_module:, schema:, mount_at: nil, host_user: nil,
33
+ host_user_path: "app/models/user.rb")
34
+ ensure_directories(engine_path)
35
+ write_dummy_config(engine_path, engine_module, mount_at)
36
+ write_dummy_app(engine_path, host_user, host_user_path)
37
+ write_dummy_db(engine_path, schema)
38
+ write_dummy_meta(engine_path)
39
+ write_spec_helpers(engine_path)
40
+ end
41
+
42
+ def ensure_directories(engine_path)
43
+ %w[
44
+ spec/dummy/config/environments
45
+ spec/dummy/config/initializers
46
+ spec/dummy/db
47
+ spec/dummy/app/models
48
+ spec/dummy/app/controllers
49
+ spec/dummy/app/mailers
50
+ spec/dummy/app/jobs
51
+ spec/dummy/log
52
+ spec/dummy/tmp
53
+ spec/runtime
54
+ ].each { |dir| FileUtils.mkdir_p(File.join(engine_path, dir)) }
55
+ end
56
+
57
+ def write_dummy_config(engine_path, engine_module, mount_at)
58
+ write(File.join(engine_path, "spec/dummy/config/boot.rb"), boot_rb)
59
+ write(File.join(engine_path, "spec/dummy/config/application.rb"), application_rb(engine_module))
60
+ write(File.join(engine_path, "spec/dummy/config/environment.rb"), environment_rb)
61
+ write(File.join(engine_path, "spec/dummy/config/database.yml"), database_yml(engine_path))
62
+ write(File.join(engine_path, "spec/dummy/config/environments/test.rb"), test_environment_rb)
63
+ write(File.join(engine_path, "spec/dummy/config/initializers/secret_key.rb"), secret_key_rb)
64
+ write(File.join(engine_path, "spec/dummy/config/routes.rb"), routes_rb(engine_module, mount_at))
65
+ end
66
+
67
+ def write_dummy_app(engine_path, host_user, host_user_path)
68
+ write(File.join(engine_path, "spec/dummy/app/models/application_record.rb"), application_record_rb)
69
+ write(File.join(engine_path, "spec/dummy/app/controllers/application_controller.rb"), application_controller_rb)
70
+ write(File.join(engine_path, "spec/dummy/app/mailers/application_mailer.rb"), application_mailer_rb)
71
+ write(File.join(engine_path, "spec/dummy/app/jobs/application_job.rb"), application_job_rb)
72
+ return unless host_user
73
+
74
+ full_user_path = File.join(engine_path, "spec/dummy", host_user_path)
75
+ FileUtils.mkdir_p(File.dirname(full_user_path))
76
+ write(full_user_path, host_user)
77
+ end
78
+
79
+ def write_dummy_db(engine_path, schema)
80
+ write(File.join(engine_path, "spec/dummy/db/schema.rb"), schema_rb(schema))
81
+ end
82
+
83
+ def write_dummy_meta(engine_path)
84
+ write(File.join(engine_path, "spec/dummy/log/.keep"), "")
85
+ write(File.join(engine_path, "spec/dummy/tmp/.keep"), "")
86
+ write(File.join(engine_path, "spec/dummy/Rakefile"), rakefile_rb)
87
+ write(File.join(engine_path, "spec/dummy/config.ru"), config_ru)
88
+ end
89
+
90
+ def write_spec_helpers(engine_path)
91
+ write(File.join(engine_path, "spec/spec_helper.rb"), spec_helper_rb(engine_path))
92
+ write(File.join(engine_path, "spec/rails_helper.rb"), rails_helper_rb)
93
+ end
94
+
95
+ def write(path, content)
96
+ File.write(path, content)
97
+ end
98
+
99
+ def boot_rb
100
+ <<~RB
101
+ # frozen_string_literal: true
102
+
103
+ ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../../../Gemfile", __dir__)
104
+ require "bundler/setup" if File.exist?(ENV["BUNDLE_GEMFILE"])
105
+ RB
106
+ end
107
+
108
+ def application_rb(engine_module)
109
+ <<~RB
110
+ # frozen_string_literal: true
111
+
112
+ require_relative "boot"
113
+
114
+ require "rails/all"
115
+
116
+ Bundler.require(*Rails.groups)
117
+
118
+ # The engine isn't a published gem; it lives at engines/<name>/.
119
+ # Put its lib/ on the load path before requiring its root file.
120
+ $LOAD_PATH.unshift File.expand_path("../../../lib", __dir__)
121
+ require "#{engine_module.downcase}"
122
+
123
+ module Dummy
124
+ class Application < Rails::Application
125
+ # Pin root to the dummy app so Rails doesn't walk up
126
+ # and pick up the host application's Rakefile/config.ru.
127
+ config.root = File.expand_path("..", __dir__)
128
+
129
+ config.load_defaults Rails::VERSION::STRING.to_f
130
+ config.eager_load = false
131
+ config.active_support.deprecation = :stderr
132
+ config.action_controller.include_all_helpers = false
133
+ end
134
+ end
135
+ RB
136
+ end
137
+
138
+ def environment_rb
139
+ <<~RB
140
+ # frozen_string_literal: true
141
+
142
+ require_relative "application"
143
+ Rails.application.initialize!
144
+ RB
145
+ end
146
+
147
+ def database_yml(engine_path)
148
+ # Postgres-only — engine schemas use jsonb columns. Per-engine
149
+ # database name keeps parallel test runs from clobbering each
150
+ # other. CI sets PG* env vars; locally they default to the
151
+ # current user with no password (Homebrew Postgres default).
152
+ db_name = "#{File.basename(engine_path)}_dummy_test"
153
+ <<~YML
154
+ test:
155
+ adapter: postgresql
156
+ database: #{db_name}
157
+ host: <%= ENV.fetch("PGHOST", "localhost") %>
158
+ port: <%= ENV.fetch("PGPORT", "5432") %>
159
+ username: <%= ENV.fetch("PGUSER", ENV["USER"]) %>
160
+ password: <%= ENV.fetch("PGPASSWORD", "") %>
161
+ pool: 5
162
+ encoding: unicode
163
+ YML
164
+ end
165
+
166
+ def test_environment_rb
167
+ <<~RB
168
+ # frozen_string_literal: true
169
+
170
+ Rails.application.configure do
171
+ config.cache_classes = true
172
+ config.eager_load = false
173
+ config.public_file_server.enabled = true
174
+ config.consider_all_requests_local = true
175
+ config.action_controller.perform_caching = false
176
+ config.action_dispatch.show_exceptions = :rescuable
177
+ config.action_controller.allow_forgery_protection = false
178
+ config.active_support.deprecation = :stderr
179
+
180
+ # Throwaway keys for the dummy app so models that declare
181
+ # `encrypts` can round-trip in specs. The dummy DB is wiped
182
+ # every run, so deterministic strings are safe here.
183
+ # Hosts use `bin/rails db:encryption:init` + Rails credentials.
184
+ config.active_record.encryption.primary_key = "dummy_primary_key_for_tests_only"
185
+ config.active_record.encryption.deterministic_key = "dummy_deterministic_key_for_tests_only"
186
+ config.active_record.encryption.key_derivation_salt = "dummy_key_derivation_salt_for_tests_only"
187
+ config.active_record.encryption.support_unencrypted_data = true
188
+
189
+ # Mailer specs render views that call URL helpers (e.g.
190
+ # `edit_password_reset_url`). Without a host they raise
191
+ # "Missing host to link to!". `test.host` is the Rails
192
+ # test convention.
193
+ config.action_mailer.delivery_method = :test
194
+ config.action_mailer.default_url_options = { host: "test.host" }
195
+ end
196
+ RB
197
+ end
198
+
199
+ def secret_key_rb
200
+ <<~RB
201
+ # frozen_string_literal: true
202
+
203
+ Rails.application.config.secret_key_base = "test_secret_key_base_for_dummy_app"
204
+ RB
205
+ end
206
+
207
+ def routes_rb(engine_module, mount_at)
208
+ body = mount_at ? %( mount #{engine_module}::Engine, at: "#{mount_at}") : ""
209
+ <<~RB
210
+ # frozen_string_literal: true
211
+
212
+ Rails.application.routes.draw do
213
+ #{body}
214
+ end
215
+ RB
216
+ end
217
+
218
+ def schema_rb(schema_body)
219
+ # Match Rails::VERSION at write-time so schema-format defaults
220
+ # match the host's Rails. A 7.1 schema declared on Rails 8.x
221
+ # still loads, but column-default semantics drift. defined?
222
+ # check guards a Rails-loaded-but-VERSION-not-yet-required boot
223
+ # window the generator specs hit.
224
+ rails_version =
225
+ if defined?(Rails::VERSION::STRING)
226
+ Rails::VERSION::STRING.split(".")[0, 2].join(".")
227
+ else
228
+ "8.1"
229
+ end
230
+ <<~RB
231
+ # frozen_string_literal: true
232
+
233
+ ActiveRecord::Schema[#{rails_version}].define(version: 0) do
234
+ #{schema_body.lines.map { |l| " #{l}" }.join.rstrip}
235
+ end
236
+ RB
237
+ end
238
+
239
+ def application_record_rb
240
+ <<~RB
241
+ # frozen_string_literal: true
242
+
243
+ class ApplicationRecord < ActiveRecord::Base
244
+ self.abstract_class = true
245
+ end
246
+ RB
247
+ end
248
+
249
+ def application_controller_rb
250
+ # Minimal host ApplicationController so engine controllers that
251
+ # inherit from ::ApplicationController (the seams:engine generator
252
+ # default) can boot inside a request spec. Real hosts will have
253
+ # their own; this is dummy-only.
254
+ <<~RB
255
+ # frozen_string_literal: true
256
+
257
+ class ApplicationController < ActionController::Base
258
+ end
259
+ RB
260
+ end
261
+
262
+ def application_mailer_rb
263
+ # Minimal host ApplicationMailer so engine mailers that inherit
264
+ # from ::ApplicationMailer (auth's PasswordsMailer,
265
+ # notifications' ApplicationMailer) can be autoloaded inside
266
+ # the dummy. Real hosts will have their own; this is
267
+ # dummy-only.
268
+ <<~RB
269
+ # frozen_string_literal: true
270
+
271
+ class ApplicationMailer < ActionMailer::Base
272
+ default from: "from@example.com"
273
+ end
274
+ RB
275
+ end
276
+
277
+ def application_job_rb
278
+ # Minimal host ApplicationJob so engine jobs that inherit from
279
+ # ::ApplicationJob (notifications' ApplicationJob, billing's
280
+ # ApplicationJob) can be autoloaded inside the dummy. Without
281
+ # this, the engine's spec suite raises NameError the moment a
282
+ # spec touches CreateNotificationJob / StartSubscriptionJob /
283
+ # any other job class.
284
+ <<~RB
285
+ # frozen_string_literal: true
286
+
287
+ class ApplicationJob < ActiveJob::Base
288
+ end
289
+ RB
290
+ end
291
+
292
+ def rakefile_rb
293
+ <<~RB
294
+ # frozen_string_literal: true
295
+
296
+ # Marker file so Rails::Engine.find_root anchors here, not
297
+ # in the parent host application.
298
+ require_relative "config/application"
299
+ Rails.application.load_tasks if defined?(Rails.application)
300
+ RB
301
+ end
302
+
303
+ def config_ru
304
+ <<~RB
305
+ # frozen_string_literal: true
306
+
307
+ require_relative "config/environment"
308
+ run Rails.application
309
+ RB
310
+ end
311
+
312
+ def spec_helper_rb(engine_path)
313
+ engine_name = File.basename(engine_path)
314
+ <<~RB
315
+ # frozen_string_literal: true
316
+
317
+ ENV["RAILS_ENV"] ||= "test"
318
+
319
+ # Rails has to load before the engine's lib/<name>.rb runs,
320
+ # because engine.rb references Rails::Engine. Specs that need
321
+ # ActiveRecord should `require "rails_helper"` instead — that
322
+ # ALSO boots the dummy app, defines the schema, and connects
323
+ # to the test DB.
324
+ require "rails"
325
+ $LOAD_PATH.unshift File.expand_path("../lib", __dir__)
326
+ require "#{engine_name}"
327
+
328
+ RSpec.configure do |config|
329
+ config.expect_with :rspec do |expectations|
330
+ expectations.include_chain_clauses_in_custom_matcher_descriptions = true
331
+ end
332
+
333
+ config.mock_with :rspec do |mocks|
334
+ mocks.verify_partial_doubles = true
335
+ end
336
+
337
+ config.shared_context_metadata_behavior = :apply_to_host_groups
338
+ config.disable_monkey_patching!
339
+ config.order = :random
340
+ Kernel.srand config.seed
341
+ end
342
+ RB
343
+ end
344
+
345
+ def rails_helper_rb
346
+ <<~RB
347
+ # frozen_string_literal: true
348
+
349
+ require_relative "spec_helper"
350
+ ENV["RAILS_ENV"] ||= "test"
351
+
352
+ # Ensure the per-engine Postgres test database exists before
353
+ # the dummy app boots and tries to connect to it. We connect
354
+ # to the maintenance "postgres" database first, CREATE DATABASE
355
+ # if missing, then let the dummy app pick up its own config.
356
+ require "active_record"
357
+ require "yaml"
358
+ require "erb"
359
+ dummy_db_yml = File.expand_path("dummy/config/database.yml", __dir__)
360
+ db_config = YAML.safe_load(ERB.new(File.read(dummy_db_yml)).result, aliases: true)["test"]
361
+ target_db = db_config["database"]
362
+ admin_config = db_config.merge("database" => "postgres")
363
+ ActiveRecord::Base.establish_connection(admin_config)
364
+ unless ActiveRecord::Base.connection.execute(
365
+ "SELECT 1 FROM pg_database WHERE datname = '\#{target_db}'"
366
+ ).any?
367
+ ActiveRecord::Base.connection.execute(%(CREATE DATABASE "\#{target_db}"))
368
+ end
369
+ ActiveRecord::Base.remove_connection
370
+
371
+ require File.expand_path("dummy/config/environment", __dir__)
372
+ abort("Rails is in production mode!") if Rails.env.production?
373
+
374
+ require "rspec/rails"
375
+
376
+ # WebMock is optional — engines that stub outbound HTTP
377
+ # (billing's stub_stripe helpers, auth's OAuth adapter
378
+ # specs) bring in `webmock` via the host Gemfile. If
379
+ # available, require it so specs can call WebMock.stub_request
380
+ # without each one re-requiring it. We disable real HTTP
381
+ # connections to make missing stubs explicit instead of
382
+ # accidentally hitting the network.
383
+ begin
384
+ require "webmock/rspec"
385
+ WebMock.disable_net_connect!(allow_localhost: true)
386
+ rescue LoadError
387
+ # webmock isn't bundled — engines that don't stub HTTP
388
+ # don't need it.
389
+ end
390
+
391
+ # FactoryBot is optional — engines that ship factories add
392
+ # `factory_bot_rails` to the host Gemfile. If it's loaded, wire
393
+ # the syntax methods + auto-discover the engine's
394
+ # spec/factories/*.rb (default search paths look in the host's
395
+ # spec/factories which doesn't exist when running engine specs
396
+ # from the host root).
397
+ if defined?(FactoryBot)
398
+ require "factory_bot_rails"
399
+ engine_factories = File.expand_path("factories", __dir__)
400
+ FactoryBot.definition_file_paths = [engine_factories]
401
+ FactoryBot.find_definitions if FactoryBot.factories.none?
402
+ end
403
+
404
+ ActiveRecord::Schema.verbose = false
405
+ # Drop and reload the schema for a clean slate every run.
406
+ ActiveRecord::Base.connection.tables.each do |t|
407
+ ActiveRecord::Base.connection.drop_table(t, force: :cascade)
408
+ end
409
+ load File.expand_path("dummy/db/schema.rb", __dir__)
410
+
411
+ RSpec.configure do |config|
412
+ config.use_transactional_fixtures = true
413
+ config.infer_spec_type_from_file_location!
414
+ config.filter_rails_from_backtrace!
415
+
416
+ if defined?(FactoryBot)
417
+ config.include FactoryBot::Syntax::Methods
418
+ end
419
+ end
420
+ RB
421
+ end
422
+ end
423
+ end
424
+ end
@@ -0,0 +1,102 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Seams
4
+ module Generators
5
+ # Mixin that lets a canonical engine generator (auth, accounts,
6
+ # billing, core, notifications, teams) cooperate with the
7
+ # `bin/seams resolve --eject` CLI introduced in Wave 10 Phase 2B.
8
+ #
9
+ # The contract is one-way: ejection happens in the host's working
10
+ # tree (the CLI prepends a `# seams:ejected from <engine>.<path>`
11
+ # header to a file the host wants to own). This module gives the
12
+ # generator the matching skip behaviour — when the destination
13
+ # file already starts with that header, the generator's
14
+ # `template_unless_ejected` helper logs a yellow "skip" line and
15
+ # leaves the file untouched.
16
+ #
17
+ # ## Eject-eligibility rule
18
+ #
19
+ # The CLI and this helper agree on a single eligibility rule:
20
+ #
21
+ # - Eligible: every templated file under `app/` and `lib/` and
22
+ # `config/` (controllers, models, services, jobs, mailers,
23
+ # views, concerns, lib classes, configuration.rb, routes.rb).
24
+ # These are pieces a host might reasonably want to own outright
25
+ # — to override behaviour, change copy, swap a layout, etc.
26
+ # - Eligible: spec files (spec/) and factories. The host owns
27
+ # the regenerated specs the same way the host owns the engine
28
+ # code; ejecting a spec is rare but legal.
29
+ # - INELIGIBLE: migrations under `db/migrate/`. Migrations run
30
+ # exactly once on a host's timetable; ejecting a migration is
31
+ # meaningless (it's already been run) and re-running the
32
+ # generator never overwrites an existing migration anyway —
33
+ # each run produces a new timestamp.
34
+ # - INELIGIBLE: framework-managed engine boot files —
35
+ # `lib/<engine>/engine.rb`, `lib/<engine>/version.rb`, the
36
+ # engine's Gemfile and gemspec. These are the contract between
37
+ # the engine and the host's Rails boot; if the host wants
38
+ # different behaviour they extend via insertion points or a
39
+ # follow-up generator.
40
+ #
41
+ # The CLI enforces this rule at eject time. The
42
+ # `template_unless_ejected` helper is intentionally permissive —
43
+ # if a host has somehow stamped the eject header onto a
44
+ # framework-managed file, we skip rather than overwrite. The
45
+ # error path is loud at eject, not at generation.
46
+ #
47
+ # ## Usage
48
+ #
49
+ # In an engine generator:
50
+ #
51
+ # class AuthGenerator < Rails::Generators::Base
52
+ # include Seams::Generators::EjectAware
53
+ #
54
+ # def create_models
55
+ # template_unless_ejected "app/models/identity.rb.tt",
56
+ # engine_path("app/models/auth/identity.rb")
57
+ # end
58
+ # end
59
+ #
60
+ # `template_unless_ejected` accepts the same positional + keyword
61
+ # arguments as Thor's `template`, including `force: true`, so
62
+ # generators can adopt it incrementally without changing call sites.
63
+ module EjectAware
64
+ EJECT_HEADER_PREFIX = "# seams:ejected from"
65
+
66
+ # Drop-in replacement for Thor's `template`. If the destination
67
+ # exists and starts with the eject header, log a `skip` line and
68
+ # return without writing. Otherwise delegate to `template` —
69
+ # Thor handles `force: true` and conflict resolution as usual.
70
+ def template_unless_ejected(source, *args, **)
71
+ destination = args.first
72
+ if destination && File.exist?(destination) && ejected?(destination)
73
+ say_status :skip, ejected_say_message(destination), :yellow
74
+ return
75
+ end
76
+
77
+ template(source, *args, **)
78
+ end
79
+
80
+ # True when the file at `path` carries the eject header on its
81
+ # first line. Reads only the first 200 bytes — the header sits
82
+ # at the very top and we don't want to slurp megabytes for files
83
+ # under app/views/.
84
+ def ejected?(path)
85
+ return false unless File.exist?(path)
86
+
87
+ File.read(path, 200).to_s.start_with?(EJECT_HEADER_PREFIX)
88
+ end
89
+
90
+ private
91
+
92
+ def ejected_say_message(destination)
93
+ if respond_to?(:destination_root)
94
+ relative = destination.sub(%r{\A#{Regexp.escape(destination_root.to_s)}/?},
95
+ "")
96
+ end
97
+ relative ||= destination
98
+ "#{relative} (ejected — kept host version)"
99
+ end
100
+ end
101
+ end
102
+ end
@@ -0,0 +1,148 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "rails/generators"
4
+ require "seams/generators/splicer"
5
+
6
+ module Seams
7
+ module Generators
8
+ # Base class for follow-up generators — net-new generators that
9
+ # extend an already-installed engine without re-templating the
10
+ # whole engine. Subclasses look like:
11
+ #
12
+ # module Seams
13
+ # module Generators
14
+ # class AddPasskeysGenerator < FollowUpGenerator
15
+ # engine_name "auth"
16
+ #
17
+ # def add_event
18
+ # splice(
19
+ # file: "lib/auth/engine.rb",
20
+ # marker: "auth.engine.events",
21
+ # content: <<~RUBY
22
+ # Seams::EventRegistry.register("identity.passkey_added.auth", emitted_by: "Auth")
23
+ # RUBY
24
+ # )
25
+ # end
26
+ # end
27
+ # end
28
+ # end
29
+ #
30
+ # The base class supplies four primitives that every follow-up
31
+ # generator needs:
32
+ #
33
+ # - +engine_path(relative)+ — resolve a path inside the host's
34
+ # `engines/<this_engine>/` directory.
35
+ # - +splice(file:, marker:, content:, ...)+ — wrap
36
+ # +Splicer.splice_after_marker+ with a concise log line.
37
+ # - +assert_marker_exists!(file:, marker:)+ — fail fast with a
38
+ # clear "this engine wasn't generated" message when a follow-up
39
+ # generator runs against a host that hasn't installed the
40
+ # target engine.
41
+ # - +report_summary+ — template method subclasses override to
42
+ # print a "what just changed" message at the end.
43
+ class FollowUpGenerator < Rails::Generators::Base
44
+ class << self
45
+ # Sets the engine this follow-up generator targets. Used by
46
+ # +engine_path+ to resolve relative paths and by
47
+ # +assert_marker_exists!+ to build the recovery hint.
48
+ def engine_name(name = nil)
49
+ @engine_name = name if name
50
+ @engine_name || raise(ArgumentError,
51
+ "#{self.name} must declare `engine_name \"<engine>\"` " \
52
+ "(e.g. \"auth\") — the base class needs it to resolve " \
53
+ "engines/<engine>/ paths.")
54
+ end
55
+ end
56
+
57
+ # The helpers below are wrapped in `no_tasks { ... }` so Thor
58
+ # treats them as plain instance methods, not generator
59
+ # commands. Without this wrapper, every public method becomes
60
+ # a task and `start` blows up trying to invoke `engine_path`
61
+ # with no arguments — and parent-class tasks invoke BEFORE
62
+ # subclass tasks (Thor's `all_commands` orders parents first),
63
+ # which would run `report_summary` before any of the
64
+ # subclass's actual work.
65
+ # The no_tasks block is naturally long — it groups every helper
66
+ # the FollowUpGenerator surface offers (engine_path, splice,
67
+ # assert_marker_exists!). Splitting them into separate blocks
68
+ # would obscure the "these are the public follow-up generator
69
+ # primitives" grouping. Disable BlockLength for this one block.
70
+ # rubocop:disable Metrics/BlockLength
71
+ no_tasks do
72
+ # Resolve a path inside the host's engines/<this_engine>/.
73
+ # Mirrors the canonical generators' +engine_path+ method but
74
+ # reads the engine name off the class accessor instead of
75
+ # hardcoding it per-generator.
76
+ def engine_path(relative)
77
+ File.join(destination_root, "engines", self.class.engine_name, relative)
78
+ end
79
+
80
+ # Splice `content` after `marker` in `file` (a path relative
81
+ # to the engine root). Logs:
82
+ #
83
+ # splice engines/auth/lib/auth/engine.rb @ auth.engine.events
84
+ #
85
+ # When `before:` is true, splices BEFORE the marker line instead.
86
+ # Returns the underlying +Splicer::Result+ so subclasses can
87
+ # branch on `result.ok?` if they want.
88
+ def splice(file:, marker:, content:, before: false, indent: nil)
89
+ full_path = engine_path(file)
90
+ result =
91
+ if before
92
+ Splicer.splice_before_marker(file_path: full_path, marker: marker, content: content, indent: indent)
93
+ else
94
+ Splicer.splice_after_marker(file_path: full_path, marker: marker, content: content, indent: indent)
95
+ end
96
+
97
+ if result.ok?
98
+ status = result.lines_added.zero? ? "exists" : "splice"
99
+ say " #{status} engines/#{self.class.engine_name}/#{file} @ #{marker}", :green
100
+ else
101
+ say " error #{result.error}", :red
102
+ end
103
+ result
104
+ end
105
+
106
+ # Raise a clear, actionable error if the marker isn't present.
107
+ # The most common cause is the host hasn't generated the target
108
+ # engine yet (or generated it from a pre-Wave-10 version of seams
109
+ # that didn't ship insertion points). Both cases get a single
110
+ # recovery hint: re-run the canonical generator.
111
+ def assert_marker_exists!(file:, marker:)
112
+ full_path = engine_path(file)
113
+ return if Splicer.find_marker(file_path: full_path, marker: marker)
114
+
115
+ engine = self.class.engine_name
116
+ raise Seams::GeneratorError, <<~MSG.chomp
117
+ [seams] cannot run #{self.class.name}: insertion-point marker
118
+ "#{marker}"
119
+ was not found in
120
+ engines/#{engine}/#{file}
121
+
122
+ Either this engine wasn't generated, or it was generated by
123
+ a pre-Wave-10 version of seams that didn't ship insertion
124
+ points. Run:
125
+
126
+ bin/rails generate seams:#{engine}
127
+
128
+ to (re)generate the engine with the current marker set, then
129
+ re-run this follow-up generator.
130
+ MSG
131
+ end
132
+ end
133
+ # rubocop:enable Metrics/BlockLength
134
+
135
+ # Subclass override-point. Subclasses define their own public
136
+ # method named `report_summary` (or any other end-of-run name);
137
+ # Thor invokes it as the last task because public methods on the
138
+ # subclass come after the parent's `no_tasks`-wrapped helpers
139
+ # in declaration order. The base-class default is a no-op,
140
+ # wrapped in `no_tasks` so it doesn't pre-empt subclass output.
141
+ no_tasks do
142
+ def report_summary
143
+ # Override in subclasses.
144
+ end
145
+ end
146
+ end
147
+ end
148
+ end