plutonium 0.64.0 → 0.65.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 +4 -4
- data/.claude/skills/plutonium/SKILL.md +4 -1
- data/.claude/skills/plutonium-app/SKILL.md +14 -0
- data/.claude/skills/plutonium-dashboard/SKILL.md +161 -0
- data/.claude/skills/plutonium-resource/SKILL.md +94 -14
- data/.claude/skills/plutonium-ui/SKILL.md +22 -1
- data/CHANGELOG.md +59 -0
- data/CLAUDE.md +282 -210
- data/SECURITY.md +1 -1
- data/app/assets/plutonium-charts.js +20604 -0
- data/app/assets/plutonium-charts.js.map +7 -0
- data/app/assets/plutonium-charts.min.js +41 -0
- data/app/assets/plutonium-charts.min.js.map +7 -0
- data/app/assets/plutonium.css +1 -1
- data/app/assets/plutonium.js +794 -629
- data/app/assets/plutonium.js.map +4 -4
- data/app/assets/plutonium.min.js +48 -48
- data/app/assets/plutonium.min.js.map +4 -4
- data/app/views/plutonium/_flash_alerts.html.erb +2 -2
- data/app/views/plutonium/_resource_header.html.erb +2 -2
- data/app/views/plutonium/_resource_sidebar.html.erb +11 -2
- data/app/views/plutonium/_toast.html.erb +2 -2
- data/app/views/rodauth/_login_form.html.erb +1 -1
- data/app/views/rodauth/_password_visibility.html.erb +1 -1
- data/app/views/rodauth/add_recovery_codes.html.erb +2 -2
- data/app/views/rodauth/change_login.html.erb +2 -2
- data/app/views/rodauth/create_account.html.erb +4 -4
- data/app/views/rodauth/reset_password_request.html.erb +1 -1
- data/app/views/rodauth/verify_account_resend.html.erb +1 -1
- data/app/views/rodauth/webauthn_remove.html.erb +1 -1
- data/config/brakeman.ignore +4 -4
- data/config/initializers/rabl.rb +0 -40
- data/config/locales/en/api_client.yml +22 -0
- data/config/locales/en/async.yml +34 -0
- data/config/locales/en/dashboard.yml +25 -0
- data/config/locales/en/invites.yml +37 -0
- data/config/locales/en/js.yml +40 -0
- data/config/locales/en/kanban.yml +28 -0
- data/config/locales/en/plutonium.yml +26 -0
- data/config/locales/en/positioning.yml +14 -0
- data/config/locales/en/profile.yml +29 -0
- data/config/locales/en/resource.yml +60 -0
- data/config/locales/en/ui.yml +93 -0
- data/config/locales/en/wizard.yml +46 -0
- data/docs/.vitepress/config.ts +34 -1
- data/docs/.vitepress/theme/components/HomeHero.vue +1 -1
- data/docs/.vitepress/theme/components/HomeInTheBox.vue +14 -0
- data/docs/.vitepress/theme/custom.css +34 -377
- data/docs/blog/introducing-plutonium-dashboards.md +175 -0
- data/docs/blog/introducing-plutonium-i18n.md +91 -0
- data/docs/blog/introducing-plutonium.md +4 -5
- data/docs/blog/whats-new-async-kanban-wizards.md +1 -1
- data/docs/guides/dashboards.md +300 -0
- data/docs/guides/index.md +1 -0
- data/docs/guides/kanban.md +2 -2
- data/docs/public/images/blog/dashboards-card-error.png +0 -0
- data/docs/public/images/blog/dashboards-loading.png +0 -0
- data/docs/public/images/blog/dashboards-overview.png +0 -0
- data/docs/public/images/blog/dashboards-sidebar.png +0 -0
- data/docs/public/images/blog/i18n-es-admin.png +0 -0
- data/docs/public/images/guides/dashboard-card-error.png +0 -0
- data/docs/public/images/guides/dashboard-loading.png +0 -0
- data/docs/public/images/guides/dashboard-overview.png +0 -0
- data/docs/public/images/guides/dashboard-sidebar.png +0 -0
- data/docs/reference/app/generators.md +2 -2
- data/docs/reference/app/portals.md +2 -0
- data/docs/reference/configuration.md +84 -27
- data/docs/reference/dashboard/dsl.md +123 -0
- data/docs/reference/dashboard/index.md +35 -0
- data/docs/reference/dashboard/registration.md +76 -0
- data/docs/reference/i18n.md +239 -0
- data/docs/reference/index.md +10 -1
- data/docs/reference/kanban/dsl.md +1 -1
- data/docs/reference/kanban/positioning.md +2 -2
- data/docs/reference/resource/actions.md +1 -1
- data/docs/reference/resource/definition.md +96 -18
- data/docs/reference/resource/export.md +5 -1
- data/docs/reference/resource/index.md +2 -0
- data/docs/reference/{positioning.md → resource/positioning.md} +2 -1
- data/docs/reference/resource/query.md +2 -0
- data/docs/reference/ui/assets.md +8 -0
- data/docs/reference/ui/displays.md +5 -8
- data/docs/reference/ui/index.md +1 -1
- data/docs/reference/wizard/dsl.md +2 -2
- data/esbuild.config.js +5 -2
- data/gemfiles/rails_7.gemfile.lock +2 -2
- data/gemfiles/rails_8.1.gemfile.lock +1 -1
- data/lib/active_model/validations/array_validator.rb +1 -1
- data/lib/active_model/validations/attached_validator.rb +1 -1
- data/lib/active_model/validations/url_validator.rb +2 -2
- data/lib/generators/pu/core/assets/assets_generator.rb +36 -21
- data/lib/generators/pu/core/assets/templates/postcss.config.js +1 -1
- data/lib/generators/pu/core/update/update_generator.rb +3 -2
- data/lib/generators/pu/dashboard/dashboard_generator.rb +96 -0
- data/lib/generators/pu/dashboard/templates/dashboard.rb.tt +25 -0
- data/lib/generators/pu/docker/install/install_generator.rb +58 -0
- data/lib/generators/pu/docker/install/templates/Dockerfile.dev.tt +18 -3
- data/lib/generators/pu/docker/install/templates/Dockerfile.tt +20 -5
- data/lib/generators/pu/docker/install/templates/{docker-compose.yml → docker-compose.yml.tt} +1 -1
- data/lib/generators/pu/gem/dotenv/dotenv_generator.rb +1 -1
- data/lib/generators/pu/invites/invitable_generator.rb +18 -1
- data/lib/generators/pu/lib/plutonium_generators/concerns/actions.rb +0 -95
- data/lib/generators/pu/lib/plutonium_generators/concerns/js_package_manager.rb +146 -0
- data/lib/generators/pu/lib/plutonium_generators/generator.rb +1 -17
- data/lib/generators/pu/pkg/package/package_generator.rb +1 -0
- data/lib/generators/pu/pkg/package/templates/config/locales/en.yml.tt +28 -0
- data/lib/generators/pu/pkg/portal/portal_generator.rb +1 -0
- data/lib/generators/pu/pkg/portal/templates/config/locales/en.yml.tt +30 -0
- data/lib/generators/pu/res/scaffold/scaffold_generator.rb +0 -8
- data/lib/generators/pu/rodauth/concerns/feature_selector.rb +0 -18
- data/lib/plutonium/action/base.rb +27 -3
- data/lib/plutonium/action/interactive.rb +17 -4
- data/lib/plutonium/api_client/concerns/create_api_client.rb +12 -11
- data/lib/plutonium/api_client/concerns/disable_api_client.rb +3 -3
- data/lib/plutonium/auth/sequel_adapter.rb +1 -1
- data/lib/plutonium/configuration.rb +5 -0
- data/lib/plutonium/core/controller.rb +25 -1
- data/lib/plutonium/core/controllers/authorizable.rb +1 -1
- data/lib/plutonium/dashboard/base.rb +114 -0
- data/lib/plutonium/dashboard/card.rb +200 -0
- data/lib/plutonium/dashboard/controller.rb +101 -0
- data/lib/plutonium/dashboard/dsl.rb +104 -0
- data/lib/plutonium/dashboard/register.rb +40 -0
- data/lib/plutonium/dashboard/route_resolution.rb +30 -0
- data/lib/plutonium/dashboard.rb +42 -0
- data/lib/plutonium/definition/actions.rb +6 -1
- data/lib/plutonium/definition/base.rb +41 -3
- data/lib/plutonium/definition/index_views.rb +1 -1
- data/lib/plutonium/definition/positioning.rb +1 -1
- data/lib/plutonium/definition/presentable.rb +2 -2
- data/lib/plutonium/helpers/assets_helper.rb +15 -2
- data/lib/plutonium/helpers/display_helper.rb +10 -2
- data/lib/plutonium/interaction/async/context.rb +6 -8
- data/lib/plutonium/interaction/async/executor.rb +6 -7
- data/lib/plutonium/interaction/async/run.rb +2 -2
- data/lib/plutonium/interaction/async/run_definition.rb +1 -1
- data/lib/plutonium/interaction/base.rb +2 -0
- data/lib/plutonium/interaction/concerns/dispatchable.rb +15 -3
- data/lib/plutonium/invites/concerns/cancel_invite.rb +3 -3
- data/lib/plutonium/invites/concerns/invite_token.rb +25 -7
- data/lib/plutonium/invites/concerns/invite_user.rb +10 -5
- data/lib/plutonium/invites/concerns/resend_invite.rb +4 -4
- data/lib/plutonium/invites/controller.rb +11 -11
- data/lib/plutonium/kanban/column.rb +13 -3
- data/lib/plutonium/kanban/dsl.rb +7 -4
- data/lib/plutonium/models/has_cents.rb +1 -1
- data/lib/plutonium/profile/security_section.rb +10 -18
- data/lib/plutonium/query/filter.rb +10 -1
- data/lib/plutonium/query/filters/association.rb +27 -2
- data/lib/plutonium/query/filters/boolean.rb +4 -4
- data/lib/plutonium/query/filters/date.rb +4 -17
- data/lib/plutonium/query/filters/date_range.rb +2 -2
- data/lib/plutonium/query/filters/select.rb +10 -1
- data/lib/plutonium/query/filters/text.rb +9 -12
- data/lib/plutonium/railtie.rb +17 -0
- data/lib/plutonium/resource/controller.rb +10 -5
- data/lib/plutonium/resource/controllers/crud_actions.rb +17 -20
- data/lib/plutonium/resource/controllers/export_csv.rb +19 -9
- data/lib/plutonium/resource/controllers/kanban_actions.rb +16 -16
- data/lib/plutonium/resource/controllers/position_actions.rb +5 -5
- data/lib/plutonium/resource/controllers/typeahead.rb +24 -1
- data/lib/plutonium/resource/query_object.rb +30 -3
- data/lib/plutonium/routing/dashboard_registration.rb +137 -0
- data/lib/plutonium/routing/resource_registration.rb +5 -0
- data/lib/plutonium/routing/route_set_extensions.rb +2 -1
- data/lib/plutonium/translation.rb +227 -0
- data/lib/plutonium/ui/actions_dropdown.rb +2 -2
- data/lib/plutonium/ui/breadcrumbs.rb +3 -3
- data/lib/plutonium/ui/color_mode_selector.rb +1 -1
- data/lib/plutonium/ui/component/behaviour.rb +46 -0
- data/lib/plutonium/ui/component/methods.rb +4 -0
- data/lib/plutonium/ui/dashboard/board.rb +80 -0
- data/lib/plutonium/ui/dashboard/card.rb +127 -0
- data/lib/plutonium/ui/dashboard/chart.rb +43 -0
- data/lib/plutonium/ui/dashboard/custom.rb +32 -0
- data/lib/plutonium/ui/dashboard/frame.rb +25 -0
- data/lib/plutonium/ui/dashboard/metric.rb +137 -0
- data/lib/plutonium/ui/dashboard/skeleton.rb +40 -0
- data/lib/plutonium/ui/display/components/attachment.rb +1 -1
- data/lib/plutonium/ui/display/components/badge.rb +7 -1
- data/lib/plutonium/ui/display/components/boolean.rb +2 -2
- data/lib/plutonium/ui/display/resource.rb +23 -16
- data/lib/plutonium/ui/export_button.rb +3 -3
- data/lib/plutonium/ui/form/components/key_value_store.rb +5 -5
- data/lib/plutonium/ui/form/components/password.rb +2 -2
- data/lib/plutonium/ui/form/components/resource_select.rb +27 -0
- data/lib/plutonium/ui/form/components/secure_association.rb +2 -0
- data/lib/plutonium/ui/form/components/uppy.rb +11 -3
- data/lib/plutonium/ui/form/concerns/renders_nested_resource_fields.rb +1 -1
- data/lib/plutonium/ui/form/concerns/renders_repeater_row_controls.rb +5 -4
- data/lib/plutonium/ui/form/concerns/renders_structured_inputs.rb +1 -1
- data/lib/plutonium/ui/form/query.rb +8 -8
- data/lib/plutonium/ui/form/resource.rb +54 -26
- data/lib/plutonium/ui/frame_navigator_panel.rb +4 -4
- data/lib/plutonium/ui/grid/card.rb +3 -3
- data/lib/plutonium/ui/grid/resource.rb +3 -3
- data/lib/plutonium/ui/interaction/async/run_progress.rb +6 -6
- data/lib/plutonium/ui/interaction/async/running_banner.rb +2 -2
- data/lib/plutonium/ui/kanban/column.rb +6 -6
- data/lib/plutonium/ui/layout/base.rb +19 -1
- data/lib/plutonium/ui/layout/header.rb +1 -1
- data/lib/plutonium/ui/layout/icon_rail.rb +4 -4
- data/lib/plutonium/ui/layout/rodauth_layout.rb +1 -1
- data/lib/plutonium/ui/layout/sidebar.rb +1 -1
- data/lib/plutonium/ui/layout/topbar.rb +2 -2
- data/lib/plutonium/ui/modal/base.rb +4 -4
- data/lib/plutonium/ui/nav_grid_menu.rb +1 -1
- data/lib/plutonium/ui/nav_user.rb +2 -2
- data/lib/plutonium/ui/page/base.rb +10 -2
- data/lib/plutonium/ui/page/dashboard.rb +54 -0
- data/lib/plutonium/ui/page/edit.rb +3 -3
- data/lib/plutonium/ui/page/interactive_action.rb +2 -2
- data/lib/plutonium/ui/page/new.rb +3 -3
- data/lib/plutonium/ui/page/show.rb +2 -2
- data/lib/plutonium/ui/page/wizard.rb +22 -16
- data/lib/plutonium/ui/page/wizard_chooser.rb +10 -10
- data/lib/plutonium/ui/page/wizard_completed.rb +2 -2
- data/lib/plutonium/ui/skeleton_table.rb +1 -1
- data/lib/plutonium/ui/table/base.rb +2 -2
- data/lib/plutonium/ui/table/components/attachment.rb +1 -1
- data/lib/plutonium/ui/table/components/bulk_actions_toolbar.rb +19 -3
- data/lib/plutonium/ui/table/components/drag_handle.rb +3 -3
- data/lib/plutonium/ui/table/components/filter_form.rb +11 -10
- data/lib/plutonium/ui/table/components/filter_pills.rb +4 -4
- data/lib/plutonium/ui/table/components/pagy_info.rb +61 -17
- data/lib/plutonium/ui/table/components/pagy_pagination.rb +9 -5
- data/lib/plutonium/ui/table/components/row_actions_dropdown.rb +1 -1
- data/lib/plutonium/ui/table/components/scopes_bar.rb +6 -2
- data/lib/plutonium/ui/table/components/scopes_pills.rb +3 -3
- data/lib/plutonium/ui/table/components/selection_column.rb +1 -1
- data/lib/plutonium/ui/table/components/toolbar.rb +2 -2
- data/lib/plutonium/ui/table/components/view_switcher.rb +12 -8
- data/lib/plutonium/ui/table/resource.rb +53 -17
- data/lib/plutonium/ui/wizard/review.rb +7 -7
- data/lib/plutonium/ui/wizard/stepper.rb +1 -1
- data/lib/plutonium/ui/wizard/summary_display.rb +6 -0
- data/lib/plutonium/version.rb +1 -1
- data/lib/plutonium/wizard/base.rb +2 -0
- data/lib/plutonium/wizard/driving.rb +8 -2
- data/lib/plutonium/wizard/dsl.rb +4 -2
- data/lib/plutonium/wizard/field_importer.rb +1 -8
- data/lib/plutonium/wizard/review_step.rb +8 -1
- data/lib/plutonium/wizard/step.rb +14 -3
- data/package.json +4 -2
- data/plutonium.gemspec +4 -1
- data/postcss-gem-import.cjs +34 -0
- data/postcss-gem-import.js +3 -28
- data/src/css/components.css +91 -0
- data/src/css/slim_select.css +0 -38
- data/src/css/tokens.css +24 -0
- data/src/js/controllers/attachment_input_controller.js +15 -6
- data/src/js/controllers/chart_controller.js +162 -0
- data/src/js/controllers/clipboard_controller.js +3 -2
- data/src/js/controllers/dirty_form_guard_controller.js +4 -3
- data/src/js/controllers/easymde_controller.js +3 -0
- data/src/js/controllers/filter_panel_controller.js +15 -6
- data/src/js/controllers/flatpickr_controller.js +8 -0
- data/src/js/controllers/frame_refresh_controller.js +59 -0
- data/src/js/controllers/intl_tel_input_controller.js +5 -0
- data/src/js/controllers/register_controllers.js +4 -0
- data/src/js/controllers/slim_select_controller.js +79 -176
- data/src/js/core.js +9 -1
- data/src/js/i18n.js +69 -0
- data/src/js/plutonium-charts.js +15 -0
- data/src/js/turbo/turbo_confirm.js +5 -3
- metadata +64 -16
- data/.yarnrc.yml +0 -8
- data/config/initializers/hotwire_turbo_monkey_patches.rb +0 -12
- data/lib/generators/pu/field/input/input_generator.rb +0 -32
- data/lib/generators/pu/field/input/templates/input.rb.tt +0 -15
- data/lib/generators/pu/lib/plutonium_generators/concerns/config.rb +0 -39
- data/lib/generators/pu/lib/plutonium_generators/concerns/serializer.rb +0 -39
- data/lib/generators/pu/lib/plutonium_generators/installer.rb +0 -205
- data/lib/generators/pu/res/scaffold/templates/presenter.rb.tt +0 -13
- data/lib/generators/pu/res/scaffold/templates/query_object.rb.tt +0 -17
- data/public/plutonium-assets/application.js +0 -31419
- data/public/plutonium-assets/plutonium.ico +0 -0
- data/yarn.lock +0 -6858
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "../lib/plutonium_generators"
|
|
4
|
+
|
|
5
|
+
module Pu
|
|
6
|
+
# Scaffolds a dashboard class and mounts it in a portal (or the main app).
|
|
7
|
+
#
|
|
8
|
+
# rails g pu:dashboard Sales --dest=admin_portal
|
|
9
|
+
# rails g pu:dashboard Home --dest=admin_portal --at=/ # the portal root
|
|
10
|
+
#
|
|
11
|
+
# Writes `app/dashboards/<portal>/<name>_dashboard.rb` inside the package
|
|
12
|
+
# and adds `register_dashboard` to its routes. A root mount replaces the
|
|
13
|
+
# generated `root to: "dashboard#index"` line.
|
|
14
|
+
class DashboardGenerator < Rails::Generators::NamedBase
|
|
15
|
+
include PlutoniumGenerators::Generator
|
|
16
|
+
|
|
17
|
+
source_root File.expand_path("templates", __dir__)
|
|
18
|
+
|
|
19
|
+
desc(
|
|
20
|
+
"Create a dashboard and mount it in a portal\n\n" \
|
|
21
|
+
"e.g. rails g pu:dashboard Sales --dest=admin_portal\n" \
|
|
22
|
+
" rails g pu:dashboard Home --dest=admin_portal --at=/"
|
|
23
|
+
)
|
|
24
|
+
|
|
25
|
+
class_option :at, type: :string, default: nil,
|
|
26
|
+
desc: "Mount path under the portal's dashboards/ segment (default: the dashboard name; \"/\" mounts at the root)"
|
|
27
|
+
|
|
28
|
+
def start
|
|
29
|
+
@app_namespace = portal_option(:dest, prompt: "Select destination portal").camelize
|
|
30
|
+
|
|
31
|
+
template "dashboard.rb", dashboard_file_path
|
|
32
|
+
register_dashboard_in_routes
|
|
33
|
+
rescue => e
|
|
34
|
+
exception "#{self.class} failed:", e
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
private
|
|
38
|
+
|
|
39
|
+
attr_reader :app_namespace
|
|
40
|
+
|
|
41
|
+
def main_app? = app_namespace == "MainApp"
|
|
42
|
+
|
|
43
|
+
def package_namespace = app_namespace.underscore
|
|
44
|
+
|
|
45
|
+
def dashboard_name = "#{class_name.delete_suffix("Dashboard")}Dashboard"
|
|
46
|
+
|
|
47
|
+
def dashboard_class_name
|
|
48
|
+
main_app? ? dashboard_name : "#{app_namespace}::#{dashboard_name}"
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def dashboard_label = dashboard_name.delete_suffix("Dashboard").titleize
|
|
52
|
+
|
|
53
|
+
def mount_path
|
|
54
|
+
path = options[:at].presence || dashboard_name.delete_suffix("Dashboard").underscore
|
|
55
|
+
path.to_s.sub(%r{\A/+}, "").sub(%r{/+\z}, "")
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def root_mount? = mount_path.empty?
|
|
59
|
+
|
|
60
|
+
def dashboard_file_path
|
|
61
|
+
file = "#{dashboard_name.underscore}.rb"
|
|
62
|
+
main_app? ? "app/dashboards/#{file}" : "packages/#{package_namespace}/app/dashboards/#{package_namespace}/#{file}"
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def routes_path
|
|
66
|
+
main_app? ? "config/routes.rb" : "packages/#{package_namespace}/config/routes.rb"
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
def register_line
|
|
70
|
+
constant = main_app? ? "::#{dashboard_class_name}" : dashboard_class_name
|
|
71
|
+
%(register_dashboard #{constant}, at: "#{root_mount? ? "/" : mount_path}")
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
# Idempotent: skips when the routes already register this dashboard. A root
|
|
75
|
+
# mount replaces the portal's generated `root to:` line, since two roots
|
|
76
|
+
# would clash.
|
|
77
|
+
def register_dashboard_in_routes
|
|
78
|
+
content = File.read(File.join(destination_root, routes_path))
|
|
79
|
+
|
|
80
|
+
if /^\s*register_dashboard #{Regexp.escape("::" + dashboard_class_name)}\b|^\s*register_dashboard #{Regexp.escape(dashboard_class_name)}\b/.match?(content)
|
|
81
|
+
say_status :identical, "#{routes_path} already registers #{dashboard_class_name}", :blue
|
|
82
|
+
return
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
if root_mount? && (match = content.match(/^\s*root to: "dashboard#index".*\n/))
|
|
86
|
+
gsub_file routes_path, match[0], indent("#{register_line}\n", 2)
|
|
87
|
+
elsif /^\s*#\s*register resources above\b/.match?(content)
|
|
88
|
+
insert_into_file routes_path, indent("#{register_line}\n", 2), before: /^\s*#\s*register resources above\b.*/
|
|
89
|
+
elsif (match = content.match(/^\s*(\w+::Engine|Rails\.application)\.routes\.draw do.*\n/))
|
|
90
|
+
insert_into_file routes_path, indent("#{register_line}\n", 2), after: match[0]
|
|
91
|
+
else
|
|
92
|
+
say_status :warn, "Could not locate routes block in #{routes_path}; add manually: #{register_line}", :yellow
|
|
93
|
+
end
|
|
94
|
+
end
|
|
95
|
+
end
|
|
96
|
+
end
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
<%- if main_app? -%>
|
|
2
|
+
class <%= dashboard_name %> < Plutonium::Dashboard::Base
|
|
3
|
+
presents label: "<%= dashboard_label %>"
|
|
4
|
+
|
|
5
|
+
# Cards sit on a 12-column grid (metric 3, chart and card 6 by default; `span:`
|
|
6
|
+
# overrides), each in its own lazy turbo frame. `group_by_day` is the groupdate gem.
|
|
7
|
+
#
|
|
8
|
+
# metric(:users, icon: Phlex::TablerIcons::Users) { authorized_resource_scope(User).count }
|
|
9
|
+
# chart(:signups, type: :line, span: 8) { User.group_by_day(:created_at, last: 30).count }
|
|
10
|
+
# card(:welcome) { p { "Signed in as #{current_user.email}" } }
|
|
11
|
+
end
|
|
12
|
+
<%- else -%>
|
|
13
|
+
module <%= app_namespace %>
|
|
14
|
+
class <%= dashboard_name %> < Plutonium::Dashboard::Base
|
|
15
|
+
presents label: "<%= dashboard_label %>"
|
|
16
|
+
|
|
17
|
+
# Cards sit on a 12-column grid (metric 3, chart and card 6 by default; `span:`
|
|
18
|
+
# overrides), each in its own lazy turbo frame. `group_by_day` is the groupdate gem.
|
|
19
|
+
#
|
|
20
|
+
# metric(:users, icon: Phlex::TablerIcons::Users) { authorized_resource_scope(User).count }
|
|
21
|
+
# chart(:signups, type: :line, span: 8) { User.group_by_day(:created_at, last: 30).count }
|
|
22
|
+
# card(:welcome) { p { "Signed in as #{current_user.email}" } }
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
<%- end -%>
|
|
@@ -11,6 +11,11 @@ module Pu
|
|
|
11
11
|
|
|
12
12
|
desc "Set up docker for project"
|
|
13
13
|
|
|
14
|
+
# Fallbacks when the tool is not on PATH at generation time.
|
|
15
|
+
NODE_LTS_VERSION = "22.23.2"
|
|
16
|
+
YARN_CLASSIC_VERSION = "1.22.22"
|
|
17
|
+
BUN_VERSION = "1.4.2"
|
|
18
|
+
|
|
14
19
|
def start
|
|
15
20
|
in_root do
|
|
16
21
|
template "Dockerfile", force: true
|
|
@@ -30,6 +35,59 @@ module Pu
|
|
|
30
35
|
def ruby_version
|
|
31
36
|
@ruby_version ||= File.read(".ruby-version").strip
|
|
32
37
|
end
|
|
38
|
+
|
|
39
|
+
# The JavaScript toolchain is chosen the same way jsbundling-rails will
|
|
40
|
+
# choose it at `assets:precompile` inside the image, and versions come
|
|
41
|
+
# from the machine generating the Dockerfile (or NODE_VERSION /
|
|
42
|
+
# YARN_VERSION / BUN_VERSION), like Rails' own template.
|
|
43
|
+
def using_bun?
|
|
44
|
+
js_package_manager == :bun
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def dockerfile_node_version
|
|
48
|
+
ENV.fetch("NODE_VERSION") { node_version || NODE_LTS_VERSION }
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def dockerfile_yarn_version
|
|
52
|
+
ENV.fetch("YARN_VERSION") { yarn_version || YARN_CLASSIC_VERSION }
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def dockerfile_bun_version
|
|
56
|
+
ENV.fetch("BUN_VERSION") { bun_version || BUN_VERSION }
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
# Yarn 2+ is installed through corepack, and its lockfile flag is
|
|
60
|
+
# --immutable rather than yarn 1's --frozen-lockfile.
|
|
61
|
+
def yarn_through_corepack?
|
|
62
|
+
js_package_manager == :yarn && dockerfile_yarn_version.to_i >= 2
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def js_lockfile_copy
|
|
66
|
+
case js_package_manager
|
|
67
|
+
when :bun then "package.json bun.lock*"
|
|
68
|
+
when :pnpm then "package.json pnpm-lock.yaml"
|
|
69
|
+
when :npm then "package.json package-lock.json"
|
|
70
|
+
# .yarnrc.yml carries nodeLinker for yarn 2+; the glob keeps the COPY
|
|
71
|
+
# valid on yarn 1 apps that have none.
|
|
72
|
+
else "package.json yarn.lock .yarnrc.yml*"
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
def js_frozen_install_command
|
|
77
|
+
case js_package_manager
|
|
78
|
+
when :bun then "bun install --frozen-lockfile"
|
|
79
|
+
when :pnpm then "pnpm install --frozen-lockfile"
|
|
80
|
+
when :npm then "npm ci"
|
|
81
|
+
else "yarn install --#{yarn_through_corepack? ? "immutable" : "frozen-lockfile"}"
|
|
82
|
+
end
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
# docker-compose runs this on every container start. `npm ci` would wipe
|
|
86
|
+
# node_modules each boot and fails without a package-lock.json, so npm
|
|
87
|
+
# gets a plain install; the others already only sync what changed.
|
|
88
|
+
def js_dev_install_command
|
|
89
|
+
(js_package_manager == :npm) ? "npm install" : js_install_command
|
|
90
|
+
end
|
|
33
91
|
end
|
|
34
92
|
end
|
|
35
93
|
end
|
|
@@ -11,13 +11,28 @@ RUN apt-get update -qq && \
|
|
|
11
11
|
apt-get install --no-install-recommends -y build-essential curl git libpq-dev node-gyp pkg-config python-is-python3
|
|
12
12
|
|
|
13
13
|
# Install JavaScript dependencies
|
|
14
|
-
|
|
15
|
-
|
|
14
|
+
<% if using_bun? -%>
|
|
15
|
+
ENV BUN_INSTALL=/usr/local/bun
|
|
16
|
+
ENV PATH=/usr/local/bun/bin:$PATH
|
|
17
|
+
ARG BUN_VERSION=<%= dockerfile_bun_version %>
|
|
18
|
+
RUN curl -fsSL https://bun.sh/install | bash -s -- "bun-v${BUN_VERSION}"
|
|
19
|
+
<% else -%>
|
|
20
|
+
ARG NODE_VERSION=<%= dockerfile_node_version %>
|
|
16
21
|
ENV PATH=/usr/local/node/bin:$PATH
|
|
17
22
|
RUN curl -sL https://github.com/nodenv/node-build/archive/master.tar.gz | tar xz -C /tmp/ && \
|
|
18
23
|
/tmp/node-build-master/bin/node-build "${NODE_VERSION}" /usr/local/node && \
|
|
19
|
-
npm install -g yarn@$YARN_VERSION && \
|
|
20
24
|
rm -rf /tmp/node-build-master
|
|
25
|
+
<% if js_package_manager == :yarn -%>
|
|
26
|
+
ARG YARN_VERSION=<%= dockerfile_yarn_version %>
|
|
27
|
+
<% if yarn_through_corepack? -%>
|
|
28
|
+
RUN corepack enable && corepack install -g yarn@$YARN_VERSION
|
|
29
|
+
<% else -%>
|
|
30
|
+
RUN npm install -g yarn@$YARN_VERSION
|
|
31
|
+
<% end -%>
|
|
32
|
+
<% elsif js_package_manager == :pnpm -%>
|
|
33
|
+
RUN corepack enable && corepack install -g pnpm@latest
|
|
34
|
+
<% end -%>
|
|
35
|
+
<% end -%>
|
|
21
36
|
|
|
22
37
|
# Additional dependencies go here
|
|
23
38
|
|
|
@@ -22,13 +22,28 @@ RUN apt-get update -qq && \
|
|
|
22
22
|
apt-get install --no-install-recommends -y build-essential curl git libpq-dev node-gyp pkg-config python-is-python3
|
|
23
23
|
|
|
24
24
|
# Install JavaScript dependencies
|
|
25
|
-
|
|
26
|
-
|
|
25
|
+
<% if using_bun? -%>
|
|
26
|
+
ENV BUN_INSTALL=/usr/local/bun
|
|
27
|
+
ENV PATH=/usr/local/bun/bin:$PATH
|
|
28
|
+
ARG BUN_VERSION=<%= dockerfile_bun_version %>
|
|
29
|
+
RUN curl -fsSL https://bun.sh/install | bash -s -- "bun-v${BUN_VERSION}"
|
|
30
|
+
<% else -%>
|
|
31
|
+
ARG NODE_VERSION=<%= dockerfile_node_version %>
|
|
27
32
|
ENV PATH=/usr/local/node/bin:$PATH
|
|
28
33
|
RUN curl -sL https://github.com/nodenv/node-build/archive/master.tar.gz | tar xz -C /tmp/ && \
|
|
29
34
|
/tmp/node-build-master/bin/node-build "${NODE_VERSION}" /usr/local/node && \
|
|
30
|
-
npm install -g yarn@$YARN_VERSION && \
|
|
31
35
|
rm -rf /tmp/node-build-master
|
|
36
|
+
<% if js_package_manager == :yarn -%>
|
|
37
|
+
ARG YARN_VERSION=<%= dockerfile_yarn_version %>
|
|
38
|
+
<% if yarn_through_corepack? -%>
|
|
39
|
+
RUN corepack enable && corepack install -g yarn@$YARN_VERSION
|
|
40
|
+
<% else -%>
|
|
41
|
+
RUN npm install -g yarn@$YARN_VERSION
|
|
42
|
+
<% end -%>
|
|
43
|
+
<% elsif js_package_manager == :pnpm -%>
|
|
44
|
+
RUN corepack enable && corepack install -g pnpm@latest
|
|
45
|
+
<% end -%>
|
|
46
|
+
<% end -%>
|
|
32
47
|
|
|
33
48
|
# Install application gems
|
|
34
49
|
COPY .ruby-version Gemfile Gemfile.lock ./
|
|
@@ -37,8 +52,8 @@ RUN bundle install && \
|
|
|
37
52
|
bundle exec bootsnap precompile --gemfile
|
|
38
53
|
|
|
39
54
|
# Install node modules
|
|
40
|
-
COPY
|
|
41
|
-
RUN
|
|
55
|
+
COPY <%= js_lockfile_copy %> ./
|
|
56
|
+
RUN <%= js_frozen_install_command %>
|
|
42
57
|
|
|
43
58
|
# Copy application code
|
|
44
59
|
COPY . .
|
data/lib/generators/pu/docker/install/templates/{docker-compose.yml → docker-compose.yml.tt}
RENAMED
|
@@ -22,7 +22,7 @@ x-app: &app
|
|
|
22
22
|
services:
|
|
23
23
|
web:
|
|
24
24
|
<<: *app
|
|
25
|
-
command: /bin/sh -c "rm -f tmp/pids/server.pid && bundle &&
|
|
25
|
+
command: /bin/sh -c "rm -f tmp/pids/server.pid && bundle && <%= js_dev_install_command %> && bin/dev" # web
|
|
26
26
|
# web properties
|
|
27
27
|
ports: # web
|
|
28
28
|
- "3000:3000" # app
|
|
@@ -19,7 +19,7 @@ module Pu
|
|
|
19
19
|
|
|
20
20
|
copy_file "config/initializers/001_ensure_required_env.rb"
|
|
21
21
|
|
|
22
|
-
gitignore "!/.env", "!/.env.template", "!/.env.local.template"
|
|
22
|
+
gitignore "!/.env", "!/.env.template", "!/.env.local.template"
|
|
23
23
|
|
|
24
24
|
insert_into_file "Gemfile", "\ngem \"dotenv\", groups: %i[development test]\n", after: /^gem ["']rails["'].*\n/
|
|
25
25
|
bundle!
|
|
@@ -22,7 +22,7 @@ module Pu
|
|
|
22
22
|
desc: "The user model name"
|
|
23
23
|
|
|
24
24
|
class_option :membership_model, type: :string,
|
|
25
|
-
desc: "The membership model name (
|
|
25
|
+
desc: "The membership model name (e.g. OrganizationUser). When omitted, the generated interaction has no membership_class override and you must implement it yourself"
|
|
26
26
|
|
|
27
27
|
class_option :dest, type: :string, default: "main_app",
|
|
28
28
|
desc: "Destination package for the interaction"
|
|
@@ -51,6 +51,23 @@ module Pu
|
|
|
51
51
|
end
|
|
52
52
|
end
|
|
53
53
|
|
|
54
|
+
def warn_membership_model_default
|
|
55
|
+
return if behavior == :revoke
|
|
56
|
+
return if options[:membership_model].present?
|
|
57
|
+
|
|
58
|
+
say_status :warning,
|
|
59
|
+
"No --membership-model specified — generated interaction will not include a membership_class override.",
|
|
60
|
+
:yellow
|
|
61
|
+
say <<~MSG.indent(2)
|
|
62
|
+
|
|
63
|
+
The generated #{model_class}::InviteUserInteraction raises
|
|
64
|
+
NotImplementedError from #membership_class at runtime until you either:
|
|
65
|
+
- re-run with --membership-model=NAME (e.g. --membership-model=OrganizationUser), or
|
|
66
|
+
- manually override #membership_class to return your membership join model.
|
|
67
|
+
|
|
68
|
+
MSG
|
|
69
|
+
end
|
|
70
|
+
|
|
54
71
|
def create_interaction
|
|
55
72
|
template "invitable/invite_user_interaction.rb",
|
|
56
73
|
interaction_path
|
|
@@ -29,101 +29,6 @@ module PlutoniumGenerators
|
|
|
29
29
|
end
|
|
30
30
|
end
|
|
31
31
|
|
|
32
|
-
# #
|
|
33
|
-
# # Adds a new gem into the Gemfile
|
|
34
|
-
# # Existing directives are updated if they do not match
|
|
35
|
-
# # When `:group` is specified, the gem is inserted into the approriate gem group.
|
|
36
|
-
# #
|
|
37
|
-
# # @param [String] name the name of the gem
|
|
38
|
-
# # @param [Hash] **kwargs set of options to append see #super
|
|
39
|
-
# #
|
|
40
|
-
# # @return [void]
|
|
41
|
-
# #
|
|
42
|
-
# def gem(name, **kwargs)
|
|
43
|
-
# groups = Array(kwargs.delete(:group))
|
|
44
|
-
|
|
45
|
-
# in_root do
|
|
46
|
-
# begin
|
|
47
|
-
# # Create a temp gemfile
|
|
48
|
-
# File.rename("Gemfile", "Gemfile.bak")
|
|
49
|
-
# File.write("Gemfile", "")
|
|
50
|
-
# # Generate the directive
|
|
51
|
-
# super
|
|
52
|
-
# # Get the generated directive
|
|
53
|
-
# directive = gemfile.strip
|
|
54
|
-
# ensure
|
|
55
|
-
# # Restore our gemfile
|
|
56
|
-
# File.delete "Gemfile"
|
|
57
|
-
# File.rename "Gemfile.bak", "Gemfile"
|
|
58
|
-
# end
|
|
59
|
-
|
|
60
|
-
# pattern = /^# gem ['"]#{name}['"].*/
|
|
61
|
-
# if gemfile.match(pattern)
|
|
62
|
-
# # Replace commented out directive
|
|
63
|
-
# gsub_file("Gemfile", pattern, directive)
|
|
64
|
-
# break
|
|
65
|
-
# end
|
|
66
|
-
|
|
67
|
-
# # Remove existing directive
|
|
68
|
-
# remove_gem name
|
|
69
|
-
|
|
70
|
-
# # Insert the new directive
|
|
71
|
-
# if groups != []
|
|
72
|
-
# str = groups.sort.map(&:inspect).join(", ")
|
|
73
|
-
# after_sentinel = "group #{str} do\n"
|
|
74
|
-
|
|
75
|
-
# unless File.read("Gemfile").match?(/^#{after_sentinel}/)
|
|
76
|
-
# inject_into_file "Gemfile", "\n#{after_sentinel}end\n"
|
|
77
|
-
# end
|
|
78
|
-
# else
|
|
79
|
-
# after_sentinel = "# Project gems\n\n"
|
|
80
|
-
# unless File.read("Gemfile").match?(/^#{after_sentinel}/)
|
|
81
|
-
# inject_into_file "Gemfile", "\n#{after_sentinel}", after: /^ruby .*\n/
|
|
82
|
-
# end
|
|
83
|
-
# end
|
|
84
|
-
|
|
85
|
-
# inject_into_file "Gemfile", "#{directive}\n", after: /^#{after_sentinel}/
|
|
86
|
-
# end
|
|
87
|
-
# end
|
|
88
|
-
|
|
89
|
-
# #
|
|
90
|
-
# # Removes a gem and any preceeding comments from the Gemfile
|
|
91
|
-
# #
|
|
92
|
-
# # @param gem [String] the name of the gem to remove
|
|
93
|
-
# #
|
|
94
|
-
# # @return [void]
|
|
95
|
-
# #
|
|
96
|
-
# def remove_gem(gem)
|
|
97
|
-
# log :remove_gem, gem
|
|
98
|
-
# gsub_file "Gemfile", /(:?^.*#.*\n)*.*gem ['"]#{gem}['"].*\n/, "", verbose: false
|
|
99
|
-
# end
|
|
100
|
-
|
|
101
|
-
# #
|
|
102
|
-
# # Evaluates the given template and merges it with the project's docker-compose.yml
|
|
103
|
-
# #
|
|
104
|
-
# # @param [String] source the template filename
|
|
105
|
-
# #
|
|
106
|
-
# # @return [void]
|
|
107
|
-
# #
|
|
108
|
-
# def docker_compose(source)
|
|
109
|
-
# log :docker_compose, source
|
|
110
|
-
|
|
111
|
-
# in_root do
|
|
112
|
-
# compose_file = "docker-compose.yml"
|
|
113
|
-
# compose_definition = YAML.load_file(compose_file) if File.exist?(compose_file)
|
|
114
|
-
# compose_definition ||= {
|
|
115
|
-
# version: "3.7",
|
|
116
|
-
# services: {}
|
|
117
|
-
# }
|
|
118
|
-
# compose_definition.deep_stringify_keys!
|
|
119
|
-
|
|
120
|
-
# new_definition = YAML.load template_eval("docker-compose/#{source}.yml.tt")
|
|
121
|
-
# compose_definition.deep_merge! new_definition.deep_stringify_keys
|
|
122
|
-
|
|
123
|
-
# create_file compose_file, YAML.dump(compose_definition), force: true
|
|
124
|
-
# end
|
|
125
|
-
# end
|
|
126
|
-
|
|
127
32
|
def managed_compose(file = "docker-compose.yml")
|
|
128
33
|
@managed_compose ||= {}
|
|
129
34
|
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
begin
|
|
4
|
+
# jsbundling-rails `main` ships the detector its build task uses (unreleased
|
|
5
|
+
# as of 1.3.1).
|
|
6
|
+
require "jsbundling/package_manager"
|
|
7
|
+
rescue LoadError
|
|
8
|
+
# Older jsbundling-rails, or an app without it: fall back to the same rules.
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
module PlutoniumGenerators
|
|
12
|
+
module Concerns
|
|
13
|
+
# Detects the host app's JavaScript package manager and builds the matching
|
|
14
|
+
# shell commands, so generators never hardcode `yarn`.
|
|
15
|
+
#
|
|
16
|
+
# Detection follows what `rails javascript:build` consults at precompile
|
|
17
|
+
# time. Installing with the same tool keeps a single lockfile: the lockfile
|
|
18
|
+
# decides, and with no lockfile the first executable on PATH among bun,
|
|
19
|
+
# yarn, pnpm and npm wins (yarn when none is found). `rails new -j esbuild`
|
|
20
|
+
# therefore yields a bun app on any machine that has bun installed, and
|
|
21
|
+
# this concern follows it rather than adding a yarn.lock next to bun.lock.
|
|
22
|
+
# When the app's jsbundling-rails provides Jsbundling::PackageManager, that
|
|
23
|
+
# detector is used as is (it skips yarn.lock and decides by PATH).
|
|
24
|
+
#
|
|
25
|
+
# Yarn 1 (classic) and Yarn 2+ (berry) share `add` but differ elsewhere;
|
|
26
|
+
# `yarn_berry?` distinguishes them by asking the yarn the app resolves to.
|
|
27
|
+
module JsPackageManager
|
|
28
|
+
ADD_COMMANDS = {
|
|
29
|
+
bun: "bun add %s",
|
|
30
|
+
pnpm: "pnpm add %s",
|
|
31
|
+
npm: "npm install %s",
|
|
32
|
+
yarn: "yarn add %s"
|
|
33
|
+
}.freeze
|
|
34
|
+
|
|
35
|
+
INSTALL_COMMANDS = {
|
|
36
|
+
bun: "bun install",
|
|
37
|
+
pnpm: "pnpm install",
|
|
38
|
+
npm: "npm ci",
|
|
39
|
+
yarn: "yarn install"
|
|
40
|
+
}.freeze
|
|
41
|
+
|
|
42
|
+
RUN_COMMANDS = {
|
|
43
|
+
bun: "bun run %s",
|
|
44
|
+
pnpm: "pnpm run %s",
|
|
45
|
+
npm: "npm run %s",
|
|
46
|
+
yarn: "yarn %s"
|
|
47
|
+
}.freeze
|
|
48
|
+
|
|
49
|
+
LOCKFILES = {
|
|
50
|
+
bun: %w[bun.lock bun.lockb],
|
|
51
|
+
pnpm: %w[pnpm-lock.yaml],
|
|
52
|
+
npm: %w[package-lock.json],
|
|
53
|
+
yarn: %w[yarn.lock]
|
|
54
|
+
}.freeze
|
|
55
|
+
|
|
56
|
+
protected
|
|
57
|
+
|
|
58
|
+
# @return [Symbol] one of :bun, :pnpm, :npm, :yarn
|
|
59
|
+
def js_package_manager
|
|
60
|
+
@js_package_manager ||= detect_js_package_manager
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
# One command that adds every package. `pkg@latest` and `pkg@^1.2.3`
|
|
64
|
+
# are understood identically by all four managers, which is why callers
|
|
65
|
+
# pass versions inline instead of running a separate upgrade step
|
|
66
|
+
# (yarn 1 `upgrade --latest` has no yarn 2+ equivalent).
|
|
67
|
+
def js_add_command(*packages)
|
|
68
|
+
format(ADD_COMMANDS.fetch(js_package_manager), packages.flatten.join(" "))
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
def js_install_command
|
|
72
|
+
INSTALL_COMMANDS.fetch(js_package_manager)
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
def js_run_command(script)
|
|
76
|
+
format(RUN_COMMANDS.fetch(js_package_manager), script)
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
# Yarn 2+ ("berry"). Asks the yarn the app resolves to, so corepack and
|
|
80
|
+
# a `packageManager` field are honoured.
|
|
81
|
+
def yarn_berry?
|
|
82
|
+
js_package_manager == :yarn && yarn_version.to_i >= 2
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
def yarn_version
|
|
86
|
+
@yarn_version ||= capture_tool_version("yarn --version")
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
def bun_version
|
|
90
|
+
@bun_version ||= capture_tool_version("bun --version")
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
def node_version
|
|
94
|
+
@node_version ||= capture_tool_version("node --version")
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
# Yarn 2+ defaults to Plug'n'Play, which Tailwind's PostCSS plugin cannot
|
|
98
|
+
# be loaded under (postcss-load-config's require() options are not
|
|
99
|
+
# supported by PnP). Pin the node-modules linker, which is what the rest
|
|
100
|
+
# of the Rails toolchain expects, unless the app already chose a linker.
|
|
101
|
+
def ensure_yarn_node_modules_linker
|
|
102
|
+
return unless yarn_berry?
|
|
103
|
+
|
|
104
|
+
yarnrc = ".yarnrc.yml"
|
|
105
|
+
existing = in_root_dir { File.exist?(yarnrc) ? File.read(yarnrc) : nil }
|
|
106
|
+
|
|
107
|
+
if existing.nil?
|
|
108
|
+
create_file yarnrc, "nodeLinker: node-modules\n"
|
|
109
|
+
elsif existing.match?(/^nodeLinker:/)
|
|
110
|
+
unless existing.match?(/^nodeLinker:\s*node-modules\b/)
|
|
111
|
+
warn "#{yarnrc} sets a nodeLinker other than node-modules; the Tailwind PostCSS plugin does not load under Plug'n'Play"
|
|
112
|
+
end
|
|
113
|
+
else
|
|
114
|
+
append_to_file yarnrc, "nodeLinker: node-modules\n"
|
|
115
|
+
end
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
private
|
|
119
|
+
|
|
120
|
+
def detect_js_package_manager
|
|
121
|
+
root = Pathname(destination_root)
|
|
122
|
+
return Jsbundling::PackageManager.detect(root) if defined?(Jsbundling::PackageManager)
|
|
123
|
+
|
|
124
|
+
LOCKFILES.each do |manager, files|
|
|
125
|
+
return manager if files.any? { |f| root.join(f).exist? }
|
|
126
|
+
end
|
|
127
|
+
return :bun if root.join("bun.config.js").exist?
|
|
128
|
+
|
|
129
|
+
%i[bun yarn pnpm npm].find { |exe| system("command -v #{exe} > /dev/null") } || :yarn
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
def capture_tool_version(command)
|
|
133
|
+
output = in_root_dir { `#{command} 2>/dev/null` }
|
|
134
|
+
output[/\d+\.\d+\.\d+/]
|
|
135
|
+
rescue SystemCallError
|
|
136
|
+
nil
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
# Thor's in_root returns the block value too, but only once the generator
|
|
140
|
+
# has a destination stack; this stays usable from plain unit tests.
|
|
141
|
+
def in_root_dir(&)
|
|
142
|
+
Dir.chdir(destination_root, &)
|
|
143
|
+
end
|
|
144
|
+
end
|
|
145
|
+
end
|
|
146
|
+
end
|
|
@@ -5,10 +5,9 @@ require "tty-prompt"
|
|
|
5
5
|
|
|
6
6
|
module PlutoniumGenerators
|
|
7
7
|
module Generator
|
|
8
|
-
include Concerns::Config
|
|
9
8
|
include Concerns::Logger
|
|
10
|
-
include Concerns::Serializer
|
|
11
9
|
include Concerns::Actions
|
|
10
|
+
include Concerns::JsPackageManager
|
|
12
11
|
|
|
13
12
|
# Finds the shared namespace prefix between two model names.
|
|
14
13
|
# Used to derive association names when models share a namespace.
|
|
@@ -58,20 +57,5 @@ module PlutoniumGenerators
|
|
|
58
57
|
TTY::Prompt.new
|
|
59
58
|
end
|
|
60
59
|
end
|
|
61
|
-
|
|
62
|
-
# def appname
|
|
63
|
-
# defined?(Rails.application) ? Rails.application.class.module_parent.name : "PlutoniumGenerators"
|
|
64
|
-
# end
|
|
65
|
-
|
|
66
|
-
# def app_name
|
|
67
|
-
# appname.underscore
|
|
68
|
-
# end
|
|
69
|
-
|
|
70
|
-
def pug_installed?(feature, version: nil)
|
|
71
|
-
installed_version = read_config(:installed, feature)
|
|
72
|
-
return false unless installed_version.present?
|
|
73
|
-
|
|
74
|
-
version.present? ? SemanticRange.satisfies?(installed_version, ">=#{version}") : true
|
|
75
|
-
end
|
|
76
60
|
end
|
|
77
61
|
end
|
|
@@ -17,6 +17,7 @@ module Pu
|
|
|
17
17
|
validate_package_name package_name
|
|
18
18
|
|
|
19
19
|
template "lib/engine.rb", "packages/#{package_namespace}/lib/engine.rb"
|
|
20
|
+
template "config/locales/en.yml", "packages/#{package_namespace}/config/locales/en.yml"
|
|
20
21
|
|
|
21
22
|
%w[controllers interactions models policies definitions].each do |dir|
|
|
22
23
|
directory "app/#{dir}", "packages/#{package_namespace}/app/#{dir}/#{package_namespace}"
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
# Wording for the <%= package_name %> package's resources.
|
|
2
|
+
#
|
|
3
|
+
# This file is loaded automatically (the package is a Rails engine) ahead of
|
|
4
|
+
# the host app's config/locales, so the app can still override anything here.
|
|
5
|
+
#
|
|
6
|
+
# Model and attribute names follow the Rails convention; placeholders, hints
|
|
7
|
+
# and derived labels follow Plutonium's:
|
|
8
|
+
#
|
|
9
|
+
# en:
|
|
10
|
+
# activerecord:
|
|
11
|
+
# models:
|
|
12
|
+
# <%= package_namespace %>/post:
|
|
13
|
+
# one: "Post"
|
|
14
|
+
# other: "Posts"
|
|
15
|
+
# attributes:
|
|
16
|
+
# <%= package_namespace %>/post:
|
|
17
|
+
# title: "Title"
|
|
18
|
+
# plutonium:
|
|
19
|
+
# fields:
|
|
20
|
+
# <%= package_namespace %>/post:
|
|
21
|
+
# title:
|
|
22
|
+
# placeholder: "A short, descriptive title"
|
|
23
|
+
# actions:
|
|
24
|
+
# <%= package_namespace %>/post:
|
|
25
|
+
# publish: "Publish now"
|
|
26
|
+
#
|
|
27
|
+
# See https://radioactive-labs.github.io/plutonium-core/reference/i18n
|
|
28
|
+
en: {}
|
|
@@ -25,6 +25,7 @@ module Pu
|
|
|
25
25
|
|
|
26
26
|
template "lib/engine.rb", "packages/#{package_namespace}/lib/engine.rb"
|
|
27
27
|
template "config/routes.rb", "packages/#{package_namespace}/config/routes.rb"
|
|
28
|
+
template "config/locales/en.yml", "packages/#{package_namespace}/config/locales/en.yml"
|
|
28
29
|
|
|
29
30
|
template "app/controllers/concerns/controller.rb",
|
|
30
31
|
"packages/#{package_namespace}/app/controllers/#{package_namespace}/concerns/controller.rb"
|