bullet_train 1.1.9 → 1.2.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (44) hide show
  1. checksums.yaml +4 -4
  2. data/app/controllers/concerns/account/invitations/controller_base.rb +3 -11
  3. data/app/controllers/concerns/account/users/controller_base.rb +4 -25
  4. data/app/controllers/concerns/documentation_support.rb +1 -1
  5. data/app/controllers/concerns/registrations/controller_base.rb +9 -6
  6. data/app/controllers/sessions_controller.rb +9 -0
  7. data/app/helpers/account/teams_helper.rb +1 -1
  8. data/app/helpers/concerns/helpers/base.rb +0 -9
  9. data/app/javascript/controllers/index.js +2 -0
  10. data/app/javascript/controllers/select_all_controller.js +82 -0
  11. data/app/models/concerns/memberships/base.rb +3 -7
  12. data/app/models/concerns/users/base.rb +3 -0
  13. data/app/views/account/memberships/_index.html.erb +4 -35
  14. data/app/views/account/memberships/_membership.html.erb +29 -0
  15. data/app/views/account/memberships/index.html.erb +6 -1
  16. data/app/views/account/memberships/show.html.erb +1 -1
  17. data/app/views/account/onboarding/user_details/edit.html.erb +6 -6
  18. data/app/views/account/teams/_index.html.erb +1 -25
  19. data/app/views/account/teams/_team.html.erb +23 -0
  20. data/app/views/account/teams/index.html.erb +3 -1
  21. data/app/views/account/teams/show.html.erb +1 -1
  22. data/app/views/devise/registrations/new.html.erb +2 -2
  23. data/app/views/devise/sessions/new.html.erb +1 -1
  24. data/app/views/layouts/docs.html.erb +12 -0
  25. data/config/locales/en/base.yml +1 -0
  26. data/config/locales/en/framework_packages.yml +13 -17
  27. data/docs/application-options.md +29 -0
  28. data/docs/authentication.md +9 -0
  29. data/docs/field-partials/file-field.md +25 -0
  30. data/docs/field-partials.md +3 -1
  31. data/docs/i18n.md +28 -0
  32. data/docs/index.md +2 -0
  33. data/docs/invitation_only.md +1 -1
  34. data/docs/themes.md +13 -40
  35. data/docs/trademark.md +25 -0
  36. data/docs/two-factor-authentication.md +16 -0
  37. data/lib/bullet_train/resolver.rb +47 -0
  38. data/lib/bullet_train/version.rb +1 -1
  39. data/lib/bullet_train.rb +6 -1
  40. data/lib/colorizer.rb +1 -1
  41. data/lib/tasks/bullet_train_tasks.rake +74 -12
  42. metadata +9 -4
  43. data/app/assets/javascripts/bullet-train.js +0 -2
  44. data/app/assets/javascripts/bullet-train.js.map +0 -1
@@ -0,0 +1,29 @@
1
+ # Application Options
2
+
3
+ Bullet Train features a list of options available at your disposal to enable/disable functionalities that would otherwise take a significant amount of time to implement. Simply add any of the following environment variables to `config/application.yml` in your main Bullet Train application and restart your server for the options to apply.
4
+
5
+ The helper methods below can also be directly invoked in your application if you wish to have parts of your code depend on the functionality in question.
6
+
7
+ | Option | Type | Example | Helper Methods |
8
+ | --- | --- | --- | --- |
9
+ | HIDE_THINGS | Boolean | `"true"` | `scaffolding_things_disabled?` |
10
+ | HIDE_EXAMPLES | Boolean | `"true"` | `scaffolding_things_disabled?` |
11
+ | STRIPE_CLIENT_ID | String | `"your_stripe_client_id"` | `stripe_enabled?` |
12
+ | CLOUDINARY_URL | String | `"cloudinary://your_cloudinary_token_here"` | `cloudinary_enabled?` |
13
+ | TWO_FACTOR_ENCRYPTION_KEY | String | `"your_encryption_key"` | `two_factor_enabled_authentication?` |
14
+ | INVITATION_KEYS | String | `"ofr9h5h9ghzeodh, ofr9h5h9ghzeodi"` | `invitation_keys` `invitation_only?` |
15
+ | FONTAWESOME_NPM_AUTH_TOKEN | String | `"your_font_awesome_token"` | `font_awesome?` |
16
+ | SILENCE_LOGS | Boolean | `"true"` | `silence_logs?` |
17
+ | TESTING_PROVISION_KEY | String | `"asdf123"` | N/A |
18
+
19
+ | Option | Description |
20
+ | --- | --- |
21
+ | HIDE_THINGS | Hides Bullet Train demo models such as `CreativeConcept` and `TangibleThing`. |
22
+ | HIDE_EXAMPLES | Hides base models such as `CreativeConcept` and `TangibleThing`.
23
+ | STRIPE_CLIENT_ID | See [Bullet Train Billing for Stripe](/docs/billing/stripe.md) for more information and related environment variables. |
24
+ | CLOUDINARY_URL | Enables use of Cloudinary for handling images. |
25
+ | TWO_FACTOR_ENCRYPTION_KEY | Enables two-factor authentication through Devise. |
26
+ | INVITATION_KEYS | See more [Invitation Only](/docs/invitation_only.md) for more information. |
27
+ | FONTAWESOME_NPM_AUTH_TOKEN | Enables use of Font Awesome. |
28
+ | SILENCE_LOGS | Silences Super Scaffolding logs. |
29
+ | TESTING_PROVISION_KEY | Creates a test `Platform::Application` by accessing `/testing/provision?key=your_provision_key` |
@@ -11,3 +11,12 @@ bin/resolve SessionsController --eject --open
11
11
 
12
12
  ## Customizing Views
13
13
  You can customize Devise views using the same workflow you would use to customize any other Bullet Train views.
14
+
15
+ ## Disabling Registration
16
+
17
+ Registration is enabled by default. You can disable registration, allowing signups via an invite code only, by using [Invitation Only Mode](/docs/invitation_only.md)
18
+
19
+ ## Two factor authentication
20
+
21
+ This feature allows users to add two factor authentication.
22
+ It requires some setup - [Two Factor Authentication](/docs/two-factor-authentication.md)
@@ -0,0 +1,25 @@
1
+ # Examples and setup for the `file_field` Field Partial
2
+
3
+ ## Active Storage
4
+
5
+ `file_field` is designed to be used with [Active Storage](https://edgeguides.rubyonrails.org/active_storage_overview.html). You will need to confgure Active Storage for your application before using this field partial. You can find instructions for doing so in the [Rails Guides](https://edgeguides.rubyonrails.org/active_storage_overview.html#setup).
6
+
7
+ In addition, Bullet Train has integrated the direct-uploads feature of Active Storage. For this to work, you need to have CORS configured for your storage endpoint. You can find instructions for doing so in the [Rails Guides](https://edgeguides.rubyonrails.org/active_storage_overview.html#cross-origin-resource-sharing-cors-configuration).
8
+
9
+ ## Example
10
+
11
+ Add a 'document' file as an attachment to a `Post` model:
12
+
13
+ Add the following to `app/models/post.rb`:
14
+
15
+ ```ruby
16
+ has_one_attached :document
17
+ ```
18
+
19
+ Note, no database migration is required as ActiveStorage uses its own tables to store the attachments.
20
+
21
+ Run the following command to generate the scaffolding for the `document` field on the `Post` model:
22
+
23
+ ```bash
24
+ ./bin/super-scaffold crud-field Post document:file_field
25
+ ```
@@ -118,7 +118,7 @@ Certain form field partials like `buttons` and `super_select` can also have thei
118
118
  | `date_and_time_field` | `datetime` | | `assign_date_and_time` | [Date Range Picker](https://www.daterangepicker.com) | | |
119
119
  | `date_field` | `date` | | `assign_date` | [Date Range Picker](https://www.daterangepicker.com) | | |
120
120
  | `email_field` | `string` | | | | | |
121
- | `file_field` | `attachment` | | [Active Storage](https://edgeguides.rubyonrails.org/active_storage_overview.html) | | |
121
+ | [`file_field`](/docs/field-partials/file-field.md) | `attachment` | | [Active Storage](https://edgeguides.rubyonrails.org/active_storage_overview.html) | | |
122
122
  | `options` | `string` | Optionally | `assign_checkboxes` | | | |
123
123
  | `password_field` | `string` | | | | | |
124
124
  | `phone_field` | `string` | | | [International Telephone Input](https://intl-tel-input.com) | Ensures telephone numbers are in a format that can be used by providers like Twilio. | |
@@ -137,3 +137,5 @@ Set the data type to `jsonb` whenever passing the `multiple` option to a new att
137
137
  ## Additional Field Partials Documentation
138
138
  - [`buttons`](/docs/field-partials/buttons.md)
139
139
  - [`super_select`](/docs/field-partials/super-select.md)
140
+ - [`file_field`](/docs/field-partials/file-field.md)
141
+
data/docs/i18n.md CHANGED
@@ -1,3 +1,31 @@
1
1
  # Translations and Internationalization
2
2
 
3
3
  Bullet Train and views generated by Super Scaffolding are localized by default, meaning all of the human-readable text in your application has been extracted into a YAML configuration file in `config/locales/en` and can be translated into any language you would like to target.
4
+
5
+ We override the native I18n translation method to automatically include the current team name or other objects depending on the string. For example, here's a description of a membership which you can find on a membership's show page:
6
+
7
+ ```
8
+ The following are the details for David’s Membership on Your Team.
9
+ ```
10
+
11
+ The view can be found here in the `bullet_train-base` gem:<br/>[bullet_train-base/app/views/account/memberships/show.html.erb](https://github.com/bullet-train-co/bullet_train-base/blob/657e932cb4eb3e0c1f56c88c8365c2611de90e06/app/views/account/memberships/show.html.erb#L16)<br/>
12
+ <br/>
13
+ Looking at the view, you can see we are only passing a key to the translation method for I18n to process:
14
+
15
+ ```erb
16
+ <%= t('.description') %>
17
+ ```
18
+
19
+ However, looking at the [locale itself](https://github.com/bullet-train-co/bullet_train-base/blob/657e932cb4eb3e0c1f56c88c8365c2611de90e06/config/locales/en/memberships.en.yml#L82), you can see that the string takes two variables, `memberships_possessive` and `team_name`, to complete the string:
20
+ ```yaml
21
+ description: The following are the details for %{memberships_possessive} Membership on %{team_name}.
22
+ ```
23
+
24
+ Usually, you would pass the variable as a keyword argument:
25
+ ```ruby
26
+ t('.description', memberships_possessive: memberships_possessive, team_name: current_team.name)
27
+ ```
28
+
29
+ However, in Bullet Train, we override the original translation method to include variable names like this automatically in our locales. Check out the [locale helper](https://github.com/bullet-train-co/bullet_train-base/blob/main/app/helpers/account/locale_helper.rb) to get a closer look at how we handle strings for internationalization. For example, the two variables above are generated by the method `model_locales` in the locale helper.
30
+
31
+ You can find more information in the [indirection documentation](indirection) about using `bin/resolve` and logs to pinpoint where your locales are coming from.
data/docs/index.md CHANGED
@@ -13,6 +13,7 @@
13
13
  - [Overriding the Framework](/docs/overriding.md)
14
14
  - [Setting up a Tunnel](/docs/tunneling.md)
15
15
  - [JavaScript](/docs/javascript.md)
16
+ - [Internationalization](/docs/i18n.md)
16
17
 
17
18
  ## Developer Tools
18
19
  - [Super Scaffolding](/docs/super-scaffolding.md)
@@ -20,6 +21,7 @@
20
21
  - [Database Seeds](/docs/seeds.md)
21
22
  - [Test Suite](/docs/testing.md)
22
23
  - [Point-and-Click Test Writing](https://github.com/bullet-train-co/magic_test) <i class="ti ti-new-window ml-2"></i>
24
+ - [Application Options](/docs/application-options.md)
23
25
 
24
26
  ## Accounts & Teams
25
27
  - [Authentication](/docs/authentication.md)
@@ -9,5 +9,5 @@ INVITATION_KEYS: ofr9h5h9ghzeodh
9
9
 
10
10
  In this case, the user will be able to register their own account by accessing the following link:
11
11
  ```
12
- http://localhost:3000/invitations?key=ofr9h5h9ghzeodh
12
+ http://localhost:3000/invitation?key=ofr9h5h9ghzeodh
13
13
  ```
data/docs/themes.md CHANGED
@@ -1,27 +1,21 @@
1
1
  # Themes
2
2
 
3
- Bullet Train has a theme subsystem designed to allow you the flexibility to either extend or completely replace the stock “Light” and “Clean” UI templates.
3
+ Bullet Train has a theme subsystem designed to allow you the flexibility to either extend or completely replace the stock “Light” UI template.
4
+ To reduce duplication of code across themes, Bullet Train implements the following three packages:
5
+ 1. `bullet_train-themes`
6
+ 2. `bullet_train-themes-tailwind_css`
7
+ 3. `bullet_train-themes-light`
4
8
 
5
- ## Inheritance Structure
9
+ This is where all of Bullet Train's standard views are contained.
6
10
 
7
- To reduce duplication of code across themes, Bullet Train implements an inheritance structure. For example, the official Bullet Train themes are structured hierarchically like so:
11
+ ## Adding a New Theme (ejecting standard views)
8
12
 
9
- - “Base”
10
- - “Bootstrap” (in the future)
11
- - “Clean” (in the future)
12
- - “Tailwind CSS”
13
- - “Light”
13
+ If you want to add a new theme, you can use the following command. This will copy all of the standard views from `bullet_train-themes-light` to `app/views/themes/` and configure your application to use the new theme. For example, let's make a new theme called "foo":
14
+ `> rake bullet_train:themes:light:eject[foo]`
14
15
 
15
- Any component partials that can be shared are pushed up the inheritance structure. For example, [Bullet Train's library of field partials](/docs/field-partials.md) provide a good example of this, illustrating the power of the approach we’ve taken here:
16
+ After running this command, you will see that a few other files are edited to use this new theme. Whenever switching a theme, you will need to make the same changes to make sure your application is running with the theme of your choice.
16
17
 
17
- - The most general field styling varies substantially between Tailwind CSS and Bootstrap, so a `_field.html.erb` component partial exists in both the foundational “Tailwind CSS” and “Bootstrap” themes, but also a further customized version exists in themes like “Light”.
18
- - However, many concrete field types like `_text_field.html.erb` and `_phone_field.html.erb` leverage `_field.html.erb`, and they themselves are completely framework agnostic as a result. These partials can live in the shared “Base” theme.
19
-
20
- At run-time, this means:
21
-
22
- - When rendering `_text_field.html.erb`, it renders from “Base”.
23
- - However, when `_text_field.html.erb` references `_field.html.erb`, that renders from “Light”.
24
- - If you extend “Light” and override `_field.html.erb`, rendering `_text_field.html.erb` will now use your theme’s `_field.html.erb`.
18
+ You can also pass an annotated path to a view after running `bin/resolve` to eject individual views to your application.
25
19
 
26
20
  ## Theme Component Usage
27
21
 
@@ -35,32 +29,11 @@ We say "within" because while a `shared` view partial directory does exist, the
35
29
 
36
30
  ### Dealing with Indirection
37
31
 
38
- This small piece of indirection buys us an incredible amount of power in building and extending themes, but as with any indirection, it could potentially come at the cost of developer experience. That's why Bullet Train includes additional tools for smoothing over this experience. Be sure to read the section on [dealing with indirection].
39
-
32
+ This small piece of indirection buys us an incredible amount of power in building and extending themes, but as with any indirection, it could potentially come at the cost of developer experience. That's why Bullet Train includes additional tools for smoothing over this experience. Be sure to read the section on [dealing with indirection](./indirection.md).
40
33
 
41
34
  ## Theme Configuration
42
35
 
43
- You can specify the theme you’d like to use and its inheritance structure in `app/helpers/theme_helper.rb`. The code there is well commented to help you.
44
-
45
- ## Theme Structure
46
-
47
- Themes are represented in a few places. Taking “Light” as an example, we have:
48
-
49
- - A directory of theme-specific component partials in `app/views/themes/light`, including a layout ERB template.
50
- - A theme-specific stylesheet in `app/javascript/stylesheets/light/application.scss`.
51
- - A theme-specific pack in `app/javascript/packs/light.js`. You’ll see there that the actual JavaScript dependencies and code are shared across all themes. The whole purpose of this theme-specific pack is to serve up the theme-specific stylesheet.
52
- - Theme-specific logos and images in `app/javascript/images/light`.
53
-
54
- ## Adding a New Theme
55
-
56
- To extend the “Light” theme in a new theme called “Tokyo”, we would:
57
-
58
- 1. Copy `app/javascript/packs/light.js` to `app/javascript/packs/tokyo.js` and update references to `light` therein to `tokyo`.
59
- 2. Copy `app/views/themes/light/layouts` to `app/views/themes/tokyo/layouts` and update references to `light` in the contained files to `tokyo`. It's possible this is too much duplication, but in practice most people want to customize these two layouts in their custom themes.
60
- 3. Create a new file at `app/javascript/stylesheets/tokyo/application.scss`. To start just add `@import "../light/application";` at the top, which represents the fact that “Tokyo” extends “Light”. Any custom styles can be added below that.
61
- 4. Add `"tokyo"` as the first item in the `THEME_DIRECTORY_ORDER` array in `app/helpers/theme_helper.rb`.
62
-
63
- You should be good to go! We'll try to add a generator for this in the future.
36
+ Your application will automatically be configured to use your new theme whenever you run the eject command. Run `> rake bullet_train:themes:light:install` to re-install the standard light theme.
64
37
 
65
38
  ## Additional Guidance and Principles
66
39
 
data/docs/trademark.md ADDED
@@ -0,0 +1,25 @@
1
+ # Trademark Information
2
+
3
+ "Bullet Train" is a registered trademark of Bullet Train, Inc.
4
+
5
+ We love and encourage contributions to the Bullet Train ecosystem. That's our dream!
6
+
7
+ It's also important when you're building packages or presenting offerings in the Bullet Train ecosystem, they're not named in such a way that anyone could confuse your package or offering as being officially maintained or sanctioned by the Bullet Train team.
8
+
9
+ Here are some examples of names we would not typically object to:
10
+
11
+ - "Super Widget __for Bullet Train__"
12
+ - "MySQL Starter Kit __for Bullet Train__"
13
+ - "__BT__ Starter Kit with MySQL"
14
+ - "__BT__ Pro Tools"
15
+
16
+ Here are some examples that would require explicit permission:
17
+
18
+ - "Bullet Train Super Widget"
19
+ - "Bullet Train MySQL Starter Kit"
20
+ - "Bullet Train Application Template with MySQL"
21
+ - "Bullet Train Pro Tools"
22
+ - "Bullet Train Conference"
23
+ - "Bullet Train Podcast"
24
+
25
+ If you've got an idea and aren't sure about the name, message Andrew Culver privately [on Discord](https://discord.gg/bullettrain) or [via Twitter DM](https://twitter.com/andrewculver). If you know anything about trademarks, you know we're required to enforce our policies, so thank you for understanding! 🙏
@@ -0,0 +1,16 @@
1
+ # Two Factor Authentication
2
+
3
+ ## Setup
4
+
5
+ run `bin/rails db:encryption:init` and use `bin/rails credentials:edit` to add the resulting keys to your `secrets.yml`
6
+
7
+ Add the following gems to your `Gemfile` and run `bundle install`
8
+
9
+ ```ruby
10
+ gem "devise-two-factor"
11
+ gem "rqrcode"
12
+ ```
13
+
14
+ If you haven't already done so, set the environment variable `RAILS_MASTER_KEY` with the contents of `config/master.key`. Note, this file should not be committed to git, and you should keep it in a safe place.
15
+
16
+ Now in the user's Account Details page there will be an option to enable two factor, and when enabled the two factor code will be required at login.
@@ -87,6 +87,18 @@ module BulletTrain
87
87
  }
88
88
 
89
89
  result[:absolute_path] = file_path || class_path || partial_path || locale_path
90
+
91
+ # If we get the partial resolver template itself, that means we couldn't find the file.
92
+ if result[:absolute_path].match?("app/views/bullet_train/partial_resolver.html.erb")
93
+ puts "We could not find the partial you're looking for: #{@needle}".red
94
+ puts ""
95
+ puts "Please try passing the partial string using either of the following two ways:"
96
+ puts "1. Without underscore and extention: ".blue + "bin/resolve shared/attributes/code"
97
+ puts "2. Literal path with package name: ".blue + "bin/resolve bullet_train-themes/app/views/themes/base/attributes/_code.html.erb"
98
+ puts ""
99
+ exit
100
+ end
101
+
90
102
  if result[:absolute_path]
91
103
  if result[:absolute_path].include?("/bullet_train")
92
104
  base_path = "bullet_train" + result[:absolute_path].partition("/bullet_train").last
@@ -123,6 +135,41 @@ module BulletTrain
123
135
  end
124
136
 
125
137
  def partial_path
138
+ # Parse literal partial strings.
139
+ if @needle.match?(/\.html\.erb$/)
140
+ partial_parts = @needle.split("/")
141
+
142
+ # TODO: We should probably just default to raising an error if the developer
143
+ # provides a literal partial string without the name of the package it's coming from.
144
+ if partial_parts.size <= 3
145
+ # If the string looks something like "shared/attributes/_code.html.erb",
146
+ # all we need to do is change it to "shared/attributes/code"
147
+ partial_parts.last.gsub!(/(_)|(\.html\.erb)/, "")
148
+ @needle = partial_parts.join("/")
149
+ elsif @needle.match?(/bullet_train-/)
150
+ # If it's a full path, we need to make sure we're getting it from the right package.
151
+ _, partial_view_package, partial_path_without_package = @needle.partition(/bullet_train-[a-z|\-_0-9.]*/)
152
+
153
+ # Pop off the version so we can call `bundle show` correctly.
154
+ # Also change `bullet_train-base` to `bullet_train`.
155
+ partial_view_package.gsub!(/[-|.0-9]*$/, "") if partial_view_package.match?(/[-|.0-9]*$/)
156
+ partial_view_package.gsub!("-base", "") if /base/.match?(@needle)
157
+
158
+ local_package_path = `bundle show #{partial_view_package}`.chomp
159
+ return local_package_path + partial_path_without_package
160
+ else
161
+ puts "You passed the absolute path for a partial literal, but we couldn't find the package name in the string:".red
162
+ puts "`#{@needle}`".red
163
+ puts ""
164
+ puts "Check the string one more time to see if the package name is there."
165
+ puts "i.e.: bullet_train-base/app/views/layouts/devise.html.erb".blue
166
+ puts ""
167
+ puts "If you're not sure what the package name is, run `bin/resolve --interactive`, follow the prompt, and pass the annotated path."
168
+ puts "i.e.: <!-- BEGIN /your/local/path/bullet_train-base/app/views/layouts/devise.html.erb -->".blue
169
+ exit
170
+ end
171
+ end
172
+
126
173
  begin
127
174
  annotated_path = ApplicationController.render(template: "bullet_train/partial_resolver", layout: nil, assigns: {needle: @needle}).lines[1].chomp
128
175
  rescue ActionView::Template::Error => e
@@ -1,3 +1,3 @@
1
1
  module BulletTrain
2
- VERSION = "1.1.9"
2
+ VERSION = "1.2.0"
3
3
  end
data/lib/bullet_train.rb CHANGED
@@ -53,8 +53,13 @@ def default_url_options_from_base_url
53
53
 
54
54
  # the name of this property doesn't match up.
55
55
  default_url_options[:protocol] = parsed_base_url.scheme
56
+ default_url_options.compact!
56
57
 
57
- default_url_options.compact
58
+ if default_url_options.empty?
59
+ raise "ENV['BASE_URL'] has not been configured correctly. Please check your environment variables and try one more time."
60
+ end
61
+
62
+ default_url_options
58
63
  end
59
64
 
60
65
  def inbound_email_enabled?
data/lib/colorizer.rb CHANGED
@@ -21,7 +21,7 @@ module Colorizer
21
21
  r = l
22
22
  g = l
23
23
  b = l
24
- v = l <= 0.5 ? (l * (1.0 + sl)) : (l + sl - l * sl)
24
+ v = (l <= 0.5) ? (l * (1.0 + sl)) : (l + sl - l * sl)
25
25
  if v > 0
26
26
  m = l + l - v
27
27
  sv = (v - m) / v
@@ -59,7 +59,16 @@ namespace :bullet_train do
59
59
  if ARGV.first.present?
60
60
  BulletTrain::Resolver.new(ARGV.first).run(eject: ARGV.include?("--eject"), open: ARGV.include?("--open"), force: ARGV.include?("--force"), interactive: ARGV.include?("--interactive"))
61
61
  else
62
- warn "\n🚅 Usage: `bin/resolve [path, partial, or URL] (--eject) (--open)`\n".blue
62
+ warn <<~MSG
63
+ 🚅 Usage: #{"`bin/resolve [path, partial, or URL] (--eject) (--open)`".blue}
64
+
65
+ OR
66
+
67
+ #{"`bin/resolve --interactive`".blue}
68
+ When you use the interactive flag, we will prompt you to pass an annotated partial like so and either eject or open the file.
69
+ These annotated paths can be found in your browser when inspecting elements:
70
+ <!-- BEGIN /your/path/.rbenv/versions/3.1.2/lib/ruby/gems/3.1.0/gems/bullet_train-themes-light-1.0.51/app/views/themes/light/_notices.html.erb -->
71
+ MSG
63
72
  end
64
73
  end
65
74
 
@@ -82,6 +91,55 @@ namespace :bullet_train do
82
91
  puts ""
83
92
  end
84
93
 
94
+ # Process any flags that were passed.
95
+ if arguments[:all_options].present?
96
+ flags_with_values = []
97
+
98
+ arguments[:all_options].split(/\s+/).each do |option|
99
+ if option.match?(/^--/)
100
+ flags_with_values << {flag: option.gsub(/^--/, "").to_sym, values: []}
101
+ else
102
+ flags_with_values.last[:values] << option
103
+ end
104
+ end
105
+
106
+ if flags_with_values.any?
107
+ flags_with_values.each do |process|
108
+ if process[:flag] == :link || process[:flag] == :reset
109
+ packages = process[:values]
110
+
111
+ gemfile_lines = File.readlines("./Gemfile")
112
+ new_lines = gemfile_lines.map do |line|
113
+ packages.each do |package|
114
+ if line.match?(package)
115
+ original_path = "gem \"bullet_train#{"-" + package unless package == "base"}\""
116
+ local_path = "gem \"bullet_train#{"-" + package unless package == "base"}\", path: \"local/bullet_train-#{package}\""
117
+
118
+ case process[:flag]
119
+ when :link
120
+ line.gsub!(original_path, local_path)
121
+ puts "Setting local '#{package}' package to the Gemfile...".blue
122
+ break
123
+ when :reset
124
+ line.gsub!(local_path, original_path)
125
+ puts "Resetting '#{package}' package in the Gemfile...".blue
126
+ break
127
+ end
128
+ end
129
+ end
130
+
131
+ line
132
+ end
133
+
134
+ File.write("./Gemfile", new_lines.join)
135
+ system "bundle install"
136
+ end
137
+ end
138
+
139
+ exit
140
+ end
141
+ end
142
+
85
143
  framework_packages = I18n.t("framework_packages")
86
144
 
87
145
  puts "Which framework package do you want to work on?".blue
@@ -97,17 +155,18 @@ namespace :bullet_train do
97
155
 
98
156
  if gem
99
157
  details = framework_packages[gem]
158
+ package = details[:git].split("/").last
100
159
 
101
160
  puts "OK! Let's work on `#{gem}` together!".green
102
161
  puts ""
103
162
 
104
- if File.exist?("local/#{gem}")
105
- puts "We found the repository in `local/#{gem}`. We will try to use what's already there.".yellow
163
+ if File.exist?("local/#{package}")
164
+ puts "We found the repository in `local/#{package}`. We will try to use what's already there.".yellow
106
165
  puts ""
107
166
 
108
167
  # Adding these flags enables us to execute git commands in the gem from our starter repo.
109
- work_tree_flag = "--work-tree=local/#{gem}"
110
- git_dir_flag = "--git-dir=local/#{gem}/.git"
168
+ work_tree_flag = "--work-tree=local/#{package}"
169
+ git_dir_flag = "--git-dir=local/#{package}/.git"
111
170
 
112
171
  git_status = `git #{work_tree_flag} #{git_dir_flag} status`
113
172
  unless git_status.match?("nothing to commit, working tree clean")
@@ -119,7 +178,7 @@ namespace :bullet_train do
119
178
  current_branch = `git #{work_tree_flag} #{git_dir_flag} branch`.split("\n").select { |branch_name| branch_name.match?(/^\*\s/) }.pop.gsub(/^\*\s/, "")
120
179
  unless current_branch == "main"
121
180
  puts "Previously on #{current_branch}.".blue
122
- puts "Switching local/#{gem} to main branch.".blue
181
+ puts "Switching local/#{package} to main branch.".blue
123
182
  stream("git #{work_tree_flag} #{git_dir_flag} checkout main")
124
183
  end
125
184
 
@@ -127,7 +186,7 @@ namespace :bullet_train do
127
186
  stream("git #{work_tree_flag} #{git_dir_flag} pull origin main")
128
187
  else
129
188
  # Use https:// URLs when using this task in Gitpod.
130
- stream "git clone #{`whoami`.chomp == "gitpod" ? "https://github.com/" : "git@github.com:"}#{details[:git]}.git local/#{gem}"
189
+ stream "git clone #{(`whoami`.chomp == "gitpod") ? "https://github.com/" : "git@github.com:"}#{details[:git]}.git local/#{package}"
131
190
  end
132
191
 
133
192
  stream("git #{work_tree_flag} #{git_dir_flag} fetch")
@@ -141,24 +200,27 @@ namespace :bullet_train do
141
200
  stream("git #{work_tree_flag} #{git_dir_flag} checkout #{input}")
142
201
  end
143
202
 
203
+ glob = if package == "bullet_train-core"
204
+ ", glob: \"#{gem}/#{gem}.gemspec\""
205
+ end
206
+
144
207
  puts ""
145
208
  puts "Now we'll try to link up that repository in the `Gemfile`.".blue
146
- if `cat Gemfile | grep "gem \\\"#{gem}\\\", path: \\\"local/#{gem}\\\""`.chomp.present?
209
+ if `cat Gemfile | grep "gem \\\"#{gem}\\\", path: \\\"local/#{package}\\\""`.chomp.present?
147
210
  puts "This gem is already linked to a checked out copy in `local` in the `Gemfile`.".green
148
211
  elsif `cat Gemfile | grep "gem \\\"#{gem}\\\","`.chomp.present?
149
212
  puts "This gem already has some sort of alternative source configured in the `Gemfile`.".yellow
150
213
  puts "We can't do anything with this. Sorry! We'll proceed, but you have to link this package yourself.".red
151
214
  elsif `cat Gemfile | grep "gem \\\"#{gem}\\\""`.chomp.present?
152
215
  puts "This gem is directly present in the `Gemfile`, so we'll update that line.".green
153
-
154
216
  text = File.read("Gemfile")
155
- new_contents = text.gsub(/gem "#{gem}"/, "gem \"#{gem}\", path: \"local/#{gem}\"")
217
+ new_contents = text.gsub(/gem "#{gem}"/, "gem \"#{gem}\", path: \"local/#{package}\"#{glob}")
156
218
  File.open("Gemfile", "w") { |file| file.puts new_contents }
157
219
  else
158
220
  puts "This gem isn't directly present in the `Gemfile`, so we'll add it temporarily.".green
159
221
  File.open("Gemfile", "a+") { |file|
160
222
  file.puts
161
- file.puts "gem \"#{gem}\", path: \"local/#{gem}\" # Added by \`bin/develop\`."
223
+ file.puts "gem \"#{gem}\", path: \"local/#{package}\"#{glob} # Added by `bin/develop`."
162
224
  }
163
225
  end
164
226
 
@@ -172,7 +234,7 @@ namespace :bullet_train do
172
234
 
173
235
  puts ""
174
236
  puts "OK, we're opening that package in your IDE, `#{ENV["IDE"] || "code"}`. (You can configure this with `export IDE=whatever`.)".blue
175
- `#{ENV["IDE"] || "code"} local/#{gem}`
237
+ `#{ENV["IDE"] || "code"} local/#{package}`
176
238
 
177
239
  puts ""
178
240
  if details[:npm]
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: bullet_train
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.1.9
4
+ version: 1.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Andrew Culver
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2022-11-16 00:00:00.000000000 Z
11
+ date: 2022-12-15 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: standard
@@ -442,8 +442,6 @@ files:
442
442
  - README.md
443
443
  - Rakefile
444
444
  - app/assets/config/bullet_train_manifest.js
445
- - app/assets/javascripts/bullet-train.js
446
- - app/assets/javascripts/bullet-train.js.map
447
445
  - app/controllers/account/invitations_controller.rb
448
446
  - app/controllers/account/memberships_controller.rb
449
447
  - app/controllers/account/onboarding/user_details_controller.rb
@@ -491,6 +489,7 @@ files:
491
489
  - app/javascript/controllers/form_controller.js
492
490
  - app/javascript/controllers/index.js
493
491
  - app/javascript/controllers/mobile_menu_controller.js
492
+ - app/javascript/controllers/select_all_controller.js
494
493
  - app/javascript/controllers/text_toggle_controller.js
495
494
  - app/javascript/electron/index.js
496
495
  - app/javascript/index.js
@@ -523,6 +522,7 @@ files:
523
522
  - app/views/account/memberships/_fields.html.erb
524
523
  - app/views/account/memberships/_form.html.erb
525
524
  - app/views/account/memberships/_index.html.erb
525
+ - app/views/account/memberships/_membership.html.erb
526
526
  - app/views/account/memberships/_menu_item.html.erb
527
527
  - app/views/account/memberships/_tombstones.html.erb
528
528
  - app/views/account/memberships/edit.html.erb
@@ -535,6 +535,7 @@ files:
535
535
  - app/views/account/teams/_form.html.erb
536
536
  - app/views/account/teams/_index.html.erb
537
537
  - app/views/account/teams/_menu_item.html.erb
538
+ - app/views/account/teams/_team.html.erb
538
539
  - app/views/account/teams/_team.json.jbuilder
539
540
  - app/views/account/teams/edit.html.erb
540
541
  - app/views/account/teams/index.html.erb
@@ -625,12 +626,14 @@ files:
625
626
  - db/migrate/20211020200855_add_doorkeeper_application_to_memberships.rb
626
627
  - db/migrate/20211027002944_add_doorkeeper_application_to_users.rb
627
628
  - docs/action-models.md
629
+ - docs/application-options.md
628
630
  - docs/authentication.md
629
631
  - docs/billing/stripe.md
630
632
  - docs/billing/usage.md
631
633
  - docs/desktop.md
632
634
  - docs/field-partials.md
633
635
  - docs/field-partials/buttons.md
636
+ - docs/field-partials/file-field.md
634
637
  - docs/field-partials/super-select.md
635
638
  - docs/font-awesome-pro.md
636
639
  - docs/getting-started.md
@@ -653,7 +656,9 @@ files:
653
656
  - docs/teams.md
654
657
  - docs/testing.md
655
658
  - docs/themes.md
659
+ - docs/trademark.md
656
660
  - docs/tunneling.md
661
+ - docs/two-factor-authentication.md
657
662
  - docs/upgrades.md
658
663
  - docs/webhooks/incoming.md
659
664
  - docs/webhooks/outgoing.md
@@ -1,2 +0,0 @@
1
- import{Controller as e}from"@hotwired/stimulus";function t(e){const t=(e.match(/^(?:\.\/)?(.+)(?:[_-]controller\..+?)$/)||[])[1];if(t)return t.replace(/_/g,"-").replace(/\//g,"--")}class l extends e{connect(){this.updateAvailability()}updateFormAndSubmit(e){return this.recreateIdsHiddenFields(),this.createOrUpdateAllField(),!0}updateIds(e){var t;null!=e&&null!=(t=e.detail)&&t.ids&&(this.idsValue=e.detail.ids,this.allValue=e.detail.all),this.updateAvailability(),this.updateButtonLabel()}updateAvailability(){this.element.classList.toggle(this.hiddenClass,0===this.idsValue.length)}updateButtonLabel(){let e=this.buttonIfAllValue;this.idsValue.length&&!1===this.allValue&&(e=this.buttonIfIdsValue.replace("{num}",this.idsValue.length)),"INPUT"===this.buttonTarget.tagName?this.buttonTarget.value=e:this.buttonTarget.textContent=e}recreateIdsHiddenFields(){this.removeIdsHiddenFields(),this.createIdsHiddenFields()}removeIdsHiddenFields(){this.idsHiddenFieldTargets.forEach(e=>{this.element.removeChild(e)})}createIdsHiddenFields(){this.idsValue.forEach(e=>{let t=document.createElement("input");t.type="hidden",t.name=this.objectNameValue+"["+this.idsFieldNameValue+"][]",t.value=e,this.element.appendChild(t)})}createOrUpdateAllField(){this.hasAllHiddenFieldTarget?this.allHiddenFieldTarget.value=this.allValue?"true":"false":this.createAllField()}createAllField(){let e=document.createElement("input");e.type="hidden",e.name=this.objectNameValue+"["+this.allFieldNameValue+"]",e.value=this.allValue?"true":"false",this.element.appendChild(e)}}l.targets=["button","idsHiddenField","allHiddenField"],l.classes=["hidden"],l.values={buttonIfAll:String,buttonIfIds:String,ids:Array,all:Boolean,objectName:String,idsFieldName:String,allFieldName:String};class s extends e{connect(){this.element.classList.add(this.selectableAvailableClass)}toggleSelectable(){this.selectableValue=!this.selectableValue}updateSelectedIds(){this.updateActions(),this.updateSelectAllCheckbox()}updateActions(){this.actionTargets.forEach(e=>{e.dispatchEvent(new CustomEvent("update-ids",{detail:{ids:this.selectedIds,all:this.allSelected}}))})}selectAllOrNone(e){this.allSelected?this.selectNone():this.selectAll(),this.updateSelectAllCheckbox()}selectAll(){this.checkboxTargets.forEach(e=>{e.checked=!0}),this.updateActions()}selectNone(){this.checkboxTargets.forEach(e=>{e.checked=!1}),this.updateActions()}updateSelectAllCheckbox(){let e=this.selectAllCheckboxTarget,t=this.selectAllLabelTarget;this.allSelected?(e.checked=!0,e.indeterminate=!1,t.dispatchEvent(new CustomEvent("toggle",{detail:{useAlternate:!0}}))):this.selectedIds.length>0?(e.indeterminate=!0,t.dispatchEvent(new CustomEvent("toggle",{detail:{useAlternate:!1}}))):(e.checked=!1,e.indeterminate=!1,t.dispatchEvent(new CustomEvent("toggle",{detail:{useAlternate:!1}})))}selectableValueChanged(){this.element.classList.toggle(this.selectableClass,this.selectableValue),this.updateToggleLabel()}updateToggleLabel(){this.selectableToggleTarget.dispatchEvent(new CustomEvent("toggle",{detail:{useAlternate:this.selectableValue}}))}get selectedIds(){let e=[];return this.checkboxTargets.forEach(t=>{t.checked&&e.push(t.value)}),e}get allSelected(){return this.selectedIds.length===this.checkboxTargets.length}}s.targets=["checkbox","selectAllCheckbox","action","selectableToggle","selectAllLabel"],s.classes=["selectableAvailable","selectable"],s.values={selectable:Boolean};class a extends e{copy(){this.inputTarget.value=this.sourceTarget.innerText,this.inputTarget.select(),document.execCommand("copy"),this.buttonTarget.innerHTML='<i id="copied" class="fas fa-check w-4 h-4 block text-green-600"></i>',setTimeout(function(){document.getElementById("copied").innerHTML='<i class="far fa-copy w-4 h-4 block text-gray-600"></i>'},1500)}}a.targets=["source","input","button"];class i extends e{constructor(){super(...arguments),this.removeTrailingNewlines=e=>{e.element.innerHTML.match(/<br><\/div>$/)&&(e.element.innerHTML=e.element.innerHTML.slice(0,-10)+"</div>",this.removeTrailingNewlines(e))},this.removeTrailingWhitespace=e=>{e.element.innerHTML.match(/&nbsp;<\/div>$/)?(e.element.innerHTML=e.element.innerHTML.slice(0,-12)+"</div>",this.removeTrailingWhitespace(e)):e.element.innerHTML.match(/&nbsp; <\/div>$/)&&(e.element.innerHTML=e.element.innerHTML.slice(0,-13)+"</div>",this.removeTrailingWhitespace(e))}}resetOnSuccess(e){e.detail.success&&e.target.reset()}stripTrix(){this.trixFieldTargets.forEach(e=>{this.removeTrailingNewlines(e.editor),this.removeTrailingWhitespace(e.editor),e.parentElement.querySelector("input").value=e.innerHTML})}submitOnReturn(e){if((e.metaKey||e.ctrlKey)&&13==e.keyCode){e.preventDefault();let t=e.target.closest("form");this.submitForm(t)}}submitForm(e){e.requestSubmit?e.requestSubmit():e.querySelector("[type=submit]").click()}}async function n(e,t,l){const s=t.dataset,a=l?`${l}-${e}`:e;let i=`transition${e.charAt(0).toUpperCase()+e.slice(1)}`;const n=s[i]?s[i].split(" "):[a],c=s[`${i}Start`]?s[`${i}Start`].split(" "):[`${a}-start`],o=s[`${i}End`]?s[`${i}End`].split(" "):[`${a}-end`];r(t,n),r(t,c),await new Promise(e=>{requestAnimationFrame(()=>{requestAnimationFrame(e)})}),d(t,c),r(t,o),await function(e){return new Promise(t=>{const l=getComputedStyle(e).transitionDuration.split(",")[0],s=1e3*Number(l.replace("s",""));setTimeout(()=>{t()},s)})}(t),d(t,o),d(t,n)}function r(e,t){e.classList.add(...t)}function d(e,t){e.classList.remove(...t)}i.targets=["trixField","scroll"];class c extends e{open(){this.showWrapper(),this.revealableTargets.forEach(e=>{!async function(e,t=null){e.classList.remove("hidden"),await n("enter",e,t)}(e)})}close(){Promise.all(this.revealableTargets.map(e=>async function(e,t=null){await n("leave",e,t),e.classList.add("hidden")}(e))).then(()=>{this.hideWrapper()})}showWrapper(){this.wrapperTarget.classList.remove(this.hiddenClass)}hideWrapper(){this.wrapperTarget.classList.add(this.hiddenClass)}}c.targets=["wrapper","revealable"],c.classes=["hidden"];class o extends e{connect(){this.updateLabel()}toggle(e){var t;this.useAlternateValue=void 0!==(null==e||null==(t=e.detail)?void 0:t.useAlternate)?e.detail.useAlternate:!this.useAlternateValue}useAlternateValueChanged(){this.updateLabel()}updateLabel(){this.hasLabelValue&&this.hasLabelAlternateValue&&this.hasUseAlternateValue&&(this.element.textContent=!0===this.useAlternateValue?this.labelAlternateValue:this.labelValue)}}o.values={label:String,labelAlternate:String,useAlternate:Boolean};const h=[[l,"bulk_action_form_controller.js"],[s,"bulk_actions_controller.js"],[a,"clipboard_controller.js"],[i,"form_controller.js"],[c,"mobile_menu_controller.js"],[o,"text_toggle_controller.js"]].map(function(e){const l=e[0];return{identifier:t(e[1]),controllerConstructor:l}});document.addEventListener("turbo:load",()=>{navigator.userAgent.toLocaleLowerCase().includes("electron")&&document.body.classList.add("electron")});export{h as controllerDefinitions};
2
- //# sourceMappingURL=bullet-train.js.map