cosmit-suspenders 1.36.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +5 -0
- data/.ruby-version +1 -0
- data/.travis.yml +11 -0
- data/CONTRIBUTING.md +51 -0
- data/Gemfile +3 -0
- data/LICENSE +21 -0
- data/NEWS.md +475 -0
- data/README.md +226 -0
- data/RELEASING.md +19 -0
- data/Rakefile +8 -0
- data/bin/rake +16 -0
- data/bin/rspec +16 -0
- data/bin/setup +13 -0
- data/bin/suspenders +18 -0
- data/lib/suspenders/actions.rb +33 -0
- data/lib/suspenders/adapters/heroku.rb +125 -0
- data/lib/suspenders/app_builder.rb +512 -0
- data/lib/suspenders/generators/app_generator.rb +254 -0
- data/lib/suspenders/version.rb +5 -0
- data/lib/suspenders.rb +5 -0
- data/spec/adapters/heroku_spec.rb +52 -0
- data/spec/fakes/bin/heroku +5 -0
- data/spec/fakes/bin/hub +5 -0
- data/spec/features/github_spec.rb +15 -0
- data/spec/features/heroku_spec.rb +93 -0
- data/spec/features/new_project_spec.rb +215 -0
- data/spec/spec_helper.rb +20 -0
- data/spec/support/fake_github.rb +21 -0
- data/spec/support/fake_heroku.rb +53 -0
- data/spec/support/suspenders.rb +58 -0
- data/suspenders.gemspec +37 -0
- data/templates/Gemfile.erb +63 -0
- data/templates/Procfile +1 -0
- data/templates/README.md.erb +64 -0
- data/templates/_flash.html.slim +6 -0
- data/templates/_javascript.html.slim +29 -0
- data/templates/action_mailer.rb +5 -0
- data/templates/app.json.erb +40 -0
- data/templates/application.scss +6 -0
- data/templates/bin_deploy +12 -0
- data/templates/bin_setup +21 -0
- data/templates/bin_setup_review_app.erb +19 -0
- data/templates/browserslist +4 -0
- data/templates/bundler_audit.rake +12 -0
- data/templates/capybara_webkit.rb +5 -0
- data/templates/circle.yml.erb +6 -0
- data/templates/config_locales_en.yml.erb +19 -0
- data/templates/database_cleaner_rspec.rb +21 -0
- data/templates/dev.rake +12 -0
- data/templates/disable_xml_params.rb +1 -0
- data/templates/dotfiles/.ctags +2 -0
- data/templates/dotfiles/.env +13 -0
- data/templates/errors.rb +34 -0
- data/templates/factories.rb +2 -0
- data/templates/factory_girl_rspec.rb +3 -0
- data/templates/hound.yml +14 -0
- data/templates/i18n.rb +3 -0
- data/templates/json_encoding.rb +1 -0
- data/templates/newrelic.yml.erb +34 -0
- data/templates/postgresql_database.yml.erb +21 -0
- data/templates/rack_mini_profiler.rb +5 -0
- data/templates/rails_helper.rb +22 -0
- data/templates/secrets.yml +15 -0
- data/templates/shoulda_matchers_config_rspec.rb +6 -0
- data/templates/smtp.rb +9 -0
- data/templates/spec_helper.rb +29 -0
- data/templates/staging.rb +5 -0
- data/templates/suspenders_gitignore +15 -0
- data/templates/suspenders_layout.html.slim.erb +24 -0
- metadata +213 -0
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
require 'bundler/setup'
|
2
|
+
|
3
|
+
Bundler.require(:default, :test)
|
4
|
+
|
5
|
+
require (Pathname.new(__FILE__).dirname + '../lib/suspenders').expand_path
|
6
|
+
|
7
|
+
Dir['./spec/support/**/*.rb'].each { |file| require file }
|
8
|
+
|
9
|
+
RSpec.configure do |config|
|
10
|
+
config.include SuspendersTestHelpers
|
11
|
+
|
12
|
+
config.before(:all) do
|
13
|
+
add_fakes_to_path
|
14
|
+
create_tmp_directory
|
15
|
+
end
|
16
|
+
|
17
|
+
config.before(:each) do
|
18
|
+
FakeGithub.clear!
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
class FakeGithub
|
2
|
+
RECORDER = File.expand_path(File.join('..', '..', 'tmp', 'hub_commands'), File.dirname(__FILE__))
|
3
|
+
|
4
|
+
def initialize(args)
|
5
|
+
@args = args
|
6
|
+
end
|
7
|
+
|
8
|
+
def run!
|
9
|
+
File.open(RECORDER, 'a') do |file|
|
10
|
+
file.write @args.join(' ')
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
def self.clear!
|
15
|
+
FileUtils.rm_rf RECORDER
|
16
|
+
end
|
17
|
+
|
18
|
+
def self.has_created_repo?(repo_name)
|
19
|
+
File.read(RECORDER) == "create #{repo_name}"
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,53 @@
|
|
1
|
+
class FakeHeroku
|
2
|
+
RECORDER = File.expand_path(File.join('..', '..', 'tmp', 'heroku_commands'), File.dirname(__FILE__))
|
3
|
+
|
4
|
+
def initialize(args)
|
5
|
+
@args = args
|
6
|
+
end
|
7
|
+
|
8
|
+
def run!
|
9
|
+
if @args.first == "plugins"
|
10
|
+
puts "heroku-pipelines@0.29.0"
|
11
|
+
end
|
12
|
+
File.open(RECORDER, 'a') do |file|
|
13
|
+
file.puts @args.join(' ')
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
def self.clear!
|
18
|
+
FileUtils.rm_rf RECORDER
|
19
|
+
end
|
20
|
+
|
21
|
+
def self.has_gem_included?(project_path, gem_name)
|
22
|
+
gemfile = File.open(File.join(project_path, 'Gemfile'), 'a')
|
23
|
+
|
24
|
+
File.foreach(gemfile).any? do |line|
|
25
|
+
line.match(/#{Regexp.quote(gem_name)}/)
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
def self.has_created_app_for?(environment, flags = nil)
|
30
|
+
app_name = "#{SuspendersTestHelpers::APP_NAME.dasherize}-#{environment}"
|
31
|
+
|
32
|
+
command = if flags
|
33
|
+
"create #{app_name} #{flags} --remote #{environment}\n"
|
34
|
+
else
|
35
|
+
"create #{app_name} --remote #{environment}\n"
|
36
|
+
end
|
37
|
+
|
38
|
+
File.foreach(RECORDER).any? { |line| line == command }
|
39
|
+
end
|
40
|
+
|
41
|
+
def self.has_configured_vars?(remote_name, var)
|
42
|
+
commands_ran =~ /^config:add #{var}=.+ --remote #{remote_name}\n/
|
43
|
+
end
|
44
|
+
|
45
|
+
def self.has_setup_pipeline_for?(app_name)
|
46
|
+
commands_ran =~ /^pipelines:create #{app_name} -a #{app_name}-staging --stage staging/ &&
|
47
|
+
commands_ran =~ /^pipelines:add #{app_name} -a #{app_name}-production --stage production/
|
48
|
+
end
|
49
|
+
|
50
|
+
def self.commands_ran
|
51
|
+
@commands_ran ||= File.read(RECORDER)
|
52
|
+
end
|
53
|
+
end
|
@@ -0,0 +1,58 @@
|
|
1
|
+
module SuspendersTestHelpers
|
2
|
+
APP_NAME = "dummy_app"
|
3
|
+
|
4
|
+
def remove_project_directory
|
5
|
+
FileUtils.rm_rf(project_path)
|
6
|
+
end
|
7
|
+
|
8
|
+
def create_tmp_directory
|
9
|
+
FileUtils.mkdir_p(tmp_path)
|
10
|
+
end
|
11
|
+
|
12
|
+
def run_suspenders(arguments = nil)
|
13
|
+
Dir.chdir(tmp_path) do
|
14
|
+
Bundler.with_clean_env do
|
15
|
+
add_fakes_to_path
|
16
|
+
`
|
17
|
+
#{suspenders_bin} #{APP_NAME} #{arguments}
|
18
|
+
`
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
def drop_dummy_database
|
24
|
+
if File.exist?(project_path)
|
25
|
+
Dir.chdir(project_path) do
|
26
|
+
Bundler.with_clean_env do
|
27
|
+
`rake db:drop`
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
def add_fakes_to_path
|
34
|
+
ENV["PATH"] = "#{support_bin}:#{ENV['PATH']}"
|
35
|
+
end
|
36
|
+
|
37
|
+
def project_path
|
38
|
+
@project_path ||= Pathname.new("#{tmp_path}/#{APP_NAME}")
|
39
|
+
end
|
40
|
+
|
41
|
+
private
|
42
|
+
|
43
|
+
def tmp_path
|
44
|
+
@tmp_path ||= Pathname.new("#{root_path}/tmp")
|
45
|
+
end
|
46
|
+
|
47
|
+
def suspenders_bin
|
48
|
+
File.join(root_path, 'bin', 'suspenders')
|
49
|
+
end
|
50
|
+
|
51
|
+
def support_bin
|
52
|
+
File.join(root_path, "spec", "fakes", "bin")
|
53
|
+
end
|
54
|
+
|
55
|
+
def root_path
|
56
|
+
File.expand_path('../../../', __FILE__)
|
57
|
+
end
|
58
|
+
end
|
data/suspenders.gemspec
ADDED
@@ -0,0 +1,37 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
$:.push File.expand_path('../lib', __FILE__)
|
3
|
+
require 'suspenders/version'
|
4
|
+
require 'date'
|
5
|
+
|
6
|
+
Gem::Specification.new do |s|
|
7
|
+
s.required_ruby_version = ">= #{Suspenders::RUBY_VERSION}"
|
8
|
+
s.authors = ['COSMIT']
|
9
|
+
s.date = Date.today.strftime('%Y-%m-%d')
|
10
|
+
|
11
|
+
s.description = <<-HERE
|
12
|
+
Suspenders is a base Rails project that you can upgrade. It is used by
|
13
|
+
cosmit to get a jump start on a working app. Use Suspenders if you're in a
|
14
|
+
rush to build something amazing; don't use it if you like missing deadlines.
|
15
|
+
HERE
|
16
|
+
|
17
|
+
s.email = 'contato@cosmit.com.br'
|
18
|
+
s.executables = ['suspenders']
|
19
|
+
s.extra_rdoc_files = %w[README.md LICENSE]
|
20
|
+
s.files = `git ls-files`.split("\n")
|
21
|
+
s.homepage = 'https://github.com/COSMITdev/suspenders'
|
22
|
+
s.license = 'MIT'
|
23
|
+
s.name = 'cosmit-suspenders'
|
24
|
+
s.rdoc_options = ['--charset=UTF-8']
|
25
|
+
s.require_paths = ['lib']
|
26
|
+
s.summary = "Generate a Rails app using cosmit's best practices."
|
27
|
+
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
28
|
+
s.version = Suspenders::VERSION
|
29
|
+
|
30
|
+
s.add_dependency 'bundler', '~> 1.3'
|
31
|
+
s.add_dependency 'rails', Suspenders::RAILS_VERSION
|
32
|
+
|
33
|
+
s.add_development_dependency 'rspec', '~> 3.2'
|
34
|
+
s.add_development_dependency 'simple_form', '~> 3.2'
|
35
|
+
s.add_development_dependency 'quiet_assets', '~> 1.1'
|
36
|
+
s.add_development_dependency 'capybara-webkit', '~> 1.8'
|
37
|
+
end
|
@@ -0,0 +1,63 @@
|
|
1
|
+
source "https://rubygems.org"
|
2
|
+
|
3
|
+
ruby "<%= Suspenders::RUBY_VERSION %>"
|
4
|
+
|
5
|
+
gem "autoprefixer-rails"
|
6
|
+
gem "delayed_job_active_record"
|
7
|
+
gem "jquery-rails"
|
8
|
+
gem "normalize-rails", "~> 3.0.0"
|
9
|
+
gem "pg"
|
10
|
+
gem "rails", "<%= Suspenders::RAILS_VERSION %>"
|
11
|
+
gem "rails-i18n", "~> 4.0.4"
|
12
|
+
gem "recipient_interceptor"
|
13
|
+
gem "sass-rails", "~> 5.0"
|
14
|
+
gem "simple_form"
|
15
|
+
gem "initjs"
|
16
|
+
gem "uglifier"
|
17
|
+
gem "devise"
|
18
|
+
gem "coffee-rails"
|
19
|
+
gem "slim-rails", "~> 3.0.1"
|
20
|
+
gem "activeadmin", github: "activeadmin"
|
21
|
+
gem "bootstrap-sass"
|
22
|
+
gem "font-awesome-rails"
|
23
|
+
|
24
|
+
group :development do
|
25
|
+
gem "quiet_assets"
|
26
|
+
gem "spring"
|
27
|
+
gem "spring-commands-rspec"
|
28
|
+
end
|
29
|
+
|
30
|
+
group :development, :test do
|
31
|
+
gem "awesome_print"
|
32
|
+
gem "bullet"
|
33
|
+
gem "bundler-audit", ">= 0.5.0", require: false
|
34
|
+
gem "dotenv-rails"
|
35
|
+
gem "factory_girl_rails"
|
36
|
+
gem "pry-rails"
|
37
|
+
gem "rspec-rails", "~> 3.4.0"
|
38
|
+
end
|
39
|
+
|
40
|
+
group :development, :staging do
|
41
|
+
gem "rack-mini-profiler", require: false
|
42
|
+
gem "thin"
|
43
|
+
end
|
44
|
+
|
45
|
+
group :test do
|
46
|
+
gem "capybara-webkit"
|
47
|
+
gem "database_cleaner"
|
48
|
+
gem "formulaic"
|
49
|
+
gem "launchy"
|
50
|
+
gem "shoulda-matchers"
|
51
|
+
gem "simplecov", require: false
|
52
|
+
gem "timecop"
|
53
|
+
gem "webmock"
|
54
|
+
end
|
55
|
+
|
56
|
+
group :staging, :production do
|
57
|
+
gem "rack-timeout"
|
58
|
+
end
|
59
|
+
|
60
|
+
group :production do
|
61
|
+
gem "passenger", "~> 5.0"
|
62
|
+
gem "rails_12factor"
|
63
|
+
end
|
data/templates/Procfile
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
web: bundle exec passenger start -p $PORT --no-friendly-error-pages --max-pool-size 3
|
@@ -0,0 +1,64 @@
|
|
1
|
+
# <%= app_name.humanize %>
|
2
|
+
|
3
|
+
* Main branch: master
|
4
|
+
* Ruby version: "<%= Suspenders::RUBY_VERSION %>"
|
5
|
+
* Rails version: "<%= Suspenders::RAILS_VERSION %>"
|
6
|
+
* PG version: ~> 0.17.1
|
7
|
+
|
8
|
+
## Installation / Getting Started
|
9
|
+
|
10
|
+
To install (development environment) on your machine, just follow the tips above:
|
11
|
+
|
12
|
+
% git clone git@github.com:cosmitdev/<%= app_name.dasherize %>.git
|
13
|
+
|
14
|
+
After you have cloned this repo, run this setup script to set up your machine
|
15
|
+
with the necessary dependencies to run and test this app:
|
16
|
+
|
17
|
+
% ./bin/setup
|
18
|
+
|
19
|
+
It assumes you have a machine equipped with Ruby, Postgres, etc. If not, set up
|
20
|
+
your machine with [this script].
|
21
|
+
|
22
|
+
[this script]: https://github.com/COSMITdev/env-setup
|
23
|
+
|
24
|
+
After setting up, you can run the application using [Heroku Local]:
|
25
|
+
|
26
|
+
% heroku local
|
27
|
+
|
28
|
+
[Heroku Local]: https://devcenter.heroku.com/articles/heroku-local
|
29
|
+
|
30
|
+
## Running Specs
|
31
|
+
|
32
|
+
We use [Capybara Webkit](https://github.com/thoughtbot/capybara-webkit) for
|
33
|
+
full-stack JavaScript integration testing. It requires QT. Instructions for
|
34
|
+
installing QT are [here](https://github.com/thoughtbot/capybara-webkit/wiki/Installing-Qt-and-compiling-capybara-webkit).
|
35
|
+
|
36
|
+
* **Create Test DB and run migrations**
|
37
|
+
|
38
|
+
```bin/rake db:create db:migrate RAILS_ENV=test```
|
39
|
+
|
40
|
+
* **Run Specs**
|
41
|
+
|
42
|
+
```bundle exec rspec .```
|
43
|
+
|
44
|
+
## Creating feature branches
|
45
|
+
|
46
|
+
In all projects we work with `feature branches`. It's a good way to controll who are doing what and to improve quality of code, once you need to up a PR with that branch after.
|
47
|
+
|
48
|
+
### Create the branch
|
49
|
+
|
50
|
+
The nomenclature of the feature branch is composite by `{name initials}-{feature name || description}`, and probably will be something like that: `pm-review-typo` or `pm-create-users`.
|
51
|
+
|
52
|
+
Also, always keep you branch up-to-date with master, and keep master updated too. To do this, always run `git checkout master && git pull origin master`
|
53
|
+
|
54
|
+
Now, to create the feature branch just run `git checkout master && git checkout -b
|
55
|
+
[name-of-branch]`.
|
56
|
+
|
57
|
+
## Openning a Pull Request
|
58
|
+
|
59
|
+
After you finish the implementations what you did on your branch, you can up this to Github and open a Pull Request. This way other persons of the project can available your things and propose improvements. Just create the PR when you have confidence you create everything you need to, like views, controllers, specs...
|
60
|
+
|
61
|
+
## Finishing/Delivering/Updating Trello
|
62
|
+
|
63
|
+
For now we are using Trello to organize the features and sprints of the project, so, just delivery a task when you finish this and up the PR. Anyone can update cards on Trello, so be confident to do it yourself when you feel that your feature is ready to launch.
|
64
|
+
https://trello.com/b/xxxxx/example
|
@@ -0,0 +1,29 @@
|
|
1
|
+
= javascript_include_tag 'application'
|
2
|
+
|
3
|
+
- if Rails.env.production && ENV["SEGMENT_KEY"]
|
4
|
+
javascript:
|
5
|
+
window.analytics=window.analytics||[],window.analytics.methods=["identify","group","track","page","pageview","alias","ready","on","once","off","trackLink","trackForm","trackClick","trackSubmit"],window.analytics.factory=function(t){return function(){var a=Array.prototype.slice.call(arguments);return a.unshift(t),window.analytics.push(a),window.analytics}};for(var i=0;i<window.analytics.methods.length;i++){var key=window.analytics.methods[i];window.analytics[key]=window.analytics.factory(key)}window.analytics.load=function(t){if(!document.getElementById("analytics-js")){var a=document.createElement("script");a.type="text/javascript",a.id="analytics-js",a.async=!0,a.src=("https:"===document.location.protocol?"https://":"http://")+"cdn.segment.com/analytics.js/v1/"+t+"/analytics.min.js";var n=document.getElementsByTagName("script")[0];n.parentNode.insertBefore(a,n)}},window.analytics.SNIPPET_VERSION="2.0.9",
|
6
|
+
window.analytics.load("<%= ENV["SEGMENT_KEY"] %>");
|
7
|
+
window.analytics.page();
|
8
|
+
|
9
|
+
- if Rails.env.production && ENV["GOOGLE_ANALYTICS"]
|
10
|
+
javascript:
|
11
|
+
(function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){
|
12
|
+
(i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),
|
13
|
+
m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)
|
14
|
+
})(window,document,'script','//www.google-analytics.com/analytics.js','ga');
|
15
|
+
|
16
|
+
ga('create', "<%= ENV["GOOGLE_ANALYTICS"] %>", 'auto');
|
17
|
+
ga('send', 'pageview');
|
18
|
+
|
19
|
+
javascript:
|
20
|
+
WebFontConfig = { google: { families: [ 'Open+Sans:400,400italic,600,700:latin' ] } };
|
21
|
+
(function() {
|
22
|
+
var wf = document.createElement('script');
|
23
|
+
wf.src = ('https:' == document.location.protocol ? 'https' : 'http') +
|
24
|
+
'://ajax.googleapis.com/ajax/libs/webfont/1/webfont.js';
|
25
|
+
wf.type = 'text/javascript';
|
26
|
+
wf.async = 'true';
|
27
|
+
var s = document.getElementsByTagName('script')[0];
|
28
|
+
s.parentNode.insertBefore(wf, s);
|
29
|
+
})();
|
@@ -0,0 +1,40 @@
|
|
1
|
+
{
|
2
|
+
"name":"<%= app_name.dasherize %>",
|
3
|
+
"scripts":{},
|
4
|
+
"env":{
|
5
|
+
"APPLICATION_HOST":{
|
6
|
+
"required":true
|
7
|
+
},
|
8
|
+
"EMAIL_RECIPIENTS":{
|
9
|
+
"required":true
|
10
|
+
},
|
11
|
+
"HEROKU_APP_NAME": {
|
12
|
+
"required":true
|
13
|
+
},
|
14
|
+
"HEROKU_PARENT_APP_NAME": {
|
15
|
+
"required":true
|
16
|
+
},
|
17
|
+
"RACK_ENV":{
|
18
|
+
"required":true
|
19
|
+
},
|
20
|
+
"SECRET_KEY_BASE":{
|
21
|
+
"generator":"secret"
|
22
|
+
},
|
23
|
+
"SMTP_ADDRESS":{
|
24
|
+
"required":true
|
25
|
+
},
|
26
|
+
"SMTP_DOMAIN":{
|
27
|
+
"required":true
|
28
|
+
},
|
29
|
+
"SMTP_PASSWORD":{
|
30
|
+
"required":true
|
31
|
+
},
|
32
|
+
"SMTP_USERNAME":{
|
33
|
+
"required":true
|
34
|
+
}
|
35
|
+
},
|
36
|
+
"addons":[
|
37
|
+
"heroku-postgresql",
|
38
|
+
"sendgrid"
|
39
|
+
]
|
40
|
+
}
|
@@ -0,0 +1,12 @@
|
|
1
|
+
#!/bin/sh
|
2
|
+
|
3
|
+
# Run this script to deploy the app to Heroku.
|
4
|
+
|
5
|
+
set -e
|
6
|
+
|
7
|
+
branch="$(git symbolic-ref HEAD --short)"
|
8
|
+
target="${1:-staging}"
|
9
|
+
|
10
|
+
git push "$target" "$branch:master"
|
11
|
+
heroku run rake db:migrate --remote "$target"
|
12
|
+
heroku restart --remote "$target"
|
data/templates/bin_setup
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
#!/bin/sh
|
2
|
+
|
3
|
+
# Set up Rails app. Run this script immediately after cloning the codebase.
|
4
|
+
# https://github.com/thoughtbot/guides/tree/master/protocol
|
5
|
+
|
6
|
+
# Exit if any subcommand fails
|
7
|
+
set -e
|
8
|
+
|
9
|
+
# Set up Ruby dependencies via Bundler
|
10
|
+
gem install bundler --conservative
|
11
|
+
bundle check || bundle install
|
12
|
+
|
13
|
+
# Set up database and add any development seed data
|
14
|
+
bin/rake dev:prime
|
15
|
+
|
16
|
+
# Add binstubs to PATH via export PATH=".git/safe/../../bin:$PATH" in ~/.zshenv
|
17
|
+
mkdir -p .git/safe
|
18
|
+
|
19
|
+
# Only if this isn't CI
|
20
|
+
# if [ -z "$CI" ]; then
|
21
|
+
# fi
|