develoz-rails 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 (172) hide show
  1. checksums.yaml +7 -0
  2. data/.github/workflows/ci.yml +31 -0
  3. data/.github/workflows/release.yml +459 -0
  4. data/.rubocop.yml +52 -0
  5. data/.sisyphus/notepads/develoz-rails-template/learnings.md +855 -0
  6. data/.tool-versions +2 -0
  7. data/CHANGELOG.md +28 -0
  8. data/CONTRIBUTING.md +120 -0
  9. data/README.md +76 -0
  10. data/Rakefile +18 -0
  11. data/config/generators.yml +105 -0
  12. data/docs/guide/installation.md +574 -0
  13. data/exe/develoz +6 -0
  14. data/install +98 -0
  15. data/lib/develoz/canonical_fetcher.rb +56 -0
  16. data/lib/develoz/cli.rb +84 -0
  17. data/lib/develoz/generators/base.rb +134 -0
  18. data/lib/develoz/manifest.rb +44 -0
  19. data/lib/develoz/options.rb +55 -0
  20. data/lib/develoz/version.rb +5 -0
  21. data/lib/develoz/version_resolver.rb +103 -0
  22. data/lib/develoz.rb +15 -0
  23. data/lib/generators/develoz/active_resource/active_resource_generator.rb +25 -0
  24. data/lib/generators/develoz/active_resource/templates/application_resource.rb.tt +7 -0
  25. data/lib/generators/develoz/active_resource/templates/example_resource.rb.tt +5 -0
  26. data/lib/generators/develoz/admin/admin_generator.rb +44 -0
  27. data/lib/generators/develoz/admin/templates/admin_base_controller.rb.tt +7 -0
  28. data/lib/generators/develoz/admin/templates/admin_layout.html.erb.tt +38 -0
  29. data/lib/generators/develoz/admin/templates/dashboard_controller.rb.tt +7 -0
  30. data/lib/generators/develoz/admin/templates/dashboard_index.html.erb.tt +2 -0
  31. data/lib/generators/develoz/admin/templates/routes.rb.tt +3 -0
  32. data/lib/generators/develoz/agents_docs/agents_docs_generator.rb +50 -0
  33. data/lib/generators/develoz/agents_docs/templates/.github/pull_request_template.md.tt +13 -0
  34. data/lib/generators/develoz/agents_docs/templates/AGENTS.md.tt +36 -0
  35. data/lib/generators/develoz/agents_docs/templates/docs/development.md.tt +121 -0
  36. data/lib/generators/develoz/agents_docs/templates/docs/performance.md.tt +46 -0
  37. data/lib/generators/develoz/agents_docs/templates/docs/testing.md.tt +92 -0
  38. data/lib/generators/develoz/agents_docs/templates/spec/cassettes/Example_API/fetches_data_from_an_external_API.yml.tt +23 -0
  39. data/lib/generators/develoz/agents_docs/templates/spec/requests/example_api_spec.rb.tt +14 -0
  40. data/lib/generators/develoz/agents_docs/templates/spec/support/faraday.rb.tt +19 -0
  41. data/lib/generators/develoz/agents_docs/templates/spec/support/vcr.rb.tt +11 -0
  42. data/lib/generators/develoz/api/api_generator.rb +55 -0
  43. data/lib/generators/develoz/api/templates/app/blueprints/example_blueprint.rb.tt +6 -0
  44. data/lib/generators/develoz/api/templates/app/controllers/api/v1/base_controller.rb.tt +96 -0
  45. data/lib/generators/develoz/api/templates/config/initializers/blueprinter.rb.tt +16 -0
  46. data/lib/generators/develoz/api/templates/config/initializers/rswag_api.rb.tt +15 -0
  47. data/lib/generators/develoz/api/templates/config/initializers/rswag_ui.rb.tt +17 -0
  48. data/lib/generators/develoz/api/templates/spec/requests/api/v1/examples_spec.rb.tt +25 -0
  49. data/lib/generators/develoz/auth/auth_generator.rb +82 -0
  50. data/lib/generators/develoz/auth/templates/app/controllers/concerns/authentication.rb.tt +53 -0
  51. data/lib/generators/develoz/auth/templates/app/controllers/passwords_controller.rb.tt +35 -0
  52. data/lib/generators/develoz/auth/templates/app/controllers/sessions_controller.rb.tt +22 -0
  53. data/lib/generators/develoz/auth/templates/app/mailers/passwords_mailer.rb.tt +8 -0
  54. data/lib/generators/develoz/auth/templates/app/models/current.rb.tt +5 -0
  55. data/lib/generators/develoz/auth/templates/app/models/user.rb.tt +7 -0
  56. data/lib/generators/develoz/auth/templates/app/views/passwords/edit.html.erb.tt +9 -0
  57. data/lib/generators/develoz/auth/templates/app/views/passwords_mailer/reset.html.erb.tt +6 -0
  58. data/lib/generators/develoz/auth/templates/app/views/sessions/new.html.erb.tt +11 -0
  59. data/lib/generators/develoz/auth/templates/db/migrate/create_users.rb.tt +13 -0
  60. data/lib/generators/develoz/auth/templates/spec/requests/passwords_spec.rb.tt +60 -0
  61. data/lib/generators/develoz/auth/templates/spec/requests/sessions_spec.rb.tt +37 -0
  62. data/lib/generators/develoz/ci/ci_generator.rb +66 -0
  63. data/lib/generators/develoz/ci/templates/.github/workflows/ci.yml.tt +18 -0
  64. data/lib/generators/develoz/ci/templates/.haml-lint.yml.tt +12 -0
  65. data/lib/generators/develoz/ci/templates/.markdownlint.json.tt +10 -0
  66. data/lib/generators/develoz/ci/templates/.reek.yml.tt +28 -0
  67. data/lib/generators/develoz/ci/templates/.rubocop.yml.tt +22 -0
  68. data/lib/generators/develoz/ci/templates/.stylelintrc.json.tt +4 -0
  69. data/lib/generators/develoz/ci/templates/.yamllint.tt +14 -0
  70. data/lib/generators/develoz/ci/templates/Rakefile.tt +73 -0
  71. data/lib/generators/develoz/ci/templates/bin/ci.tt +6 -0
  72. data/lib/generators/develoz/ci/templates/biome.json.tt +41 -0
  73. data/lib/generators/develoz/ci/templates/config/ci.rb.tt +23 -0
  74. data/lib/generators/develoz/concerns/concerns_generator.rb +48 -0
  75. data/lib/generators/develoz/concerns/templates/app/models/concerns/configurable.rb.tt +120 -0
  76. data/lib/generators/develoz/concerns/templates/app/models/concerns/optimized_finders.rb.tt +93 -0
  77. data/lib/generators/develoz/concerns/templates/app/models/concerns/searchable_concern.rb.tt +31 -0
  78. data/lib/generators/develoz/concerns/templates/app/models/concerns/transitionable.rb.tt +144 -0
  79. data/lib/generators/develoz/concerns/templates/app/models/configuration.rb.tt +104 -0
  80. data/lib/generators/develoz/concerns/templates/db/migrate/add_status_transitions.rb.tt +30 -0
  81. data/lib/generators/develoz/concerns/templates/db/migrate/create_configurations.rb.tt +25 -0
  82. data/lib/generators/develoz/concerns/templates/spec/models/concerns/configurable_spec.rb.tt +172 -0
  83. data/lib/generators/develoz/concerns/templates/spec/models/concerns/optimized_finders_spec.rb.tt +200 -0
  84. data/lib/generators/develoz/concerns/templates/spec/models/concerns/searchable_concern_spec.rb.tt +84 -0
  85. data/lib/generators/develoz/concerns/templates/spec/models/concerns/transitionable_spec.rb.tt +234 -0
  86. data/lib/generators/develoz/concerns/templates/spec/models/configuration_spec.rb.tt +223 -0
  87. data/lib/generators/develoz/database/database_generator.rb +44 -0
  88. data/lib/generators/develoz/database/templates/config/database.yml.tt +53 -0
  89. data/lib/generators/develoz/database/templates/config/initializers/pg_search.rb.tt +5 -0
  90. data/lib/generators/develoz/database/templates/db/migrate/create_pg_extensions.rb.tt +13 -0
  91. data/lib/generators/develoz/db_backup/db_backup_generator.rb +47 -0
  92. data/lib/generators/develoz/db_backup/templates/backup_rake.tt +48 -0
  93. data/lib/generators/develoz/db_backup/templates/bin_db_backup.tt +25 -0
  94. data/lib/generators/develoz/db_backup/templates/compose_service.tt +14 -0
  95. data/lib/generators/develoz/doc_specs/doc_specs_generator.rb +33 -0
  96. data/lib/generators/develoz/doc_specs/templates/bin/generate-docs.tt +392 -0
  97. data/lib/generators/develoz/doc_specs/templates/lib/tasks/docs_check.rake.tt +8 -0
  98. data/lib/generators/develoz/doc_specs/templates/spec/support/doc_screenshot_helper.rb.tt +17 -0
  99. data/lib/generators/develoz/doc_specs/templates/spec/system/example_doc_spec.rb.tt +38 -0
  100. data/lib/generators/develoz/docker/docker_generator.rb +54 -0
  101. data/lib/generators/develoz/docker/templates/bin_dev.tt +5 -0
  102. data/lib/generators/develoz/docker/templates/bin_docker_entrypoint.tt +8 -0
  103. data/lib/generators/develoz/docker/templates/bin_run.tt +152 -0
  104. data/lib/generators/develoz/docker/templates/bin_setup.tt +15 -0
  105. data/lib/generators/develoz/docker/templates/docker-compose.yml.tt +59 -0
  106. data/lib/generators/develoz/docker/templates/dockerfile_dev.tt +24 -0
  107. data/lib/generators/develoz/docs_render/docs_render_generator.rb +46 -0
  108. data/lib/generators/develoz/docs_render/templates/app/assets/stylesheets/documentation.scss.tt +145 -0
  109. data/lib/generators/develoz/docs_render/templates/app/controllers/docs_controller.rb.tt +7 -0
  110. data/lib/generators/develoz/docs_render/templates/app/javascript/docs.js.tt +15 -0
  111. data/lib/generators/develoz/docs_render/templates/app/models/document.rb.tt +189 -0
  112. data/lib/generators/develoz/docs_render/templates/app/views/docs/show.html.erb.tt +7 -0
  113. data/lib/generators/develoz/docs_render/templates/config/initializers/redcarpet_rouge.rb.tt +16 -0
  114. data/lib/generators/develoz/frontend_core/frontend_core_generator.rb +40 -0
  115. data/lib/generators/develoz/frontend_core/templates/.annotaterb.yml.tt +16 -0
  116. data/lib/generators/develoz/frontend_core/templates/config/importmap.rb.tt +12 -0
  117. data/lib/generators/develoz/frontend_core/templates/config/initializers/pagy.rb.tt +10 -0
  118. data/lib/generators/develoz/install/install_generator.rb +51 -0
  119. data/lib/generators/develoz/kamal/kamal_generator.rb +59 -0
  120. data/lib/generators/develoz/kamal/templates/accessories_postgres.yml.tt +14 -0
  121. data/lib/generators/develoz/kamal/templates/deploy.yml.tt +43 -0
  122. data/lib/generators/develoz/kamal/templates/dockerfile_prod.tt +54 -0
  123. data/lib/generators/develoz/kamal/templates/kamal_secrets.tt +22 -0
  124. data/lib/generators/develoz/maintenance/maintenance_generator.rb +29 -0
  125. data/lib/generators/develoz/maintenance/templates/app/tasks/maintenance/example_task.rb.tt +20 -0
  126. data/lib/generators/develoz/maintenance/templates/lib/tasks/maintenance_counters.rake.tt +27 -0
  127. data/lib/generators/develoz/push/push_generator.rb +61 -0
  128. data/lib/generators/develoz/push/templates/app/javascript/pwa/subscription.js.tt +57 -0
  129. data/lib/generators/develoz/push/templates/app/models/push_subscription.rb.tt +14 -0
  130. data/lib/generators/develoz/push/templates/app/services/push_notification_service.rb.tt +36 -0
  131. data/lib/generators/develoz/push/templates/app/views/pwa/sw_push_handlers.js.tt +49 -0
  132. data/lib/generators/develoz/push/templates/constants_env.tt +5 -0
  133. data/lib/generators/develoz/push/templates/db/migrate/create_push_subscriptions.rb.tt +15 -0
  134. data/lib/generators/develoz/pwa/pwa_generator.rb +46 -0
  135. data/lib/generators/develoz/pwa/templates/app/controllers/pwa_controller.rb.tt +17 -0
  136. data/lib/generators/develoz/pwa/templates/app/javascript/pwa/registration.js.tt +25 -0
  137. data/lib/generators/develoz/pwa/templates/app/views/pwa/manifest.json.erb.tt +24 -0
  138. data/lib/generators/develoz/pwa/templates/app/views/pwa/offline.html.erb.tt +51 -0
  139. data/lib/generators/develoz/pwa/templates/app/views/pwa/service-worker.js.tt +109 -0
  140. data/lib/generators/develoz/pwa/templates/routes.rb.tt +3 -0
  141. data/lib/generators/develoz/solid/solid_generator.rb +52 -0
  142. data/lib/generators/develoz/solid/templates/app/jobs/application_job.rb.tt +7 -0
  143. data/lib/generators/develoz/solid/templates/config/cable.yml.tt +10 -0
  144. data/lib/generators/develoz/solid/templates/config/cache.yml.tt +16 -0
  145. data/lib/generators/develoz/solid/templates/config/initializers/mission_control.rb.tt +8 -0
  146. data/lib/generators/develoz/solid/templates/config/initializers/solid.rb.tt +7 -0
  147. data/lib/generators/develoz/solid/templates/config/queue.yml.tt +18 -0
  148. data/lib/generators/develoz/solid/templates/config/recurring.yml.tt +20 -0
  149. data/lib/generators/develoz/strict_loading/strict_loading_generator.rb +17 -0
  150. data/lib/generators/develoz/strict_loading/templates/config/initializers/strict_loading.rb.tt +13 -0
  151. data/lib/generators/develoz/testing/templates/rspec.tt +2 -0
  152. data/lib/generators/develoz/testing/templates/rspec_parallel.tt +2 -0
  153. data/lib/generators/develoz/testing/templates/spec/rails_helper.rb.tt +32 -0
  154. data/lib/generators/develoz/testing/templates/spec/spec_helper.rb.tt +31 -0
  155. data/lib/generators/develoz/testing/testing_generator.rb +39 -0
  156. data/lib/generators/develoz/tooling/templates/constants.rb.tt +17 -0
  157. data/lib/generators/develoz/tooling/templates/env.example.tt +4 -0
  158. data/lib/generators/develoz/tooling/templates/vscode/extensions.json.tt +8 -0
  159. data/lib/generators/develoz/tooling/templates/vscode/settings.json.tt +26 -0
  160. data/lib/generators/develoz/tooling/templates/vscode/tasks.json.tt +26 -0
  161. data/lib/generators/develoz/tooling/tooling_generator.rb +33 -0
  162. data/lib/generators/develoz/ui/templates/bin/setup_develoz_ui.tt +20 -0
  163. data/lib/generators/develoz/ui/ui_generator.rb +57 -0
  164. data/lib/generators/develoz/versioning/templates/app/helpers/application_helper.rb.tt +7 -0
  165. data/lib/generators/develoz/versioning/templates/app/views/shared/_app_version.html.erb.tt +3 -0
  166. data/lib/generators/develoz/versioning/templates/spec/helpers/application_helper_spec.rb.tt +11 -0
  167. data/lib/generators/develoz/versioning/versioning_generator.rb +48 -0
  168. data/lib/tasks/canonical.rake +27 -0
  169. data/templates/.keep +0 -0
  170. data/templates/CANONICAL_SOURCES.md +93 -0
  171. data/templates/pull_request_template.md.tt +13 -0
  172. metadata +383 -0
@@ -0,0 +1,51 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="utf-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1">
6
+ <title>You're offline</title>
7
+ <style>
8
+ body {
9
+ font-family: system-ui, -apple-system, sans-serif;
10
+ display: flex;
11
+ align-items: center;
12
+ justify-content: center;
13
+ min-height: 100vh;
14
+ margin: 0;
15
+ background: #f5f5f5;
16
+ color: #333;
17
+ }
18
+ .container {
19
+ text-align: center;
20
+ padding: 2rem;
21
+ }
22
+ h1 {
23
+ font-size: 1.5rem;
24
+ margin-bottom: 0.5rem;
25
+ }
26
+ p {
27
+ color: #666;
28
+ margin-bottom: 1.5rem;
29
+ }
30
+ button {
31
+ padding: 0.75rem 1.5rem;
32
+ font-size: 1rem;
33
+ background: #333;
34
+ color: #fff;
35
+ border: none;
36
+ border-radius: 0.375rem;
37
+ cursor: pointer;
38
+ }
39
+ button:hover {
40
+ background: #555;
41
+ }
42
+ </style>
43
+ </head>
44
+ <body>
45
+ <div class="container">
46
+ <h1>You're offline</h1>
47
+ <p>You appear to be offline. Please check your connection and try again.</p>
48
+ <button onclick="window.location.reload()">Retry</button>
49
+ </div>
50
+ </body>
51
+ </html>
@@ -0,0 +1,109 @@
1
+ // Develoz PWA service worker - offline caching + offline fallback page
2
+ // Push notification handlers are added by the develoz:push generator (T27)
3
+
4
+ const STATIC_CACHE = "develoz-static-v1"
5
+ const PAGES_CACHE = "develoz-pages-v1"
6
+ const OFFLINE_URL = "/offline"
7
+
8
+ // App shell assets to pre-cache on install
9
+ const APP_SHELL = [
10
+ "/",
11
+ OFFLINE_URL
12
+ ]
13
+
14
+ // install: pre-cache the app shell
15
+ self.addEventListener("install", (event) => {
16
+ event.waitUntil(
17
+ caches.open(STATIC_CACHE).then((cache) => cache.addAll(APP_SHELL))
18
+ )
19
+ self.skipWaiting()
20
+ })
21
+
22
+ // activate: clean up old caches
23
+ self.addEventListener("activate", (event) => {
24
+ const validCaches = [STATIC_CACHE, PAGES_CACHE]
25
+ event.waitUntil(
26
+ caches.keys().then((cacheNames) =>
27
+ Promise.all(
28
+ cacheNames
29
+ .filter((name) => !validCaches.includes(name))
30
+ .map((name) => caches.delete(name))
31
+ )
32
+ )
33
+ )
34
+ self.clients.claim()
35
+ })
36
+
37
+ // fetch: cache-first for app shell, network-first for everything else
38
+ self.addEventListener("fetch", (event) => {
39
+ const request = event.request
40
+
41
+ // Only handle GET requests
42
+ if (request.method !== "GET") return
43
+
44
+ const url = new URL(request.url)
45
+
46
+ // Same-origin only
47
+ if (url.origin !== self.location.origin) return
48
+
49
+ // Cache-first for static assets (JS, CSS, fonts, images)
50
+ if (isStaticAsset(request)) {
51
+ event.respondWith(cacheFirst(request, STATIC_CACHE))
52
+ return
53
+ }
54
+
55
+ // Network-first for HTML pages with offline fallback
56
+ if (request.mode === "navigate" || request.headers.get("accept")?.includes("text/html")) {
57
+ event.respondWith(networkFirstWithOffline(request))
58
+ return
59
+ }
60
+
61
+ // Network-first for everything else
62
+ event.respondWith(networkFirst(request, PAGES_CACHE))
63
+ })
64
+
65
+ function isStaticAsset(request) {
66
+ const destination = request.destination
67
+ return ["style", "script", "font", "image", "manifest"].includes(destination)
68
+ }
69
+
70
+ async function cacheFirst(request, cacheName) {
71
+ const cache = await caches.open(cacheName)
72
+ const cached = await cache.match(request)
73
+ if (cached) return cached
74
+ try {
75
+ const response = await fetch(request)
76
+ if (response.ok) cache.put(request, response.clone())
77
+ return response
78
+ } catch {
79
+ return new Response("", { status: 504, statusText: "Gateway Timeout" })
80
+ }
81
+ }
82
+
83
+ async function networkFirst(request, cacheName) {
84
+ const cache = await caches.open(cacheName)
85
+ try {
86
+ const response = await fetch(request)
87
+ if (response.ok) cache.put(request, response.clone())
88
+ return response
89
+ } catch {
90
+ const cached = await cache.match(request)
91
+ return cached || new Response("", { status: 504, statusText: "Gateway Timeout" })
92
+ }
93
+ }
94
+
95
+ async function networkFirstWithOffline(request) {
96
+ const cache = await caches.open(PAGES_CACHE)
97
+ try {
98
+ const response = await fetch(request)
99
+ if (response.ok) cache.put(request, response.clone())
100
+ return response
101
+ } catch {
102
+ const cached = await cache.match(request)
103
+ if (cached) return cached
104
+ // Fall back to the offline page
105
+ const offlineCache = await caches.open(STATIC_CACHE)
106
+ const offlinePage = await offlineCache.match(OFFLINE_URL)
107
+ return offlinePage || new Response("You're offline", { status: 503, headers: { "Content-Type": "text/html" } })
108
+ }
109
+ }
@@ -0,0 +1,3 @@
1
+ get "manifest" => "pwa#manifest", as: :pwa_manifest
2
+ get "service-worker" => "pwa#service_worker", as: :pwa_service_worker
3
+ get "offline" => "pwa#offline", as: :pwa_offline
@@ -0,0 +1,52 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "develoz"
4
+
5
+ module Develoz
6
+ module Generators
7
+ class SolidGenerator < Develoz::Generators::Base
8
+ def self.source_root
9
+ File.expand_path("templates", __dir__)
10
+ end
11
+
12
+ def add_solid_gems
13
+ add_gem "solid_queue"
14
+ add_gem "solid_cache"
15
+ add_gem "solid_cable"
16
+ add_gem "mission_control-jobs"
17
+ end
18
+
19
+ def create_queue_config
20
+ template "config/queue.yml.tt", "config/queue.yml"
21
+ end
22
+
23
+ def create_cache_config
24
+ template "config/cache.yml.tt", "config/cache.yml"
25
+ end
26
+
27
+ def create_cable_config
28
+ template "config/cable.yml.tt", "config/cable.yml"
29
+ end
30
+
31
+ def create_recurring_config
32
+ template "config/recurring.yml.tt", "config/recurring.yml"
33
+ end
34
+
35
+ def create_application_job
36
+ template "app/jobs/application_job.rb.tt", "app/jobs/application_job.rb"
37
+ end
38
+
39
+ def create_mission_control_initializer
40
+ template "config/initializers/mission_control.rb.tt", "config/initializers/mission_control.rb"
41
+ end
42
+
43
+ def create_solid_initializer
44
+ template "config/initializers/solid.rb.tt", "config/initializers/solid.rb"
45
+ end
46
+
47
+ def insert_mission_control_route
48
+ insert_route('mount MissionControl::Jobs::Engine, at: "/jobs"')
49
+ end
50
+ end
51
+ end
52
+ end
@@ -0,0 +1,7 @@
1
+ class ApplicationJob < ActiveJob::Base
2
+ # Automatically retry jobs that encountered a deadlock
3
+ # retry_on ActiveRecord::Deadlocked
4
+
5
+ # Most jobs are safe to ignore if the underlying records are no longer available
6
+ # discard_on ActiveJob::DeserializationError
7
+ end
@@ -0,0 +1,10 @@
1
+ development:
2
+ adapter: async
3
+
4
+ test:
5
+ adapter: test
6
+
7
+ production:
8
+ adapter: redis
9
+ url: <%= ENV.fetch("REDIS_URL") { "redis://localhost:6379/1" } %>
10
+ channel_prefix: <%= app_name %>_production
@@ -0,0 +1,16 @@
1
+ default: &default
2
+ store_options:
3
+ # Cap age of oldest cache entry to fulfill retention policies
4
+ # max_age: 5184000
5
+ max_size: 268435456
6
+ namespace: <%= ENV.fetch("RAILS_ENV", "development") %>
7
+
8
+ development:
9
+ <<: *default
10
+
11
+ test:
12
+ <<: *default
13
+
14
+ production:
15
+ database: cache
16
+ <<: *default
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Mission Control - Jobs authentication
4
+ # Disable HTTP basic auth — Mission Control is gated behind the app's
5
+ # authentication system instead (e.g. Rails 8 authentication via --auth).
6
+ # When no authentication is configured, Mission Control is accessible
7
+ # without credentials in development and test environments.
8
+ MissionControl::Jobs.http_basic_auth_enabled = false
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Solid Queue, Cache, and Cable configuration
4
+ Rails.application.configure do
5
+ config.active_job.queue_adapter = :solid_queue
6
+ config.cache_store = :solid_cache_store
7
+ end
@@ -0,0 +1,18 @@
1
+ default: &default
2
+ dispatchers:
3
+ - polling_interval: 1
4
+ batch_size: 500
5
+ workers:
6
+ - queues: "*"
7
+ threads: 3
8
+ processes: <%= ENV.fetch("JOB_CONCURRENCY", 1) %>
9
+ polling_interval: 0.1
10
+
11
+ development:
12
+ <<: *default
13
+
14
+ test:
15
+ <<: *default
16
+
17
+ production:
18
+ <<: *default
@@ -0,0 +1,20 @@
1
+ # examples:
2
+ # periodic_cleanup:
3
+ # class: CleanSoftDeletedRecordsJob
4
+ # queue: background
5
+ # args: [ 1000, { batch_size: 500 } ]
6
+ # schedule: every hour
7
+ # periodic_cleanup_with_command:
8
+ # command: "SoftDeletedRecord.due.delete_all"
9
+ # priority: 2
10
+ # schedule: at 5am every day
11
+
12
+ development:
13
+ clear_solid_queue_finished_jobs:
14
+ command: "SolidQueue::Job.clear_finished_in_batches(sleep_between_batches: 0.3)"
15
+ schedule: every hour at minute 12
16
+
17
+ production:
18
+ clear_solid_queue_finished_jobs:
19
+ command: "SolidQueue::Job.clear_finished_in_batches(sleep_between_batches: 0.3)"
20
+ schedule: every hour at minute 12
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "develoz"
4
+
5
+ module Develoz
6
+ module Generators
7
+ class StrictLoadingGenerator < Develoz::Generators::Base
8
+ def self.source_root
9
+ File.expand_path("templates", __dir__)
10
+ end
11
+
12
+ def create_strict_loading_initializer
13
+ template "config/initializers/strict_loading.rb.tt", "config/initializers/strict_loading.rb"
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Strict loading configuration
4
+ # Enables strict loading by default with n_plus_one_only mode.
5
+ # Violations raise in test, log in development and production.
6
+ Rails.application.config.active_record.strict_loading_by_default = true
7
+ Rails.application.config.active_record.strict_loading_mode = :n_plus_one_only
8
+
9
+ ActiveRecord.action_on_strict_loading_violation =
10
+ case Rails.env
11
+ when "test" then :raise
12
+ else :log
13
+ end
@@ -0,0 +1,2 @@
1
+ --require spec_helper
2
+ --format documentation
@@ -0,0 +1,2 @@
1
+ --require spec_helper
2
+ --format documentation
@@ -0,0 +1,32 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "spec_helper"
4
+ require_relative "../config/environment"
5
+ require "rspec/rails"
6
+ require "factory_bot"
7
+ require "capybara/rspec"
8
+ require "selenium-webdriver"
9
+
10
+ Dir[Rails.root.join("spec/support/**/*.rb")].sort.each { |f| require f }
11
+
12
+ RSpec.configure do |config|
13
+ config.fixture_paths = [ "#{::Rails.root}/spec/fixtures" ]
14
+ config.use_transactional_fixtures = true
15
+ config.infer_spec_type_from_file_location!
16
+ config.filter_rails_from_backtrace!
17
+ end
18
+
19
+ Capybara.register_driver :headless_chrome do |app|
20
+ options = Selenium::WebDriver::Chrome::Options.new
21
+ options.add_argument("--headless=new")
22
+ options.add_argument("--no-sandbox")
23
+ options.add_argument("--disable-gpu")
24
+ Capybara::Selenium::Driver.new(app, browser: :chrome, options: options)
25
+ end
26
+
27
+ Capybara.javascript_driver = :headless_chrome
28
+
29
+ FactoryBot.definition_file_paths = [ File.expand_path("factories", __dir__) ]
30
+ FactoryBot.find_definitions
31
+
32
+ ActiveRecord::Migration.maintain_test_schema!
@@ -0,0 +1,31 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "simplecov"
4
+ require "simplecov-lcov"
5
+
6
+ SimpleCov.formatters = [
7
+ SimpleCov::Formatter::HTMLFormatter,
8
+ SimpleCov::Formatter::LcovFormatter
9
+ ]
10
+
11
+ SimpleCov.start do
12
+ enable_coverage :branch
13
+ minimum_coverage line: 100, branch: 100
14
+ coverage_dir "public/coverage"
15
+ command_name "RSpec_#{ENV['TEST_ENV_NUMBER']}" if ENV['TEST_ENV_NUMBER']
16
+ use_merging true
17
+ add_filter %w[/spec/ /config/ /db/]
18
+ add_filter %w[/bin/ *.sh]
19
+ end
20
+
21
+ RSpec.configure do |config|
22
+ config.expect_with :rspec do |expectations|
23
+ expectations.include_chain_clauses_in_custom_matcher_descriptions = true
24
+ end
25
+
26
+ config.mock_with :rspec do |mocks|
27
+ mocks.verify_partial_doubles = true
28
+ end
29
+
30
+ config.shared_context_metadata_behavior = :apply_to_host_groups
31
+ end
@@ -0,0 +1,39 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "develoz"
4
+
5
+ module Develoz
6
+ module Generators
7
+ class TestingGenerator < Develoz::Generators::Base
8
+ def self.source_root
9
+ File.expand_path("templates", __dir__)
10
+ end
11
+
12
+ def add_test_gems
13
+ add_gem "rspec-rails", group: %i[development test]
14
+ add_gem "capybara", group: %i[test]
15
+ add_gem "selenium-webdriver", group: %i[test]
16
+ add_gem "simplecov", require: false, group: %i[test]
17
+ add_gem "simplecov-lcov", require: false, group: %i[test]
18
+ add_gem "parallel_tests", group: %i[test]
19
+ add_gem "factory_bot_rails", group: %i[test]
20
+ end
21
+
22
+ def create_rspec_config
23
+ template "rspec.tt", ".rspec"
24
+ end
25
+
26
+ def create_spec_helper
27
+ template "spec/spec_helper.rb.tt", "spec/spec_helper.rb"
28
+ end
29
+
30
+ def create_rails_helper
31
+ template "spec/rails_helper.rb.tt", "spec/rails_helper.rb"
32
+ end
33
+
34
+ def create_rspec_parallel_config
35
+ template "rspec_parallel.tt", ".rspec_parallel"
36
+ end
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ ## Assign all environment variables used by the Rails app to constants for easy access and tracking
4
+ module Constants
5
+ DATABASE_HOST = ENV.fetch("DATABASE_HOST", "localhost")
6
+ DATABASE_PORT = ENV.fetch("DATABASE_PORT", 5432)
7
+ DATABASE_NAME = ENV.fetch("DATABASE_NAME", "<%= app_name %>")
8
+ TEST_DATABASE_NAME = ENV.fetch("TEST_DATABASE_NAME", "#{DATABASE_NAME}_test")
9
+ REDIS_URL = ENV.fetch("REDIS_URL", "redis://localhost:6379/#{ENV.fetch("TEST_ENV_NUMBER", 1)}")
10
+ RAILS_MAX_THREADS = ENV.fetch("RAILS_MAX_THREADS", `nproc || echo 2`.strip.to_i * 5)
11
+ SERVER_PORT = ENV.fetch("SERVER_PORT", 3000)
12
+ HOST = ENV.fetch("APP_HOST") { ENV.fetch("HOST", "localhost:#{SERVER_PORT}") }
13
+ CACHING_DEV = ENV.fetch("CACHING_DEV", nil) == "true" # Enable caching in development if set to "true"
14
+ MEMCACHIER_SERVERS = ENV.fetch("MEMCACHIER_SERVERS", "").split(",")
15
+ APP_NAME = ENV.fetch("APP_NAME", "<%= app_class %>")
16
+ # additional constants appended by generators
17
+ end
@@ -0,0 +1,4 @@
1
+ # Application Configuration
2
+ APP_NAME=<%= app_name %>
3
+ DATABASE_URL=
4
+ RAILS_ENV=development
@@ -0,0 +1,8 @@
1
+ {
2
+ "recommendations": [
3
+ "Shopify.ruby-lsp",
4
+ "EditorConfig.EditorConfig",
5
+ "GitHub.copilot",
6
+ "ms-vscode.makefile-tools"
7
+ ]
8
+ }
@@ -0,0 +1,26 @@
1
+ {
2
+ "[ruby]": {
3
+ "editor.defaultFormatter": "Shopify.ruby-lsp",
4
+ "editor.formatOnSave": true,
5
+ "editor.codeActionsOnSave": {
6
+ "source.fixAll.rubocop": "explicit"
7
+ }
8
+ },
9
+ "files.trimTrailingWhitespace": true,
10
+ "files.insertFinalNewline": true,
11
+ "editor.formatOnSave": true,
12
+ "editor.rulers": [80, 120],
13
+ "files.exclude": {
14
+ "**/tmp": true,
15
+ "**/log": true,
16
+ "**/.git": true,
17
+ "**/coverage": true,
18
+ "**/.rspec_status": true
19
+ },
20
+ "search.exclude": {
21
+ "**/node_modules": true,
22
+ "**/tmp": true,
23
+ "**/log": true,
24
+ "**/coverage": true
25
+ }
26
+ }
@@ -0,0 +1,26 @@
1
+ {
2
+ "version": "2.0.0",
3
+ "tasks": [
4
+ {
5
+ "label": "rspec",
6
+ "type": "shell",
7
+ "command": "bundle",
8
+ "args": ["exec", "rspec"],
9
+ "problemMatcher": [],
10
+ "group": {
11
+ "kind": "test",
12
+ "isDefault": true
13
+ }
14
+ },
15
+ {
16
+ "label": "rubocop",
17
+ "type": "shell",
18
+ "command": "bundle",
19
+ "args": ["exec", "rubocop"],
20
+ "problemMatcher": [],
21
+ "group": {
22
+ "kind": "build"
23
+ }
24
+ }
25
+ ]
26
+ }
@@ -0,0 +1,33 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "develoz"
4
+
5
+ module Develoz
6
+ module Generators
7
+ class ToolingGenerator < Develoz::Generators::Base
8
+ def self.source_root
9
+ File.expand_path("templates", __dir__)
10
+ end
11
+
12
+ def create_vscode
13
+ template "vscode/settings.json.tt", ".vscode/settings.json"
14
+ template "vscode/extensions.json.tt", ".vscode/extensions.json"
15
+ template "vscode/tasks.json.tt", ".vscode/tasks.json"
16
+ end
17
+
18
+ def create_env_files
19
+ template "env.example.tt", ".env.example"
20
+ create_file ".env", "" unless File.exist?(File.join(destination_root, ".env"))
21
+ ensure_gitignore(".env")
22
+ end
23
+
24
+ def create_constants
25
+ template "constants.rb.tt", "config/initializers/constants.rb"
26
+ end
27
+
28
+ def add_dotenv
29
+ add_gem "dotenv-rails", group: %i[development test]
30
+ end
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,20 @@
1
+ #!/usr/bin/env bash
2
+ set -e
3
+
4
+ # Setup script for the develoz-ui submodule.
5
+ # Initializes the develoz-ui git submodule at vendor/develoz-ui.
6
+ #
7
+ # If the submodule is not initialized, the develoz_ui gem will fail to load
8
+ # in development/test environments where it uses `path: "vendor/develoz-ui"`.
9
+ # In production, the gem is fetched from GitHub directly.
10
+
11
+ echo "Initializing develoz-ui submodule..."
12
+ git submodule update --init --recursive vendor/develoz-ui
13
+
14
+ if [ ! -d "vendor/develoz-ui" ] || [ -z "$(ls -A vendor/develoz-ui 2>/dev/null)" ]; then
15
+ echo "ERROR: develoz-ui submodule is not initialized."
16
+ echo "Run 'bin/setup_develoz_ui' to initialize it."
17
+ exit 1
18
+ fi
19
+
20
+ echo "develoz-ui submodule is ready."
@@ -0,0 +1,57 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "develoz"
4
+
5
+ module Develoz
6
+ module Generators
7
+ class UiGenerator < Develoz::Generators::Base
8
+ def self.source_root
9
+ File.expand_path("templates", __dir__)
10
+ end
11
+
12
+ def add_develoz_ui_gems
13
+ inject_once(
14
+ into: "Gemfile",
15
+ content: <<~RUBY
16
+ group :development, :test do
17
+ gem "develoz_ui", path: "vendor/develoz-ui"
18
+ end
19
+
20
+ group :production do
21
+ gem "develoz_ui", github: "develoz-com/develoz-ui"
22
+ end
23
+ RUBY
24
+ )
25
+ end
26
+
27
+ def create_gitmodules
28
+ gitmodules_path = File.join(destination_root, ".gitmodules")
29
+ File.write(gitmodules_path, "") unless File.exist?(gitmodules_path)
30
+
31
+ inject_once(
32
+ into: ".gitmodules",
33
+ content: "[submodule \"vendor/develoz-ui\"]\n" \
34
+ "\tpath = vendor/develoz-ui\n" \
35
+ "\turl = git@github.com:develoz-com/develoz-ui.git"
36
+ )
37
+ end
38
+
39
+ def create_setup_script
40
+ template "bin/setup_develoz_ui.tt", "bin/setup_develoz_ui"
41
+ dest = File.join(destination_root, "bin/setup_develoz_ui")
42
+ File.chmod(0o755, dest)
43
+ end
44
+
45
+ def inject_importmap_pins
46
+ inject_once(
47
+ into: "config/importmap.rb",
48
+ content: <<~RUBY
49
+ # develoz-ui Stimulus controllers
50
+ pin "develoz-ui", to: "develoz-ui/index.js"
51
+ pin_all_from "vendor/develoz-ui/app/javascript/controllers", under: "develoz-ui/controllers"
52
+ RUBY
53
+ )
54
+ end
55
+ end
56
+ end
57
+ end
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ApplicationHelper
4
+ def app_version
5
+ APP_VERSION
6
+ end
7
+ end