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,59 @@
1
+ services:
2
+ postgres:
3
+ image: postgres:18
4
+ environment:
5
+ POSTGRES_USER: ${POSTGRES_USER:-postgres}
6
+ POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:-postgres}
7
+ POSTGRES_DB: ${POSTGRES_DB:-<%= app_name %>_development}
8
+ ports:
9
+ - "5432:5432"
10
+ volumes:
11
+ - postgres_data:/var/lib/postgresql
12
+ healthcheck:
13
+ test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER:-postgres}"]
14
+ interval: 5s
15
+ timeout: 5s
16
+ retries: 5
17
+
18
+ selenium:
19
+ image: selenium/standalone-chromium:latest
20
+ ports:
21
+ - "4444:4444"
22
+ - "7900:7900"
23
+ volumes:
24
+ - /dev/shm:/dev/shm
25
+
26
+ mailcatcher:
27
+ image: schickling/mailcatcher:latest
28
+ ports:
29
+ - "1080:1080"
30
+ - "1025:1025"
31
+
32
+ app:
33
+ build:
34
+ context: .
35
+ dockerfile: Dockerfile.dev
36
+ depends_on:
37
+ postgres:
38
+ condition: service_healthy
39
+ selenium:
40
+ condition: service_started
41
+ mailcatcher:
42
+ condition: service_started
43
+ environment:
44
+ DATABASE_HOST: ${DATABASE_HOST:-postgres}
45
+ DATABASE_PORT: ${DATABASE_PORT:-5432}
46
+ DATABASE_USER: ${DATABASE_USER:-postgres}
47
+ DATABASE_PASSWORD: ${DATABASE_PASSWORD:-postgres}
48
+ SELENIUM_URL: ${SELENIUM_URL:-http://selenium:4444/wd/hub}
49
+ MAILCATCHER_URL: ${MAILCATCHER_URL:-http://mailcatcher:1080}
50
+ ports:
51
+ - "3000:3000"
52
+ volumes:
53
+ - .:/rails
54
+ - bundle_cache:/usr/local/bundle
55
+ command: bin/docker-entrypoint
56
+
57
+ volumes:
58
+ postgres_data:
59
+ bundle_cache:
@@ -0,0 +1,24 @@
1
+ FROM ruby:<%= RUBY_VERSION %>
2
+
3
+ RUN apt-get update -qq && apt-get install -y \
4
+ build-essential \
5
+ libpq-dev \
6
+ libvips \
7
+ nodejs \
8
+ npm \
9
+ git \
10
+ curl \
11
+ && rm -rf /var/lib/apt/lists/*
12
+
13
+ WORKDIR /rails
14
+
15
+ RUN gem install bundler
16
+
17
+ COPY Gemfile Gemfile.lock ./
18
+ RUN bundle install
19
+
20
+ COPY . .
21
+
22
+ EXPOSE 3000
23
+
24
+ CMD ["bin/docker-entrypoint"]
@@ -0,0 +1,46 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "develoz"
4
+
5
+ module Develoz
6
+ module Generators
7
+ class DocsRenderGenerator < Develoz::Generators::Base
8
+ def self.source_root
9
+ File.expand_path("templates", __dir__)
10
+ end
11
+
12
+ def add_docs_gems
13
+ add_gem "redcarpet"
14
+ add_gem "rouge"
15
+ end
16
+
17
+ def create_docs_controller
18
+ template "app/controllers/docs_controller.rb.tt", "app/controllers/docs_controller.rb"
19
+ end
20
+
21
+ def create_document_model
22
+ template "app/models/document.rb.tt", "app/models/document.rb"
23
+ end
24
+
25
+ def create_docs_views
26
+ template "app/views/docs/show.html.erb.tt", "app/views/docs/show.html.erb"
27
+ end
28
+
29
+ def create_docs_javascript
30
+ template "app/javascript/docs.js.tt", "app/javascript/docs.js"
31
+ end
32
+
33
+ def create_docs_stylesheet
34
+ template "app/assets/stylesheets/documentation.scss.tt", "app/assets/stylesheets/documentation.scss"
35
+ end
36
+
37
+ def create_redcarpet_rouge_initializer
38
+ template "config/initializers/redcarpet_rouge.rb.tt", "config/initializers/redcarpet_rouge.rb"
39
+ end
40
+
41
+ def insert_docs_route
42
+ insert_route 'get "docs(/*id)" => "docs#show"'
43
+ end
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,145 @@
1
+ section.documentation {
2
+ table {
3
+ margin-bottom: 20px;
4
+
5
+ th,
6
+ td {
7
+ border: 1px solid #dee2e6;
8
+ padding: 5px;
9
+ }
10
+ }
11
+
12
+ .box-body {
13
+ padding: 10px 30px;
14
+ max-width: 1200px;
15
+
16
+ h1,
17
+ h2,
18
+ h3,
19
+ h4 {
20
+ margin-top: 40px;
21
+ }
22
+
23
+ pre.code-block {
24
+ margin-bottom: 20px;
25
+ padding: 16px 20px;
26
+ overflow-x: auto;
27
+ font-size: 0.875rem;
28
+ line-height: 1.5;
29
+ color: #c9d1d9;
30
+ background-color: #161b22;
31
+ border: 1px solid #30363d;
32
+ border-radius: 8px;
33
+
34
+ code {
35
+ padding: 0;
36
+ color: inherit;
37
+ white-space: pre;
38
+ background-color: transparent;
39
+ border: 0;
40
+ }
41
+ }
42
+
43
+ :not(pre) > code {
44
+ padding: 0.2em 0.4em;
45
+ font-size: 0.875em;
46
+ color: #24292f;
47
+ background-color: rgb(175 184 193 / 20%);
48
+ border-radius: 6px;
49
+
50
+ .dark & {
51
+ color: #c9d1d9;
52
+ }
53
+ }
54
+
55
+ pre.mermaid {
56
+ margin-bottom: 20px;
57
+ padding: 0;
58
+ text-align: center;
59
+ background: transparent;
60
+ border: 0;
61
+
62
+ &:not([data-processed="true"]) {
63
+ visibility: hidden;
64
+ }
65
+ }
66
+
67
+ blockquote {
68
+ margin: 20px 0;
69
+ padding: 12px 16px;
70
+ color: #495057;
71
+ background: #f8f9fa;
72
+ border-left: 4px solid #0d6efd;
73
+ border-radius: 0 6px 6px 0;
74
+
75
+ p:last-child {
76
+ margin-bottom: 0;
77
+ }
78
+
79
+ &.callout {
80
+ --callout-accent: #57606a;
81
+ --callout-background: #f6f8fa;
82
+ --callout-foreground: #24292f;
83
+
84
+ color: var(--callout-foreground);
85
+ background: var(--callout-background);
86
+ border-left-color: var(--callout-accent);
87
+
88
+ .dark & {
89
+ --callout-background: #161b22;
90
+ --callout-foreground: #c9d1d9;
91
+ }
92
+ }
93
+
94
+ &.callout-note {
95
+ --callout-accent: #0969da;
96
+
97
+ .dark & {
98
+ --callout-accent: #58a6ff;
99
+ }
100
+ }
101
+
102
+ &.callout-tip {
103
+ --callout-accent: #1a7f37;
104
+
105
+ .dark & {
106
+ --callout-accent: #3fb950;
107
+ }
108
+ }
109
+
110
+ &.callout-important {
111
+ --callout-accent: #8250df;
112
+
113
+ .dark & {
114
+ --callout-accent: #a371f7;
115
+ }
116
+ }
117
+
118
+ &.callout-warning {
119
+ --callout-accent: #9a6700;
120
+
121
+ .dark & {
122
+ --callout-accent: #d29922;
123
+ }
124
+ }
125
+
126
+ &.callout-caution {
127
+ --callout-accent: #cf222e;
128
+
129
+ .dark & {
130
+ --callout-accent: #f85149;
131
+ }
132
+ }
133
+
134
+ .callout-title {
135
+ margin-bottom: 8px;
136
+ font-weight: 600;
137
+ color: var(--callout-accent);
138
+
139
+ .fa {
140
+ margin-right: 6px;
141
+ }
142
+ }
143
+ }
144
+ }
145
+ }
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ class DocsController < ApplicationController
4
+ def show
5
+ @document = Document.find(params[:id] || "README")
6
+ end
7
+ end
@@ -0,0 +1,15 @@
1
+ import mermaid from 'mermaid'
2
+
3
+ mermaid.initialize({
4
+ startOnLoad: false,
5
+ securityLevel: 'strict',
6
+ theme: window.darkMode ? 'dark' : 'default',
7
+ })
8
+
9
+ const renderDiagrams = () => {
10
+ const nodes = document.querySelectorAll('pre.mermaid:not([data-processed="true"])')
11
+ if (nodes.length) mermaid.run({ nodes })
12
+ }
13
+
14
+ document.addEventListener('turbo:load', renderDiagrams)
15
+ if (document.readyState === 'complete') renderDiagrams()
@@ -0,0 +1,189 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Document
4
+ include ActiveModel::Model
5
+
6
+ class RecordNotFound < StandardError; end
7
+
8
+ FOLDER_INDEX_NAMES = [ "README", "00 - Index" ].freeze
9
+
10
+ class Renderer < Redcarpet::Render::HTML
11
+ include ActionView::Helpers::TagHelper
12
+ include ActionView::Helpers::SanitizeHelper
13
+
14
+ # Obsidian wikilink: [[Note]], [[Note#Heading]], [[Note|Alias]] (and combinations).
15
+ WIKILINK = /\[\[([^\]#|]+)(?:#([^\]|]+))?(?:\|([^\]]+))?\]\]/
16
+ CALLOUT = /\A<p>\[!(NOTE|TIP|IMPORTANT|WARNING|CAUTION)\](?:<br>\s*)?/i
17
+ CALLOUT_ICONS = {
18
+ "note" => "info-circle",
19
+ "tip" => "lightbulb-o",
20
+ "important" => "exclamation-circle",
21
+ "warning" => "warning",
22
+ "caution" => "ban"
23
+ }.freeze
24
+
25
+ def self.render(path, base_dir: nil)
26
+ new(base_dir:).render(path)
27
+ end
28
+
29
+ def initialize(base_dir: nil, **options)
30
+ @base_dir = base_dir
31
+ super(options.merge(with_toc_data: true, hard_wrap: true))
32
+ end
33
+
34
+ def sanitize(html)
35
+ tags = ActionView::Base.sanitized_allowed_tags + %w[table thead tbody th tr td img i]
36
+ attributes = ActionView::Base.sanitized_allowed_attributes + %w[style colspan src alt id class aria-hidden]
37
+ super(html, tags:, attributes:)
38
+ end
39
+
40
+ def markdown
41
+ @markdown ||= Redcarpet::Markdown.new(
42
+ self,
43
+ fenced_code_blocks: true,
44
+ tables: true,
45
+ autolink: true,
46
+ strikethrough: true
47
+ )
48
+ end
49
+
50
+ def render(path)
51
+ sanitize(markdown.render(File.read(path)))
52
+ end
53
+
54
+ # Convert Obsidian wikilinks to markdown links before parsing, leaving any
55
+ # text inside fenced or inline code untouched.
56
+ def preprocess(document)
57
+ replace_wikilinks(document)
58
+ end
59
+
60
+ def block_code(code, language)
61
+ # Mermaid diagrams are rendered client-side: emit the raw source in a
62
+ # <pre class="mermaid"> for mermaid.js, never syntax-highlighted.
63
+ return content_tag(:pre, code, class: "mermaid") if language == "mermaid"
64
+
65
+ lexer = Rouge::Lexer.find_fancy(language.to_s, code) || Rouge::Lexers::PlainText.new
66
+ formatter = Rouge::Formatters::HTMLInline.new(Rouge::Themes::Github.mode(:dark).new)
67
+ code = formatter.format(lexer.lex(code))
68
+ content_tag(:pre, class: "code-block") do
69
+ content_tag(:code, sanitize(code), class: "language-#{lexer.tag}")
70
+ end
71
+ end
72
+
73
+ def image(link, title, alt_text)
74
+ tag(:img, src: link, alt: alt_text, title:, class: "img-responsive")
75
+ end
76
+
77
+ def block_quote(quote)
78
+ callout = quote.match(CALLOUT)
79
+
80
+ if callout
81
+ type = callout[1].downcase
82
+ quote = quote.sub(CALLOUT, "<p>")
83
+ icon = tag.i(class: "fa fa-#{CALLOUT_ICONS.fetch(type)}", aria: { hidden: true })
84
+ callout_title = %(<p class="callout-title">#{icon} #{type.titleize}</p>)
85
+
86
+ %(<blockquote class="callout callout-#{type}">#{callout_title}\n#{quote}</blockquote>)
87
+ else
88
+ %(<blockquote>#{quote}</blockquote>)
89
+ end
90
+ end
91
+
92
+ private
93
+
94
+ def replace_wikilinks(document)
95
+ in_fence = false
96
+
97
+ document.each_line.map do |line|
98
+ if line.start_with?("```")
99
+ in_fence = !in_fence
100
+ line
101
+ elsif in_fence
102
+ line
103
+ else
104
+ replace_wikilinks_in_line(line)
105
+ end
106
+ end.join
107
+ end
108
+
109
+ def replace_wikilinks_in_line(line)
110
+ line.split(/(`[^`]*`)/).map do |part|
111
+ if part.start_with?("`")
112
+ part
113
+ else
114
+ part.gsub(WIKILINK) do
115
+ wikilink_markdown(::Regexp.last_match(1), ::Regexp.last_match(2), ::Regexp.last_match(3))
116
+ end
117
+ end
118
+ end.join
119
+ end
120
+
121
+ def wikilink_markdown(name, heading, alias_text)
122
+ name = name.strip
123
+ label = alias_text&.strip
124
+ label ||= heading ? "#{name} > #{heading.strip}" : name
125
+ "[#{escape_markdown(label)}](#{wikilink_url(name, heading)})"
126
+ end
127
+
128
+ # Escape characters the link label would otherwise trigger as markdown
129
+ # (emphasis, links, code) so names like assign_preorder render literally.
130
+ def escape_markdown(text)
131
+ text.gsub(/[\\_*\[\]`]/) { |char| "\\#{char}" }
132
+ end
133
+
134
+ def wikilink_url(name, heading)
135
+ segments = [ @base_dir, name ].compact_blank
136
+ url = "/docs/#{segments.map { |segment| ERB::Util.url_encode(segment) }.join('/')}"
137
+ url += "##{heading_anchor(heading)}" if heading
138
+ url
139
+ end
140
+
141
+ # Mirror Redcarpet's with_toc_data anchor generation for [[Note#Heading]]:
142
+ # drop non-alphanumerics (underscores included), then collapse whitespace to "-".
143
+ def heading_anchor(heading)
144
+ heading.strip.downcase.gsub(/[^a-z0-9\s]/, "").gsub(/\s+/, "-").gsub(/\A-+|-+\z/, "")
145
+ end
146
+ end
147
+
148
+ attr_reader :id, :name, :path
149
+
150
+ class << self
151
+ def where(folder: nil, name: nil)
152
+ glob = folder ? "docs/#{folder}/" : "{docs/**/,}"
153
+ Rails.root.glob("#{glob}#{name || "*"}.md").sort.map { |path| new(path) }
154
+ end
155
+
156
+ alias all where
157
+ delegate :first, :last, to: :where
158
+
159
+ def find(name)
160
+ document = where(name:).first
161
+ document ||= FOLDER_INDEX_NAMES.lazy.filter_map { |index| where(folder: name, name: index).first }.first
162
+
163
+ return document if document
164
+
165
+ raise RecordNotFound, "Couldn't find document #{name}"
166
+ end
167
+ end
168
+
169
+ def initialize(path)
170
+ @path = path
171
+ relative_path = Pathname(path).relative_path_from(Rails.root).to_s
172
+ @id = relative_path.match(%r{\A(?:docs/)?(?<id>.*)\.md\z})&.[](:id)
173
+ @name = File.basename(path, ".md")
174
+ end
175
+
176
+ def render
177
+ Renderer.render(path, base_dir: File.dirname(id).then { |dir| dir == "." ? nil : dir })
178
+ end
179
+
180
+ def title
181
+ name.titleize
182
+ end
183
+
184
+ def breadcrumbs
185
+ parts = id.split("/")
186
+
187
+ parts.each_with_index.map { |part, i| { name: part.titleize, path: "/docs/#{parts[0..i].join('/')}" } }
188
+ end
189
+ end
@@ -0,0 +1,7 @@
1
+ <%% content_for :head do %>
2
+ <%%= javascript_include_tag 'docs', 'data-turbo-track': 'reload' %>
3
+ <%% end %>
4
+
5
+ <section class="content documentation">
6
+ <%%= @document.render %>
7
+ </section>
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Redcarpet and Rouge configuration for documentation rendering.
4
+ #
5
+ # The Document model (app/models/document.rb) creates its own
6
+ # Redcarpet::Markdown instance with the required extensions
7
+ # (fenced_code_blocks, tables, autolink, strikethrough) and uses
8
+ # Rouge::Formatters::HTMLInline with the Github dark theme for syntax
9
+ # highlighting of fenced code blocks.
10
+ #
11
+ # Mermaid diagrams are rendered client-side by app/javascript/docs.js,
12
+ # which imports the mermaid package and processes <pre class="mermaid">
13
+ # elements after each Turbo navigation.
14
+ #
15
+ # To use Mermaid, pin it in config/importmap.rb:
16
+ # pin "mermaid", to: "https://cdn.jsdelivr.net/npm/mermaid@11/dist/mermaid.esm.min.mjs"
@@ -0,0 +1,40 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "develoz"
4
+
5
+ module Develoz
6
+ module Generators
7
+ class FrontendCoreGenerator < Develoz::Generators::Base
8
+ def self.source_root
9
+ File.expand_path("templates", __dir__)
10
+ end
11
+
12
+ class_option :skip_pagy, type: :boolean, default: false
13
+
14
+ def add_frontend_gems
15
+ add_gem "importmap-rails"
16
+ add_gem "annotaterb", group: :development
17
+ end
18
+
19
+ def add_pagy_gem
20
+ return if options[:skip_pagy]
21
+
22
+ add_gem "pagy"
23
+ end
24
+
25
+ def create_importmap_config
26
+ template "config/importmap.rb.tt", "config/importmap.rb"
27
+ end
28
+
29
+ def create_pagy_initializer
30
+ return if options[:skip_pagy]
31
+
32
+ template "config/initializers/pagy.rb.tt", "config/initializers/pagy.rb"
33
+ end
34
+
35
+ def create_annotaterb_config
36
+ template ".annotaterb.yml.tt", ".annotaterb.yml"
37
+ end
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,16 @@
1
+ ---
2
+ :models: true
3
+ :routes: false
4
+ :active_admin: false
5
+ :additional_file_patterns: []
6
+ :exclude_tests: true
7
+ :exclude_fixtures: true
8
+ :exclude_factories: true
9
+ :exclude_serializers: true
10
+ :exclude_controllers: true
11
+ :exclude_helpers: true
12
+ :exclude_scaffolds: true
13
+ :classified_sort: true
14
+ :show_indexes: true
15
+ :show_foreign_keys: true
16
+ :position: after
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Pin npm packages by running ./bin/importmap
4
+
5
+ pin "application"
6
+ pin "@hotwired/turbo-rails", to: "turbo.min.js"
7
+ pin "@hotwired/stimulus", to: "stimulus.min.js"
8
+ pin "@hotwired/stimulus-loading", to: "stimulus-loading.js"
9
+ pin "trix", to: "https://esm.sh/trix@2.1.18" # @2.1.18
10
+ pin "@rails/actiontext", to: "@rails--actiontext.js" # @8.1.100
11
+ pin "@rails/activestorage", to: "activestorage.esm.js"
12
+ pin_all_from "app/javascript/controllers", under: "controllers"
@@ -0,0 +1,10 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Pagy initializer file
4
+ # See https://ddnexus.github.io/pagy/resources/initializer/
5
+
6
+ # Default items per page
7
+ Pagy::OPTIONS[:limit] = 25
8
+
9
+ # When you are done setting your own default freeze it
10
+ Pagy::OPTIONS.freeze
@@ -0,0 +1,51 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "develoz"
4
+
5
+ module Develoz
6
+ module Generators
7
+ class InstallGenerator < Develoz::Generators::Base
8
+ class_option :api, type: :boolean, default: false
9
+ class_option :auth, type: :boolean, default: false
10
+ class_option :pwa, type: :boolean, default: false
11
+ class_option :push, type: :boolean, default: false
12
+ class_option :active_resource, type: :boolean, default: false
13
+ class_option :admin, type: :boolean, default: false
14
+ class_option :ui, type: :boolean, default: false
15
+ class_option :kamal, type: :boolean, default: false
16
+ class_option :docker, type: :boolean, default: false
17
+ class_option :db_backup, type: :boolean, default: false
18
+ class_option :skip_pagy, type: :boolean, default: false
19
+
20
+ def install
21
+ develoz_options = build_options
22
+ Develoz::Manifest.for(develoz_options).each do |entry|
23
+ invoke_generator(entry.name, develoz_options.to_h)
24
+ end
25
+ end
26
+
27
+ private
28
+
29
+ OPTION_FLAGS = %i[api auth pwa push active_resource admin ui kamal docker db_backup skip_pagy].freeze
30
+ private_constant :OPTION_FLAGS
31
+
32
+ def build_options
33
+ flags = OPTION_FLAGS.index_with { |flag| options[flag] }
34
+ Develoz::Options.new(**flags, app_name: app_name)
35
+ end
36
+
37
+ def invoke_generator(name, generator_options)
38
+ require "generators/develoz/#{name}/#{name}_generator"
39
+ klass = Develoz::Generators.const_get("#{name.camelize}Generator")
40
+ gen = klass.new([], generator_options, destination_root: destination_root)
41
+ klass.public_instance_methods(false).each { |method| gen.public_send(method) }
42
+ rescue LoadError
43
+ say "generator develoz:#{name} not yet available", :yellow
44
+ rescue NameError => e
45
+ raise unless e.name.to_s == "#{name.camelize}Generator"
46
+
47
+ say "generator develoz:#{name} not yet available", :yellow
48
+ end
49
+ end
50
+ end
51
+ end