bullet_train 1.2.4 → 1.2.6

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 6d69973f61994b65b605d9336953c6993b580b82781d412391583bc7d5bf9cd0
4
- data.tar.gz: 4ea8ca252e6156f7fcbb01ef4b1115475bed681efd2b46fa83b328192a2652a3
3
+ metadata.gz: 7f272526d56b142f946f7a0b4ebdbb8537120199a0f78be7cad85ba7265003b6
4
+ data.tar.gz: 1e50a2ae1848bd7613f8ce2ece37de6bb2fb85c485207226cad6b39e5d7ed908
5
5
  SHA512:
6
- metadata.gz: 17c23db90040684969305697f996aed697799c166e826dd8fbc29d58f57f5c4db2ff1d3b05e6f7e7e337e5fb50e235f26c72d2b0e7fb279e9d0d1477f91f3650
7
- data.tar.gz: '06852869d26adba82a25a67033254d43125fb6a730f82e65d7c5ab6097dbf6b1ecd523b4c848ca637402f2cf4abefaccdd3fbc5f8f3fef6f3f32ef7ed7570eda'
6
+ metadata.gz: 34c1d594e23e6ba2e11dc06b69f012cb118d8901bc47d08e697f26ecd5081228e02bfeb923746906730ee2f4e5ec8a8338da11234b828feca22c5d2cf1090e87
7
+ data.tar.gz: 538ac4bb1e3723f929059c382c642b00e9e30ddaee8f2d68880ac75a83e197fa9d7d0463e951f21d41123cce765622b16a6ff52696d75ee8944b5e08b91e231e
@@ -10,6 +10,18 @@ class SessionsController < Devise::SessionsController
10
10
  end
11
11
  helper_method :user_return_to_is_oauth
12
12
 
13
+ def new
14
+ # We allow people to pass in a URL to redirect to after sign in is complete. We have to do this because Safari
15
+ # doesn't allow them to set this in a session before a redirect if there isn't already a session. However, for
16
+ # security reasons we have to make sure we control the URL where we will redirect to, otherwise people could
17
+ # trick folks into redirecting to a fake destination in a phishing scheme.
18
+ if params[:return_url]&.start_with?(ENV["BASE_URL"])
19
+ store_location_for(resource_name, params[:return_url])
20
+ end
21
+
22
+ super
23
+ end
24
+
13
25
  def destroy
14
26
  if params.include?(:onboard_logout)
15
27
  signed_out = (Devise.sign_out_all_scopes ? sign_out : sign_out(resource_name))
@@ -25,6 +25,8 @@ module Memberships::Base
25
25
  end
26
26
  end
27
27
 
28
+ scope :excluding_platform_agents, -> { where(platform_agent_of: nil) }
29
+ scope :platform_agents, -> { where.not(platform_agent_of: nil) }
28
30
  scope :current_and_invited, -> { includes(:invitation).where("user_id IS NOT NULL OR invitations.id IS NOT NULL").references(:invitation) }
29
31
  scope :current, -> { where("user_id IS NOT NULL") }
30
32
  scope :tombstones, -> { includes(:invitation).where("user_id IS NULL AND invitations.id IS NULL AND platform_agent IS FALSE").references(:invitation) }
@@ -38,6 +38,12 @@ module Teams::Base
38
38
  validates :time_zone, inclusion: {in: ActiveSupport::TimeZone.all.map(&:name)}, allow_nil: true
39
39
  end
40
40
 
41
+ def platform_agent_access_tokens
42
+ # TODO This could be written better.
43
+ platform_agent_user_ids = memberships.platform_agents.map(&:user_id).compact
44
+ Platform::AccessToken.joins(:application).where(resource_owner_id: platform_agent_user_ids, application: {team: nil})
45
+ end
46
+
41
47
  def admins
42
48
  memberships.current_and_invited.admins
43
49
  end
@@ -215,15 +215,21 @@
215
215
  <% end %>
216
216
 
217
217
  <%= render 'account/shared/menu/section', title: 'Integration' do %>
218
- <%= render 'account/shared/menu/item', url: '/docs/oauth', label: 'OAuth Providers' do |p| %>
218
+ <%= render 'account/shared/menu/item', url: '/docs/api', label: 'REST API' do |p| %>
219
219
  <% p.content_for :icon do %>
220
- <i class="fal fa-at ti ti-reload"></i>
220
+ <i class="fal fa-brackets-curly ti ti-settings"></i>
221
221
  <% end %>
222
222
  <% end %>
223
223
 
224
- <%= render 'account/shared/menu/item', url: '/docs/api', label: 'REST API' do |p| %>
224
+ <%= render 'account/shared/menu/item', url: '/docs/zapier', label: 'Zapier' do |p| %>
225
225
  <% p.content_for :icon do %>
226
- <i class="fal fa-brackets-curly ti ti-settings"></i>
226
+ <i class="fal fa-bolt ti ti-bolt"></i>
227
+ <% end %>
228
+ <% end %>
229
+
230
+ <%= render 'account/shared/menu/item', url: '/docs/oauth', label: 'OAuth Providers' do |p| %>
231
+ <% p.content_for :icon do %>
232
+ <i class="fal fa-at ti ti-reload"></i>
227
233
  <% end %>
228
234
  <% end %>
229
235
 
@@ -0,0 +1,63 @@
1
+ # API Versioning
2
+ Bullet Train's API layer is designed to help support the need of software developers to evolve their API over time while continuing to maintain support for versions of the API that users have already built against.
3
+
4
+ ## What is API versioning?
5
+ By default, Bullet Train will build out a "V1" version of your API. The version number is intended to represent a contract with your users that as long as they're hitting `/api/v1` endpoints, the structure of URLs, requests, and responses won't change in a way that will break the integrations they've created.
6
+
7
+ If a change to the API would break the established contract, we want to bump the API version number so we can differentiate between developers building against the latest version of the API (e.g. "V2") and developers who wrote code against the earlier version of the API (e.g. "V1"). This allows us the opportunity to ensure that older versions of the API continue to work as previously expected by the earlier developers.
8
+
9
+ ## When should you take advantage of API versioning?
10
+ You want to bump API versions as sparingly as possible. Even with all the tooling Bullet Train provides, maintaining backwards compatibility of older API versions comes at an ongoing cost. Generally speaking, you should only bump your API version when a customer is already using an API endpoint and you're making changes to the structure of your domain model that are not strictly additive and will break the established contract.
11
+
12
+ Importantly, if the changes you're making to your domain model are only additive, you don't need to bump your API version. Users shouldn't care that you're adding new attributes or new endpoints to your API, just as long as the ones they're already using don't change in a way that is breaking for them.
13
+
14
+ ## Background
15
+ By default, the following components in your API are created in versioned namespaces:
16
+
17
+ - API controllers are in `app/controllers/api/v1` and live in an `Api::V1` module.
18
+ - JSON views are in `app/controllers/api/v1`.
19
+ - Routes are in `config/routes/api/v1.rb`.
20
+ - Tests are in `test/controllers/api/v1` and live in an `Api::V1` module.
21
+
22
+ > It's also impotant to keep in mind that some dependencies of your API and API tests like models, factories, and permissions are not versioned, but as we'll cover later, this is something our approach helps you work around.
23
+
24
+ ## Bumping Your API Version
25
+
26
+ ⚠️ You must do this _before_ making the breaking changes to your API.
27
+
28
+ If you're in a situation where you know you need to bump your API version to help lock-in a backward compatible version of your API, you can simply run:
29
+
30
+ ```
31
+ rake bullet_train:api:bump_version
32
+ ```
33
+
34
+ > TODO This Rake task doesn't exist yet.
35
+
36
+ ## What happens when you bump an API version?
37
+ When you bump your API version, all of the files and directories that are namespaced with the API version number will be duplicated into a new namespace for the new API version number.
38
+
39
+ For example, when bumping from "V1" to "V2":
40
+
41
+ - A copy of all the API controllers in `app/controllers/api/v1` are copied into `app/controllers/api/v2`.
42
+ - A copy of all the JSON views in `app/views/api/v1` are copied into `app/views/api/v2`.
43
+ - A copy of all the routes in `config/routes/api/v1.rb` are copied into `config/routes/api/v2.rb`.
44
+ - A copy of all the tests in `test/controllers/api/v1` are copied into `test/controllers/api/v2`.
45
+
46
+ We also bump the value of `BulletTrain::Api.current_version` in `config/initializers/api.rb` so tools like Super Scaffolding know which version of your API to update going forward.
47
+
48
+ ## How does this help?
49
+ As a baseline, keeping a wholesale copy of the versioned API components helps lock in their behavior and protect them from change going forward. It's not a silver bullet, since unversioned dependencies (like your model, factories, and permissions) can still affect the behavior of these versioned API components, but even in that case these copied files give us a place where we can implement the logic that helps older versions of the API continue to operate even as unversioned components like our domain model continue changing.
50
+
51
+ ### Versioned API Tests
52
+ By versioning our API tests, we lock in a copy of what the assumptions were for older versions of the API. Should unversioned dependencies like our domain model change in ways that break earlier versions of our API, the test suite will let us know and help us figure out when we've implemented the appropriate logic in the older version of the API controller to restore the expected behavior for that version of the API.
53
+
54
+ ## Advanced Topics
55
+
56
+ ### Object-Oriented Inheritance
57
+ In order to reduce the surface area of legacy API controllers that you're maintaining, it might make sense in some cases to have an older versioned API controller simply inherit from a newer version or the current version of the same API controller. For example, this might make sense for endpoints that you know didn't have breaking changes across API versions.
58
+
59
+ ### Backporting New Features to Legacy API Versions
60
+ Typically we'd recommend you use new feature availability to encourage existing API users to upgrade to the latest version of the API. However, in some situations you may really need to make a newer API feature available to a user who is locked into a legacy version of your API for some other endpoint. This is totally fine if the feature is only additive. For example, if you're just adding a newer API endpoint in a legacy version of the API, you can simply have the new API controller in the legacy version of the API inherit from the API controller in the current version of the API.
61
+
62
+ ### Pruning Unused Legacy API Endpoints
63
+ Maintaining legacy endpoints has a very real cost, so you may choose to identify which endpoints aren't being used on legacy versions of your API and prune them from that version entirely. This has the effect of requiring existing API users to keep their API usage up-to-date before expanding the surface area of usage, which may or may not be desirable for you.
data/docs/api.md ADDED
@@ -0,0 +1,106 @@
1
+ # REST API
2
+ We believe every SaaS application should have an API and [webhooks](/docs/webhooks/outgoing.md) available to users, so Bullet Train aims to help automate the creation of a production-grade REST API using Rails-native tooling and provides a forward-thinking strategy for its long-term maintenance.
3
+
4
+ ## Background
5
+ Vanilla Rails scaffolding actually provides simple API functionality out-of-the-box: You can append `.json` to the URL of any scaffold and it will render a JSON representation instead of an HTML view. This functionality continues to work in Bullet Train, but our API implementation also builds on this simple baseline using the same tools with additional organization and some new patterns.
6
+
7
+ ## Goals
8
+
9
+ ### Zero-Effort API
10
+ As with vanilla Rails scaffolding, Super Scaffolding automatically generates your API as you scaffold new models, and unlike vanilla Rails scaffolding, it will automatically keep it up-to-date as you scaffold additional attributes onto your models.
11
+
12
+ ### Versioning by Default
13
+ By separating out and versioning API controllers, views, routes, and tests, Bullet Train provides [a methodology and tooling](/docs/api/versioning.md) to help ensure that once users have built against your API, changes in the structure of your domain model and API don't unexpectedly break existing integrations. You can [read more about API versioning](/docs/api/versioning.md).
14
+
15
+ ### Standard Rails Tooling
16
+ APIs are built using standard Rails tools like `ActiveController::API`, [Strong Parameters](https://api.rubyonrails.org/classes/ActionController/StrongParameters.html), `config/routes.rb`, and [Jbuilder](https://github.com/rails/jbuilder). Maintaining API endpoints doesn't require special knowledge and feels like regular Rails development.
17
+
18
+ ### Outsourced Authentication
19
+ In the same way we've adopted [Devise](https://github.com/heartcombo/devise) for best-of-breed and battle-tested authentication on the browser side, we've adopted [Doorkeeper](https://github.com/doorkeeper-gem/doorkeeper) for best-of-breed and battle-tested authentication on the API side.
20
+
21
+ ### DRY Authorization Logic
22
+ Because our API endpoints are standard Rails controllers, they're able to leverage the exact same [permissions definitions and authorization logic](https://github.com/bullet-train-co/bullet_train-base/blob/main/docs/permissions.md) as our account controllers.
23
+
24
+ ## Structure
25
+ Where vanilla Rails uses a single controller in `app/controllers` for both in-browser and API requests, Bullet Train splits these into two separate controllers, one in `app/controllers/account` and another in `app/controllers/api/v1`, although a lot of logic is shared between the two.
26
+
27
+ API endpoints are defined in three parts:
28
+
29
+ 1. Routes are defined in `config/routes/api/v1.rb`.
30
+ 2. Controllers are defined in the `app/controllers/api/v1` directory.
31
+ 3. Jbuilder views are defined in the `app/views/api/v1` directory.
32
+
33
+ ## "API First" and Supporting Account Controllers
34
+ As previously mentioned, there is a lot of shared logic between account and API controllers. Importantly, there are a couple of responsbilities that are implemented "API first" in API controllers and then utilized by account controllers.
35
+
36
+ ### Strong Parameters
37
+ The primary definition of Strong Parameters for a given resource is defined in the most recent version of the API controller and included from there by the account controller. In account controllers, where you might expect to see a Strong Parameters definition, you'll see the following instead:
38
+
39
+ ```ruby
40
+ include strong_parameters_from_api
41
+ ```
42
+
43
+ > This may feel counter-intuitive to some developers and you might wonder why we don't flip this around and have the primary definition in the account controller and have the API controller delegate to it. The answer is a pragmatic one: creating and maintaining the defintion of Strong Paramters in the API controller means it gets automatically frozen in time should you ever need to [bump your API version number](/api/docs/versioning.md). We probably _could_ accomplish this if things were the other way around, but it wouldn't happen automatically.
44
+
45
+ If by chance there are additional attributes that should be permitted or specific logic that needs to be run as part of the account controller (or inversely, only in the API controller), you can specify that in the controller like so:
46
+
47
+ ```ruby
48
+ def permitted_fields
49
+ [:some_specific_attribute]
50
+ end
51
+
52
+ def permitted_arrays
53
+ {some_collection: []}
54
+ end
55
+
56
+ def process_params(strong_params)
57
+ assign_checkboxes(strong_params, :some_checkboxes)
58
+ strong_params
59
+ end
60
+ ```
61
+
62
+ ### Delegating `.json` View Rendering on Account Controllers
63
+
64
+ In Bullet Train, when you append `.json` to an account URL, the account controller doesn't actually have any `.json.jbuilder` templates in its view directory within `app/views/account`. Instead, by default the controller is configured to delegate the JSON rendering to the corresponding Jbuilder templates in the most recent version of the API, like so:
65
+
66
+ ```ruby
67
+ # GET /account/projects/:id or /account/projects/:id.json
68
+ def show
69
+ delegate_json_to_api
70
+ end
71
+ ```
72
+
73
+ ## Usage Example
74
+ First, provision a platform application in section titled "Your Applications" in the "Developers" menu of the application. When you create a new platform application, an access token that doesn't automatically expire will be automatically provisioned along with it. You can then use the access token to hit the API, as seen in the following Ruby-based example:
75
+
76
+ ```ruby
77
+ require 'net/http'
78
+ require 'uri'
79
+
80
+ # Configure an API client.
81
+ client = Net::HTTP.new('localhost', 3000)
82
+
83
+ headers = {
84
+ "Content-Type" => "application/json",
85
+ "Authorization" => "Bearer GfNLkDmzOTqAacR1Kqv0VJo7ft2TT-S_p8C6zPDBFhg"
86
+ }
87
+
88
+ # Fetch the team details.
89
+ response = client.get("/api/v1/teams/1", headers)
90
+
91
+ # Parse response.
92
+ team = JSON.parse(response.body)
93
+
94
+ # Update team name.
95
+ team["name"] = "Updated Team Name"
96
+
97
+ # Push the update to the API.
98
+ # Note that the team attributes are nested under a `team` key in the JSON body.
99
+ response = client.patch("/api/v1/teams/1", {team: team}.to_json, headers)
100
+ ```
101
+
102
+ ## Advanced Topics
103
+ - [API Versioning](/docs/api/versioning.md)
104
+
105
+ ## A Note About Other Serializers and API Frameworks
106
+ In early versions of Bullet Train we made the decision to adopt a specific serialization library, [ActiveModelSerializers](https://github.com/rails-api/active_model_serializers) and in subsequent versions we went as far as to adopt an entire third-party framework ([Grape](https://github.com/ruby-grape/grape)) and a third-party API specification ([JSON:API](https://jsonapi.org)). We now consider it out-of-scope to try and make such decisions on behalf of developers. Support for them in Bullet Train applications and in Super Scaffolding could be created by third-parties.
data/docs/index.md CHANGED
@@ -40,11 +40,12 @@
40
40
  - [Stripe](/docs/billing/stripe.md)
41
41
 
42
42
  ## Integration
43
- - [OAuth Providers](/docs/oauth.md)
44
43
  - [REST API](/docs/api.md)
44
+ - [Zapier](/docs/zapier.md)
45
+ - [OAuth Providers](/docs/oauth.md)
45
46
  - [Outgoing Webhooks](/docs/webhooks/outgoing.md)
46
47
  - [Incoming Webhooks](/docs/webhooks/incoming.md)
47
-
48
+
48
49
  ## Add-Ons
49
50
  - [Font Awesome Pro](/docs/font-awesome-pro.md)
50
51
 
data/docs/zapier.md ADDED
@@ -0,0 +1,46 @@
1
+ # Integrating with Zapier
2
+ Bullet Train provides out-of-the-box support for Zapier. New Bullet Train projects include a preconfigured Zapier CLI project that is ready to `zapier deploy`.
3
+
4
+ ## Background
5
+ Zapier was designed to take advantage of an application's existing [REST API](/docs/api.md), [outgoing webhook capabilities](/docs/webhooks/outgoing.md), and OAuth2 authorization workflows. Thankfully for us, Bullet Train provides the first two and pre-configures Doorkeeper to provide the latter. We also have an smooth OAuth2 connection workflow that accounts for the mismatch between the user-based OAuth2 standard and team-based multitenancy.
6
+
7
+ ## Prerequitesites
8
+ - You must be developing in an environment with [tunneling enabled](/docs/tunneling.md).
9
+
10
+ ## Getting Started in Development
11
+ First, install the Zapier CLI tooling and deploy:
12
+
13
+ ```
14
+ cd zapier
15
+ yarn install
16
+ zapier login
17
+ zapier register
18
+ zapier push
19
+ ```
20
+
21
+ Once the application is registered in your account, you can re-run seeds in your development environment and it will create a `Platform::Application` record for Zapier:
22
+
23
+ ```
24
+ cd ..
25
+ rake db:seed
26
+ ```
27
+
28
+ When you do this for the first time, it will output some credentials for you to go back and configure for the Zapier application, like so:
29
+
30
+ ```
31
+ cd zapier
32
+ zapier env:set 1.0.0 \
33
+ BASE_URL=https://andrewculver.ngrok.io \
34
+ CLIENT_ID=... \
35
+ CLIENT_SECRET=...
36
+ cd ..
37
+ ```
38
+
39
+ You're done and can now test creating Zaps that react to example objects being created or create example objects based on other triggers.
40
+
41
+ ## Deploying in Production
42
+ We haven't figured out a good suggested process for breaking out development and production versions of the Zapier application yet, but we'll update this section when we have.
43
+
44
+ ## Future Plans
45
+ - Extend Super Scaffolding to automatically add new resources to the Zapier CLI project. For now you have to extend the Zapier CLI definitions manually.
46
+
@@ -1,3 +1,3 @@
1
1
  module BulletTrain
2
- VERSION = "1.2.4"
2
+ VERSION = "1.2.6"
3
3
  end
@@ -91,13 +91,15 @@ namespace :bullet_train do
91
91
  puts ""
92
92
  end
93
93
 
94
+ framework_packages = I18n.t("framework_packages")
95
+
94
96
  # Process any flags that were passed.
95
97
  if arguments[:all_options].present?
96
98
  flags_with_values = []
97
99
 
98
100
  arguments[:all_options].split(/\s+/).each do |option|
99
101
  if option.match?(/^--/)
100
- flags_with_values << {flag: option.gsub(/^--/, "").to_sym, values: []}
102
+ flags_with_values << {flag: option, values: []}
101
103
  else
102
104
  flags_with_values.last[:values] << option
103
105
  end
@@ -105,34 +107,27 @@ namespace :bullet_train do
105
107
 
106
108
  if flags_with_values.any?
107
109
  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 if package}\""
116
- local_path = "gem \"bullet_train#{"-" + package if package}\", path: \"local/bullet_train#{"-" + package if 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"
110
+ case process[:flag]
111
+ when "--help"
112
+ puts "bin/hack: Clone bullet_train-core and link up gems (will only link up gems if already cloned).".blue
113
+ puts "bin/hack --link: Link all of your Bullet Train gems to `local/bullet_train-core`".blue
114
+ puts "bin/hack --reset: Resets all of your gems to their original definition.".blue
115
+ puts "bin/hack --watch-js: Watches for any changes in JavaScript files gems that have an npm package.".blue
116
+ puts "bin/hack --clean-js: Resets all of your npm packages from `local/bullet_train-core` to their original definition".blue
117
+ exit
118
+ when "--link", "--reset"
119
+ set_core_gems(process[:flag], framework_packages)
120
+ stream "bundle install"
121
+ when "--watch-js"
122
+ set_npm_packages(process[:flag], framework_packages)
123
+
124
+ puts "Proceeding to reset your Bullet Train npm packages according to your root directory's `package.json`.".yellow
125
+ puts "If you press `Ctrl + C` before the process completely exits, just run `bin/hack --clean-js`".yellow
126
+ set_npm_packages("--clean-js", framework_packages)
127
+ break
128
+ when "--clean-js"
129
+ set_npm_packages(process[:flag], framework_packages)
130
+ break
136
131
  end
137
132
  end
138
133
 
@@ -140,123 +135,147 @@ namespace :bullet_train do
140
135
  end
141
136
  end
142
137
 
143
- framework_packages = I18n.t("framework_packages")
138
+ puts "Welcome! Let's get hacking.".blue
144
139
 
145
- puts "Which framework package do you want to work on?".blue
146
- puts ""
147
- framework_packages.each do |gem, details|
148
- puts " #{framework_packages.keys.find_index(gem) + 1}. #{gem}".blue
149
- end
150
- puts ""
151
- puts "Enter a number below and hit <Enter>:".blue
152
- number = $stdin.gets.chomp
140
+ # Adding these flags enables us to execute git commands in the gem from our starter repo.
141
+ work_tree_flag = "--work-tree=local/bullet_train-core"
142
+ git_dir_flag = "--git-dir=local/bullet_train-core/.git"
153
143
 
154
- gem = framework_packages.keys[number.to_i - 1]
144
+ if File.exist?("local/bullet_train-core")
145
+ puts "We found the repository in `local/bullet_train-core`. We will try to use what's already there.".yellow
146
+ puts ""
155
147
 
156
- if gem
157
- details = framework_packages[gem]
158
- package = details[:git].split("/").last
148
+ git_status = `git #{work_tree_flag} #{git_dir_flag} status`
149
+ unless git_status.match?("nothing to commit, working tree clean")
150
+ puts "This package currently has uncommitted changes.".red
151
+ puts "Please make sure the branch is clean and try again.".red
152
+ exit
153
+ end
159
154
 
160
- puts "OK! Let's work on `#{gem}` together!".green
161
- puts ""
155
+ current_branch = `git #{work_tree_flag} #{git_dir_flag} branch`.split("\n").select { |branch_name| branch_name.match?(/^\*\s/) }.pop.gsub(/^\*\s/, "")
156
+ unless current_branch == "main"
157
+ puts "Previously on #{current_branch}.".blue
158
+ puts "Switching local/bullet_train-core to main branch.".blue
159
+ stream("git #{work_tree_flag} #{git_dir_flag} checkout main")
160
+ end
162
161
 
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
165
- puts ""
162
+ puts "Updating the main branch with the latest changes.".blue
163
+ stream("git #{work_tree_flag} #{git_dir_flag} pull origin main")
164
+ else
165
+ # Use https:// URLs when using this task in Gitpod.
166
+ stream "git clone #{(`whoami`.chomp == "gitpod") ? "https://github.com/" : "git@github.com:"}/bullet-train-co/bullet_train-core.git local/bullet_train-core"
167
+ end
166
168
 
167
- # Adding these flags enables us to execute git commands in the gem from our starter repo.
168
- work_tree_flag = "--work-tree=local/#{package}"
169
- git_dir_flag = "--git-dir=local/#{package}/.git"
169
+ stream("git #{work_tree_flag} #{git_dir_flag} fetch")
170
+ stream("git #{work_tree_flag} #{git_dir_flag} branch -r")
171
+ puts "The above is a list of remote branches.".blue
172
+ puts "If there's one you'd like to work on, please enter the branch name and press <Enter>.".blue
173
+ puts "If not, just press <Enter> to continue.".blue
174
+ input = $stdin.gets.strip
175
+ unless input.empty?
176
+ puts "Switching to #{input.gsub("origin/", "")}".blue # TODO: Should we remove origin/ here if the developer types it?
177
+ stream("git #{work_tree_flag} #{git_dir_flag} checkout #{input}")
178
+ end
170
179
 
171
- git_status = `git #{work_tree_flag} #{git_dir_flag} status`
172
- unless git_status.match?("nothing to commit, working tree clean")
173
- puts "This package currently has uncommitted changes.".red
174
- puts "Please make sure the branch is clean and try again.".red
175
- exit
176
- end
180
+ # Link all of the local gems to the current Gemfile.
181
+ puts "Now we'll try to link up the Bullet Train core repositories in the `Gemfile`.".blue
182
+ set_core_gems("--link", framework_packages)
177
183
 
178
- current_branch = `git #{work_tree_flag} #{git_dir_flag} branch`.split("\n").select { |branch_name| branch_name.match?(/^\*\s/) }.pop.gsub(/^\*\s/, "")
179
- unless current_branch == "main"
180
- puts "Previously on #{current_branch}.".blue
181
- puts "Switching local/#{package} to main branch.".blue
182
- stream("git #{work_tree_flag} #{git_dir_flag} checkout main")
183
- end
184
+ puts ""
185
+ puts "Now we'll run `bundle install`.".blue
186
+ stream "bundle install"
184
187
 
185
- puts "Updating the main branch with the latest changes.".blue
186
- stream("git #{work_tree_flag} #{git_dir_flag} pull origin main")
187
- else
188
- # Use https:// URLs when using this task in Gitpod.
189
- stream "git clone #{(`whoami`.chomp == "gitpod") ? "https://github.com/" : "git@github.com:"}#{details[:git]}.git local/#{package}"
190
- end
188
+ puts ""
189
+ puts "We'll restart any running Rails server now.".blue
190
+ stream "rails restart"
191
191
 
192
- stream("git #{work_tree_flag} #{git_dir_flag} fetch")
193
- stream("git #{work_tree_flag} #{git_dir_flag} branch -r")
194
- puts "The above is a list of remote branches.".blue
195
- puts "If there's one you'd like to work on, please enter the branch name and press <Enter>.".blue
196
- puts "If not, just press <Enter> to continue.".blue
197
- input = $stdin.gets.strip
198
- unless input.empty?
199
- puts "Switching to #{input.gsub("origin/", "")}".blue # TODO: Should we remove origin/ here if the developer types it?
200
- stream("git #{work_tree_flag} #{git_dir_flag} checkout #{input}")
201
- end
192
+ puts ""
193
+ puts "OK, we're opening bullet_train-core in your IDE, `#{ENV["IDE"] || "code"}`. (You can configure this with `export IDE=whatever`.)".blue
194
+ `#{ENV["IDE"] || "code"} local/bullet_train-core`
195
+ puts ""
202
196
 
203
- glob = if package == "bullet_train-core"
204
- ", glob: \"#{gem}/#{gem}.gemspec\""
205
- end
197
+ puts "Bullet Train has a few npm packages, so we will and install those now.".blue
198
+ puts "We will also watch for any changes in your JavaScript files and recompile as we go.".blue
199
+ puts "When you're done, you can hit <Control + C> and we'll clean all off this up.".blue
200
+ puts ""
201
+ set_npm_packages("--watch-js", framework_packages)
206
202
 
207
- puts ""
208
- puts "Now we'll try to link up that repository in the `Gemfile`.".blue
209
- if `cat Gemfile | grep "gem \\\"#{gem}\\\", path: \\\"local/#{package}\\\""`.chomp.present?
210
- puts "This gem is already linked to a checked out copy in `local` in the `Gemfile`.".green
211
- elsif `cat Gemfile | grep "gem \\\"#{gem}\\\","`.chomp.present?
212
- puts "This gem already has some sort of alternative source configured in the `Gemfile`.".yellow
213
- puts "We can't do anything with this. Sorry! We'll proceed, but you have to link this package yourself.".red
214
- elsif `cat Gemfile | grep "gem \\\"#{gem}\\\""`.chomp.present?
215
- puts "This gem is directly present in the `Gemfile`, so we'll update that line.".green
216
- text = File.read("Gemfile")
217
- new_contents = text.gsub(/gem "#{gem}"/, "gem \"#{gem}\", path: \"local/#{package}\"#{glob}")
218
- File.open("Gemfile", "w") { |file| file.puts new_contents }
219
- else
220
- puts "This gem isn't directly present in the `Gemfile`, so we'll add it temporarily.".green
221
- File.open("Gemfile", "a+") { |file|
222
- file.puts
223
- file.puts "gem \"#{gem}\", path: \"local/#{package}\"#{glob} # Added by `bin/develop`."
224
- }
203
+ # Clean up the npm packages after the developer enters `Ctrl + C`.
204
+ puts "Cleaning up npm packages...".blue
205
+ puts "If you cancel out of this process early, just run `bin/hack --clean-js` to revert to your original npm packages.".blue
206
+ set_npm_packages("--clean-js", framework_packages)
207
+
208
+ puts ""
209
+ puts "OK, here's a list of things this script still doesn't do you for you:".yellow
210
+ puts "1. It doesn't clean up the repository that was cloned into `local`.".yellow
211
+ puts "2. Unless you remove it, it won't update that repository the next time you link to it.".yellow
212
+ end
213
+
214
+ # Pass "--link" or "--reset" as a flag to set the gems.
215
+ def set_core_gems(flag, framework_packages)
216
+ packages = framework_packages.keys
217
+ gemfile_lines = File.readlines("./Gemfile")
218
+ new_lines = gemfile_lines.map do |line|
219
+ packages.each do |package|
220
+ if line.match?(/"#{package}"/)
221
+ original_path = "gem \"#{package}\""
222
+ local_path = "gem \"#{package}\", path: \"local/bullet_train-core/#{package}\""
223
+
224
+ case flag
225
+ when "--link"
226
+ if `cat Gemfile | grep "gem \\\"#{package}\\\", path: \\\"local/#{package}\\\""`.chomp.present?
227
+ puts "#{package} is already linked to a checked out copy in `local` in the `Gemfile`.".green
228
+ elsif `cat Gemfile | grep "gem \\\"#{package}\\\","`.chomp.present?
229
+ puts "#{package} already has some sort of alternative source configured in the `Gemfile`.".yellow
230
+ puts "We can't do anything with this. Sorry! We'll proceed, but you have to link this package yourself.".red
231
+ elsif `cat Gemfile | grep "gem \\\"#{package}\\\""`.chomp.present?
232
+ puts "#{package} is directly present in the `Gemfile`, so we'll update that line.".green
233
+ line.gsub!(original_path, local_path)
234
+ end
235
+ break
236
+ when "--reset"
237
+ line.gsub!(local_path, original_path)
238
+ puts "Resetting '#{package}' package in the Gemfile...".blue
239
+ break
240
+ end
241
+ end
225
242
  end
243
+ line
244
+ end
226
245
 
227
- puts ""
228
- puts "Now we'll run `bundle install`.".blue
229
- stream "bundle install"
246
+ File.write("./Gemfile", new_lines.join)
247
+ end
230
248
 
231
- puts ""
232
- puts "We'll restart any running Rails server now.".blue
233
- stream "rails restart"
249
+ def set_npm_packages(flag, framework_packages)
250
+ packages = framework_packages.select { |k, v| v[:npm].present? }.compact
234
251
 
235
- puts ""
236
- puts "OK, we're opening that package in your IDE, `#{ENV["IDE"] || "code"}`. (You can configure this with `export IDE=whatever`.)".blue
237
- `#{ENV["IDE"] || "code"} local/#{package}`
252
+ if flag == "--watch-js"
253
+ puts "Make sure your server is running before proceeding. When you're ready, press <Enter>".blue
254
+ $stdin.gets.strip
238
255
 
256
+ puts "Linking npm packages...".blue
239
257
  puts ""
240
- if details[:npm]
241
- puts "This package also has an npm package, so we'll link that up as well.".blue
242
- stream "cd local/#{gem} && yarn install && npm_config_yes=true npx yalc link && cd ../.. && npm_config_yes=true npx yalc link \"#{details[:npm]}\""
243
258
 
259
+ yarn_watch_command = []
260
+ packages.each do |package_name, details|
261
+ puts "Linking JavaScript for #{package_name}".blue
262
+ stream "cd local/bullet_train-core/#{package_name} && yarn install && npm_config_yes=true && npx yalc link && cd ../../.. && npm_config_yes=true npx yalc link \"#{details[:npm]}\""
263
+ puts "#{package_name} has been linked.".blue
244
264
  puts ""
245
- puts "And now we're going to watch for any changes you make to the JavaScript and recompile as we go.".blue
246
- puts "When you're done, you can hit <Control + C> and we'll clean all off this up.".blue
247
- stream "cd local/#{gem} && yarn watch"
248
- else
249
- puts "This package has no npm package, so we'll just hang out here and do nothing. However, when you hit <Enter> here, we'll start the process of cleaning all of this up.".blue
250
- $stdin.gets
265
+ yarn_watch_command << "yarn --cwd local/bullet_train-core/#{package_name} watch"
251
266
  end
252
267
 
268
+ # We use `&` to run the processes in parallel.
269
+ puts "Preparing to watch changes".blue
270
+ stream yarn_watch_command.join(" & ")
271
+ elsif flag == "--clean-js"
272
+ puts "Resetting packages to their original path".blue
253
273
  puts ""
254
- puts "OK, here's a list of things this script still doesn't do you for you:".yellow
255
- puts "1. It doesn't clean up the repository that was cloned into `local`.".yellow
256
- puts "2. Unless you remove it, it won't update that repository the next time you link to it.".yellow
257
- else
258
- puts ""
259
- puts "Invalid option, \"#{number}\". Try again.".red
274
+
275
+ packages.each do |package_name, details|
276
+ system "yarn yalc remove #{details[:npm]}"
277
+ system "yarn add #{details[:npm]}"
278
+ end
260
279
  end
261
280
  end
262
281
  end
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.2.4
4
+ version: 1.2.6
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-12-17 00:00:00.000000000 Z
11
+ date: 2022-12-28 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: standard
@@ -626,6 +626,8 @@ files:
626
626
  - db/migrate/20211020200855_add_doorkeeper_application_to_memberships.rb
627
627
  - db/migrate/20211027002944_add_doorkeeper_application_to_users.rb
628
628
  - docs/action-models.md
629
+ - docs/api.md
630
+ - docs/api/versioning.md
629
631
  - docs/application-options.md
630
632
  - docs/authentication.md
631
633
  - docs/billing/stripe.md
@@ -662,6 +664,7 @@ files:
662
664
  - docs/upgrades.md
663
665
  - docs/webhooks/incoming.md
664
666
  - docs/webhooks/outgoing.md
667
+ - docs/zapier.md
665
668
  - lib/bullet_train.rb
666
669
  - lib/bullet_train/core_ext/string_emoji_helper.rb
667
670
  - lib/bullet_train/engine.rb