plutonium 0.33.0 → 0.34.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (145) hide show
  1. checksums.yaml +4 -4
  2. data/# Plutonium: The pre-alpha demo.md +4 -2
  3. data/.claude/skills/assets/SKILL.md +416 -0
  4. data/.claude/skills/connect-resource/SKILL.md +112 -0
  5. data/.claude/skills/controller/SKILL.md +302 -0
  6. data/.claude/skills/create-resource/SKILL.md +240 -0
  7. data/.claude/skills/definition/SKILL.md +218 -0
  8. data/.claude/skills/definition-actions/SKILL.md +386 -0
  9. data/.claude/skills/definition-fields/SKILL.md +474 -0
  10. data/.claude/skills/definition-query/SKILL.md +334 -0
  11. data/.claude/skills/forms/SKILL.md +439 -0
  12. data/.claude/skills/installation/SKILL.md +300 -0
  13. data/.claude/skills/interaction/SKILL.md +382 -0
  14. data/.claude/skills/model/SKILL.md +267 -0
  15. data/.claude/skills/model-features/SKILL.md +286 -0
  16. data/.claude/skills/nested-resources/SKILL.md +274 -0
  17. data/.claude/skills/package/SKILL.md +191 -0
  18. data/.claude/skills/policy/SKILL.md +352 -0
  19. data/.claude/skills/portal/SKILL.md +400 -0
  20. data/.claude/skills/resource/SKILL.md +281 -0
  21. data/.claude/skills/rodauth/SKILL.md +452 -0
  22. data/.claude/skills/views/SKILL.md +563 -0
  23. data/Appraisals +46 -4
  24. data/CHANGELOG.md +36 -0
  25. data/app/assets/plutonium.css +2 -2
  26. data/config/brakeman.ignore +239 -0
  27. data/config/initializers/action_policy.rb +1 -1
  28. data/docs/.vitepress/config.ts +132 -47
  29. data/docs/concepts/architecture.md +226 -0
  30. data/docs/concepts/auto-detection.md +254 -0
  31. data/docs/concepts/index.md +61 -0
  32. data/docs/concepts/packages-portals.md +304 -0
  33. data/docs/concepts/resources.md +224 -0
  34. data/docs/cookbook/blog.md +412 -0
  35. data/docs/cookbook/index.md +289 -0
  36. data/docs/cookbook/saas.md +481 -0
  37. data/docs/getting-started/index.md +56 -0
  38. data/docs/getting-started/installation.md +146 -0
  39. data/docs/getting-started/tutorial/01-setup.md +118 -0
  40. data/docs/getting-started/tutorial/02-first-resource.md +180 -0
  41. data/docs/getting-started/tutorial/03-authentication.md +246 -0
  42. data/docs/getting-started/tutorial/04-authorization.md +170 -0
  43. data/docs/getting-started/tutorial/05-custom-actions.md +202 -0
  44. data/docs/getting-started/tutorial/06-nested-resources.md +147 -0
  45. data/docs/getting-started/tutorial/07-customizing-ui.md +254 -0
  46. data/docs/getting-started/tutorial/index.md +64 -0
  47. data/docs/guides/adding-resources.md +420 -0
  48. data/docs/guides/authentication.md +551 -0
  49. data/docs/guides/authorization.md +468 -0
  50. data/docs/guides/creating-packages.md +380 -0
  51. data/docs/guides/custom-actions.md +523 -0
  52. data/docs/guides/index.md +45 -0
  53. data/docs/guides/multi-tenancy.md +302 -0
  54. data/docs/guides/nested-resources.md +411 -0
  55. data/docs/guides/search-filtering.md +266 -0
  56. data/docs/guides/theming.md +321 -0
  57. data/docs/index.md +67 -26
  58. data/docs/public/CLAUDE.md +64 -21
  59. data/docs/reference/assets/index.md +496 -0
  60. data/docs/reference/controller/index.md +363 -0
  61. data/docs/reference/definition/actions.md +400 -0
  62. data/docs/reference/definition/fields.md +350 -0
  63. data/docs/reference/definition/index.md +252 -0
  64. data/docs/reference/definition/query.md +342 -0
  65. data/docs/reference/generators/index.md +469 -0
  66. data/docs/reference/index.md +49 -0
  67. data/docs/reference/interaction/index.md +445 -0
  68. data/docs/reference/model/features.md +248 -0
  69. data/docs/reference/model/index.md +219 -0
  70. data/docs/reference/policy/index.md +385 -0
  71. data/docs/reference/portal/index.md +382 -0
  72. data/docs/reference/views/forms.md +396 -0
  73. data/docs/reference/views/index.md +479 -0
  74. data/gemfiles/rails_7.gemfile +9 -2
  75. data/gemfiles/rails_7.gemfile.lock +146 -111
  76. data/gemfiles/rails_8.0.gemfile +20 -0
  77. data/gemfiles/rails_8.0.gemfile.lock +417 -0
  78. data/gemfiles/rails_8.1.gemfile +20 -0
  79. data/gemfiles/rails_8.1.gemfile.lock +419 -0
  80. data/lib/generators/pu/gem/dotenv/templates/.env +2 -0
  81. data/lib/generators/pu/gem/dotenv/templates/config/initializers/001_ensure_required_env.rb +3 -1
  82. data/lib/generators/pu/lib/plutonium_generators/model_generator_base.rb +13 -16
  83. data/lib/generators/pu/pkg/portal/USAGE +65 -0
  84. data/lib/generators/pu/pkg/portal/portal_generator.rb +22 -9
  85. data/lib/generators/pu/res/conn/USAGE +71 -0
  86. data/lib/generators/pu/res/model/USAGE +106 -110
  87. data/lib/generators/pu/res/model/templates/model.rb.tt +6 -2
  88. data/lib/generators/pu/res/scaffold/USAGE +85 -0
  89. data/lib/generators/pu/rodauth/install_generator.rb +2 -6
  90. data/lib/generators/pu/rodauth/templates/config/initializers/url_options.rb +17 -0
  91. data/lib/generators/pu/skills/sync/USAGE +14 -0
  92. data/lib/generators/pu/skills/sync/sync_generator.rb +66 -0
  93. data/lib/plutonium/action_policy/sti_policy_lookup.rb +1 -1
  94. data/lib/plutonium/core/controller.rb +2 -2
  95. data/lib/plutonium/interaction/base.rb +1 -0
  96. data/lib/plutonium/package/engine.rb +2 -2
  97. data/lib/plutonium/query/adhoc_block.rb +6 -2
  98. data/lib/plutonium/query/model_scope.rb +1 -1
  99. data/lib/plutonium/railtie.rb +4 -0
  100. data/lib/plutonium/resource/controllers/crud_actions/index_action.rb +1 -1
  101. data/lib/plutonium/resource/controllers/crud_actions.rb +21 -18
  102. data/lib/plutonium/resource/controllers/interactive_actions.rb +21 -25
  103. data/lib/plutonium/resource/query_object.rb +38 -8
  104. data/lib/plutonium/ui/table/components/scopes_bar.rb +39 -34
  105. data/lib/plutonium/version.rb +1 -1
  106. data/lib/tasks/release.rake +19 -4
  107. data/package.json +1 -1
  108. metadata +76 -39
  109. data/brakeman.ignore +0 -28
  110. data/docs/api-examples.md +0 -49
  111. data/docs/guide/claude-code-guide.md +0 -74
  112. data/docs/guide/deep-dive/authorization.md +0 -189
  113. data/docs/guide/deep-dive/multitenancy.md +0 -256
  114. data/docs/guide/deep-dive/resources.md +0 -390
  115. data/docs/guide/getting-started/01-installation.md +0 -165
  116. data/docs/guide/index.md +0 -28
  117. data/docs/guide/introduction/01-what-is-plutonium.md +0 -211
  118. data/docs/guide/introduction/02-core-concepts.md +0 -440
  119. data/docs/guide/tutorial/01-project-setup.md +0 -75
  120. data/docs/guide/tutorial/02-creating-a-feature-package.md +0 -45
  121. data/docs/guide/tutorial/03-defining-resources.md +0 -90
  122. data/docs/guide/tutorial/04-creating-a-portal.md +0 -101
  123. data/docs/guide/tutorial/05-customizing-the-ui.md +0 -128
  124. data/docs/guide/tutorial/06-adding-custom-actions.md +0 -101
  125. data/docs/guide/tutorial/07-implementing-authorization.md +0 -90
  126. data/docs/markdown-examples.md +0 -85
  127. data/docs/modules/action.md +0 -244
  128. data/docs/modules/authentication.md +0 -236
  129. data/docs/modules/configuration.md +0 -599
  130. data/docs/modules/controller.md +0 -443
  131. data/docs/modules/core.md +0 -316
  132. data/docs/modules/definition.md +0 -1308
  133. data/docs/modules/display.md +0 -759
  134. data/docs/modules/form.md +0 -495
  135. data/docs/modules/generator.md +0 -400
  136. data/docs/modules/index.md +0 -167
  137. data/docs/modules/interaction.md +0 -642
  138. data/docs/modules/package.md +0 -151
  139. data/docs/modules/policy.md +0 -176
  140. data/docs/modules/portal.md +0 -710
  141. data/docs/modules/query.md +0 -297
  142. data/docs/modules/resource_record.md +0 -618
  143. data/docs/modules/routing.md +0 -690
  144. data/docs/modules/table.md +0 -301
  145. data/docs/modules/ui.md +0 -631
@@ -1,113 +1,109 @@
1
1
  Description:
2
- Generates a new model. Pass the model name, either CamelCased or
3
- under_scored, and an optional list of attribute pairs as arguments.
4
-
5
- Attribute pairs are field:type arguments specifying the
6
- model's attributes. Timestamps are added by default, so you don't have to
7
- specify them by hand as 'created_at:datetime updated_at:datetime'.
8
-
9
- As a special case, specifying 'password:digest' will generate a
10
- password_digest field of string type, and configure your generated model and
11
- tests for use with Active Model has_secure_password (assuming the default ORM
12
- and test framework are being used).
13
-
14
- You don't have to think up every attribute up front, but it helps to
15
- sketch out a few so you can start working with the model immediately.
16
-
17
- This generator invokes your configured ORM and test framework, which
18
- defaults to Active Record and TestUnit.
19
-
20
- Finally, if --parent option is given, it's used as superclass of the
21
- created model. This allows you create Single Table Inheritance models.
22
-
23
- If you pass a namespaced model name (e.g. admin/account or Admin::Account)
24
- then the generator will create a module with a table_name_prefix method
25
- to prefix the model's table name with the module name (e.g. admin_accounts)
26
-
27
- Available field types:
28
-
29
- Just after the field name you can specify a type like text or boolean.
30
- It will generate the column with the associated SQL type. For instance:
31
-
32
- `bin/rails generate model post title:string body:text`
33
-
34
- will generate a title column with a varchar type and a body column with a text
35
- type. If no type is specified the string type will be used by default.
36
- You can use the following types:
37
-
38
- integer
39
- primary_key
40
- decimal
41
- float
42
- boolean
43
- binary
44
- string
45
- text
46
- date
47
- time
48
- datetime
49
-
50
- You can also consider `references` as a kind of type. For instance, if you run:
51
-
52
- `bin/rails generate model photo title:string album:references`
53
-
54
- It will generate an `album_id` column. You should generate these kinds of fields when
55
- you will use a `belongs_to` association, for instance. `references` also supports
56
- polymorphism, you can enable polymorphism like this:
57
-
58
- `bin/rails generate model product supplier:references{polymorphic}`
59
-
60
- For integer, string, text and binary fields, an integer in curly braces will
61
- be set as the limit:
62
-
63
- `bin/rails generate model user pseudo:string{30}`
64
-
65
- For decimal, two integers separated by a comma in curly braces will be used
66
- for precision and scale:
67
-
68
- `bin/rails generate model product 'price:decimal{10,2}'`
69
-
70
- You can add a `:uniq` or `:index` suffix for unique or standard indexes
71
- respectively:
72
-
73
- `bin/rails generate model user pseudo:string:uniq`
74
- `bin/rails generate model user pseudo:string:index`
75
-
76
- You can combine any single curly brace option with the index options:
77
-
78
- `bin/rails generate model user username:string{30}:uniq`
79
- `bin/rails generate model product supplier:references{polymorphic}:index`
80
-
81
- If you require a `password_digest` string column for use with
82
- has_secure_password, you can specify `password:digest`:
83
-
84
- `bin/rails generate model user password:digest`
85
-
86
- If you require a `token` string column for use with
87
- has_secure_token, you can specify `auth_token:token`:
88
-
89
- `bin/rails generate model user auth_token:token`
2
+ Generates a Plutonium model with migration.
3
+
4
+ Usage:
5
+ rails g pu:res:model NAME [field:type field:type] [options]
6
+
7
+ Options:
8
+ --dest=DESTINATION Target destination (required to avoid interactive prompts)
9
+ Use 'main_app' for main application resources
10
+ Use 'package_name' for feature package resources
11
+
12
+ Field Syntax:
13
+ name:type Required field
14
+ 'name:type?' Nullable field (quote to prevent shell expansion)
15
+ name:type:index Field with index
16
+ name:type:uniq Field with unique index
17
+
18
+ Supported Types:
19
+ string VARCHAR
20
+ text TEXT
21
+ integer INTEGER
22
+ float FLOAT
23
+ decimal DECIMAL
24
+ boolean BOOLEAN
25
+ date DATE
26
+ datetime DATETIME
27
+ time TIME
28
+ binary BINARY
29
+ belongs_to Foreign key with association
30
+ references Same as belongs_to
31
+ rich_text ActionText (has_rich_text)
32
+ attachment Single file (has_one_attached)
33
+ attachments Multiple files (has_many_attached)
34
+ token Secure token (has_secure_token, auto unique index)
35
+ password_digest Secure password (has_secure_password)
36
+
37
+ Type Options (in curly braces):
38
+ 'field:decimal{10,2}' Decimal precision and scale
39
+ 'field:decimal?{10,2}' Nullable with precision
40
+ 'field:string{100}' String limit (optional)
41
+ 'ref:references{polymorphic}' Polymorphic association (optional)
42
+
43
+ Note: For default values, edit the migration manually after generation.
44
+
45
+ Nullable Fields:
46
+ Append ? to any type to make it nullable. Must quote to prevent shell expansion:
47
+
48
+ 'name:string?' null: true
49
+ 'score:integer?' null: true
50
+ 'parent:belongs_to?' null: true, optional: true in model
51
+ 'amount:decimal?{10,2}' null: true with precision
52
+
53
+ Index Types:
54
+ email:string:index Regular index
55
+ email:string:uniq Unique index
56
+ 'code:string{50}:uniq' With limit and unique index
90
57
 
91
58
  Examples:
92
- `bin/rails generate model account`
93
-
94
- For Active Record and TestUnit it creates:
95
-
96
- Model: app/models/account.rb
97
- Test: test/models/account_test.rb
98
- Fixtures: test/fixtures/accounts.yml
99
- Migration: db/migrate/XXX_create_accounts.rb
100
-
101
- `bin/rails generate model post title:string body:text published:boolean`
102
-
103
- Creates a Post model with a string title, text body, and published flag.
104
-
105
- `bin/rails generate model admin/account`
106
-
107
- For Active Record and TestUnit it creates:
108
-
109
- Module: app/models/admin.rb
110
- Model: app/models/admin/account.rb
111
- Test: test/models/admin/account_test.rb
112
- Fixtures: test/fixtures/admin/accounts.yml
113
- Migration: db/migrate/XXX_create_admin_accounts.rb
59
+ # Main app model
60
+ rails g pu:res:model post title:string 'body:text?' --dest=main_app
61
+
62
+ # Package model
63
+ rails g pu:res:model post title:string 'body:text?' --dest=blogging
64
+
65
+ # With associations
66
+ rails g pu:res:model article \
67
+ user:belongs_to \
68
+ 'category:belongs_to?' \
69
+ title:string \
70
+ slug:string:uniq \
71
+ --dest=blogging
72
+
73
+ # With decimal precision
74
+ rails g pu:res:model product \
75
+ name:string \
76
+ 'price:decimal{10,2}' \
77
+ 'cost:decimal?{10,2}' \
78
+ --dest=inventory
79
+
80
+ # Cross-package reference
81
+ rails g pu:res:model order_item \
82
+ order:belongs_to \
83
+ inventory/product:belongs_to \
84
+ quantity:integer \
85
+ 'unit_price:decimal{10,2}' \
86
+ --dest=sales
87
+
88
+ # With attachments and rich text
89
+ rails g pu:res:model article \
90
+ title:string \
91
+ content:rich_text \
92
+ cover:attachment \
93
+ gallery:attachments \
94
+ --dest=blogging
95
+
96
+ # With token
97
+ rails g pu:res:model api_key \
98
+ user:belongs_to \
99
+ access_token:token \
100
+ 'expires_at:datetime?' \
101
+ --dest=auth
102
+
103
+ Generated Files (main_app):
104
+ Model: app/models/[name].rb
105
+ Migration: db/migrate/xxx_create_[names].rb
106
+
107
+ Generated Files (package):
108
+ Model: app/models/[package]/[name].rb
109
+ Migration: db/migrate/xxx_create_[package]_[names].rb
@@ -6,13 +6,17 @@ require_relative "../<%= class_path.last.underscore %>"
6
6
  class <%= class_name %> < <%= [feature_package_name, "ResourceRecord"].join "::" %>
7
7
  # add concerns above.
8
8
 
9
+ # add constants above.
10
+
11
+ # add enums above.
12
+
9
13
  <% attributes.select(&:cents?).each do |attribute| -%>
10
14
  has_cents :<%= attribute.name %>
11
15
  <% end -%>
12
16
  # add model configurations above.
13
17
 
14
18
  <% attributes.select(&:reference?).each do |attribute| -%>
15
- belongs_to :<%= attribute.name %><%= ", polymorphic: true" if attribute.polymorphic? %><%= ", class_name: \"#{attribute.attr_options[:class_name]}\"" if attribute.attr_options[:class_name] %>
19
+ belongs_to :<%= attribute.name %><%= ", optional: true" unless attribute.required? %><%= ", polymorphic: true" if attribute.polymorphic? %><%= ", class_name: \"#{attribute.attr_options[:class_name]}\"" if attribute.attr_options[:class_name] %>
16
20
  <% end -%>
17
21
  # add belongs_to associations above.
18
22
 
@@ -51,6 +55,6 @@ class <%= class_name %> < <%= [feature_package_name, "ResourceRecord"].join "::"
51
55
  <% end -%>
52
56
  # add misc attribute macros above.
53
57
 
54
- # add methods above.
58
+ # add methods above. add private methods below.
55
59
  end
56
60
  <% end -%>
@@ -0,0 +1,85 @@
1
+ Description:
2
+ Generates a complete Plutonium resource with model, migration, controller,
3
+ policy, and definition.
4
+
5
+ Usage:
6
+ rails g pu:res:scaffold NAME [field:type field:type] --dest=DESTINATION
7
+
8
+ Options:
9
+ --dest=DESTINATION Target destination (required to avoid interactive prompts)
10
+ Use 'main_app' for main application resources
11
+ Use 'package_name' for feature package resources
12
+ --no-model Skip model and migration generation
13
+
14
+ Field Syntax:
15
+ name:type Required field
16
+ 'name:type?' Nullable field (quote to prevent shell expansion)
17
+ name:type:index Field with index
18
+ name:type:uniq Field with unique index
19
+
20
+ Supported Types:
21
+ string, text, integer, float, decimal, boolean, date, datetime, time,
22
+ binary, belongs_to, references, rich_text, attachment, attachments, token
23
+
24
+ Type Options (decimal only):
25
+ 'field:decimal{10,2}' Precision and scale
26
+ 'field:decimal?{10,2}' Nullable with precision
27
+
28
+ Note: The {precision,scale} syntax only works for decimal types.
29
+ For default values on other types, edit the migration manually.
30
+
31
+ Examples:
32
+ # Main app resource
33
+ rails g pu:res:scaffold post title:string 'body:text?' --dest=main_app
34
+
35
+ # Package resource with associations and indexes
36
+ rails g pu:res:scaffold article \
37
+ user:belongs_to \
38
+ title:string \
39
+ slug:string:uniq \
40
+ 'content:text?' \
41
+ 'published_at:datetime?' \
42
+ --dest=blogging
43
+
44
+ # Nullable association
45
+ rails g pu:res:scaffold comment \
46
+ user:belongs_to \
47
+ 'parent:belongs_to?' \
48
+ body:text \
49
+ --dest=main_app
50
+
51
+ # With decimal precision
52
+ rails g pu:res:scaffold product \
53
+ name:string \
54
+ 'price:decimal{10,2}' \
55
+ 'weight:decimal?{8,3}' \
56
+ --dest=inventory
57
+
58
+ # Scaffold existing model (imports columns automatically)
59
+ rails g pu:res:scaffold ExistingModel --dest=main_app
60
+
61
+ # Cross-package reference
62
+ rails g pu:res:scaffold order \
63
+ customer:belongs_to \
64
+ inventory/product:belongs_to \
65
+ quantity:integer \
66
+ --dest=sales
67
+
68
+ Generated Files (main_app):
69
+ Model: app/models/[name].rb
70
+ Migration: db/migrate/xxx_create_[names].rb
71
+ Controller: app/controllers/[names]_controller.rb
72
+ Policy: app/policies/[name]_policy.rb
73
+ Definition: app/definitions/[name]_definition.rb
74
+
75
+ Generated Files (package):
76
+ Model: app/models/[package]/[name].rb
77
+ Migration: db/migrate/xxx_create_[package]_[names].rb
78
+ Controller: packages/[package]/app/controllers/[package]/[names]_controller.rb
79
+ Policy: packages/[package]/app/policies/[package]/[name]_policy.rb
80
+ Definition: packages/[package]/app/definitions/[package]/[name]_definition.rb
81
+
82
+ After Generation:
83
+ 1. Review migration (add cascade delete, defaults, composite indexes)
84
+ 2. Run: rails db:migrate
85
+ 3. Connect to portal: rails g pu:res:conn Post --dest=admin_portal
@@ -40,12 +40,8 @@ module Pu
40
40
  template "app/rodauth/rodauth_plugin.rb"
41
41
  end
42
42
 
43
- def add_dev_config
44
- return if Rails.version.to_f >= 8.0
45
-
46
- insert_into_file "config/environments/development.rb",
47
- "\n config.action_mailer.default_url_options = { host: '127.0.0.1', port: ENV.fetch('PORT', 3000) }\n",
48
- before: /^end/
43
+ def create_url_options_initializer
44
+ template "config/initializers/url_options.rb"
49
45
  end
50
46
 
51
47
  def create_install_migration
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ Rails.application.config.after_initialize do
4
+ default_url = ENV["RAILS_DEFAULT_URL"]
5
+ if default_url && Rails.application.config.action_mailer.default_url_options.blank?
6
+ uri = URI.parse(default_url)
7
+ Rails.application.config.action_mailer.default_url_options = {
8
+ host: uri.host,
9
+ port: uri.port,
10
+ protocol: uri.scheme
11
+ }
12
+ end
13
+
14
+ if Rails.application.routes.default_url_options.blank?
15
+ Rails.application.routes.default_url_options = Rails.application.config.action_mailer.default_url_options
16
+ end
17
+ end
@@ -0,0 +1,14 @@
1
+ Description:
2
+ Syncs Claude Code skills from the Plutonium gem into your project's
3
+ .claude/skills/ directory.
4
+
5
+ This gives Claude Code access to Plutonium-specific skills for:
6
+ - Creating resources (scaffold generator)
7
+ - Connecting resources to portals
8
+ - Definition configuration (fields, actions, queries)
9
+
10
+ Example:
11
+ rails generate pu:skills:sync
12
+
13
+ This will copy all skills from the Plutonium gem to:
14
+ .claude/skills/
@@ -0,0 +1,66 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "../../lib/plutonium_generators"
4
+
5
+ module Pu
6
+ module Skills
7
+ class SyncGenerator < Rails::Generators::Base
8
+ include PlutoniumGenerators::Generator
9
+
10
+ desc "Sync Claude Code skills from Plutonium into your project"
11
+
12
+ def start
13
+ source_dir = Plutonium.root.join(".claude", "skills")
14
+ destination_dir = Rails.root.join(".claude", "skills")
15
+
16
+ unless File.directory?(source_dir)
17
+ say_status("error", "Source skills directory not found: #{source_dir}", :red)
18
+ return
19
+ end
20
+
21
+ # Create destination directory if it doesn't exist
22
+ FileUtils.mkdir_p(destination_dir)
23
+
24
+ # Get all skill directories
25
+ skill_dirs = Dir.children(source_dir).select { |f| File.directory?(source_dir.join(f)) }
26
+
27
+ if skill_dirs.empty?
28
+ say_status("info", "No skills found to sync", :yellow)
29
+ return
30
+ end
31
+
32
+ say_status("info", "Syncing #{skill_dirs.size} skills from Plutonium...", :blue)
33
+
34
+ skill_dirs.each do |skill_name|
35
+ sync_skill(source_dir, destination_dir, skill_name)
36
+ end
37
+
38
+ say_status("success", "Skills synced successfully!", :green)
39
+ rescue => e
40
+ exception "#{self.class} failed:", e
41
+ end
42
+
43
+ private
44
+
45
+ def sync_skill(source_dir, destination_dir, skill_name)
46
+ source_skill_dir = source_dir.join(skill_name)
47
+ dest_skill_dir = destination_dir.join(skill_name)
48
+
49
+ # Create skill directory
50
+ FileUtils.mkdir_p(dest_skill_dir)
51
+
52
+ # Copy all files in the skill directory
53
+ Dir.glob(source_skill_dir.join("*")).each do |source_file|
54
+ next unless File.file?(source_file)
55
+
56
+ filename = File.basename(source_file)
57
+ dest_file = dest_skill_dir.join(filename)
58
+
59
+ FileUtils.cp(source_file, dest_file)
60
+ end
61
+
62
+ say_status("synced", skill_name, :green)
63
+ end
64
+ end
65
+ end
66
+ end
@@ -40,4 +40,4 @@ module Plutonium
40
40
  end
41
41
  end
42
42
  end
43
- end
43
+ end
@@ -8,8 +8,8 @@ module Plutonium
8
8
 
9
9
  included do
10
10
  add_flash_types :success, :warning, :error
11
-
12
- protect_from_forgery with: :null_session, if: -> { request.headers['Authorization'].present? }
11
+
12
+ protect_from_forgery with: :null_session, if: -> { request.headers["Authorization"].present? }
13
13
 
14
14
  before_action do
15
15
  next unless defined?(ActiveStorage)
@@ -27,6 +27,7 @@ module Plutonium
27
27
  include Plutonium::Definition::Presentable
28
28
  include Plutonium::Definition::NestedInputs
29
29
  include Plutonium::Interaction::NestedAttributes
30
+
30
31
  # include Plutonium::Interaction::Concerns::WorkflowDSL
31
32
 
32
33
  class Form < Plutonium::UI::Form::Interaction; end
@@ -10,17 +10,17 @@ module Plutonium
10
10
  config.before_configuration do
11
11
  # this touches the internals of rails, but I could not find a good way of doing this
12
12
  # we get the initializer instance and set the block property to a noop
13
- # There is no error handling, to ensure we know when it breaks.
14
13
  add_view_paths_initializer = Rails.application.initializers.find do |a|
15
14
  a.context_class == self && a.name.to_s == "add_view_paths"
16
15
  end
17
- add_view_paths_initializer.instance_variable_set(:@block, ->(app) {})
16
+ add_view_paths_initializer&.instance_variable_set(:@block, ->(app) {})
18
17
  end
19
18
 
20
19
  initializer :append_migrations do |app|
21
20
  unless app.root.to_s.match root.to_s
22
21
  config.paths["db/migrate"].expanded.each do |expanded_path|
23
22
  app.config.paths["db/migrate"] << expanded_path
23
+ ActiveRecord::Migrator.migrations_paths << expanded_path
24
24
  end
25
25
  end
26
26
  end
@@ -11,8 +11,12 @@ module Plutonium
11
11
  @body = body
12
12
  end
13
13
 
14
- def apply(scope, **)
15
- body.call(scope, **)
14
+ def apply(scope, context: nil, **)
15
+ if context
16
+ context.instance_exec(scope, **, &body)
17
+ else
18
+ body.call(scope, **)
19
+ end
16
20
  end
17
21
  end
18
22
  end
@@ -11,7 +11,7 @@ module Plutonium
11
11
  @name = name
12
12
  end
13
13
 
14
- def apply(scope, **)
14
+ def apply(scope, context: nil, **)
15
15
  scope.public_send(name, **)
16
16
  end
17
17
  end
@@ -23,6 +23,10 @@ module Plutonium
23
23
  Rails.application.class.include Plutonium::Engine
24
24
  end
25
25
 
26
+ initializer "plutonium.rescue_responses" do |app|
27
+ app.config.action_dispatch.rescue_responses["ActionPolicy::Unauthorized"] = :forbidden
28
+ end
29
+
26
30
  initializer "plutonium.deprecator" do |app|
27
31
  app.deprecators[:plutonium] = Plutonium.deprecator
28
32
  end
@@ -17,7 +17,7 @@ module Plutonium
17
17
  .extract_input(params, view_context:)[:q]
18
18
 
19
19
  base_query = current_authorized_scope
20
- current_query_object.apply(base_query, query_params)
20
+ current_query_object.apply(base_query, query_params, context: self)
21
21
  end
22
22
  end
23
23
  end
@@ -16,7 +16,10 @@ module Plutonium
16
16
 
17
17
  setup_index_action!
18
18
 
19
- render :index
19
+ respond_to do |format|
20
+ format.any(:html, :turbo_stream) { render :index, formats: [:html] }
21
+ format.any { render :index }
22
+ end
20
23
  end
21
24
 
22
25
  # GET /resources/1(.{format})
@@ -24,7 +27,10 @@ module Plutonium
24
27
  authorize_current! resource_record!
25
28
  set_page_title resource_record!.to_label.titleize
26
29
 
27
- render :show
30
+ respond_to do |format|
31
+ format.any(:html, :turbo_stream) { render :show, formats: [:html] }
32
+ format.any { render :show }
33
+ end
28
34
  end
29
35
 
30
36
  # GET /resources/new
@@ -35,7 +41,7 @@ module Plutonium
35
41
  @resource_record = resource_class.new
36
42
  maybe_apply_submitted_resource_params!
37
43
 
38
- render :new
44
+ render :new, formats: [:html]
39
45
  end
40
46
 
41
47
  # POST /resources(.{format})
@@ -47,9 +53,9 @@ module Plutonium
47
53
 
48
54
  respond_to do |format|
49
55
  if params[:pre_submit]
50
- format.html { render :new, status: :unprocessable_content }
56
+ format.any(:html, :turbo_stream) { render :new, formats: [:html], status: :unprocessable_content }
51
57
  elsif resource_record!.save
52
- format.html do
58
+ format.any(:html, :turbo_stream) do
53
59
  redirect_to redirect_url_after_submit,
54
60
  notice: "#{resource_class.model_name.human} was successfully created."
55
61
  end
@@ -60,7 +66,7 @@ module Plutonium
60
66
  location: redirect_url_after_submit
61
67
  end
62
68
  else
63
- format.html { render :new, status: :unprocessable_content }
69
+ format.any(:html, :turbo_stream) { render :new, formats: [:html], status: :unprocessable_content }
64
70
  format.any do
65
71
  @errors = resource_record!.errors
66
72
  render "errors", status: :unprocessable_content
@@ -76,7 +82,7 @@ module Plutonium
76
82
 
77
83
  maybe_apply_submitted_resource_params!
78
84
 
79
- render :edit
85
+ render :edit, formats: [:html]
80
86
  end
81
87
 
82
88
  # PATCH/PUT /resources/1(.{format})
@@ -88,19 +94,18 @@ module Plutonium
88
94
 
89
95
  respond_to do |format|
90
96
  if params[:pre_submit]
91
- format.html { render :edit, status: :unprocessable_content }
97
+ format.any(:html, :turbo_stream) { render :edit, formats: [:html], status: :unprocessable_content }
92
98
  elsif resource_record!.save
93
- format.html do
99
+ format.any(:html, :turbo_stream) do
94
100
  redirect_to redirect_url_after_submit,
95
- notice:
96
- "#{resource_class.model_name.human} was successfully updated.",
101
+ notice: "#{resource_class.model_name.human} was successfully updated.",
97
102
  status: :see_other
98
103
  end
99
104
  format.any do
100
105
  render :show, status: :ok, location: redirect_url_after_submit
101
106
  end
102
107
  else
103
- format.html { render :edit, status: :unprocessable_content }
108
+ format.any(:html, :turbo_stream) { render :edit, formats: [:html], status: :unprocessable_content }
104
109
  format.any do
105
110
  @errors = resource_record!.errors
106
111
  render "errors", status: :unprocessable_content
@@ -116,17 +121,15 @@ module Plutonium
116
121
  respond_to do |format|
117
122
  resource_record!.destroy
118
123
 
119
- format.html do
124
+ format.any(:html, :turbo_stream) do
120
125
  redirect_to redirect_url_after_destroy,
121
- notice:
122
- "#{resource_class.model_name.human} was successfully deleted."
126
+ notice: "#{resource_class.model_name.human} was successfully deleted."
123
127
  end
124
128
  format.json { head :no_content }
125
129
  rescue ActiveRecord::InvalidForeignKey
126
- format.html do
130
+ format.any(:html, :turbo_stream) do
127
131
  redirect_to resource_url_for(resource_record!),
128
- alert:
129
- "#{resource_class.model_name.human} is referenced by other records."
132
+ alert: "#{resource_class.model_name.human} is referenced by other records."
130
133
  end
131
134
  format.any do
132
135
  @errors = ActiveModel::Errors.new resource_record!