tsykvas_rails_template 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.
- checksums.yaml +7 -0
- data/CHANGELOG.md +200 -0
- data/CODE_OF_CONDUCT.md +10 -0
- data/LICENSE.txt +21 -0
- data/README.md +589 -0
- data/Rakefile +17 -0
- data/lib/generators/tsykvas_rails_template/companions/companions_generator.rb +273 -0
- data/lib/generators/tsykvas_rails_template/concept/concept_generator.rb +145 -0
- data/lib/generators/tsykvas_rails_template/concept/templates/component/edit.html.slim.tt +5 -0
- data/lib/generators/tsykvas_rails_template/concept/templates/component/edit.rb.tt +11 -0
- data/lib/generators/tsykvas_rails_template/concept/templates/component/index.html.slim.tt +5 -0
- data/lib/generators/tsykvas_rails_template/concept/templates/component/index.rb.tt +11 -0
- data/lib/generators/tsykvas_rails_template/concept/templates/component/new.html.slim.tt +5 -0
- data/lib/generators/tsykvas_rails_template/concept/templates/component/new.rb.tt +11 -0
- data/lib/generators/tsykvas_rails_template/concept/templates/component/show.html.slim.tt +4 -0
- data/lib/generators/tsykvas_rails_template/concept/templates/component/show.rb.tt +11 -0
- data/lib/generators/tsykvas_rails_template/concept/templates/controller.rb.tt +45 -0
- data/lib/generators/tsykvas_rails_template/concept/templates/operation/create.rb.tt +31 -0
- data/lib/generators/tsykvas_rails_template/concept/templates/operation/destroy.rb.tt +13 -0
- data/lib/generators/tsykvas_rails_template/concept/templates/operation/edit.rb.tt +10 -0
- data/lib/generators/tsykvas_rails_template/concept/templates/operation/index.rb.tt +9 -0
- data/lib/generators/tsykvas_rails_template/concept/templates/operation/new.rb.tt +10 -0
- data/lib/generators/tsykvas_rails_template/concept/templates/operation/show.rb.tt +10 -0
- data/lib/generators/tsykvas_rails_template/concept/templates/operation/update.rb.tt +31 -0
- data/lib/generators/tsykvas_rails_template/install/bootstrap_installer.rb +225 -0
- data/lib/generators/tsykvas_rails_template/install/install_generator.rb +298 -0
- data/lib/generators/tsykvas_rails_template/install/templates/.claude/agents/buddy.md +157 -0
- data/lib/generators/tsykvas_rails_template/install/templates/.claude/agents/code-reviewer.md +117 -0
- data/lib/generators/tsykvas_rails_template/install/templates/.claude/agents/security-reviewer.md +113 -0
- data/lib/generators/tsykvas_rails_template/install/templates/.claude/agents/tech-lead.md +150 -0
- data/lib/generators/tsykvas_rails_template/install/templates/.claude/commands/check.md +51 -0
- data/lib/generators/tsykvas_rails_template/install/templates/.claude/commands/code-review.md +60 -0
- data/lib/generators/tsykvas_rails_template/install/templates/.claude/commands/docs-create.md +102 -0
- data/lib/generators/tsykvas_rails_template/install/templates/.claude/commands/pr-review.md +81 -0
- data/lib/generators/tsykvas_rails_template/install/templates/.claude/commands/pushit.md +160 -0
- data/lib/generators/tsykvas_rails_template/install/templates/.claude/commands/refactor.md +132 -0
- data/lib/generators/tsykvas_rails_template/install/templates/.claude/commands/task-sum.md +47 -0
- data/lib/generators/tsykvas_rails_template/install/templates/.claude/commands/tests.md +67 -0
- data/lib/generators/tsykvas_rails_template/install/templates/.claude/commands/tsykvas-claude.md +262 -0
- data/lib/generators/tsykvas_rails_template/install/templates/.claude/commands/update-docs.md +78 -0
- data/lib/generators/tsykvas_rails_template/install/templates/.claude/commands/update-rules.md +102 -0
- data/lib/generators/tsykvas_rails_template/install/templates/.claude/commands/update-tests.md +135 -0
- data/lib/generators/tsykvas_rails_template/install/templates/.claude/docs/architecture.md +315 -0
- data/lib/generators/tsykvas_rails_template/install/templates/.claude/docs/authentication.md +96 -0
- data/lib/generators/tsykvas_rails_template/install/templates/.claude/docs/background-jobs.md +135 -0
- data/lib/generators/tsykvas_rails_template/install/templates/.claude/docs/code-style.md +101 -0
- data/lib/generators/tsykvas_rails_template/install/templates/.claude/docs/commands.md +34 -0
- data/lib/generators/tsykvas_rails_template/install/templates/.claude/docs/companions.md +128 -0
- data/lib/generators/tsykvas_rails_template/install/templates/.claude/docs/concepts-refactoring.md +194 -0
- data/lib/generators/tsykvas_rails_template/install/templates/.claude/docs/database.md +135 -0
- data/lib/generators/tsykvas_rails_template/install/templates/.claude/docs/deployment.md +138 -0
- data/lib/generators/tsykvas_rails_template/install/templates/.claude/docs/design-system.md +322 -0
- data/lib/generators/tsykvas_rails_template/install/templates/.claude/docs/documentation.md +89 -0
- data/lib/generators/tsykvas_rails_template/install/templates/.claude/docs/forms.md +174 -0
- data/lib/generators/tsykvas_rails_template/install/templates/.claude/docs/i18n.md +165 -0
- data/lib/generators/tsykvas_rails_template/install/templates/.claude/docs/routing-and-namespaces.md +114 -0
- data/lib/generators/tsykvas_rails_template/install/templates/.claude/docs/security.md +122 -0
- data/lib/generators/tsykvas_rails_template/install/templates/.claude/docs/stimulus-controllers.md +166 -0
- data/lib/generators/tsykvas_rails_template/install/templates/.claude/docs/testing-examples.md +180 -0
- data/lib/generators/tsykvas_rails_template/install/templates/.claude/docs/testing.md +117 -0
- data/lib/generators/tsykvas_rails_template/install/templates/.claude/docs/tsykvas_rails_template.md +280 -0
- data/lib/generators/tsykvas_rails_template/install/templates/.claude/docs/ui-components.md +196 -0
- data/lib/generators/tsykvas_rails_template/install/templates/CLAUDE.md.tt +81 -0
- data/lib/generators/tsykvas_rails_template/install/templates/app/concepts/base/component/base.rb +6 -0
- data/lib/generators/tsykvas_rails_template/install/templates/app/concepts/base/operation/base.rb +124 -0
- data/lib/generators/tsykvas_rails_template/install/templates/app/concepts/base/operation/result.rb +56 -0
- data/lib/generators/tsykvas_rails_template/install/templates/app/concepts/home/component/index.html.slim +49 -0
- data/lib/generators/tsykvas_rails_template/install/templates/app/concepts/home/component/index.rb +11 -0
- data/lib/generators/tsykvas_rails_template/install/templates/app/concepts/home/operation/index.rb +17 -0
- data/lib/generators/tsykvas_rails_template/install/templates/app/controllers/concerns/operations_methods.rb +148 -0
- data/lib/generators/tsykvas_rails_template/install/templates/app/controllers/home_controller.rb +10 -0
- data/lib/generators/tsykvas_rails_template/install/templates/app/policies/application_policy.rb +33 -0
- data/lib/generators/tsykvas_rails_template/install/templates/app/policies/home_policy.rb +8 -0
- data/lib/tasks/tsykvas.rake +11 -0
- data/lib/tsykvas_rails_template/probe.rb +236 -0
- data/lib/tsykvas_rails_template/railtie.rb +13 -0
- data/lib/tsykvas_rails_template/version.rb +5 -0
- data/lib/tsykvas_rails_template.rb +18 -0
- metadata +183 -0
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module OperationsMethods
|
|
4
|
+
include ActionView::Helpers::JavaScriptHelper
|
|
5
|
+
extend ActiveSupport::Concern
|
|
6
|
+
|
|
7
|
+
protected
|
|
8
|
+
|
|
9
|
+
def endpoint(operation, component = nil, &block)
|
|
10
|
+
result = operation.call(params:, current_user: try(:current_user))
|
|
11
|
+
|
|
12
|
+
check_authorization_is_called result
|
|
13
|
+
|
|
14
|
+
# Call custom block if provided (useful for Devise sign-in, etc.)
|
|
15
|
+
block.call(result) if block_given?
|
|
16
|
+
|
|
17
|
+
respond_to do |format|
|
|
18
|
+
format.html do
|
|
19
|
+
if action_name == "create" || action_name == "update" || action_name.include?("destroy")
|
|
20
|
+
if result.success? || (result.failure? && action_name.include?("destroy"))
|
|
21
|
+
flash[:notice] = result.message if result.message.present?
|
|
22
|
+
flash[:alert] = result.error_message if result.error_message.present?
|
|
23
|
+
path = result.redirect_path || public_send("#{controller_name}_path")
|
|
24
|
+
redirect_to path
|
|
25
|
+
else
|
|
26
|
+
flash[:alert] = result.error_message if result.error_message.present?
|
|
27
|
+
params = if result.model.is_a?(::OpenStruct)
|
|
28
|
+
result.model.to_h
|
|
29
|
+
else
|
|
30
|
+
key = operation.to_s.split("::").first.underscore
|
|
31
|
+
{ "#{key}": result.model }
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
render component.new(**params), status: :unprocessable_content
|
|
35
|
+
end
|
|
36
|
+
elsif action_name == "index"
|
|
37
|
+
flash[:notice] = result.message if result.message.present?
|
|
38
|
+
flash[:alert] = result.error_message if result.error_message.present?
|
|
39
|
+
params = if result.model.is_a?(::OpenStruct)
|
|
40
|
+
result.model.to_h
|
|
41
|
+
else
|
|
42
|
+
key = operation.to_s.split("::").first.underscore.pluralize
|
|
43
|
+
{ "#{key}": result.model }
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
render component.new(**params)
|
|
47
|
+
elsif action_name == "show"
|
|
48
|
+
flash[:notice] = result.message if result.message.present?
|
|
49
|
+
flash[:alert] = result.error_message if result.error_message.present?
|
|
50
|
+
params = if result.model.is_a?(::OpenStruct)
|
|
51
|
+
result.model.to_h
|
|
52
|
+
else
|
|
53
|
+
key = operation.to_s.split("::").first.underscore
|
|
54
|
+
{ "#{key}": result.model }
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
render component.new(**params)
|
|
58
|
+
elsif action_name == "edit" || action_name == "new"
|
|
59
|
+
flash[:notice] = result.message if result.message.present?
|
|
60
|
+
flash[:alert] = result.error_message if result.error_message.present?
|
|
61
|
+
params = if result.model.is_a?(::OpenStruct)
|
|
62
|
+
result.model.to_h
|
|
63
|
+
else
|
|
64
|
+
key = operation.to_s.split("::").first.underscore
|
|
65
|
+
{ "#{key}": result.model }
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
render component.new(**params)
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
# Used for rendering new/edit modals via JS.
|
|
73
|
+
#
|
|
74
|
+
# Assumes Bootstrap modals are available globally as `window.bootstrap.Modal`.
|
|
75
|
+
# If your app uses a different modal library (or none), the inline JS below
|
|
76
|
+
# gracefully no-ops on the dismiss step. Replace this branch entirely if
|
|
77
|
+
# you ship your own modal stack.
|
|
78
|
+
format.js do
|
|
79
|
+
params = if result.model.is_a?(::OpenStruct)
|
|
80
|
+
result.model.to_h
|
|
81
|
+
else
|
|
82
|
+
key = operation.to_s.split("::").first.underscore
|
|
83
|
+
{ "#{key}": result.model }
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
if result.success? && (action_name == "create" || action_name == "update" || action_name.include?("destroy"))
|
|
87
|
+
flash[:notice] = result.message if result.message.present?
|
|
88
|
+
flash[:alert] = result.error_message if result.error_message.present?
|
|
89
|
+
path = result.redirect_path || public_send("#{controller_name}_path")
|
|
90
|
+
render js: "window.location.href='#{path}'"
|
|
91
|
+
else
|
|
92
|
+
modal = render_to_string(component.new(**params), layout: false)
|
|
93
|
+
render js: <<~JS
|
|
94
|
+
var activeModal = document.querySelector('.modal.show');
|
|
95
|
+
if (activeModal && window.bootstrap && window.bootstrap.Modal) {
|
|
96
|
+
window.bootstrap.Modal.getOrCreateInstance(activeModal).hide();
|
|
97
|
+
}
|
|
98
|
+
var modalsContainer = document.getElementById('modals');
|
|
99
|
+
if (modalsContainer) {
|
|
100
|
+
modalsContainer.innerHTML = "";
|
|
101
|
+
var renderedHtml = "#{escape_javascript(modal)}";
|
|
102
|
+
var tempContainer = document.createElement("div");
|
|
103
|
+
tempContainer.innerHTML = renderedHtml;
|
|
104
|
+
if (tempContainer.firstElementChild) {
|
|
105
|
+
modalsContainer.appendChild(tempContainer.firstElementChild);
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
JS
|
|
109
|
+
end
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
# Used for select2 search results.
|
|
113
|
+
format.json do
|
|
114
|
+
collection = if result.model.is_a?(::OpenStruct)
|
|
115
|
+
key = operation.to_s.split("::").first.underscore.pluralize
|
|
116
|
+
result.model[key]
|
|
117
|
+
else
|
|
118
|
+
result.model
|
|
119
|
+
end
|
|
120
|
+
render json: {
|
|
121
|
+
result: collection.map(&:select2_search_result),
|
|
122
|
+
pagination: {
|
|
123
|
+
more: collection.respond_to?(:next_page) && collection.next_page.present?
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
# Fallback for auto-submit controllers that send null requests.
|
|
129
|
+
format.any do
|
|
130
|
+
flash[:notice] = result.message if result.message.present?
|
|
131
|
+
flash[:alert] = result.error_message if result.error_message.present?
|
|
132
|
+
params = if result.model.is_a?(::OpenStruct)
|
|
133
|
+
result.model.to_h
|
|
134
|
+
else
|
|
135
|
+
key = operation.to_s.split("::").first.underscore.pluralize
|
|
136
|
+
{ "#{key}": result.model }
|
|
137
|
+
end
|
|
138
|
+
render component.new(**params)
|
|
139
|
+
end
|
|
140
|
+
end
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
def check_authorization_is_called(result)
|
|
144
|
+
skip_authorization if result[:pundit] || result["policy.run"] || result.failure?
|
|
145
|
+
skip_policy_scope if result[:pundit_scope] || result.failure?
|
|
146
|
+
result[:model]
|
|
147
|
+
end
|
|
148
|
+
end
|
data/lib/generators/tsykvas_rails_template/install/templates/app/controllers/home_controller.rb
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# Generated by tsykvas_rails_template:install. Canonical example of the
|
|
4
|
+
# `endpoint` DSL: controllers stay one-liners, business logic lives in the
|
|
5
|
+
# operation, presentation in the component, authorization in the policy.
|
|
6
|
+
class HomeController < ApplicationController
|
|
7
|
+
def index
|
|
8
|
+
endpoint Home::Operation::Index, Home::Component::Index
|
|
9
|
+
end
|
|
10
|
+
end
|
data/lib/generators/tsykvas_rails_template/install/templates/app/policies/application_policy.rb
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
class ApplicationPolicy
|
|
4
|
+
attr_reader :user, :record
|
|
5
|
+
|
|
6
|
+
def initialize(user, record)
|
|
7
|
+
@user = user
|
|
8
|
+
@record = record
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def index? = false
|
|
12
|
+
def show? = false
|
|
13
|
+
def create? = false
|
|
14
|
+
def new? = create?
|
|
15
|
+
def update? = false
|
|
16
|
+
def edit? = update?
|
|
17
|
+
def destroy? = false
|
|
18
|
+
|
|
19
|
+
class Scope
|
|
20
|
+
def initialize(user, scope)
|
|
21
|
+
@user = user
|
|
22
|
+
@scope = scope
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def resolve
|
|
26
|
+
raise NotImplementedError, "You must define #resolve in #{self.class}"
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
private
|
|
30
|
+
|
|
31
|
+
attr_reader :user, :scope
|
|
32
|
+
end
|
|
33
|
+
end
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# Canonical example policy paired with Home::Operation::Index. The home
|
|
4
|
+
# page is public, so `index?` returns true unconditionally. Replace with
|
|
5
|
+
# real authorization rules when the home page is no longer a placeholder.
|
|
6
|
+
class HomePolicy < ApplicationPolicy
|
|
7
|
+
def index? = true
|
|
8
|
+
end
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "json"
|
|
4
|
+
require "tsykvas_rails_template/probe"
|
|
5
|
+
|
|
6
|
+
namespace :tsykvas do
|
|
7
|
+
desc "Print a deterministic JSON inventory of the host project (consumed by /tsykvas-claude)"
|
|
8
|
+
task :probe do
|
|
9
|
+
puts JSON.pretty_generate(TsykvasRailsTemplate::Probe.run)
|
|
10
|
+
end
|
|
11
|
+
end
|
|
@@ -0,0 +1,236 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "pathname"
|
|
4
|
+
require "yaml"
|
|
5
|
+
|
|
6
|
+
module TsykvasRailsTemplate
|
|
7
|
+
# Deterministic project inventory for `/tsykvas-claude`.
|
|
8
|
+
#
|
|
9
|
+
# Reads files from a Rails app root and returns a structured Hash that the
|
|
10
|
+
# slash command can consume verbatim — keeps the LLM out of the inventory
|
|
11
|
+
# step so what's "true about the project" is testable and stable run-to-run.
|
|
12
|
+
class Probe
|
|
13
|
+
SCHEMA_VERSION = 2
|
|
14
|
+
|
|
15
|
+
def self.run(root: Dir.pwd)
|
|
16
|
+
new(root).run
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def initialize(root)
|
|
20
|
+
@root = Pathname(root)
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def run
|
|
24
|
+
{
|
|
25
|
+
schema_version: SCHEMA_VERSION,
|
|
26
|
+
gem_version: TsykvasRailsTemplate::VERSION,
|
|
27
|
+
root: root.to_s,
|
|
28
|
+
ruby_version: detect_ruby_version,
|
|
29
|
+
rails_version: detect_rails_version,
|
|
30
|
+
default_branch: detect_default_branch,
|
|
31
|
+
api_only: detect_api_only,
|
|
32
|
+
engine_host: detect_engine_host,
|
|
33
|
+
template_engine: detect_template_engine,
|
|
34
|
+
auth: detect_auth,
|
|
35
|
+
authorization: detect_authorization,
|
|
36
|
+
has_api_v1: detect_api_v1,
|
|
37
|
+
has_bootstrap: detect_bootstrap,
|
|
38
|
+
test_framework: detect_test_framework,
|
|
39
|
+
background_jobs: detect_background_jobs,
|
|
40
|
+
databases: detect_databases,
|
|
41
|
+
concept_folders: detect_concept_folders,
|
|
42
|
+
application_controller_includes: detect_application_controller_includes
|
|
43
|
+
}
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
private
|
|
47
|
+
|
|
48
|
+
attr_reader :root
|
|
49
|
+
|
|
50
|
+
def gemfile_lock
|
|
51
|
+
@gemfile_lock ||= read("Gemfile.lock").to_s
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def routes_rb
|
|
55
|
+
@routes_rb ||= read("config/routes.rb").to_s
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def application_rb
|
|
59
|
+
@application_rb ||= read("config/application.rb").to_s
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def application_controller_rb
|
|
63
|
+
@application_controller_rb ||= read("app/controllers/application_controller.rb").to_s
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
def detect_ruby_version
|
|
67
|
+
ruby_file = read(".ruby-version")
|
|
68
|
+
return ruby_file.strip.sub(/^ruby[ \t-]+/, "") if ruby_file
|
|
69
|
+
|
|
70
|
+
gemfile_lock[/RUBY VERSION\s+ruby (\S+)/, 1]
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
def detect_rails_version
|
|
74
|
+
gemfile_lock[/^\s+rails \((\S+)\)/, 1]
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
def detect_default_branch
|
|
78
|
+
head = read(".git/HEAD")
|
|
79
|
+
return head[%r{ref: refs/heads/(\S+)}, 1] if head
|
|
80
|
+
|
|
81
|
+
nil
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
# config.api_only = true → host is API-only (no template engine, no AssetPipeline)
|
|
85
|
+
def detect_api_only
|
|
86
|
+
application_rb.match?(/config\.api_only\s*=\s*true/)
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
# `class XxxApplication < Rails::Engine` instead of `< Rails::Application` → host is an engine
|
|
90
|
+
def detect_engine_host
|
|
91
|
+
application_rb.match?(/<\s*Rails::Engine\b/)
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
def detect_template_engine
|
|
95
|
+
return nil if detect_api_only
|
|
96
|
+
return :slim if gem?("slim-rails") || gem?("slim")
|
|
97
|
+
return :haml if gem?("haml-rails") || gem?("haml")
|
|
98
|
+
|
|
99
|
+
:erb
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
# Returns a structured Hash with detected auth signals.
|
|
103
|
+
#
|
|
104
|
+
# `method` is a coarse classification: :devise, :devise_omniauth, :custom,
|
|
105
|
+
# :basic_auth, :jwt, :warden, or :none. Multiple flags can be true.
|
|
106
|
+
def detect_auth
|
|
107
|
+
flags = auth_flags
|
|
108
|
+
flags[:method] = classify_auth_method(flags)
|
|
109
|
+
flags
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
AUTH_METHOD_PRIORITY = %i[devise warden jwt basic_auth custom_current_user].freeze
|
|
113
|
+
private_constant :AUTH_METHOD_PRIORITY
|
|
114
|
+
|
|
115
|
+
def auth_flags
|
|
116
|
+
ac = application_controller_rb
|
|
117
|
+
{
|
|
118
|
+
devise: gem?("devise"),
|
|
119
|
+
omniauth: gem?("omniauth"),
|
|
120
|
+
omniauth_openid_connect: gem?("omniauth_openid_connect"),
|
|
121
|
+
warden: gem?("warden") || ac.include?("Warden::"),
|
|
122
|
+
jwt: gem?("jwt") || ac.include?("JsonWebToken") || ac.include?("decode_jwt"),
|
|
123
|
+
basic_auth: ac.include?("authenticate_or_request_with_http_basic"),
|
|
124
|
+
custom_current_user: ac.match?(/^\s*def\s+current_user\b/) ||
|
|
125
|
+
ac.match?(/helper_method\s+.*:current_user/)
|
|
126
|
+
}
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
def classify_auth_method(flags)
|
|
130
|
+
return :devise_omniauth if flags[:devise] && flags[:omniauth_openid_connect]
|
|
131
|
+
|
|
132
|
+
hit = AUTH_METHOD_PRIORITY.find { |key| flags[key] }
|
|
133
|
+
return :none unless hit
|
|
134
|
+
|
|
135
|
+
hit == :custom_current_user ? :custom : hit
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
def detect_authorization
|
|
139
|
+
return :pundit if gem?("pundit")
|
|
140
|
+
return :action_policy if gem?("action_policy")
|
|
141
|
+
return :cancancan if gem?("cancancan") || gem?("cancan")
|
|
142
|
+
|
|
143
|
+
:none
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
def detect_api_v1
|
|
147
|
+
return true if routes_rb.match?(/namespace\s+:api\b/) &&
|
|
148
|
+
routes_rb.match?(/namespace\s+:v1\b/)
|
|
149
|
+
return true if routes_rb.match?(%r{["']/?api/v1["']})
|
|
150
|
+
|
|
151
|
+
Dir.glob(root.join("app/controllers/api/v1/**/*.rb").to_s).any?
|
|
152
|
+
end
|
|
153
|
+
|
|
154
|
+
def detect_bootstrap
|
|
155
|
+
return true if gem?("bootstrap") || gem?("bootstrap-rubygem")
|
|
156
|
+
return true if file_contains?("config/importmap.rb", /bootstrap/i)
|
|
157
|
+
|
|
158
|
+
file_contains?("package.json", /"bootstrap"\s*:/)
|
|
159
|
+
end
|
|
160
|
+
|
|
161
|
+
def detect_test_framework
|
|
162
|
+
return :rspec if gem?("rspec-rails") || gem?("rspec")
|
|
163
|
+
|
|
164
|
+
:minitest
|
|
165
|
+
end
|
|
166
|
+
|
|
167
|
+
def detect_background_jobs
|
|
168
|
+
[
|
|
169
|
+
(:solid_queue if gem?("solid_queue")),
|
|
170
|
+
(:sidekiq if gem?("sidekiq")),
|
|
171
|
+
(:good_job if gem?("good_job")),
|
|
172
|
+
(:delayed_job if gem?("delayed_job")),
|
|
173
|
+
(:resque if gem?("resque")),
|
|
174
|
+
(:que if gem?("que"))
|
|
175
|
+
].compact
|
|
176
|
+
end
|
|
177
|
+
|
|
178
|
+
# Detect databases from config/database.yml top-level keys (multi-DB-aware).
|
|
179
|
+
# Returns a list of names like ["primary", "queue", "cache", "cable"]; falls
|
|
180
|
+
# back to ["primary"] when only the legacy single-DB shape is present.
|
|
181
|
+
def detect_databases
|
|
182
|
+
raw = read("config/database.yml")
|
|
183
|
+
return [] unless raw
|
|
184
|
+
|
|
185
|
+
parsed = safe_yaml(raw)
|
|
186
|
+
return [] unless parsed.is_a?(Hash)
|
|
187
|
+
|
|
188
|
+
env = parsed["development"] || parsed[parsed.keys.first]
|
|
189
|
+
return ["primary"] unless env.is_a?(Hash)
|
|
190
|
+
|
|
191
|
+
keys = env.keys
|
|
192
|
+
keys.all? { |k| env[k].is_a?(Hash) } ? keys.sort : ["primary"]
|
|
193
|
+
end
|
|
194
|
+
|
|
195
|
+
def detect_concept_folders
|
|
196
|
+
base = root.join("app/concepts")
|
|
197
|
+
return [] unless base.directory?
|
|
198
|
+
|
|
199
|
+
base.children
|
|
200
|
+
.select(&:directory?)
|
|
201
|
+
.map { |p| p.basename.to_s }
|
|
202
|
+
.reject { |n| n == "base" }
|
|
203
|
+
.sort
|
|
204
|
+
end
|
|
205
|
+
|
|
206
|
+
def detect_application_controller_includes
|
|
207
|
+
application_controller_rb.scan(/^\s*include\s+([\w:]+)/).flatten
|
|
208
|
+
end
|
|
209
|
+
|
|
210
|
+
def gem?(name)
|
|
211
|
+
gemfile_lock.match?(/^\s+#{Regexp.escape(name)} \(/)
|
|
212
|
+
end
|
|
213
|
+
|
|
214
|
+
def file_contains?(rel, pattern)
|
|
215
|
+
contents = read(rel)
|
|
216
|
+
!!contents && contents.match?(pattern)
|
|
217
|
+
end
|
|
218
|
+
|
|
219
|
+
def read(rel)
|
|
220
|
+
path = root.join(rel)
|
|
221
|
+
return nil unless path.file?
|
|
222
|
+
|
|
223
|
+
path.read
|
|
224
|
+
rescue Errno::ENOENT
|
|
225
|
+
nil
|
|
226
|
+
end
|
|
227
|
+
|
|
228
|
+
def safe_yaml(raw)
|
|
229
|
+
# Strip ERB so YAML.safe_load doesn't choke on `<%= ENV[...] %>` etc.
|
|
230
|
+
stripped = raw.gsub(/<%=?.*?%>/m, "''")
|
|
231
|
+
YAML.safe_load(stripped, aliases: true) || {}
|
|
232
|
+
rescue Psych::SyntaxError, ArgumentError
|
|
233
|
+
{}
|
|
234
|
+
end
|
|
235
|
+
end
|
|
236
|
+
end
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "rails/railtie"
|
|
4
|
+
|
|
5
|
+
module TsykvasRailsTemplate
|
|
6
|
+
class Railtie < ::Rails::Railtie
|
|
7
|
+
railtie_name :tsykvas_rails_template
|
|
8
|
+
|
|
9
|
+
rake_tasks do
|
|
10
|
+
load File.expand_path("../tasks/tsykvas.rake", __dir__)
|
|
11
|
+
end
|
|
12
|
+
end
|
|
13
|
+
end
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "tsykvas_rails_template/version"
|
|
4
|
+
require_relative "tsykvas_rails_template/probe"
|
|
5
|
+
|
|
6
|
+
# Eager-require runtime deps so the host's `Bundler.require` (which only loads
|
|
7
|
+
# gems listed directly in its Gemfile) still picks them up transitively. Without
|
|
8
|
+
# this, `include Pundit::Authorization` and the ViewComponent/Slim references
|
|
9
|
+
# blow up at boot inside the host app.
|
|
10
|
+
require "pundit"
|
|
11
|
+
require "view_component"
|
|
12
|
+
require "slim-rails"
|
|
13
|
+
|
|
14
|
+
module TsykvasRailsTemplate
|
|
15
|
+
class Error < StandardError; end
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
require_relative "tsykvas_rails_template/railtie" if defined?(::Rails::Railtie)
|
metadata
ADDED
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: tsykvas_rails_template
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 0.1.0
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- Yurii Tsykvas
|
|
8
|
+
bindir: exe
|
|
9
|
+
cert_chain: []
|
|
10
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
|
11
|
+
dependencies:
|
|
12
|
+
- !ruby/object:Gem::Dependency
|
|
13
|
+
name: pundit
|
|
14
|
+
requirement: !ruby/object:Gem::Requirement
|
|
15
|
+
requirements:
|
|
16
|
+
- - ">="
|
|
17
|
+
- !ruby/object:Gem::Version
|
|
18
|
+
version: '2.3'
|
|
19
|
+
type: :runtime
|
|
20
|
+
prerelease: false
|
|
21
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
22
|
+
requirements:
|
|
23
|
+
- - ">="
|
|
24
|
+
- !ruby/object:Gem::Version
|
|
25
|
+
version: '2.3'
|
|
26
|
+
- !ruby/object:Gem::Dependency
|
|
27
|
+
name: rails
|
|
28
|
+
requirement: !ruby/object:Gem::Requirement
|
|
29
|
+
requirements:
|
|
30
|
+
- - ">="
|
|
31
|
+
- !ruby/object:Gem::Version
|
|
32
|
+
version: '7.1'
|
|
33
|
+
type: :runtime
|
|
34
|
+
prerelease: false
|
|
35
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
36
|
+
requirements:
|
|
37
|
+
- - ">="
|
|
38
|
+
- !ruby/object:Gem::Version
|
|
39
|
+
version: '7.1'
|
|
40
|
+
- !ruby/object:Gem::Dependency
|
|
41
|
+
name: slim-rails
|
|
42
|
+
requirement: !ruby/object:Gem::Requirement
|
|
43
|
+
requirements:
|
|
44
|
+
- - ">="
|
|
45
|
+
- !ruby/object:Gem::Version
|
|
46
|
+
version: '3.6'
|
|
47
|
+
type: :runtime
|
|
48
|
+
prerelease: false
|
|
49
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
50
|
+
requirements:
|
|
51
|
+
- - ">="
|
|
52
|
+
- !ruby/object:Gem::Version
|
|
53
|
+
version: '3.6'
|
|
54
|
+
- !ruby/object:Gem::Dependency
|
|
55
|
+
name: view_component
|
|
56
|
+
requirement: !ruby/object:Gem::Requirement
|
|
57
|
+
requirements:
|
|
58
|
+
- - ">="
|
|
59
|
+
- !ruby/object:Gem::Version
|
|
60
|
+
version: '3.0'
|
|
61
|
+
type: :runtime
|
|
62
|
+
prerelease: false
|
|
63
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
64
|
+
requirements:
|
|
65
|
+
- - ">="
|
|
66
|
+
- !ruby/object:Gem::Version
|
|
67
|
+
version: '3.0'
|
|
68
|
+
description: |
|
|
69
|
+
Installs an opinionated Rails skeleton: thin `endpoint Operation, Component`
|
|
70
|
+
controllers, plain-Ruby `Base::Operation::Base` + `Result` classes, a
|
|
71
|
+
ViewComponent-based `app/concepts/<feature>/{operation,component}/` layout,
|
|
72
|
+
and a `.claude/` directory pre-loaded with battle-tested slash commands,
|
|
73
|
+
subagents, and architecture docs. Ships generators to scaffold the host app
|
|
74
|
+
and to create new concept directories.
|
|
75
|
+
email:
|
|
76
|
+
- tsykvasyurii@gmail.com
|
|
77
|
+
executables: []
|
|
78
|
+
extensions: []
|
|
79
|
+
extra_rdoc_files: []
|
|
80
|
+
files:
|
|
81
|
+
- CHANGELOG.md
|
|
82
|
+
- CODE_OF_CONDUCT.md
|
|
83
|
+
- LICENSE.txt
|
|
84
|
+
- README.md
|
|
85
|
+
- Rakefile
|
|
86
|
+
- lib/generators/tsykvas_rails_template/companions/companions_generator.rb
|
|
87
|
+
- lib/generators/tsykvas_rails_template/concept/concept_generator.rb
|
|
88
|
+
- lib/generators/tsykvas_rails_template/concept/templates/component/edit.html.slim.tt
|
|
89
|
+
- lib/generators/tsykvas_rails_template/concept/templates/component/edit.rb.tt
|
|
90
|
+
- lib/generators/tsykvas_rails_template/concept/templates/component/index.html.slim.tt
|
|
91
|
+
- lib/generators/tsykvas_rails_template/concept/templates/component/index.rb.tt
|
|
92
|
+
- lib/generators/tsykvas_rails_template/concept/templates/component/new.html.slim.tt
|
|
93
|
+
- lib/generators/tsykvas_rails_template/concept/templates/component/new.rb.tt
|
|
94
|
+
- lib/generators/tsykvas_rails_template/concept/templates/component/show.html.slim.tt
|
|
95
|
+
- lib/generators/tsykvas_rails_template/concept/templates/component/show.rb.tt
|
|
96
|
+
- lib/generators/tsykvas_rails_template/concept/templates/controller.rb.tt
|
|
97
|
+
- lib/generators/tsykvas_rails_template/concept/templates/operation/create.rb.tt
|
|
98
|
+
- lib/generators/tsykvas_rails_template/concept/templates/operation/destroy.rb.tt
|
|
99
|
+
- lib/generators/tsykvas_rails_template/concept/templates/operation/edit.rb.tt
|
|
100
|
+
- lib/generators/tsykvas_rails_template/concept/templates/operation/index.rb.tt
|
|
101
|
+
- lib/generators/tsykvas_rails_template/concept/templates/operation/new.rb.tt
|
|
102
|
+
- lib/generators/tsykvas_rails_template/concept/templates/operation/show.rb.tt
|
|
103
|
+
- lib/generators/tsykvas_rails_template/concept/templates/operation/update.rb.tt
|
|
104
|
+
- lib/generators/tsykvas_rails_template/install/bootstrap_installer.rb
|
|
105
|
+
- lib/generators/tsykvas_rails_template/install/install_generator.rb
|
|
106
|
+
- lib/generators/tsykvas_rails_template/install/templates/.claude/agents/buddy.md
|
|
107
|
+
- lib/generators/tsykvas_rails_template/install/templates/.claude/agents/code-reviewer.md
|
|
108
|
+
- lib/generators/tsykvas_rails_template/install/templates/.claude/agents/security-reviewer.md
|
|
109
|
+
- lib/generators/tsykvas_rails_template/install/templates/.claude/agents/tech-lead.md
|
|
110
|
+
- lib/generators/tsykvas_rails_template/install/templates/.claude/commands/check.md
|
|
111
|
+
- lib/generators/tsykvas_rails_template/install/templates/.claude/commands/code-review.md
|
|
112
|
+
- lib/generators/tsykvas_rails_template/install/templates/.claude/commands/docs-create.md
|
|
113
|
+
- lib/generators/tsykvas_rails_template/install/templates/.claude/commands/pr-review.md
|
|
114
|
+
- lib/generators/tsykvas_rails_template/install/templates/.claude/commands/pushit.md
|
|
115
|
+
- lib/generators/tsykvas_rails_template/install/templates/.claude/commands/refactor.md
|
|
116
|
+
- lib/generators/tsykvas_rails_template/install/templates/.claude/commands/task-sum.md
|
|
117
|
+
- lib/generators/tsykvas_rails_template/install/templates/.claude/commands/tests.md
|
|
118
|
+
- lib/generators/tsykvas_rails_template/install/templates/.claude/commands/tsykvas-claude.md
|
|
119
|
+
- lib/generators/tsykvas_rails_template/install/templates/.claude/commands/update-docs.md
|
|
120
|
+
- lib/generators/tsykvas_rails_template/install/templates/.claude/commands/update-rules.md
|
|
121
|
+
- lib/generators/tsykvas_rails_template/install/templates/.claude/commands/update-tests.md
|
|
122
|
+
- lib/generators/tsykvas_rails_template/install/templates/.claude/docs/architecture.md
|
|
123
|
+
- lib/generators/tsykvas_rails_template/install/templates/.claude/docs/authentication.md
|
|
124
|
+
- lib/generators/tsykvas_rails_template/install/templates/.claude/docs/background-jobs.md
|
|
125
|
+
- lib/generators/tsykvas_rails_template/install/templates/.claude/docs/code-style.md
|
|
126
|
+
- lib/generators/tsykvas_rails_template/install/templates/.claude/docs/commands.md
|
|
127
|
+
- lib/generators/tsykvas_rails_template/install/templates/.claude/docs/companions.md
|
|
128
|
+
- lib/generators/tsykvas_rails_template/install/templates/.claude/docs/concepts-refactoring.md
|
|
129
|
+
- lib/generators/tsykvas_rails_template/install/templates/.claude/docs/database.md
|
|
130
|
+
- lib/generators/tsykvas_rails_template/install/templates/.claude/docs/deployment.md
|
|
131
|
+
- lib/generators/tsykvas_rails_template/install/templates/.claude/docs/design-system.md
|
|
132
|
+
- lib/generators/tsykvas_rails_template/install/templates/.claude/docs/documentation.md
|
|
133
|
+
- lib/generators/tsykvas_rails_template/install/templates/.claude/docs/forms.md
|
|
134
|
+
- lib/generators/tsykvas_rails_template/install/templates/.claude/docs/i18n.md
|
|
135
|
+
- lib/generators/tsykvas_rails_template/install/templates/.claude/docs/routing-and-namespaces.md
|
|
136
|
+
- lib/generators/tsykvas_rails_template/install/templates/.claude/docs/security.md
|
|
137
|
+
- lib/generators/tsykvas_rails_template/install/templates/.claude/docs/stimulus-controllers.md
|
|
138
|
+
- lib/generators/tsykvas_rails_template/install/templates/.claude/docs/testing-examples.md
|
|
139
|
+
- lib/generators/tsykvas_rails_template/install/templates/.claude/docs/testing.md
|
|
140
|
+
- lib/generators/tsykvas_rails_template/install/templates/.claude/docs/tsykvas_rails_template.md
|
|
141
|
+
- lib/generators/tsykvas_rails_template/install/templates/.claude/docs/ui-components.md
|
|
142
|
+
- lib/generators/tsykvas_rails_template/install/templates/CLAUDE.md.tt
|
|
143
|
+
- lib/generators/tsykvas_rails_template/install/templates/app/concepts/base/component/base.rb
|
|
144
|
+
- lib/generators/tsykvas_rails_template/install/templates/app/concepts/base/operation/base.rb
|
|
145
|
+
- lib/generators/tsykvas_rails_template/install/templates/app/concepts/base/operation/result.rb
|
|
146
|
+
- lib/generators/tsykvas_rails_template/install/templates/app/concepts/home/component/index.html.slim
|
|
147
|
+
- lib/generators/tsykvas_rails_template/install/templates/app/concepts/home/component/index.rb
|
|
148
|
+
- lib/generators/tsykvas_rails_template/install/templates/app/concepts/home/operation/index.rb
|
|
149
|
+
- lib/generators/tsykvas_rails_template/install/templates/app/controllers/concerns/operations_methods.rb
|
|
150
|
+
- lib/generators/tsykvas_rails_template/install/templates/app/controllers/home_controller.rb
|
|
151
|
+
- lib/generators/tsykvas_rails_template/install/templates/app/policies/application_policy.rb
|
|
152
|
+
- lib/generators/tsykvas_rails_template/install/templates/app/policies/home_policy.rb
|
|
153
|
+
- lib/tasks/tsykvas.rake
|
|
154
|
+
- lib/tsykvas_rails_template.rb
|
|
155
|
+
- lib/tsykvas_rails_template/probe.rb
|
|
156
|
+
- lib/tsykvas_rails_template/railtie.rb
|
|
157
|
+
- lib/tsykvas_rails_template/version.rb
|
|
158
|
+
homepage: https://github.com/tsykvas/tsykvas_rails_template
|
|
159
|
+
licenses:
|
|
160
|
+
- MIT
|
|
161
|
+
metadata:
|
|
162
|
+
homepage_uri: https://github.com/tsykvas/tsykvas_rails_template
|
|
163
|
+
source_code_uri: https://github.com/tsykvas/tsykvas_rails_template
|
|
164
|
+
changelog_uri: https://github.com/tsykvas/tsykvas_rails_template/blob/main/CHANGELOG.md
|
|
165
|
+
rdoc_options: []
|
|
166
|
+
require_paths:
|
|
167
|
+
- lib
|
|
168
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
169
|
+
requirements:
|
|
170
|
+
- - ">="
|
|
171
|
+
- !ruby/object:Gem::Version
|
|
172
|
+
version: 3.2.0
|
|
173
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
174
|
+
requirements:
|
|
175
|
+
- - ">="
|
|
176
|
+
- !ruby/object:Gem::Version
|
|
177
|
+
version: '0'
|
|
178
|
+
requirements: []
|
|
179
|
+
rubygems_version: 4.0.8
|
|
180
|
+
specification_version: 4
|
|
181
|
+
summary: 'Rails template: thin controllers, Operation/Component architecture, and
|
|
182
|
+
Claude tooling.'
|
|
183
|
+
test_files: []
|