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
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: ee93be7d4583674313e36b90ee84aaecf4f65d08eb23659e179b419608cd38dd
4
+ data.tar.gz: 5e9dc27ad205be346f3c71740e0572294dabbd63357abe6b6ad788589faa9e13
5
+ SHA512:
6
+ metadata.gz: 4c2c7a71236567800a089344d3b15dbae4d42eceed06b3234e8d24bd5ef1c7e115c1ba79c0d5eb473f03696a5de86cc6f5d2759b1b56628da40c2ba1452fcbb9
7
+ data.tar.gz: 8e942cdcb7d0a22f2df4eb50c31bb1719e0ff904d7b0a5c04dbdab69ce0c93e408588973643516f2069d0de2ea1e0e6ba938f11c54e29a78d1a1cbc5083972de
@@ -0,0 +1,31 @@
1
+ name: CI
2
+
3
+ on:
4
+ push:
5
+ branches: [main, develop]
6
+ pull_request:
7
+ branches: [main, develop]
8
+
9
+ jobs:
10
+ test:
11
+ runs-on: ubuntu-latest
12
+
13
+ steps:
14
+ - uses: actions/checkout@v4
15
+
16
+ - name: Set up Ruby
17
+ uses: ruby/setup-ruby@v1
18
+ with:
19
+ ruby-version: 4.0.5
20
+ bundler-cache: true
21
+
22
+ - name: Run CI checks
23
+ run: bin/ci
24
+
25
+ - name: Upload coverage
26
+ uses: actions/upload-artifact@v4
27
+ if: always()
28
+ with:
29
+ name: coverage-report
30
+ path: coverage/
31
+ retention-days: 30
@@ -0,0 +1,459 @@
1
+ name: Release
2
+
3
+ on:
4
+ release:
5
+ types: [published]
6
+
7
+ permissions: {}
8
+
9
+ concurrency:
10
+ group: release-${{ github.event.release.tag_name }}
11
+ cancel-in-progress: false
12
+
13
+ jobs:
14
+ validate:
15
+ if: ${{ github.event.release.prerelease == false }}
16
+ runs-on: ubuntu-latest
17
+ permissions:
18
+ contents: read
19
+
20
+ steps:
21
+ - name: Check out release tag
22
+ uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd
23
+ with:
24
+ ref: ${{ github.event.release.tag_name }}
25
+ fetch-depth: 0
26
+ persist-credentials: false
27
+
28
+ - name: Set up Ruby
29
+ uses: ruby/setup-ruby@003a5c4d8d6321bd302e38f6f0ec593f77f06600
30
+ with:
31
+ ruby-version: 4.0.5
32
+ bundler-cache: true
33
+
34
+ - name: Validate release identity
35
+ env:
36
+ RELEASE_SHA: ${{ github.sha }}
37
+ RELEASE_TAG: ${{ github.event.release.tag_name }}
38
+ REPOSITORY: ${{ github.repository }}
39
+ run: |
40
+ set -euo pipefail
41
+
42
+ fail() {
43
+ echo "::error::$1"
44
+ exit 1
45
+ }
46
+
47
+ [[ "$REPOSITORY" == "develoz-com/rails-template" ]] ||
48
+ fail "release workflow must run in develoz-com/rails-template"
49
+ [[ "$RELEASE_TAG" =~ ^v[0-9]+\.[0-9]+\.[0-9]+$ ]] ||
50
+ fail "release tag must match vX.Y.Z"
51
+
52
+ tag_sha="$(git rev-parse --verify "refs/tags/${RELEASE_TAG}^{commit}")"
53
+ head_sha="$(git rev-parse --verify HEAD)"
54
+ main_sha="$(git rev-parse --verify "refs/remotes/origin/main^{commit}")"
55
+
56
+ [[ "$tag_sha" == "$head_sha" ]] ||
57
+ fail "checked-out commit does not match ${RELEASE_TAG}"
58
+ [[ "$tag_sha" == "$RELEASE_SHA" ]] ||
59
+ fail "${RELEASE_TAG} does not match the release event SHA"
60
+ [[ "$tag_sha" == "$main_sha" ]] ||
61
+ fail "${RELEASE_TAG} is not the current origin/main commit"
62
+
63
+ version="$(ruby -Ilib -rdeveloz/version -e 'print Develoz::VERSION')"
64
+ [[ "$RELEASE_TAG" == "v${version}" ]] ||
65
+ fail "${RELEASE_TAG} does not match Develoz::VERSION ${version}"
66
+
67
+ - name: Run CI
68
+ run: bin/ci
69
+
70
+ - name: Validate changelog update
71
+ env:
72
+ PUBLISHED_AT: ${{ github.event.release.published_at }}
73
+ RELEASE_TAG: ${{ github.event.release.tag_name }}
74
+ REPOSITORY: ${{ github.repository }}
75
+ run: |
76
+ set -euo pipefail
77
+
78
+ release_date="${PUBLISHED_AT%%T*}"
79
+ [[ "$release_date" =~ ^[0-9]{4}-[0-9]{2}-[0-9]{2}$ ]] || {
80
+ echo "::error::release published_at must contain a YYYY-MM-DD date"
81
+ exit 1
82
+ }
83
+
84
+ bin/update_changelog \
85
+ --tag "$RELEASE_TAG" \
86
+ --date "$release_date" \
87
+ --repository "$REPOSITORY" \
88
+ --check \
89
+ CHANGELOG.md
90
+
91
+ publish:
92
+ needs: validate
93
+ if: ${{ github.event.release.prerelease == false && needs.validate.result == 'success' }}
94
+ runs-on: ubuntu-latest
95
+ environment: rubygems
96
+ permissions:
97
+ contents: read
98
+ id-token: write
99
+
100
+ steps:
101
+ - name: Check out immutable release commit
102
+ uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd
103
+ with:
104
+ ref: ${{ github.sha }}
105
+ fetch-depth: 0
106
+ fetch-tags: true
107
+ persist-credentials: false
108
+
109
+ - name: Validate immutable release refs
110
+ env:
111
+ RELEASE_SHA: ${{ github.sha }}
112
+ RELEASE_TAG: ${{ github.event.release.tag_name }}
113
+ REPOSITORY: ${{ github.repository }}
114
+ run: |
115
+ set -euo pipefail
116
+
117
+ fail() {
118
+ echo "::error::$1"
119
+ exit 1
120
+ }
121
+
122
+ [[ "$REPOSITORY" == "develoz-com/rails-template" ]] ||
123
+ fail "release workflow must run in develoz-com/rails-template"
124
+ [[ "$RELEASE_TAG" =~ ^v[0-9]+\.[0-9]+\.[0-9]+$ ]] ||
125
+ fail "release tag must match vX.Y.Z"
126
+
127
+ tag_sha="$(git rev-parse --verify "refs/tags/${RELEASE_TAG}^{commit}")"
128
+ head_sha="$(git rev-parse --verify HEAD)"
129
+ main_sha="$(git rev-parse --verify "refs/remotes/origin/main^{commit}")"
130
+
131
+ [[ "$head_sha" == "$RELEASE_SHA" ]] ||
132
+ fail "checked-out commit does not match the release event SHA"
133
+ [[ "$tag_sha" == "$RELEASE_SHA" ]] ||
134
+ fail "${RELEASE_TAG} does not match the release event SHA"
135
+ [[ "$main_sha" == "$RELEASE_SHA" ]] ||
136
+ fail "${RELEASE_TAG} is not the current origin/main commit"
137
+
138
+ - name: Set up Ruby
139
+ uses: ruby/setup-ruby@003a5c4d8d6321bd302e38f6f0ec593f77f06600
140
+ with:
141
+ ruby-version: 4.0.5
142
+ bundler: none
143
+
144
+ - name: Validate gem version
145
+ env:
146
+ RELEASE_TAG: ${{ github.event.release.tag_name }}
147
+ run: |
148
+ set -euo pipefail
149
+
150
+ version="$(ruby -Ilib -rdeveloz/version -e 'print Develoz::VERSION')"
151
+ if [[ "$RELEASE_TAG" != "v${version}" ]]; then
152
+ echo "::error::${RELEASE_TAG} does not match Develoz::VERSION ${version}"
153
+ exit 1
154
+ fi
155
+ printf 'VERSION=%s\n' "$version" >> "$GITHUB_ENV"
156
+
157
+ - name: Build exact gem
158
+ run: |
159
+ set -euo pipefail
160
+
161
+ gem_path="pkg/develoz-rails-${VERSION}.gem"
162
+ mkdir -p pkg
163
+ rm -f -- "$gem_path"
164
+ gem build develoz-rails.gemspec --output "$gem_path"
165
+ [[ -f "$gem_path" ]] || {
166
+ echo "::error::gem build did not create ${gem_path}"
167
+ exit 1
168
+ }
169
+ printf 'GEM_PATH=%s\n' "$gem_path" >> "$GITHUB_ENV"
170
+
171
+ - name: Inspect built gem
172
+ run: |
173
+ ruby -rrubygems/package <<'RUBY'
174
+ gem_path = ENV.fetch("GEM_PATH")
175
+ version = ENV.fetch("VERSION")
176
+ package = Gem::Package.new(gem_path)
177
+ package.verify
178
+ specification = package.spec
179
+
180
+ abort "expected gem name develoz-rails, got #{specification.name}" unless specification.name == "develoz-rails"
181
+ abort "expected gem version #{version}, got #{specification.version}" unless specification.version.to_s == version
182
+ abort "expected ruby platform, got #{specification.platform}" unless specification.platform.to_s == "ruby"
183
+ abort "expected only the develoz executable" unless specification.executables == ["develoz"]
184
+
185
+ files = package.contents
186
+ required_files = %w[
187
+ CHANGELOG.md
188
+ README.md
189
+ config/generators.yml
190
+ exe/develoz
191
+ lib/develoz.rb
192
+ lib/develoz/version.rb
193
+ lib/generators/develoz/ci/templates/.github/workflows/ci.yml.tt
194
+ templates/pull_request_template.md.tt
195
+ ]
196
+ missing_files = required_files - files
197
+ abort "gem is missing required files: #{missing_files.join(', ')}" unless missing_files.empty?
198
+
199
+ excluded_files = %w[.gitignore .rspec Gemfile develoz-rails.gemspec]
200
+ excluded_prefixes = %w[bin/ spec/]
201
+ unexpected_files = files.select do |file|
202
+ excluded_files.include?(file) || excluded_prefixes.any? { |prefix| file.start_with?(prefix) }
203
+ end
204
+ abort "gem contains excluded files: #{unexpected_files.join(', ')}" unless unexpected_files.empty?
205
+ RUBY
206
+
207
+ - name: Check RubyGems version state
208
+ id: version-state
209
+ env:
210
+ RUN_ATTEMPT: ${{ github.run_attempt }}
211
+ run: |
212
+ set -euo pipefail
213
+
214
+ [[ "$RUN_ATTEMPT" =~ ^[1-9][0-9]*$ ]] || {
215
+ echo "::error::invalid workflow run attempt ${RUN_ATTEMPT}"
216
+ exit 1
217
+ }
218
+
219
+ api_url="https://rubygems.org/api/v2/rubygems/develoz-rails/versions/${VERSION}.json"
220
+ checks=1
221
+ if (( RUN_ATTEMPT > 1 )); then
222
+ checks=12
223
+ fi
224
+
225
+ status=""
226
+ for ((check = 1; check <= checks; check++)); do
227
+ status="$(curl \
228
+ --silent \
229
+ --show-error \
230
+ --location \
231
+ --connect-timeout 10 \
232
+ --max-time 30 \
233
+ --output /dev/null \
234
+ --write-out '%{http_code}' \
235
+ "$api_url")"
236
+
237
+ if [[ "$status" == "200" ]]; then
238
+ break
239
+ fi
240
+ [[ "$status" == "404" ]] || {
241
+ echo "::error::RubyGems API returned HTTP ${status}"
242
+ exit 1
243
+ }
244
+ if (( check < checks )); then
245
+ sleep 5
246
+ fi
247
+ done
248
+
249
+ if [[ "$status" == "200" ]]; then
250
+ if (( RUN_ATTEMPT == 1 )); then
251
+ echo "::error::develoz-rails ${VERSION} already exists on RubyGems during the first run attempt"
252
+ exit 1
253
+ fi
254
+ echo "should-push=false" >> "$GITHUB_OUTPUT"
255
+ echo "develoz-rails ${VERSION} is already visible; skipping gem push on rerun"
256
+ else
257
+ echo "should-push=true" >> "$GITHUB_OUTPUT"
258
+ fi
259
+
260
+ - name: Configure RubyGems credentials
261
+ if: ${{ steps.version-state.outputs.should-push == 'true' }}
262
+ uses: rubygems/configure-rubygems-credentials@dc5a8d8553e6ee01fc26761a49e99e733d17954a
263
+
264
+ - name: Push exact gem
265
+ if: ${{ steps.version-state.outputs.should-push == 'true' }}
266
+ run: gem push "pkg/develoz-rails-${VERSION}.gem"
267
+
268
+ - name: Verify RubyGems API visibility
269
+ run: |
270
+ set -euo pipefail
271
+
272
+ api_url="https://rubygems.org/api/v2/rubygems/develoz-rails/versions/${VERSION}.json"
273
+ api_response="$RUNNER_TEMP/develoz-rails-${VERSION}.json"
274
+ status=""
275
+
276
+ for ((check = 1; check <= 18; check++)); do
277
+ status="$(curl \
278
+ --silent \
279
+ --show-error \
280
+ --location \
281
+ --connect-timeout 10 \
282
+ --max-time 30 \
283
+ --output "$api_response" \
284
+ --write-out '%{http_code}' \
285
+ "$api_url")"
286
+
287
+ if [[ "$status" == "200" ]]; then
288
+ break
289
+ fi
290
+ [[ "$status" == "404" ]] || {
291
+ echo "::error::RubyGems API returned HTTP ${status}"
292
+ exit 1
293
+ }
294
+ if (( check < 18 )); then
295
+ sleep 5
296
+ fi
297
+ done
298
+
299
+ [[ "$status" == "200" ]] || {
300
+ echo "::error::develoz-rails ${VERSION} did not become visible through the RubyGems API"
301
+ exit 1
302
+ }
303
+
304
+ API_RESPONSE="$api_response" ruby -rjson -rdigest <<'RUBY'
305
+ payload = JSON.parse(File.read(ENV.fetch("API_RESPONSE")))
306
+ version = ENV.fetch("VERSION")
307
+ gem_path = ENV.fetch("GEM_PATH")
308
+ expected_sha = Digest::SHA256.file(gem_path).hexdigest
309
+
310
+ abort "RubyGems API returned the wrong gem name" unless payload.fetch("name") == "develoz-rails"
311
+ abort "RubyGems API returned the wrong version" unless payload.fetch("version") == version
312
+ abort "RubyGems API returned the wrong platform" unless payload.fetch("platform") == "ruby"
313
+ abort "RubyGems API reports the release as yanked" if payload.fetch("yanked")
314
+ abort "RubyGems API SHA does not match the built gem" unless payload.fetch("sha") == expected_sha
315
+ RUBY
316
+
317
+ finalize-changelog:
318
+ needs: publish
319
+ if: ${{ github.event.release.prerelease == false && needs.publish.result == 'success' }}
320
+ runs-on: ubuntu-latest
321
+ permissions:
322
+ contents: write
323
+
324
+ steps:
325
+ - name: Check out main
326
+ uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd
327
+ with:
328
+ ref: main
329
+ fetch-depth: 0
330
+
331
+ - name: Set up Ruby
332
+ uses: ruby/setup-ruby@003a5c4d8d6321bd302e38f6f0ec593f77f06600
333
+ with:
334
+ ruby-version: 4.0.5
335
+ bundler: none
336
+
337
+ - name: Validate main and release tag
338
+ env:
339
+ RELEASE_SHA: ${{ github.sha }}
340
+ RELEASE_TAG: ${{ github.event.release.tag_name }}
341
+ REPOSITORY: ${{ github.repository }}
342
+ run: |
343
+ set -euo pipefail
344
+
345
+ fail() {
346
+ echo "::error::$1"
347
+ exit 1
348
+ }
349
+
350
+ [[ "$REPOSITORY" == "develoz-com/rails-template" ]] ||
351
+ fail "release workflow must run in develoz-com/rails-template"
352
+ [[ "$RELEASE_TAG" =~ ^v[0-9]+\.[0-9]+\.[0-9]+$ ]] ||
353
+ fail "release tag must match vX.Y.Z"
354
+
355
+ tag_sha="$(git rev-parse --verify "refs/tags/${RELEASE_TAG}^{commit}")"
356
+ head_sha="$(git rev-parse --verify HEAD)"
357
+ main_sha="$(git rev-parse --verify "refs/remotes/origin/main^{commit}")"
358
+
359
+ [[ "$tag_sha" == "$RELEASE_SHA" ]] ||
360
+ fail "${RELEASE_TAG} no longer matches the release event SHA"
361
+ [[ "$head_sha" == "$main_sha" ]] ||
362
+ fail "checked-out main does not match origin/main"
363
+
364
+ if [[ "$main_sha" == "$RELEASE_SHA" ]]; then
365
+ finalize_state="fresh"
366
+ else
367
+ parents="$(git show -s --format=%P "$main_sha")"
368
+ [[ "$parents" == "$RELEASE_SHA" ]] ||
369
+ fail "main is not the release commit or its single direct child"
370
+
371
+ subject="$(git show -s --format=%s "$main_sha")"
372
+ expected_subject="docs: finalize ${RELEASE_TAG} changelog"
373
+ [[ "$subject" == "$expected_subject" ]] ||
374
+ fail "the commit after the release has an unexpected subject"
375
+
376
+ changed_files="$(git diff-tree --no-commit-id --name-only -r "$main_sha")"
377
+ [[ "$changed_files" == "CHANGELOG.md" ]] ||
378
+ fail "the commit after the release must change only CHANGELOG.md"
379
+
380
+ finalize_state="already-finalized"
381
+ fi
382
+
383
+ version="$(ruby -Ilib -rdeveloz/version -e 'print Develoz::VERSION')"
384
+ [[ "$RELEASE_TAG" == "v${version}" ]] ||
385
+ fail "${RELEASE_TAG} does not match Develoz::VERSION ${version}"
386
+ printf 'FINALIZE_STATE=%s\n' "$finalize_state" >> "$GITHUB_ENV"
387
+
388
+ - name: Finalize changelog
389
+ env:
390
+ PUBLISHED_AT: ${{ github.event.release.published_at }}
391
+ RELEASE_TAG: ${{ github.event.release.tag_name }}
392
+ REPOSITORY: ${{ github.repository }}
393
+ run: |
394
+ set -euo pipefail
395
+
396
+ release_date="${PUBLISHED_AT%%T*}"
397
+ [[ "$release_date" =~ ^[0-9]{4}-[0-9]{2}-[0-9]{2}$ ]] || {
398
+ echo "::error::release published_at must contain a YYYY-MM-DD date"
399
+ exit 1
400
+ }
401
+
402
+ bin/update_changelog \
403
+ --tag "$RELEASE_TAG" \
404
+ --date "$release_date" \
405
+ --repository "$REPOSITORY" \
406
+ CHANGELOG.md
407
+
408
+ - name: Commit changelog if changed
409
+ env:
410
+ RELEASE_SHA: ${{ github.sha }}
411
+ RELEASE_TAG: ${{ github.event.release.tag_name }}
412
+ run: |
413
+ set -euo pipefail
414
+
415
+ if [[ "$FINALIZE_STATE" == "already-finalized" ]]; then
416
+ working_tree="$(git status --porcelain --untracked-files=all)"
417
+ if [[ -n "$working_tree" ]]; then
418
+ echo "::error::changelog updater was not idempotent on the finalized commit"
419
+ exit 1
420
+ fi
421
+ echo "CHANGELOG.md is already finalized for ${RELEASE_TAG}"
422
+ exit 0
423
+ fi
424
+
425
+ [[ "$FINALIZE_STATE" == "fresh" ]] || {
426
+ echo "::error::unexpected changelog finalization state ${FINALIZE_STATE}"
427
+ exit 1
428
+ }
429
+
430
+ if git diff --quiet -- CHANGELOG.md; then
431
+ echo "CHANGELOG.md is already finalized for ${RELEASE_TAG}"
432
+ exit 0
433
+ fi
434
+
435
+ unexpected_files="$(git diff --name-only -- . ':(exclude)CHANGELOG.md')"
436
+ [[ -z "$unexpected_files" ]] || {
437
+ echo "::error::changelog finalization changed unexpected files: ${unexpected_files}"
438
+ exit 1
439
+ }
440
+ git diff --check
441
+
442
+ git add -- CHANGELOG.md
443
+ staged_files="$(git diff --cached --name-only)"
444
+ [[ "$staged_files" == "CHANGELOG.md" ]] || {
445
+ echo "::error::only CHANGELOG.md may be committed"
446
+ exit 1
447
+ }
448
+
449
+ git config user.name "github-actions[bot]"
450
+ git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
451
+ git commit -m "docs: finalize ${RELEASE_TAG} changelog" -- CHANGELOG.md
452
+
453
+ tag_sha="$(git rev-parse --verify "refs/tags/${RELEASE_TAG}^{commit}")"
454
+ [[ "$tag_sha" == "$RELEASE_SHA" ]] || {
455
+ echo "::error::release tag changed during changelog finalization"
456
+ exit 1
457
+ }
458
+
459
+ git push origin HEAD:main
data/.rubocop.yml ADDED
@@ -0,0 +1,52 @@
1
+ plugins:
2
+ - rubocop-rails
3
+ - rubocop-rspec
4
+ - rubocop-performance
5
+
6
+ AllCops:
7
+ TargetRubyVersion: 3.4
8
+ NewCops: enable
9
+ SuggestExtensions: false
10
+ Exclude:
11
+ - "bin/**/*"
12
+ - "exe/**/*"
13
+ - "vendor/**/*"
14
+ - ".git/**/*"
15
+ - "config/**/*"
16
+ - "spec/fixtures/canonical/**/*"
17
+
18
+ # Gem-specific relaxations
19
+ Metrics/BlockLength:
20
+ Exclude:
21
+ - "spec/**/*"
22
+
23
+ Metrics/MethodLength:
24
+ Max: 20
25
+
26
+ RSpec/ExampleLength:
27
+ Max: 15
28
+
29
+ RSpec/MultipleExpectations:
30
+ Max: 3
31
+
32
+ # CLI/generator gem legitimately writes to stdout; puts/say is the interface
33
+ Rails/Output:
34
+ Enabled: false
35
+
36
+ # Development dependencies intentionally declared in gemspec per gem best practices
37
+ Gemspec/DevelopmentDependencies:
38
+ Enabled: false
39
+
40
+ # Many small classes across generators; top-level doc comments add noise
41
+ Style/Documentation:
42
+ Enabled: false
43
+
44
+ Style/StringLiterals:
45
+ EnforcedStyle: double_quotes
46
+
47
+ Style/FrozenStringLiteralComment:
48
+ Enabled: true
49
+
50
+ Rails/RakeEnvironment:
51
+ Exclude:
52
+ - "Rakefile"