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.
- checksums.yaml +4 -4
- data/app/controllers/concerns/account/invitations/controller_base.rb +3 -11
- data/app/controllers/concerns/account/users/controller_base.rb +4 -25
- data/app/controllers/concerns/documentation_support.rb +1 -1
- data/app/controllers/concerns/registrations/controller_base.rb +9 -6
- data/app/controllers/sessions_controller.rb +9 -0
- data/app/helpers/account/teams_helper.rb +1 -1
- data/app/helpers/concerns/helpers/base.rb +0 -9
- data/app/javascript/controllers/index.js +2 -0
- data/app/javascript/controllers/select_all_controller.js +82 -0
- data/app/models/concerns/memberships/base.rb +3 -7
- data/app/models/concerns/users/base.rb +3 -0
- data/app/views/account/memberships/_index.html.erb +4 -35
- data/app/views/account/memberships/_membership.html.erb +29 -0
- data/app/views/account/memberships/index.html.erb +6 -1
- data/app/views/account/memberships/show.html.erb +1 -1
- data/app/views/account/onboarding/user_details/edit.html.erb +6 -6
- data/app/views/account/teams/_index.html.erb +1 -25
- data/app/views/account/teams/_team.html.erb +23 -0
- data/app/views/account/teams/index.html.erb +3 -1
- data/app/views/account/teams/show.html.erb +1 -1
- data/app/views/devise/registrations/new.html.erb +2 -2
- data/app/views/devise/sessions/new.html.erb +1 -1
- data/app/views/layouts/docs.html.erb +12 -0
- data/config/locales/en/base.yml +1 -0
- data/config/locales/en/framework_packages.yml +13 -17
- data/docs/application-options.md +29 -0
- data/docs/authentication.md +9 -0
- data/docs/field-partials/file-field.md +25 -0
- data/docs/field-partials.md +3 -1
- data/docs/i18n.md +28 -0
- data/docs/index.md +2 -0
- data/docs/invitation_only.md +1 -1
- data/docs/themes.md +13 -40
- data/docs/trademark.md +25 -0
- data/docs/two-factor-authentication.md +16 -0
- data/lib/bullet_train/resolver.rb +47 -0
- data/lib/bullet_train/version.rb +1 -1
- data/lib/bullet_train.rb +6 -1
- data/lib/colorizer.rb +1 -1
- data/lib/tasks/bullet_train_tasks.rake +74 -12
- metadata +9 -4
- data/app/assets/javascripts/bullet-train.js +0 -2
- 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` |
|
data/docs/authentication.md
CHANGED
@@ -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
|
+
```
|
data/docs/field-partials.md
CHANGED
@@ -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)
|
data/docs/invitation_only.md
CHANGED
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”
|
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
|
-
|
9
|
+
This is where all of Bullet Train's standard views are contained.
|
6
10
|
|
7
|
-
|
11
|
+
## Adding a New Theme (ejecting standard views)
|
8
12
|
|
9
|
-
-
|
10
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
data/lib/bullet_train/version.rb
CHANGED
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.
|
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
@@ -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
|
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/#{
|
105
|
-
puts "We found the repository in `local/#{
|
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/#{
|
110
|
-
git_dir_flag = "--git-dir=local/#{
|
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/#{
|
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/#{
|
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/#{
|
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/#{
|
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/#{
|
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/#{
|
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.
|
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
|
+
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(/ <\/div>$/)?(e.element.innerHTML=e.element.innerHTML.slice(0,-12)+"</div>",this.removeTrailingWhitespace(e)):e.element.innerHTML.match(/ <\/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
|