nitro_kit 0.9.0 → 2.0.0.alpha.2
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 +4 -4
- data/.agents/plugins/marketplace.json +20 -0
- data/CHANGELOG.md +172 -0
- data/LICENSE +28 -0
- data/README.md +22 -15
- data/STYLE_GUIDE.md +425 -0
- data/app/assets/stylesheets/nitro_kit-tailwind-v4.css +38 -0
- data/app/assets/stylesheets/nitro_kit.css +7239 -0
- data/app/components/nitro_kit/accordion.rb +125 -65
- data/app/components/nitro_kit/alert.rb +80 -43
- data/app/components/nitro_kit/app_navigation.rb +307 -0
- data/app/components/nitro_kit/app_shell.rb +227 -0
- data/app/components/nitro_kit/appearance_bootstrap.rb +146 -0
- data/app/components/nitro_kit/appearance_picker.rb +156 -0
- data/app/components/nitro_kit/auth_shell.rb +34 -0
- data/app/components/nitro_kit/avatar.rb +85 -29
- data/app/components/nitro_kit/avatar_stack.rb +155 -9
- data/app/components/nitro_kit/badge.rb +43 -81
- data/app/components/nitro_kit/button.rb +180 -103
- data/app/components/nitro_kit/button_group.rb +63 -11
- data/app/components/nitro_kit/button_to.rb +69 -0
- data/app/components/nitro_kit/card.rb +42 -45
- data/app/components/nitro_kit/checkbox.rb +115 -62
- data/app/components/nitro_kit/checkbox_group.rb +131 -22
- data/app/components/nitro_kit/choice.rb +55 -0
- data/app/components/nitro_kit/combobox.rb +292 -105
- data/app/components/nitro_kit/command_palette.rb +412 -0
- data/app/components/nitro_kit/component.rb +322 -46
- data/app/components/nitro_kit/container.rb +32 -0
- data/app/components/nitro_kit/control_group.rb +45 -0
- data/app/components/nitro_kit/danger_zone.rb +78 -0
- data/app/components/nitro_kit/data_section.rb +106 -0
- data/app/components/nitro_kit/details_table.rb +229 -0
- data/app/components/nitro_kit/dialog.rb +257 -83
- data/app/components/nitro_kit/dropdown.rb +335 -122
- data/app/components/nitro_kit/dropzone.rb +326 -0
- data/app/components/nitro_kit/empty_state.rb +99 -0
- data/app/components/nitro_kit/field.rb +461 -236
- data/app/components/nitro_kit/field_group.rb +15 -8
- data/app/components/nitro_kit/fieldset.rb +37 -39
- data/app/components/nitro_kit/flex.rb +49 -0
- data/app/components/nitro_kit/form_builder.rb +313 -72
- data/app/components/nitro_kit/form_section.rb +79 -0
- data/app/components/nitro_kit/grid.rb +34 -0
- data/app/components/nitro_kit/icon.rb +54 -25
- data/app/components/nitro_kit/input.rb +97 -10
- data/app/components/nitro_kit/label.rb +22 -6
- data/app/components/nitro_kit/layout_options.rb +7 -0
- data/app/components/nitro_kit/page_header.rb +76 -0
- data/app/components/nitro_kit/pagination.rb +355 -69
- data/app/components/nitro_kit/pagination_bar.rb +90 -0
- data/app/components/nitro_kit/progressive_image.rb +181 -0
- data/app/components/nitro_kit/radio_button.rb +77 -55
- data/app/components/nitro_kit/radio_button_group.rb +107 -36
- data/app/components/nitro_kit/responsive_value.rb +94 -0
- data/app/components/nitro_kit/rich_text_area.rb +31 -0
- data/app/components/nitro_kit/select.rb +129 -64
- data/app/components/nitro_kit/settings_layout.rb +152 -0
- data/app/components/nitro_kit/sheet.rb +202 -0
- data/app/components/nitro_kit/stat_grid.rb +75 -0
- data/app/components/nitro_kit/switch.rb +108 -51
- data/app/components/nitro_kit/table.rb +237 -45
- data/app/components/nitro_kit/tabs.rb +168 -63
- data/app/components/nitro_kit/textarea.rb +81 -10
- data/app/components/nitro_kit/toast.rb +211 -80
- data/app/components/nitro_kit/toolbar.rb +75 -0
- data/app/components/nitro_kit/tooltip.rb +235 -34
- data/app/components/nitro_kit/typeset.rb +28 -0
- data/app/javascript/controllers/nk/app_shell_controller.js +224 -0
- data/app/javascript/controllers/nk/appearance_controller.js +73 -0
- data/app/javascript/controllers/nk/avatar_controller.js +15 -0
- data/app/javascript/controllers/nk/button_controller.js +56 -0
- data/app/javascript/controllers/nk/checkable_controller.js +41 -0
- data/app/javascript/controllers/nk/combobox_controller.js +314 -82
- data/app/javascript/controllers/nk/command_palette_controller.js +249 -0
- data/app/javascript/controllers/nk/dialog_controller.js +14 -10
- data/app/javascript/controllers/nk/dropdown_controller.js +106 -58
- data/app/javascript/controllers/nk/dropzone/direct_upload.js +61 -0
- data/app/javascript/controllers/nk/dropzone/file_rules.js +77 -0
- data/app/javascript/controllers/nk/dropzone/form_submit_lock.js +30 -0
- data/app/javascript/controllers/nk/dropzone_controller.js +482 -0
- data/app/javascript/controllers/nk/overlay_position.js +37 -0
- data/app/javascript/controllers/nk/progressive_image_controller.js +116 -0
- data/app/javascript/controllers/nk/tabs_controller.js +135 -23
- data/app/javascript/controllers/nk/toast_controller.js +112 -45
- data/app/javascript/controllers/nk/tooltip_controller.js +12 -47
- data/config/importmap.rb +2 -0
- data/config/locales/en.yml +98 -0
- data/docs/agent_guide.md +139 -0
- data/docs/agent_native_spec.md +422 -0
- data/docs/component_contracts.md +263 -0
- data/docs/customization.md +595 -0
- data/docs/hotwire.md +94 -0
- data/docs/initialization_prompt.md +36 -0
- data/docs/migration_1_to_2.md +318 -0
- data/docs/new_app_strategy.md +22 -0
- data/docs/patterns/application_foundation.md +136 -0
- data/docs/patterns/crud_resource.md +144 -0
- data/docs/patterns/destructive_action.md +105 -0
- data/docs/patterns/flash_and_toast.md +57 -0
- data/docs/patterns/inline_edit.md +59 -0
- data/docs/patterns/queryable_collection.md +187 -0
- data/docs/patterns/resource_form.md +126 -0
- data/docs/rails_conventions.md +95 -0
- data/docs/rails_integration.md +514 -0
- data/lib/generators/nitro_kit/install_generator.rb +48 -0
- data/lib/generators/nitro_kit/upgrade_smoke_tests_generator.rb +67 -0
- data/lib/nitro_kit/engine.rb +20 -0
- data/lib/nitro_kit/installation.rb +671 -0
- data/lib/nitro_kit/migration_inventory.rb +345 -0
- data/lib/nitro_kit/upgrade_smoke_test.rb +403 -0
- data/lib/nitro_kit/version.rb +1 -1
- data/lib/nitro_kit.rb +5 -42
- data/lib/rails/commands/nitro_kit/nitro_kit_command.rb +42 -0
- data/lib/tasks/nitro_kit_tasks.rake +79 -4
- data/plugins/nitro-kit/.codex-plugin/plugin.json +28 -0
- data/plugins/nitro-kit/skills/nitro-kit-hotwire/SKILL.md +67 -0
- data/plugins/nitro-kit/skills/nitro-kit-hotwire/agents/openai.yaml +4 -0
- data/plugins/nitro-kit/skills/nitro-kit-rails/SKILL.md +60 -0
- data/plugins/nitro-kit/skills/nitro-kit-rails/agents/openai.yaml +4 -0
- data/plugins/nitro-kit/skills/nitro-kit-ui/SKILL.md +62 -0
- data/plugins/nitro-kit/skills/nitro-kit-ui/agents/openai.yaml +4 -0
- data/src/stylesheets/nitro_kit/components/accordion.css +119 -0
- data/src/stylesheets/nitro_kit/components/alert.css +82 -0
- data/src/stylesheets/nitro_kit/components/app_navigation.css +156 -0
- data/src/stylesheets/nitro_kit/components/app_shell.css +605 -0
- data/src/stylesheets/nitro_kit/components/appearance_picker.css +117 -0
- data/src/stylesheets/nitro_kit/components/auth_shell.css +8 -0
- data/src/stylesheets/nitro_kit/components/avatar.css +81 -0
- data/src/stylesheets/nitro_kit/components/avatar_stack.css +80 -0
- data/src/stylesheets/nitro_kit/components/badge.css +76 -0
- data/src/stylesheets/nitro_kit/components/button.css +283 -0
- data/src/stylesheets/nitro_kit/components/button_group.css +53 -0
- data/src/stylesheets/nitro_kit/components/button_to.css +6 -0
- data/src/stylesheets/nitro_kit/components/card.css +70 -0
- data/src/stylesheets/nitro_kit/components/checkbox.css +160 -0
- data/src/stylesheets/nitro_kit/components/checkbox_group.css +61 -0
- data/src/stylesheets/nitro_kit/components/combobox.css +148 -0
- data/src/stylesheets/nitro_kit/components/command_palette.css +255 -0
- data/src/stylesheets/nitro_kit/components/container.css +25 -0
- data/src/stylesheets/nitro_kit/components/control_group.css +149 -0
- data/src/stylesheets/nitro_kit/components/danger_zone.css +54 -0
- data/src/stylesheets/nitro_kit/components/data_section.css +40 -0
- data/src/stylesheets/nitro_kit/components/details_table.css +53 -0
- data/src/stylesheets/nitro_kit/components/dialog.css +80 -0
- data/src/stylesheets/nitro_kit/components/dropdown.css +152 -0
- data/src/stylesheets/nitro_kit/components/dropzone.css +198 -0
- data/src/stylesheets/nitro_kit/components/empty_state.css +53 -0
- data/src/stylesheets/nitro_kit/components/field.css +79 -0
- data/src/stylesheets/nitro_kit/components/field_group.css +6 -0
- data/src/stylesheets/nitro_kit/components/fieldset.css +28 -0
- data/src/stylesheets/nitro_kit/components/flex.css +455 -0
- data/src/stylesheets/nitro_kit/components/form_section.css +71 -0
- data/src/stylesheets/nitro_kit/components/grid.css +308 -0
- data/src/stylesheets/nitro_kit/components/icon.css +26 -0
- data/src/stylesheets/nitro_kit/components/input.css +80 -0
- data/src/stylesheets/nitro_kit/components/label.css +9 -0
- data/src/stylesheets/nitro_kit/components/layout.css +440 -0
- data/src/stylesheets/nitro_kit/components/page_header.css +61 -0
- data/src/stylesheets/nitro_kit/components/pagination.css +119 -0
- data/src/stylesheets/nitro_kit/components/pagination_bar.css +41 -0
- data/src/stylesheets/nitro_kit/components/palette.css +113 -0
- data/src/stylesheets/nitro_kit/components/progressive_image.css +139 -0
- data/src/stylesheets/nitro_kit/components/radio_button.css +127 -0
- data/src/stylesheets/nitro_kit/components/radio_button_group.css +143 -0
- data/src/stylesheets/nitro_kit/components/rich_text_area.css +21 -0
- data/src/stylesheets/nitro_kit/components/select.css +70 -0
- data/src/stylesheets/nitro_kit/components/settings_layout.css +100 -0
- data/src/stylesheets/nitro_kit/components/sheet.css +100 -0
- data/src/stylesheets/nitro_kit/components/stat_grid.css +51 -0
- data/src/stylesheets/nitro_kit/components/switch.css +152 -0
- data/src/stylesheets/nitro_kit/components/table.css +116 -0
- data/src/stylesheets/nitro_kit/components/tabs.css +138 -0
- data/src/stylesheets/nitro_kit/components/textarea.css +39 -0
- data/src/stylesheets/nitro_kit/components/toast.css +131 -0
- data/src/stylesheets/nitro_kit/components/toolbar.css +78 -0
- data/src/stylesheets/nitro_kit/components/tooltip.css +124 -0
- data/src/stylesheets/nitro_kit/components/typeset.css +191 -0
- data/src/stylesheets/nitro_kit/layers.css +7 -0
- data/src/stylesheets/nitro_kit/reset.css +192 -0
- data/src/stylesheets/nitro_kit/tokens.css +198 -0
- metadata +182 -51
- data/Rakefile +0 -8
- data/app/assets/tailwind/application.css +0 -49
- data/app/components/nitro_kit/datepicker.rb +0 -9
- data/app/helpers/nitro_kit/accordion_helper.rb +0 -9
- data/app/helpers/nitro_kit/alert_helper.rb +0 -11
- data/app/helpers/nitro_kit/avatar_helper.rb +0 -13
- data/app/helpers/nitro_kit/badge_helper.rb +0 -13
- data/app/helpers/nitro_kit/button_group_helper.rb +0 -9
- data/app/helpers/nitro_kit/button_helper.rb +0 -49
- data/app/helpers/nitro_kit/card_helper.rb +0 -9
- data/app/helpers/nitro_kit/checkbox_helper.rb +0 -50
- data/app/helpers/nitro_kit/combobox_helper.rb +0 -9
- data/app/helpers/nitro_kit/datepicker_helper.rb +0 -9
- data/app/helpers/nitro_kit/dialog_helper.rb +0 -9
- data/app/helpers/nitro_kit/dropdown_helper.rb +0 -9
- data/app/helpers/nitro_kit/field_group_helper.rb +0 -9
- data/app/helpers/nitro_kit/field_helper.rb +0 -9
- data/app/helpers/nitro_kit/fieldset_helper.rb +0 -9
- data/app/helpers/nitro_kit/form_helper.rb +0 -13
- data/app/helpers/nitro_kit/icon_helper.rb +0 -9
- data/app/helpers/nitro_kit/input_helper.rb +0 -37
- data/app/helpers/nitro_kit/label_helper.rb +0 -19
- data/app/helpers/nitro_kit/pagination_helper.rb +0 -46
- data/app/helpers/nitro_kit/radio_button_helper.rb +0 -23
- data/app/helpers/nitro_kit/select_helper.rb +0 -24
- data/app/helpers/nitro_kit/switch_helper.rb +0 -9
- data/app/helpers/nitro_kit/table_helper.rb +0 -9
- data/app/helpers/nitro_kit/tabs_helper.rb +0 -9
- data/app/helpers/nitro_kit/textarea_helper.rb +0 -9
- data/app/helpers/nitro_kit/toast_helper.rb +0 -25
- data/app/helpers/nitro_kit/tooltip_helper.rb +0 -9
- data/app/javascript/controllers/nk/accordion_controller.js +0 -18
- data/app/javascript/controllers/nk/datepicker_controller.js +0 -5
- data/app/javascript/controllers/nk/switch_controller.js +0 -32
- data/lib/generators/nitro_kit/component_generator.rb +0 -84
- data/lib/nitro_kit/schema_builder.rb +0 -129
- data/lib/nitro_kit/variants.rb +0 -21
|
@@ -0,0 +1,345 @@
|
|
|
1
|
+
require "pathname"
|
|
2
|
+
require "ripper"
|
|
3
|
+
|
|
4
|
+
module NitroKit
|
|
5
|
+
class MigrationInventory
|
|
6
|
+
Finding = Struct.new(:status, :path, :guidance, keyword_init: true)
|
|
7
|
+
|
|
8
|
+
LEGACY_COMPONENTS = %w[
|
|
9
|
+
accordion alert avatar avatar_stack badge button button_group card checkbox
|
|
10
|
+
checkbox_group combobox component datepicker dialog dropdown field field_group
|
|
11
|
+
fieldset form_builder icon input label pagination radio_button radio_button_group
|
|
12
|
+
select switch table tabs textarea toast tooltip
|
|
13
|
+
].freeze
|
|
14
|
+
LEGACY_CONTROLLERS = %w[
|
|
15
|
+
accordion combobox datepicker dialog dropdown switch tabs toast tooltip
|
|
16
|
+
].freeze
|
|
17
|
+
HELPER_REPLACEMENTS = {
|
|
18
|
+
"nk_button_link_to" => "replace with Button(..., href:)",
|
|
19
|
+
"nk_button_to" => "replace with ButtonTo(..., href:, method:)",
|
|
20
|
+
"nk_form_with" => "replace with Rails form_with(..., builder: NitroKit::FormBuilder)",
|
|
21
|
+
"nk_form_for" => "replace with Rails form_with(..., builder: NitroKit::FormBuilder)",
|
|
22
|
+
"nk_pagy_nav" => "replace with Pagination(pagy:)",
|
|
23
|
+
"nk_toast_flash_messages" => "replace with NitroKit::Toast::FlashMessages",
|
|
24
|
+
"nk_toast_turbo_stream_refresh" => "append NitroKit::Toast::Item to the toast list",
|
|
25
|
+
"nk_toast_action" => "replace the client toast action with Rails flash or a rendered Toast::Item",
|
|
26
|
+
"nk_checkbox_tag" => "replace with Checkbox(...) and preserve the submitted name and values",
|
|
27
|
+
"nk_check_box_tag" => "replace with Checkbox(...) and preserve the submitted name and values"
|
|
28
|
+
}.freeze
|
|
29
|
+
DIRECT_HELPERS = (LEGACY_COMPONENTS - %w[component datepicker form_builder]).freeze
|
|
30
|
+
FIELD_TAG_REPLACEMENTS = {
|
|
31
|
+
"color" => :color,
|
|
32
|
+
"date" => :date,
|
|
33
|
+
"datetime" => :"datetime-local",
|
|
34
|
+
"datetime_local" => :"datetime-local",
|
|
35
|
+
"email" => :email,
|
|
36
|
+
"file" => :file,
|
|
37
|
+
"hidden" => :hidden,
|
|
38
|
+
"month" => :month,
|
|
39
|
+
"number" => :number,
|
|
40
|
+
"password" => :password,
|
|
41
|
+
"phone" => :tel,
|
|
42
|
+
"range" => :range,
|
|
43
|
+
"search" => :search,
|
|
44
|
+
"telephone" => :tel,
|
|
45
|
+
"text" => :text,
|
|
46
|
+
"time" => :time,
|
|
47
|
+
"url" => :url,
|
|
48
|
+
"week" => :week
|
|
49
|
+
}.transform_keys { "nk_#{_1}_field_tag" }.freeze
|
|
50
|
+
VENDORED_MODULES = {
|
|
51
|
+
"@floating-ui/core" => "remove the 1.x Floating UI pin or package after copied controllers are removed",
|
|
52
|
+
"@floating-ui/dom" => "remove the 1.x Floating UI pin or package after copied controllers are removed",
|
|
53
|
+
"@floating-ui/utils" => "remove the 1.x Floating UI pin or package after copied controllers are removed",
|
|
54
|
+
"@github/combobox-nav" => "remove the 1.x combobox package; Nitro Kit 2 owns combobox behavior"
|
|
55
|
+
}.freeze
|
|
56
|
+
|
|
57
|
+
attr_reader :application_root
|
|
58
|
+
|
|
59
|
+
def initialize(application_root)
|
|
60
|
+
@application_root = Pathname.new(application_root)
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def categories
|
|
64
|
+
{
|
|
65
|
+
"Legacy helpers" => legacy_helpers,
|
|
66
|
+
"Copied or shadow components" => copied_components,
|
|
67
|
+
"Custom or legacy controllers" => controllers,
|
|
68
|
+
"Vendored dependencies" => vendored_dependencies,
|
|
69
|
+
"Known replacements" => replacement_summary,
|
|
70
|
+
"Unresolved or application-owned items" => disposition_summary
|
|
71
|
+
}
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
private
|
|
75
|
+
def legacy_helpers
|
|
76
|
+
@legacy_helpers ||= source_files.flat_map do |path|
|
|
77
|
+
helper_identifiers(path).filter_map do |helper, line_number|
|
|
78
|
+
next unless legacy_helper?(helper)
|
|
79
|
+
|
|
80
|
+
Finding.new(
|
|
81
|
+
status: helper == "nk_datepicker" ? :application_owned : :unresolved,
|
|
82
|
+
path: location(path, line_number),
|
|
83
|
+
guidance: helper_guidance(helper)
|
|
84
|
+
)
|
|
85
|
+
end
|
|
86
|
+
end.uniq { [ _1.status, _1.path, _1.guidance ] }
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
def copied_components
|
|
90
|
+
application_root.glob("app/components/nitro_kit/**/*.rb").map do |path|
|
|
91
|
+
name = path.basename(".rb").to_s
|
|
92
|
+
known = legacy_component?(path, name)
|
|
93
|
+
|
|
94
|
+
Finding.new(
|
|
95
|
+
status: known && name != "datepicker" ? :unresolved : :application_owned,
|
|
96
|
+
path: relative(path),
|
|
97
|
+
guidance: component_guidance(name, known:)
|
|
98
|
+
)
|
|
99
|
+
end
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
def controllers
|
|
103
|
+
application_root.glob("app/javascript/controllers/nk/**/*_controller.js").map do |path|
|
|
104
|
+
name = path.basename("_controller.js").to_s
|
|
105
|
+
expected_path = "app/javascript/controllers/nk/#{name}_controller.js"
|
|
106
|
+
known = LEGACY_CONTROLLERS.include?(name) && relative(path) == expected_path
|
|
107
|
+
|
|
108
|
+
Finding.new(
|
|
109
|
+
status: known && name != "datepicker" ? :unresolved : :application_owned,
|
|
110
|
+
path: relative(path),
|
|
111
|
+
guidance: controller_guidance(name, known:)
|
|
112
|
+
)
|
|
113
|
+
end
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
def vendored_dependencies
|
|
117
|
+
findings = application_root.glob("vendor/javascript/{@floating-ui--*,@github--combobox-nav*}.js").map do |path|
|
|
118
|
+
Finding.new(
|
|
119
|
+
status: :unresolved,
|
|
120
|
+
path: relative(path),
|
|
121
|
+
guidance: "delete the vendored 1.x runtime after removing copied Nitro controllers"
|
|
122
|
+
)
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
dependency_files.each do |path|
|
|
126
|
+
path.each_line.with_index(1) do |line, line_number|
|
|
127
|
+
next if line.lstrip.start_with?("#", "//")
|
|
128
|
+
|
|
129
|
+
VENDORED_MODULES.each do |name, guidance|
|
|
130
|
+
findings << Finding.new(status: :unresolved, path: location(path, line_number), guidance:) if line.include?(name)
|
|
131
|
+
end
|
|
132
|
+
if line.match?(/\btailwind_merge\b/)
|
|
133
|
+
findings << Finding.new(
|
|
134
|
+
status: :unresolved,
|
|
135
|
+
path: location(path, line_number),
|
|
136
|
+
guidance: "remove tailwind_merge; Nitro Kit 2 ships static CSS and does not use it"
|
|
137
|
+
)
|
|
138
|
+
end
|
|
139
|
+
end
|
|
140
|
+
end
|
|
141
|
+
|
|
142
|
+
findings.uniq { [ _1.path, _1.guidance ] }
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
def replacement_summary
|
|
146
|
+
unresolved_findings.group_by(&:guidance).map do |guidance, findings|
|
|
147
|
+
Finding.new(
|
|
148
|
+
status: :unresolved,
|
|
149
|
+
path: "#{findings.size} occurrence#{"s" unless findings.one?}",
|
|
150
|
+
guidance:
|
|
151
|
+
)
|
|
152
|
+
end
|
|
153
|
+
end
|
|
154
|
+
|
|
155
|
+
def disposition_summary
|
|
156
|
+
all_findings.group_by(&:status).map do |status, findings|
|
|
157
|
+
guidance = case status
|
|
158
|
+
when :application_owned
|
|
159
|
+
"preserve the behavior under an application namespace; do not keep a Nitro shadow"
|
|
160
|
+
else
|
|
161
|
+
"apply the per-file replacement or removal listed in the category above"
|
|
162
|
+
end
|
|
163
|
+
Finding.new(
|
|
164
|
+
status:,
|
|
165
|
+
path: "#{findings.size} finding#{"s" unless findings.one?}",
|
|
166
|
+
guidance:
|
|
167
|
+
)
|
|
168
|
+
end
|
|
169
|
+
end
|
|
170
|
+
|
|
171
|
+
def unresolved_findings
|
|
172
|
+
all_findings.select { _1.status == :unresolved }
|
|
173
|
+
end
|
|
174
|
+
|
|
175
|
+
def all_findings
|
|
176
|
+
@all_findings ||= (legacy_helpers + copied_components + controllers + vendored_dependencies)
|
|
177
|
+
.uniq { [ _1.status, _1.path, _1.guidance ] }
|
|
178
|
+
end
|
|
179
|
+
|
|
180
|
+
def source_files
|
|
181
|
+
application_root.glob("app/**/*.{rb,erb,haml,slim}").select(&:file?)
|
|
182
|
+
end
|
|
183
|
+
|
|
184
|
+
def dependency_files
|
|
185
|
+
files = %w[Gemfile config/importmap.rb package.json].filter_map do |name|
|
|
186
|
+
path = application_root.join(name)
|
|
187
|
+
path if path.file?
|
|
188
|
+
end
|
|
189
|
+
files + application_root.glob("app/javascript/controllers/nk/**/*.js")
|
|
190
|
+
end
|
|
191
|
+
|
|
192
|
+
def helper_identifiers(path)
|
|
193
|
+
case path.extname
|
|
194
|
+
when ".rb"
|
|
195
|
+
ruby_identifiers(path.read)
|
|
196
|
+
when ".erb"
|
|
197
|
+
erb_identifiers(path.read)
|
|
198
|
+
when ".haml", ".slim"
|
|
199
|
+
template_line_identifiers(path.read)
|
|
200
|
+
else
|
|
201
|
+
[]
|
|
202
|
+
end
|
|
203
|
+
end
|
|
204
|
+
|
|
205
|
+
def ruby_identifiers(source, line_offset = 0)
|
|
206
|
+
tokens = Ripper.lex(source)
|
|
207
|
+
tokens.each_index.filter_map do |index|
|
|
208
|
+
position, event, token, = tokens[index]
|
|
209
|
+
next unless event == :on_ident && token.start_with?("nk_")
|
|
210
|
+
|
|
211
|
+
previous = tokens[0...index].reverse.find { !%i[on_sp on_nl on_ignored_nl].include?(_1[1]) }
|
|
212
|
+
[ token, position.first + line_offset ] unless previous&.at(1) == :on_symbeg
|
|
213
|
+
end
|
|
214
|
+
end
|
|
215
|
+
|
|
216
|
+
def erb_identifiers(source)
|
|
217
|
+
source.to_enum(:scan, /<%(?!%|#)[=-]?(.*?)%>/m).flat_map do
|
|
218
|
+
match = Regexp.last_match
|
|
219
|
+
line_offset = source[0...match.begin(1)].count("\n")
|
|
220
|
+
ruby_identifiers(match[1], line_offset)
|
|
221
|
+
end
|
|
222
|
+
end
|
|
223
|
+
|
|
224
|
+
def template_line_identifiers(source)
|
|
225
|
+
source.each_line.with_index(1).flat_map do |line, line_number|
|
|
226
|
+
code = line.lstrip
|
|
227
|
+
next [] unless code.start_with?("=", "-") && !code.start_with?("-#")
|
|
228
|
+
|
|
229
|
+
ruby_identifiers(code.delete_prefix(code[0]), line_number - 1)
|
|
230
|
+
end
|
|
231
|
+
end
|
|
232
|
+
|
|
233
|
+
def legacy_component?(path, name)
|
|
234
|
+
return false unless LEGACY_COMPONENTS.include?(name)
|
|
235
|
+
return false unless relative(path) == "app/components/nitro_kit/#{name}.rb"
|
|
236
|
+
|
|
237
|
+
declared_constants(path.read).include?("NitroKit::#{camelize(name)}")
|
|
238
|
+
end
|
|
239
|
+
|
|
240
|
+
def declared_constants(source)
|
|
241
|
+
constants = []
|
|
242
|
+
walk_constants(Ripper.sexp(source), nil, constants)
|
|
243
|
+
constants
|
|
244
|
+
end
|
|
245
|
+
|
|
246
|
+
def walk_constants(node, namespace, constants)
|
|
247
|
+
return unless node.is_a?(Array)
|
|
248
|
+
|
|
249
|
+
if %i[module class].include?(node.first)
|
|
250
|
+
name = constant_name(node[1])
|
|
251
|
+
if name
|
|
252
|
+
full_name = name.include?("::") || namespace.nil? ? name : "#{namespace}::#{name}"
|
|
253
|
+
constants << full_name
|
|
254
|
+
walk_constants(node[node.first == :class ? 3 : 2], full_name, constants)
|
|
255
|
+
return
|
|
256
|
+
end
|
|
257
|
+
end
|
|
258
|
+
|
|
259
|
+
node.each { walk_constants(_1, namespace, constants) if _1.is_a?(Array) }
|
|
260
|
+
end
|
|
261
|
+
|
|
262
|
+
def constant_name(node)
|
|
263
|
+
return unless node.is_a?(Array)
|
|
264
|
+
|
|
265
|
+
case node.first
|
|
266
|
+
when :const_ref, :var_ref, :top_const_ref
|
|
267
|
+
constant_name(node[1])
|
|
268
|
+
when :const_path_ref
|
|
269
|
+
[ constant_name(node[1]), constant_name(node[2]) ].compact.join("::")
|
|
270
|
+
when :@const
|
|
271
|
+
node[1]
|
|
272
|
+
end
|
|
273
|
+
end
|
|
274
|
+
|
|
275
|
+
def legacy_helper?(helper)
|
|
276
|
+
HELPER_REPLACEMENTS.key?(helper) || FIELD_TAG_REPLACEMENTS.key?(helper) || helper == "nk_datepicker" ||
|
|
277
|
+
DIRECT_HELPERS.include?(helper.delete_prefix("nk_")) || automatic_variant_helper?(helper)
|
|
278
|
+
end
|
|
279
|
+
|
|
280
|
+
def automatic_variant_helper?(helper)
|
|
281
|
+
helper.match?(/\Ank_[a-z0-9]+_(?:alert|badge|button|button_to|button_link_to)\z/)
|
|
282
|
+
end
|
|
283
|
+
|
|
284
|
+
def helper_guidance(helper)
|
|
285
|
+
return HELPER_REPLACEMENTS.fetch(helper) if HELPER_REPLACEMENTS.key?(helper)
|
|
286
|
+
if FIELD_TAG_REPLACEMENTS.key?(helper)
|
|
287
|
+
type = FIELD_TAG_REPLACEMENTS.fetch(helper).inspect
|
|
288
|
+
return "replace with Input(type: #{type}) and preserve the submitted name and value"
|
|
289
|
+
end
|
|
290
|
+
if helper == "nk_datepicker"
|
|
291
|
+
return "use Input(type: :date) only when native date semantics are equivalent; otherwise preserve the behavior in an application-owned component"
|
|
292
|
+
end
|
|
293
|
+
|
|
294
|
+
if helper.end_with?("_button_link_to")
|
|
295
|
+
return "replace #{helper} with Button(..., href:) and pass the old variant explicitly"
|
|
296
|
+
end
|
|
297
|
+
if helper.end_with?("_button_to")
|
|
298
|
+
return "replace #{helper} with ButtonTo(..., href:, method:) and pass the old variant explicitly"
|
|
299
|
+
end
|
|
300
|
+
if automatic_variant_helper?(helper)
|
|
301
|
+
component = helper.delete_prefix("nk_").sub(/\A[a-z0-9]+_/, "")
|
|
302
|
+
return "replace #{helper} with direct Phlex #{camelize(component)}(...) and review the old variant against the current contract"
|
|
303
|
+
end
|
|
304
|
+
|
|
305
|
+
component = helper.delete_prefix("nk_")
|
|
306
|
+
"replace #{helper} with direct Phlex #{camelize(component)}(...) composition"
|
|
307
|
+
end
|
|
308
|
+
|
|
309
|
+
def component_guidance(name, known:)
|
|
310
|
+
if name == "component" && known
|
|
311
|
+
"delete the copied base component; include NitroKit once in the application-owned Phlex base"
|
|
312
|
+
elsif name == "form_builder" && known
|
|
313
|
+
"delete the shadow and select the gem-owned NitroKit::FormBuilder through Rails form_with"
|
|
314
|
+
elsif name == "datepicker" && known
|
|
315
|
+
"remove the 1.x copy; use Input(type: :date) only when equivalent, otherwise rebuild it in the application namespace"
|
|
316
|
+
elsif known
|
|
317
|
+
"delete the shadow and compose the gem-owned NitroKit::#{camelize(name)}"
|
|
318
|
+
else
|
|
319
|
+
"move this customization out of NitroKit into the application namespace and compose public Nitro components"
|
|
320
|
+
end
|
|
321
|
+
end
|
|
322
|
+
|
|
323
|
+
def controller_guidance(name, known:)
|
|
324
|
+
if name == "datepicker" && known
|
|
325
|
+
"remove the 1.x controller; keep specialized behavior only in an application-owned controller and component"
|
|
326
|
+
elsif known
|
|
327
|
+
"delete the 1.x controller and use the gem-owned Nitro behavior"
|
|
328
|
+
else
|
|
329
|
+
"move this custom behavior out of controllers/nk and register it under an application-owned identifier"
|
|
330
|
+
end
|
|
331
|
+
end
|
|
332
|
+
|
|
333
|
+
def camelize(value)
|
|
334
|
+
value.split("_").map(&:capitalize).join
|
|
335
|
+
end
|
|
336
|
+
|
|
337
|
+
def location(path, line_number)
|
|
338
|
+
"#{relative(path)}:#{line_number}"
|
|
339
|
+
end
|
|
340
|
+
|
|
341
|
+
def relative(path)
|
|
342
|
+
path.relative_path_from(application_root).to_s
|
|
343
|
+
end
|
|
344
|
+
end
|
|
345
|
+
end
|