env_lint 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/.gitignore +17 -0
- data/.travis.yml +6 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +22 -0
- data/README.md +104 -0
- data/Rakefile +1 -0
- data/env_lint.gemspec +26 -0
- data/lib/env_lint/capistrano.rb +51 -0
- data/lib/env_lint/dot_env_file.rb +48 -0
- data/lib/env_lint/dot_env_parser.rb +39 -0
- data/lib/env_lint/env_key_parser.rb +11 -0
- data/lib/env_lint/errors.rb +62 -0
- data/lib/env_lint/formatter.rb +49 -0
- data/lib/env_lint/linted_env.rb +29 -0
- data/lib/env_lint/tasks.rb +21 -0
- data/lib/env_lint/variable.rb +5 -0
- data/lib/env_lint/version.rb +3 -0
- data/lib/env_lint.rb +24 -0
- data/spec/env_lint/capistrano_spec.rb +94 -0
- data/spec/env_lint/dot_env_file_spec.rb +73 -0
- data/spec/env_lint/dot_env_parser_spec.rb +105 -0
- data/spec/env_lint/env_key_parser_spec.rb +36 -0
- data/spec/env_lint/linted_env_spec.rb +85 -0
- data/spec/integration_spec.rb +109 -0
- data/spec/spec_helper.rb +5 -0
- metadata +146 -0
    
        checksums.yaml
    ADDED
    
    | @@ -0,0 +1,7 @@ | |
| 1 | 
            +
            ---
         | 
| 2 | 
            +
            SHA1:
         | 
| 3 | 
            +
              metadata.gz: 7074acbb267701af05f8e2f5307fb5d8f563a1dc
         | 
| 4 | 
            +
              data.tar.gz: 1f07d11602f9e8230159f83a0719b3cfdb503047
         | 
| 5 | 
            +
            SHA512:
         | 
| 6 | 
            +
              metadata.gz: f1895359dbf17fca012f7ed52ff250ba0ea60b0edfd493691aabbf7b424f4d209d4b8d07c747a9b91e856f6e30f8b478c6fc07462f1592e6ba0a5c720226ae32
         | 
| 7 | 
            +
              data.tar.gz: 564fa51150c32279cfcdc4fc965dd0e6b72c1c90d75b9c8ec33fe516394cb8c26fce7034c39d86446c24830275d624689ca4b6caf9ed4c9253df15b2add49573
         | 
    
        data/.gitignore
    ADDED
    
    
    
        data/.travis.yml
    ADDED
    
    
    
        data/Gemfile
    ADDED
    
    
    
        data/LICENSE.txt
    ADDED
    
    | @@ -0,0 +1,22 @@ | |
| 1 | 
            +
            Copyright (c) 2014 Tim Fischbach
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            MIT License
         | 
| 4 | 
            +
             | 
| 5 | 
            +
            Permission is hereby granted, free of charge, to any person obtaining
         | 
| 6 | 
            +
            a copy of this software and associated documentation files (the
         | 
| 7 | 
            +
            "Software"), to deal in the Software without restriction, including
         | 
| 8 | 
            +
            without limitation the rights to use, copy, modify, merge, publish,
         | 
| 9 | 
            +
            distribute, sublicense, and/or sell copies of the Software, and to
         | 
| 10 | 
            +
            permit persons to whom the Software is furnished to do so, subject to
         | 
| 11 | 
            +
            the following conditions:
         | 
| 12 | 
            +
             | 
| 13 | 
            +
            The above copyright notice and this permission notice shall be
         | 
| 14 | 
            +
            included in all copies or substantial portions of the Software.
         | 
| 15 | 
            +
             | 
| 16 | 
            +
            THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
         | 
| 17 | 
            +
            EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
         | 
| 18 | 
            +
            MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
         | 
| 19 | 
            +
            NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
         | 
| 20 | 
            +
            LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
         | 
| 21 | 
            +
            OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
         | 
| 22 | 
            +
            WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
         | 
    
        data/README.md
    ADDED
    
    | @@ -0,0 +1,104 @@ | |
| 1 | 
            +
            # Env Lint
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            <img src="https://travis-ci.org/tf/env_lint.png" data-bindattr-466="466" title="Build Status Images">
         | 
| 4 | 
            +
             | 
| 5 | 
            +
            Check environment variables accoring to a `.env.example` file.
         | 
| 6 | 
            +
             | 
| 7 | 
            +
            * Avoid spelling errors in variable names in your code or on the
         | 
| 8 | 
            +
              command line
         | 
| 9 | 
            +
            * Ensure all relevant environment variables are described in the
         | 
| 10 | 
            +
              `.env.example` file.
         | 
| 11 | 
            +
            * Ensure all required environment variables are configured before
         | 
| 12 | 
            +
              deploying a new version of an app
         | 
| 13 | 
            +
            * Ease setting up a new development machine
         | 
| 14 | 
            +
             | 
| 15 | 
            +
            # Status
         | 
| 16 | 
            +
             | 
| 17 | 
            +
            Only tested with [recap](https://github.com/tomafro/recap) capistrano
         | 
| 18 | 
            +
            tasks.
         | 
| 19 | 
            +
             | 
| 20 | 
            +
            ## Installation
         | 
| 21 | 
            +
             | 
| 22 | 
            +
            Add this line to your application's Gemfile:
         | 
| 23 | 
            +
             | 
| 24 | 
            +
                gem 'env_lint'
         | 
| 25 | 
            +
             | 
| 26 | 
            +
            ## Usage
         | 
| 27 | 
            +
             | 
| 28 | 
            +
            Define a `.env.example` file:
         | 
| 29 | 
            +
             | 
| 30 | 
            +
                # Explain each variable in comments like this one
         | 
| 31 | 
            +
                APP_NAME=my_app
         | 
| 32 | 
            +
                
         | 
| 33 | 
            +
                # Comments are also recognized if they span multiple
         | 
| 34 | 
            +
                # lines
         | 
| 35 | 
            +
                FEATURE=true
         | 
| 36 | 
            +
                
         | 
| 37 | 
            +
                # Optional variables
         | 
| 38 | 
            +
                # OPTIONAL_VAR="set me if you like"
         | 
| 39 | 
            +
             | 
| 40 | 
            +
            ### Rake Task
         | 
| 41 | 
            +
             | 
| 42 | 
            +
            Require it in your `Rakefile`:
         | 
| 43 | 
            +
             | 
| 44 | 
            +
                require 'env_lint/tasks'
         | 
| 45 | 
            +
             | 
| 46 | 
            +
            Now you can check your environment:
         | 
| 47 | 
            +
             | 
| 48 | 
            +
                $ rake env:lint
         | 
| 49 | 
            +
                => Complains if non optional variables are missing
         | 
| 50 | 
            +
             | 
| 51 | 
            +
            If special steps are needed 
         | 
| 52 | 
            +
             | 
| 53 | 
            +
            ### Capistrano Task
         | 
| 54 | 
            +
             | 
| 55 | 
            +
            Require it in your `Capfile`:
         | 
| 56 | 
            +
             | 
| 57 | 
            +
                require 'env_lint/capistrano'
         | 
| 58 | 
            +
             | 
| 59 | 
            +
            Now you can check your servers:
         | 
| 60 | 
            +
             | 
| 61 | 
            +
                $ cap env:lint
         | 
| 62 | 
            +
                => Complains if non optional variables are missing
         | 
| 63 | 
            +
             | 
| 64 | 
            +
            You might want to lint the environment automatically before each
         | 
| 65 | 
            +
            deploy.
         | 
| 66 | 
            +
             | 
| 67 | 
            +
                before 'deploy', 'env:lint'
         | 
| 68 | 
            +
             | 
| 69 | 
            +
            Lint variable names before setting them:
         | 
| 70 | 
            +
             | 
| 71 | 
            +
                before 'env:set', 'env:lint_args'
         | 
| 72 | 
            +
                
         | 
| 73 | 
            +
                $ cap env:set APP_NAME=myapp
         | 
| 74 | 
            +
                => Complains if APP_NAME is defined
         | 
| 75 | 
            +
             | 
| 76 | 
            +
            ### Lint at Runtime
         | 
| 77 | 
            +
             | 
| 78 | 
            +
            Access ENV through a `LintedEnv`:
         | 
| 79 | 
            +
             | 
| 80 | 
            +
                require 'env_lint'
         | 
| 81 | 
            +
             | 
| 82 | 
            +
                class MyApp
         | 
| 83 | 
            +
                  LINTED_ENV = EnvLint::LintedEnv.from_file('.env.example')
         | 
| 84 | 
            +
                  
         | 
| 85 | 
            +
                  def self.env
         | 
| 86 | 
            +
                    LINTED_ENV
         | 
| 87 | 
            +
                  end
         | 
| 88 | 
            +
                end
         | 
| 89 | 
            +
             | 
| 90 | 
            +
            Accessing env variables:
         | 
| 91 | 
            +
             | 
| 92 | 
            +
                # Ensures APP_NAME is defined in .env.example
         | 
| 93 | 
            +
                MyApp.env.fetch(:app_name, 'App name')
         | 
| 94 | 
            +
             | 
| 95 | 
            +
                # Ensures APP_NAME is non optional in .env.example
         | 
| 96 | 
            +
                MyApp.env.fetch(:app_name)
         | 
| 97 | 
            +
             | 
| 98 | 
            +
            ## Contributing
         | 
| 99 | 
            +
             | 
| 100 | 
            +
            1. Fork it
         | 
| 101 | 
            +
            2. Create your feature branch (`git checkout -b my-new-feature`)
         | 
| 102 | 
            +
            3. Commit your changes (`git commit -am 'Add some feature'`)
         | 
| 103 | 
            +
            4. Push to the branch (`git push origin my-new-feature`)
         | 
| 104 | 
            +
            5. Create new Pull Request
         | 
    
        data/Rakefile
    ADDED
    
    | @@ -0,0 +1 @@ | |
| 1 | 
            +
            require "bundler/gem_tasks"
         | 
    
        data/env_lint.gemspec
    ADDED
    
    | @@ -0,0 +1,26 @@ | |
| 1 | 
            +
            # coding: utf-8
         | 
| 2 | 
            +
            lib = File.expand_path('../lib', __FILE__)
         | 
| 3 | 
            +
            $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
         | 
| 4 | 
            +
            require 'env_lint/version'
         | 
| 5 | 
            +
             | 
| 6 | 
            +
            Gem::Specification.new do |spec|
         | 
| 7 | 
            +
              spec.name          = 'env_lint'
         | 
| 8 | 
            +
              spec.version       = EnvLint::VERSION
         | 
| 9 | 
            +
              spec.authors       = ['Tim Fischbach']
         | 
| 10 | 
            +
              spec.email         = ['mail@timfischbach.de']
         | 
| 11 | 
            +
              spec.summary       = 'Lint the environment according by .env.example'
         | 
| 12 | 
            +
              spec.homepage      = ''
         | 
| 13 | 
            +
              spec.license       = 'MIT'
         | 
| 14 | 
            +
             | 
| 15 | 
            +
              spec.files         = `git ls-files`.split($/)
         | 
| 16 | 
            +
              spec.executables   = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
         | 
| 17 | 
            +
              spec.test_files    = spec.files.grep(%r{^(test|spec|features)/})
         | 
| 18 | 
            +
              spec.require_paths = ['lib']
         | 
| 19 | 
            +
             | 
| 20 | 
            +
              spec.add_runtime_dependency 'formatador'
         | 
| 21 | 
            +
              spec.add_runtime_dependency 'capistrano', '~> 2.9'
         | 
| 22 | 
            +
             | 
| 23 | 
            +
              spec.add_development_dependency 'rspec', '3.0.0.beta1'
         | 
| 24 | 
            +
              spec.add_development_dependency 'bundler', '~> 1.3'
         | 
| 25 | 
            +
              spec.add_development_dependency 'rake'
         | 
| 26 | 
            +
            end
         | 
| @@ -0,0 +1,51 @@ | |
| 1 | 
            +
            require 'env_lint'
         | 
| 2 | 
            +
            require 'capistrano'
         | 
| 3 | 
            +
             | 
| 4 | 
            +
            module EnvLint
         | 
| 5 | 
            +
              module Capistrano
         | 
| 6 | 
            +
                def self.load_into(config, formatter)
         | 
| 7 | 
            +
                  config.load do
         | 
| 8 | 
            +
                    set(:env_definition_file) { '.env.example' }
         | 
| 9 | 
            +
                    set(:env_probe_command) { "su - #{application_user} -c 'export'" }
         | 
| 10 | 
            +
             | 
| 11 | 
            +
                    namespace :env do
         | 
| 12 | 
            +
                      desc 'Check that every non optional ENV variable is defined.'
         | 
| 13 | 
            +
                      task :lint do
         | 
| 14 | 
            +
                        begin
         | 
| 15 | 
            +
                          EnvLint.verify_export_output(env_definition_file, capture(env_probe_command, via: :sudo))
         | 
| 16 | 
            +
                          formatter.ok('env looks ok')
         | 
| 17 | 
            +
                        rescue EnvLint::MissingVariables => e
         | 
| 18 | 
            +
                          formatter.missing_variables(e.dot_env_file, e.missing_variables)
         | 
| 19 | 
            +
                          abort
         | 
| 20 | 
            +
                        rescue EnvLint::Error => e
         | 
| 21 | 
            +
                          formatter.error(e.message)
         | 
| 22 | 
            +
                          abort
         | 
| 23 | 
            +
                        end
         | 
| 24 | 
            +
                      end
         | 
| 25 | 
            +
             | 
| 26 | 
            +
                      desc 'Lint args passed to command.'
         | 
| 27 | 
            +
                      task :lint_args do
         | 
| 28 | 
            +
                        begin
         | 
| 29 | 
            +
                          EnvLint.verify_args(env_definition_file, env_args)
         | 
| 30 | 
            +
                        rescue EnvLint::UnknownVariables => e
         | 
| 31 | 
            +
                          formatter.unknown_variables(e.dot_env_file, e.unknown_variables)
         | 
| 32 | 
            +
                          abort
         | 
| 33 | 
            +
                        rescue EnvLint::Error => e
         | 
| 34 | 
            +
                          formatter.error(e.message)
         | 
| 35 | 
            +
                          abort
         | 
| 36 | 
            +
                        end
         | 
| 37 | 
            +
                      end
         | 
| 38 | 
            +
             | 
| 39 | 
            +
                      def env_args
         | 
| 40 | 
            +
                        ARGV[1..-1]
         | 
| 41 | 
            +
                      end
         | 
| 42 | 
            +
                    end
         | 
| 43 | 
            +
                  end
         | 
| 44 | 
            +
                end
         | 
| 45 | 
            +
              end
         | 
| 46 | 
            +
            end
         | 
| 47 | 
            +
             | 
| 48 | 
            +
            if Capistrano::Configuration.instance
         | 
| 49 | 
            +
              EnvLint::Capistrano.load_into(Capistrano::Configuration.instance(:must_exist),
         | 
| 50 | 
            +
                                            Formatter.new)
         | 
| 51 | 
            +
            end
         | 
| @@ -0,0 +1,48 @@ | |
| 1 | 
            +
            module EnvLint
         | 
| 2 | 
            +
              class DotEnvFile
         | 
| 3 | 
            +
                attr_reader :name, :variables
         | 
| 4 | 
            +
             | 
| 5 | 
            +
                def initialize(name, variables)
         | 
| 6 | 
            +
                  @name = name
         | 
| 7 | 
            +
                  @variables = variables
         | 
| 8 | 
            +
             | 
| 9 | 
            +
                  @variables_by_name = variables.each_with_object({}) do |variable, hash|
         | 
| 10 | 
            +
                    hash[variable.name] = variable
         | 
| 11 | 
            +
                  end
         | 
| 12 | 
            +
                end
         | 
| 13 | 
            +
             | 
| 14 | 
            +
                def find_variable(name)
         | 
| 15 | 
            +
                  @variables_by_name[name]
         | 
| 16 | 
            +
                end
         | 
| 17 | 
            +
             | 
| 18 | 
            +
                def verify_no_missing(variable_names)
         | 
| 19 | 
            +
                  find_missing(variable_names).tap do |missing_variables|
         | 
| 20 | 
            +
                    raise(MissingVariables.new(self, missing_variables)) if missing_variables.any?
         | 
| 21 | 
            +
                  end
         | 
| 22 | 
            +
                end
         | 
| 23 | 
            +
             | 
| 24 | 
            +
                def verify_no_unknown(variable_names)
         | 
| 25 | 
            +
                  find_unknown(variable_names).tap do |unknown_names|
         | 
| 26 | 
            +
                    raise(UnknownVariables.new(self, unknown_names)) if unknown_names.any?
         | 
| 27 | 
            +
                  end
         | 
| 28 | 
            +
                end
         | 
| 29 | 
            +
             | 
| 30 | 
            +
                def self.from_file(file_name)
         | 
| 31 | 
            +
                  new(file_name, DotEnvParser.new.parse(File.read(file_name)))
         | 
| 32 | 
            +
                end
         | 
| 33 | 
            +
             | 
| 34 | 
            +
                private
         | 
| 35 | 
            +
             | 
| 36 | 
            +
                def find_missing(variable_names)
         | 
| 37 | 
            +
                  @variables.find_all do |variable|
         | 
| 38 | 
            +
                    !variable.optional? && !variable_names.include?(variable.name)
         | 
| 39 | 
            +
                  end
         | 
| 40 | 
            +
                end
         | 
| 41 | 
            +
             | 
| 42 | 
            +
                def find_unknown(variable_names)
         | 
| 43 | 
            +
                  variable_names.find_all do |name|
         | 
| 44 | 
            +
                    !@variables_by_name.key?(name)
         | 
| 45 | 
            +
                  end
         | 
| 46 | 
            +
                end
         | 
| 47 | 
            +
              end
         | 
| 48 | 
            +
            end
         | 
| @@ -0,0 +1,39 @@ | |
| 1 | 
            +
            module EnvLint
         | 
| 2 | 
            +
              class DotEnvParser
         | 
| 3 | 
            +
                def parse(text)
         | 
| 4 | 
            +
                  comment_lines = []
         | 
| 5 | 
            +
             | 
| 6 | 
            +
                  variables = text.lines.each_with_object([]) do |line, result|
         | 
| 7 | 
            +
                    if match = line.strip.match(ASSIGNMENT)
         | 
| 8 | 
            +
                      optional, name, value = match.captures
         | 
| 9 | 
            +
                      value ||= ''
         | 
| 10 | 
            +
                      value = value.strip.sub(/\A(['"])(.*)\1\z/, '\2')
         | 
| 11 | 
            +
             | 
| 12 | 
            +
                      result << Variable.new(name, value, !!optional, comment_lines * "\n")
         | 
| 13 | 
            +
                      comment_lines = []
         | 
| 14 | 
            +
                    elsif match = line.strip.match(COMMENT)
         | 
| 15 | 
            +
                      comment_lines << match.captures.first
         | 
| 16 | 
            +
                    elsif line.strip.empty?
         | 
| 17 | 
            +
                      comment_lines = []
         | 
| 18 | 
            +
                    else
         | 
| 19 | 
            +
                      raise(UnrecognizedDotEnvLine.new(line))
         | 
| 20 | 
            +
                    end
         | 
| 21 | 
            +
                  end
         | 
| 22 | 
            +
             | 
| 23 | 
            +
                  variables
         | 
| 24 | 
            +
                end
         | 
| 25 | 
            +
             | 
| 26 | 
            +
                COMMENT = /\A#\s*(.*)\z/
         | 
| 27 | 
            +
             | 
| 28 | 
            +
                ASSIGNMENT = /
         | 
| 29 | 
            +
                  \A
         | 
| 30 | 
            +
                  (\#\s*)?          # optional variable marker
         | 
| 31 | 
            +
                  ([\w\.]+)         # key
         | 
| 32 | 
            +
                  =                 # separator
         | 
| 33 | 
            +
                  (                 # optional value begin
         | 
| 34 | 
            +
                    [^#\n]+         #   unquoted value
         | 
| 35 | 
            +
                  )?                # value end
         | 
| 36 | 
            +
                  \z
         | 
| 37 | 
            +
                 /x
         | 
| 38 | 
            +
              end
         | 
| 39 | 
            +
            end
         | 
| @@ -0,0 +1,11 @@ | |
| 1 | 
            +
            module EnvLint
         | 
| 2 | 
            +
              class EnvKeyParser
         | 
| 3 | 
            +
                def parse_args(args)
         | 
| 4 | 
            +
                  args.map { |arg| arg.split('=').first if arg.include?('=') }.compact
         | 
| 5 | 
            +
                end
         | 
| 6 | 
            +
             | 
| 7 | 
            +
                def parse_export_output(text)
         | 
| 8 | 
            +
                  parse_args(text.split(/[\n\r]/).map { |line| line.gsub('declare -x', '').strip })
         | 
| 9 | 
            +
                end
         | 
| 10 | 
            +
              end
         | 
| 11 | 
            +
            end
         | 
| @@ -0,0 +1,62 @@ | |
| 1 | 
            +
            module EnvLint
         | 
| 2 | 
            +
              class Error < StandardError
         | 
| 3 | 
            +
              end
         | 
| 4 | 
            +
             | 
| 5 | 
            +
              class UnrecognizedDotEnvLine < Error
         | 
| 6 | 
            +
                attr_reader :line
         | 
| 7 | 
            +
             | 
| 8 | 
            +
                def initialize(line)
         | 
| 9 | 
            +
                  super("Unrecognized line in dot env file: '#{line}'")
         | 
| 10 | 
            +
                  @line = line
         | 
| 11 | 
            +
                end
         | 
| 12 | 
            +
              end
         | 
| 13 | 
            +
             | 
| 14 | 
            +
              class VariableError < StandardError
         | 
| 15 | 
            +
                attr_reader :variable_name
         | 
| 16 | 
            +
             | 
| 17 | 
            +
                def initialize(variable_name, message)
         | 
| 18 | 
            +
                  super(message)
         | 
| 19 | 
            +
                  @variable_name = variable_name
         | 
| 20 | 
            +
                end
         | 
| 21 | 
            +
              end
         | 
| 22 | 
            +
             | 
| 23 | 
            +
              class UnknownVariable < VariableError
         | 
| 24 | 
            +
                def initialize(variable_name)
         | 
| 25 | 
            +
                  super(variable_name, "Unknown variable #{variable_name}.")
         | 
| 26 | 
            +
                end
         | 
| 27 | 
            +
              end
         | 
| 28 | 
            +
             | 
| 29 | 
            +
              class DefaultValueRequiredForOptionalVariable < VariableError
         | 
| 30 | 
            +
                def initialize(variable_name)
         | 
| 31 | 
            +
                  super(variable_name, "Non optional variable #{variable_name} used without default value.")
         | 
| 32 | 
            +
                end
         | 
| 33 | 
            +
              end
         | 
| 34 | 
            +
             | 
| 35 | 
            +
              class MissingVariable < VariableError
         | 
| 36 | 
            +
                def initialize(variable_name)
         | 
| 37 | 
            +
                  super(variable_name, "Missing variable #{variable_name}. Check your .env file")
         | 
| 38 | 
            +
                end
         | 
| 39 | 
            +
              end
         | 
| 40 | 
            +
             | 
| 41 | 
            +
              class MissingVariables < Error
         | 
| 42 | 
            +
                attr_reader :dot_env_file, :missing_variables
         | 
| 43 | 
            +
             | 
| 44 | 
            +
                def initialize(dot_env_file, missing_variables)
         | 
| 45 | 
            +
                  @dot_env_file = dot_env_file
         | 
| 46 | 
            +
                  @missing_variables = missing_variables
         | 
| 47 | 
            +
             | 
| 48 | 
            +
                  super("Missing variables #{missing_variables * ', '}.")
         | 
| 49 | 
            +
                end
         | 
| 50 | 
            +
              end
         | 
| 51 | 
            +
             | 
| 52 | 
            +
              class UnknownVariables < Error
         | 
| 53 | 
            +
                attr_reader :dot_env_file, :unknown_variables
         | 
| 54 | 
            +
             | 
| 55 | 
            +
                def initialize(dot_env_file, unknown_variables)
         | 
| 56 | 
            +
                  @dot_env_file = dot_env_file
         | 
| 57 | 
            +
                  @unknown_variables = unknown_variables
         | 
| 58 | 
            +
             | 
| 59 | 
            +
                  super("Unknown variables #{unknown_variables * ', '}.")
         | 
| 60 | 
            +
                end
         | 
| 61 | 
            +
              end
         | 
| 62 | 
            +
            end
         | 
| @@ -0,0 +1,49 @@ | |
| 1 | 
            +
            require 'formatador'
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            module EnvLint
         | 
| 4 | 
            +
              class Formatter
         | 
| 5 | 
            +
                def initialize(out = Formatador)
         | 
| 6 | 
            +
                  @out = out
         | 
| 7 | 
            +
                end
         | 
| 8 | 
            +
             | 
| 9 | 
            +
                def missing_variables(dot_env_file, variables)
         | 
| 10 | 
            +
                  error("Missing env variables:\n")
         | 
| 11 | 
            +
             | 
| 12 | 
            +
                  variables.each do |variable|
         | 
| 13 | 
            +
                    @out.display_line("  [yellow]#{variable.name}[/] - #{variable.comment}")
         | 
| 14 | 
            +
                  end
         | 
| 15 | 
            +
             | 
| 16 | 
            +
                  new_line
         | 
| 17 | 
            +
                  info("Either set the variable or make it optional in the #{dot_env_file.name} file.")
         | 
| 18 | 
            +
                end
         | 
| 19 | 
            +
             | 
| 20 | 
            +
                def unknown_variables(dot_env_file, variable_names)
         | 
| 21 | 
            +
                  error("Unknown env variables:\n")
         | 
| 22 | 
            +
             | 
| 23 | 
            +
                  variable_names.each do |name|
         | 
| 24 | 
            +
                    @out.display_line("  [yellow]#{name}[/]")
         | 
| 25 | 
            +
                  end
         | 
| 26 | 
            +
             | 
| 27 | 
            +
                  new_line
         | 
| 28 | 
            +
                  info("Only variables descibred in #{dot_env_file.name} can be used.")
         | 
| 29 | 
            +
                end
         | 
| 30 | 
            +
             | 
| 31 | 
            +
                def error(message)
         | 
| 32 | 
            +
                  @out.display_line("* [red]#{message}[/]")
         | 
| 33 | 
            +
                end
         | 
| 34 | 
            +
             | 
| 35 | 
            +
                def ok(message)
         | 
| 36 | 
            +
                  @out.display_line("* [green]#{message}[/]")
         | 
| 37 | 
            +
                end
         | 
| 38 | 
            +
             | 
| 39 | 
            +
                private
         | 
| 40 | 
            +
             | 
| 41 | 
            +
                def info(message)
         | 
| 42 | 
            +
                  @out.display_line("  #{message}")
         | 
| 43 | 
            +
                end
         | 
| 44 | 
            +
             | 
| 45 | 
            +
                def new_line
         | 
| 46 | 
            +
                  @out.display_line('')
         | 
| 47 | 
            +
                end
         | 
| 48 | 
            +
              end
         | 
| 49 | 
            +
            end
         | 
| @@ -0,0 +1,29 @@ | |
| 1 | 
            +
            module EnvLint
         | 
| 2 | 
            +
              class LintedEnv
         | 
| 3 | 
            +
                def initialize(env, dot_env_file)
         | 
| 4 | 
            +
                  @env = env
         | 
| 5 | 
            +
                  @dot_env_file = dot_env_file
         | 
| 6 | 
            +
                end
         | 
| 7 | 
            +
             | 
| 8 | 
            +
                def fetch(name, *args, &block)
         | 
| 9 | 
            +
                  name = name.to_s.upcase if name.is_a?(Symbol)
         | 
| 10 | 
            +
                  variable = @dot_env_file.find_variable(name)
         | 
| 11 | 
            +
             | 
| 12 | 
            +
                  raise(UnknownVariable.new(name)) unless variable
         | 
| 13 | 
            +
             | 
| 14 | 
            +
                  if variable.optional? && args.empty? && !block_given?
         | 
| 15 | 
            +
                    raise(DefaultValueRequiredForOptionalVariable.new(name))
         | 
| 16 | 
            +
                  end
         | 
| 17 | 
            +
             | 
| 18 | 
            +
                  block ||= lambda do |i|
         | 
| 19 | 
            +
                    args.any? ? args.first : raise(MissingVariable.new(name))
         | 
| 20 | 
            +
                  end
         | 
| 21 | 
            +
             | 
| 22 | 
            +
                  @env.fetch(name, &block)
         | 
| 23 | 
            +
                end
         | 
| 24 | 
            +
             | 
| 25 | 
            +
                def self.from_file(file_name)
         | 
| 26 | 
            +
                  new(ENV, DotEnvFile.from_file(file_name))
         | 
| 27 | 
            +
                end
         | 
| 28 | 
            +
              end
         | 
| 29 | 
            +
            end
         | 
| @@ -0,0 +1,21 @@ | |
| 1 | 
            +
            namespace :env do
         | 
| 2 | 
            +
              desc 'Ensure every non optional ENV variable is defined.'
         | 
| 3 | 
            +
              task :lint => :load do
         | 
| 4 | 
            +
                begin
         | 
| 5 | 
            +
                  EnvLint.verify_hash(env_definition_file, ENV)
         | 
| 6 | 
            +
                  EnvLint.formatter.ok('env looks ok')
         | 
| 7 | 
            +
                rescue EnvLint::MissingVariables => e
         | 
| 8 | 
            +
                  EnvLint.formatter.missing_variables(e.dot_env_file, e.missing_variables)
         | 
| 9 | 
            +
                  abort
         | 
| 10 | 
            +
                rescue EnvLint::Error => e
         | 
| 11 | 
            +
                  EnvLint.formatter.error(e.message)
         | 
| 12 | 
            +
                end
         | 
| 13 | 
            +
              end
         | 
| 14 | 
            +
             | 
| 15 | 
            +
              task :load do
         | 
| 16 | 
            +
              end
         | 
| 17 | 
            +
             | 
| 18 | 
            +
              def env_definition_file
         | 
| 19 | 
            +
                ENV['DEFINITION'] || '.env.example'
         | 
| 20 | 
            +
              end
         | 
| 21 | 
            +
            end
         | 
    
        data/lib/env_lint.rb
    ADDED
    
    | @@ -0,0 +1,24 @@ | |
| 1 | 
            +
            require 'env_lint/dot_env_file'
         | 
| 2 | 
            +
            require 'env_lint/dot_env_parser'
         | 
| 3 | 
            +
            require 'env_lint/formatter'
         | 
| 4 | 
            +
            require 'env_lint/env_key_parser'
         | 
| 5 | 
            +
            require 'env_lint/errors'
         | 
| 6 | 
            +
            require 'env_lint/linted_env'
         | 
| 7 | 
            +
            require 'env_lint/variable'
         | 
| 8 | 
            +
            require 'env_lint/version'
         | 
| 9 | 
            +
             | 
| 10 | 
            +
            module EnvLint
         | 
| 11 | 
            +
              def self.verify_hash(env_definition_file, env)
         | 
| 12 | 
            +
                DotEnvFile.from_file(env_definition_file).verify_no_missing(env.keys)
         | 
| 13 | 
            +
              end
         | 
| 14 | 
            +
             | 
| 15 | 
            +
              def self.verify_export_output(env_definition_file, export_output)
         | 
| 16 | 
            +
                DotEnvFile.from_file(env_definition_file)
         | 
| 17 | 
            +
                  .verify_no_missing(EnvKeyParser.new.parse_export_output(export_output))
         | 
| 18 | 
            +
              end
         | 
| 19 | 
            +
             | 
| 20 | 
            +
              def self.verify_args(env_definition_file, args)
         | 
| 21 | 
            +
                DotEnvFile.from_file(env_definition_file)
         | 
| 22 | 
            +
                  .verify_no_unknown(EnvKeyParser.new.parse_args(args))
         | 
| 23 | 
            +
              end
         | 
| 24 | 
            +
            end
         | 
| @@ -0,0 +1,94 @@ | |
| 1 | 
            +
            require 'spec_helper'
         | 
| 2 | 
            +
            require 'env_lint/capistrano'
         | 
| 3 | 
            +
             | 
| 4 | 
            +
            module EnvLint
         | 
| 5 | 
            +
              describe Capistrano do
         | 
| 6 | 
            +
                let :config do
         | 
| 7 | 
            +
                  ::Capistrano::Configuration.new
         | 
| 8 | 
            +
                end
         | 
| 9 | 
            +
             | 
| 10 | 
            +
                let :namespace do
         | 
| 11 | 
            +
                  config.env
         | 
| 12 | 
            +
                end
         | 
| 13 | 
            +
             | 
| 14 | 
            +
                let :formatter do
         | 
| 15 | 
            +
                  instance_double(Formatter, ok: true, missing_variables: true, unknown_variables: true, error: true)
         | 
| 16 | 
            +
                end
         | 
| 17 | 
            +
             | 
| 18 | 
            +
                before do
         | 
| 19 | 
            +
                  config.set(:application_user, 'myapp')
         | 
| 20 | 
            +
             | 
| 21 | 
            +
                  EnvLint::Capistrano.load_into(config, formatter)
         | 
| 22 | 
            +
                end
         | 
| 23 | 
            +
             | 
| 24 | 
            +
                describe 'env:lint' do
         | 
| 25 | 
            +
                  it 'calls verify_export_output with result of capute' do
         | 
| 26 | 
            +
                    config.set(:env_definition_file, '.env.example')
         | 
| 27 | 
            +
             | 
| 28 | 
            +
                    allow(namespace).to receive(:capture).and_return('declare -x APP=1')
         | 
| 29 | 
            +
                    expect(EnvLint).to receive(:verify_export_output).with('.env.example', 'declare -x APP=1')
         | 
| 30 | 
            +
             | 
| 31 | 
            +
                    config.find_and_execute_task('env:lint')
         | 
| 32 | 
            +
                  end
         | 
| 33 | 
            +
             | 
| 34 | 
            +
                  it 'passes missing variables to formatter and aborts' do
         | 
| 35 | 
            +
                    dot_env_file = {}
         | 
| 36 | 
            +
                    variables = ['APP']
         | 
| 37 | 
            +
             | 
| 38 | 
            +
                    allow(namespace).to receive(:capture).and_return('declare -x APP=1')
         | 
| 39 | 
            +
                    allow(EnvLint).to receive(:verify_export_output).and_raise(MissingVariables.new(dot_env_file, variables))
         | 
| 40 | 
            +
                    expect(namespace).to receive(:abort)
         | 
| 41 | 
            +
                    expect(formatter).to receive(:missing_variables).with(dot_env_file, variables)
         | 
| 42 | 
            +
             | 
| 43 | 
            +
                    config.find_and_execute_task('env:lint')
         | 
| 44 | 
            +
                  end
         | 
| 45 | 
            +
             | 
| 46 | 
            +
                  it 'passes error message to formatter and aborts' do
         | 
| 47 | 
            +
                    dot_env_file = {}
         | 
| 48 | 
            +
                    variables = ['APP']
         | 
| 49 | 
            +
             | 
| 50 | 
            +
                    allow(namespace).to receive(:capture).and_return('declare -x APP=1')
         | 
| 51 | 
            +
                    allow(EnvLint).to receive(:verify_export_output).and_raise(Error.new('message'))
         | 
| 52 | 
            +
                    expect(namespace).to receive(:abort)
         | 
| 53 | 
            +
                    expect(formatter).to receive(:error).with('message')
         | 
| 54 | 
            +
             | 
| 55 | 
            +
                    config.find_and_execute_task('env:lint')
         | 
| 56 | 
            +
                  end
         | 
| 57 | 
            +
                end
         | 
| 58 | 
            +
             | 
| 59 | 
            +
                describe 'env:lint_args' do
         | 
| 60 | 
            +
                  it 'calls verify_args with result of capute' do
         | 
| 61 | 
            +
                    config.set(:env_definition_file, '.env.example')
         | 
| 62 | 
            +
             | 
| 63 | 
            +
                    allow(namespace).to receive(:env_args).and_return(['APP=1'])
         | 
| 64 | 
            +
                    expect(EnvLint).to receive(:verify_args).with('.env.example', ['APP=1'])
         | 
| 65 | 
            +
             | 
| 66 | 
            +
                    config.find_and_execute_task('env:lint_args')
         | 
| 67 | 
            +
                  end
         | 
| 68 | 
            +
             | 
| 69 | 
            +
                  it 'passes missing variables to formatter and aborts' do
         | 
| 70 | 
            +
                    dot_env_file = {}
         | 
| 71 | 
            +
                    variables = ['APP']
         | 
| 72 | 
            +
             | 
| 73 | 
            +
                    allow(namespace).to receive(:env_args).and_return(['APP=1'])
         | 
| 74 | 
            +
                    allow(EnvLint).to receive(:verify_args).and_raise(UnknownVariables.new(dot_env_file, variables))
         | 
| 75 | 
            +
                    expect(namespace).to receive(:abort)
         | 
| 76 | 
            +
                    expect(formatter).to receive(:unknown_variables).with(dot_env_file, variables)
         | 
| 77 | 
            +
             | 
| 78 | 
            +
                    config.find_and_execute_task('env:lint_args')
         | 
| 79 | 
            +
                  end
         | 
| 80 | 
            +
             | 
| 81 | 
            +
                  it 'passes error message to formatter and aborts' do
         | 
| 82 | 
            +
                    dot_env_file = {}
         | 
| 83 | 
            +
                    variables = ['APP']
         | 
| 84 | 
            +
             | 
| 85 | 
            +
                    allow(namespace).to receive(:env_args).and_return(['APP=1'])
         | 
| 86 | 
            +
                    allow(EnvLint).to receive(:verify_args).and_raise(Error.new('message'))
         | 
| 87 | 
            +
                    expect(namespace).to receive(:abort)
         | 
| 88 | 
            +
                    expect(formatter).to receive(:error).with('message')
         | 
| 89 | 
            +
             | 
| 90 | 
            +
                    config.find_and_execute_task('env:lint_args')
         | 
| 91 | 
            +
                  end
         | 
| 92 | 
            +
                end
         | 
| 93 | 
            +
              end
         | 
| 94 | 
            +
            end
         | 
| @@ -0,0 +1,73 @@ | |
| 1 | 
            +
            require 'spec_helper'
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            module EnvLint
         | 
| 4 | 
            +
              describe DotEnvFile do
         | 
| 5 | 
            +
                describe '#verify_no_missing' do
         | 
| 6 | 
            +
                  it 'passes if all non optional variables are present in the env' do
         | 
| 7 | 
            +
                    dot_env_file = DotEnvFile.new('.env.example', [Variable.new('APP')])
         | 
| 8 | 
            +
             | 
| 9 | 
            +
                    expect {
         | 
| 10 | 
            +
                      dot_env_file.verify_no_missing(['APP'])
         | 
| 11 | 
            +
                    }.not_to raise_error
         | 
| 12 | 
            +
                  end
         | 
| 13 | 
            +
             | 
| 14 | 
            +
                  it 'raises an exception listing undefined non optional variables' do
         | 
| 15 | 
            +
                    dot_env_file = DotEnvFile.new('.env.example', [Variable.new('APP'), Variable.new('URL')])
         | 
| 16 | 
            +
             | 
| 17 | 
            +
                    expect {
         | 
| 18 | 
            +
                      dot_env_file.verify_no_missing(['APP'])
         | 
| 19 | 
            +
                    }.to raise_error(MissingVariables)
         | 
| 20 | 
            +
                  end
         | 
| 21 | 
            +
             | 
| 22 | 
            +
                  it 'lists undefined non optional variables in exception' do
         | 
| 23 | 
            +
                    dot_env_file = DotEnvFile.new('.env.example', [Variable.new('APP'), Variable.new('URL'), Variable.new('OTHER')])
         | 
| 24 | 
            +
             | 
| 25 | 
            +
                    begin
         | 
| 26 | 
            +
                      dot_env_file.verify_no_missing(['APP'])
         | 
| 27 | 
            +
                    rescue MissingVariables => e
         | 
| 28 | 
            +
                      expect(e.missing_variables.size).to eq(2)
         | 
| 29 | 
            +
                      expect(e.missing_variables.first.name).to eq('URL')
         | 
| 30 | 
            +
                      expect(e.missing_variables.last.name).to eq('OTHER')
         | 
| 31 | 
            +
                    end
         | 
| 32 | 
            +
                  end
         | 
| 33 | 
            +
             | 
| 34 | 
            +
                  it 'passes if optional variable is not defined' do
         | 
| 35 | 
            +
                    dot_env_file = DotEnvFile.new('.env.example', [Variable.new('APP', '', true)])
         | 
| 36 | 
            +
             | 
| 37 | 
            +
                    expect {
         | 
| 38 | 
            +
                      dot_env_file.verify_no_missing([''])
         | 
| 39 | 
            +
                    }.not_to raise_error
         | 
| 40 | 
            +
                  end
         | 
| 41 | 
            +
                end
         | 
| 42 | 
            +
             | 
| 43 | 
            +
                describe '#verfy_no_unknown' do
         | 
| 44 | 
            +
                  it 'passes if all variables of the env are known' do
         | 
| 45 | 
            +
                    dot_env_file = DotEnvFile.new('.env.example', [Variable.new('APP')])
         | 
| 46 | 
            +
             | 
| 47 | 
            +
                    expect {
         | 
| 48 | 
            +
                      dot_env_file.verify_no_unknown(['APP'])
         | 
| 49 | 
            +
                    }.not_to raise_error
         | 
| 50 | 
            +
                  end
         | 
| 51 | 
            +
             | 
| 52 | 
            +
                  it 'raises an exception if a variable is unknown' do
         | 
| 53 | 
            +
                    dot_env_file = DotEnvFile.new('.env.example', [Variable.new('APP')])
         | 
| 54 | 
            +
             | 
| 55 | 
            +
                    expect {
         | 
| 56 | 
            +
                      dot_env_file.verify_no_unknown(['APP', 'UNKNOWN', 'OTHER'])
         | 
| 57 | 
            +
                    }.to raise_error(UnknownVariables)
         | 
| 58 | 
            +
                  end
         | 
| 59 | 
            +
             | 
| 60 | 
            +
                  it 'lists unknown variables in exception' do
         | 
| 61 | 
            +
                    dot_env_file = DotEnvFile.new('.env.example', [Variable.new('APP')])
         | 
| 62 | 
            +
             | 
| 63 | 
            +
                    begin
         | 
| 64 | 
            +
                      dot_env_file.verify_no_unknown(['APP', 'UNKNOWN', 'OTHER'])
         | 
| 65 | 
            +
                    rescue UnknownVariables => e
         | 
| 66 | 
            +
                      expect(e.unknown_variables.size).to eq(2)
         | 
| 67 | 
            +
                      expect(e.unknown_variables.first).to eq('UNKNOWN')
         | 
| 68 | 
            +
                      expect(e.unknown_variables.last).to eq('OTHER')
         | 
| 69 | 
            +
                    end
         | 
| 70 | 
            +
                  end
         | 
| 71 | 
            +
                end
         | 
| 72 | 
            +
              end
         | 
| 73 | 
            +
            end
         | 
| @@ -0,0 +1,105 @@ | |
| 1 | 
            +
            require 'spec_helper'
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            module EnvLint
         | 
| 4 | 
            +
              describe DotEnvParser do
         | 
| 5 | 
            +
                describe '#parse' do
         | 
| 6 | 
            +
                  it 'recognizes variables' do
         | 
| 7 | 
            +
                    variables = DotEnvParser.new.parse(<<-END)
         | 
| 8 | 
            +
                      APP=myapp
         | 
| 9 | 
            +
                    END
         | 
| 10 | 
            +
             | 
| 11 | 
            +
                    expect(variables.first.name).to eq('APP')
         | 
| 12 | 
            +
                    expect(variables.first.value).to eq('myapp')
         | 
| 13 | 
            +
                  end
         | 
| 14 | 
            +
             | 
| 15 | 
            +
                  it 'recognizes double quoted values' do
         | 
| 16 | 
            +
                    variables = DotEnvParser.new.parse(<<-END)
         | 
| 17 | 
            +
                      APP="my app"
         | 
| 18 | 
            +
                    END
         | 
| 19 | 
            +
             | 
| 20 | 
            +
                    expect(variables.first.value).to eq('my app')
         | 
| 21 | 
            +
                  end
         | 
| 22 | 
            +
             | 
| 23 | 
            +
                  it 'recognizes single quoted values' do
         | 
| 24 | 
            +
                    variables = DotEnvParser.new.parse(<<-END)
         | 
| 25 | 
            +
                      APP='my app'
         | 
| 26 | 
            +
                    END
         | 
| 27 | 
            +
             | 
| 28 | 
            +
                    expect(variables.first.value).to eq('my app')
         | 
| 29 | 
            +
                  end
         | 
| 30 | 
            +
             | 
| 31 | 
            +
                  it 'handles empty assignments' do
         | 
| 32 | 
            +
                    variables = DotEnvParser.new.parse(<<-END)
         | 
| 33 | 
            +
                      APP=
         | 
| 34 | 
            +
                    END
         | 
| 35 | 
            +
             | 
| 36 | 
            +
                    expect(variables.first.value).to eq('')
         | 
| 37 | 
            +
                  end
         | 
| 38 | 
            +
             | 
| 39 | 
            +
                  it 'ignores blank linkes' do
         | 
| 40 | 
            +
                    variables = DotEnvParser.new.parse(<<-END)
         | 
| 41 | 
            +
                      APP1=myapp
         | 
| 42 | 
            +
             | 
| 43 | 
            +
                      APP2=myapp
         | 
| 44 | 
            +
                    END
         | 
| 45 | 
            +
             | 
| 46 | 
            +
                    expect(variables.count).to eq(2)
         | 
| 47 | 
            +
                    expect(variables.first.name).to eq('APP1')
         | 
| 48 | 
            +
                    expect(variables.last.name).to eq('APP2')
         | 
| 49 | 
            +
                  end
         | 
| 50 | 
            +
             | 
| 51 | 
            +
                  it 'recognizes optional variables' do
         | 
| 52 | 
            +
                    variables = DotEnvParser.new.parse(<<-END)
         | 
| 53 | 
            +
                      # APP=myapp
         | 
| 54 | 
            +
                      #OTHER=myapp
         | 
| 55 | 
            +
                      APP=myapp
         | 
| 56 | 
            +
                    END
         | 
| 57 | 
            +
             | 
| 58 | 
            +
                    expect(variables[0]).to be_optional
         | 
| 59 | 
            +
                    expect(variables[1]).to be_optional
         | 
| 60 | 
            +
                    expect(variables[2]).not_to be_optional
         | 
| 61 | 
            +
                  end
         | 
| 62 | 
            +
             | 
| 63 | 
            +
                  it 'recognizes comments' do
         | 
| 64 | 
            +
                    variables = DotEnvParser.new.parse(<<-END)
         | 
| 65 | 
            +
                      # The name of the app
         | 
| 66 | 
            +
                      APP=myapp
         | 
| 67 | 
            +
                    END
         | 
| 68 | 
            +
             | 
| 69 | 
            +
                    expect(variables.first.comment).to eq('The name of the app')
         | 
| 70 | 
            +
                  end
         | 
| 71 | 
            +
             | 
| 72 | 
            +
                  it 'recognizes multi line comments' do
         | 
| 73 | 
            +
                    variables = DotEnvParser.new.parse(<<-END)
         | 
| 74 | 
            +
                      # The name of the app
         | 
| 75 | 
            +
                      # and here the text goes on
         | 
| 76 | 
            +
                      APP=myapp
         | 
| 77 | 
            +
                    END
         | 
| 78 | 
            +
             | 
| 79 | 
            +
                    expect(variables.first.comment).to eq("The name of the app\nand here the text goes on")
         | 
| 80 | 
            +
                  end
         | 
| 81 | 
            +
             | 
| 82 | 
            +
                  it 'starts new comment at blank line' do
         | 
| 83 | 
            +
                    variables = DotEnvParser.new.parse(<<-END)
         | 
| 84 | 
            +
                      # This is some header which is not related
         | 
| 85 | 
            +
                      # to the following variable
         | 
| 86 | 
            +
             | 
| 87 | 
            +
                      # This is the comment
         | 
| 88 | 
            +
                      APP=myapp
         | 
| 89 | 
            +
                    END
         | 
| 90 | 
            +
             | 
| 91 | 
            +
                    expect(variables.first.comment).to eq('This is the comment')
         | 
| 92 | 
            +
                  end
         | 
| 93 | 
            +
             | 
| 94 | 
            +
                  it 'raises exception for unrecognized line' do
         | 
| 95 | 
            +
                    text = <<-END
         | 
| 96 | 
            +
                      what is this?
         | 
| 97 | 
            +
                    END
         | 
| 98 | 
            +
             | 
| 99 | 
            +
                    expect {
         | 
| 100 | 
            +
                      DotEnvParser.new.parse(text)
         | 
| 101 | 
            +
                    }.to raise_error(UnrecognizedDotEnvLine)
         | 
| 102 | 
            +
                  end
         | 
| 103 | 
            +
                end
         | 
| 104 | 
            +
              end
         | 
| 105 | 
            +
            end
         | 
| @@ -0,0 +1,36 @@ | |
| 1 | 
            +
            require 'spec_helper'
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            module EnvLint
         | 
| 4 | 
            +
              describe EnvKeyParser do
         | 
| 5 | 
            +
                describe '.parse_args' do
         | 
| 6 | 
            +
                  it 'recognizes assignments' do
         | 
| 7 | 
            +
                    parser = EnvKeyParser.new
         | 
| 8 | 
            +
             | 
| 9 | 
            +
                    keys = parser.parse_args(['APP=myname', 'NAME=test'])
         | 
| 10 | 
            +
             | 
| 11 | 
            +
                    expect(keys).to eq(['APP', 'NAME'])
         | 
| 12 | 
            +
                  end
         | 
| 13 | 
            +
             | 
| 14 | 
            +
                  it 'ignores other args' do
         | 
| 15 | 
            +
                    parser = EnvKeyParser.new
         | 
| 16 | 
            +
             | 
| 17 | 
            +
                    keys = parser.parse_args(['env:set', 'APP=myname'])
         | 
| 18 | 
            +
             | 
| 19 | 
            +
                    expect(keys).to eq(['APP'])
         | 
| 20 | 
            +
                  end
         | 
| 21 | 
            +
                end
         | 
| 22 | 
            +
             | 
| 23 | 
            +
                describe '.parse_export_output' do
         | 
| 24 | 
            +
                  it 'recognizes assignments' do
         | 
| 25 | 
            +
                    parser = EnvKeyParser.new
         | 
| 26 | 
            +
             | 
| 27 | 
            +
                    keys = parser.parse_export_output(<<-END)
         | 
| 28 | 
            +
                      declare -x APP="myapp"
         | 
| 29 | 
            +
                      declare -x NAME="test"
         | 
| 30 | 
            +
                    END
         | 
| 31 | 
            +
             | 
| 32 | 
            +
                    expect(keys).to eq(['APP', 'NAME'])
         | 
| 33 | 
            +
                  end
         | 
| 34 | 
            +
                end
         | 
| 35 | 
            +
              end
         | 
| 36 | 
            +
            end
         | 
| @@ -0,0 +1,85 @@ | |
| 1 | 
            +
            require 'spec_helper'
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            module EnvLint
         | 
| 4 | 
            +
              describe LintedEnv do
         | 
| 5 | 
            +
                describe '#fetch' do
         | 
| 6 | 
            +
                  it 'returns value from env for known variable' do
         | 
| 7 | 
            +
                    env = {'APP' => 'myapp'}
         | 
| 8 | 
            +
                    dot_env_file = DotEnvFile.new('.env.example', [Variable.new('APP')])
         | 
| 9 | 
            +
                    linted_env = LintedEnv.new(env, dot_env_file)
         | 
| 10 | 
            +
             | 
| 11 | 
            +
                    expect(linted_env.fetch('APP')).to eq('myapp')
         | 
| 12 | 
            +
                  end
         | 
| 13 | 
            +
             | 
| 14 | 
            +
                  it 'translates symbols to uppercase strings' do
         | 
| 15 | 
            +
                    env = {'APP_NAME' => 'myapp'}
         | 
| 16 | 
            +
                    dot_env_file = DotEnvFile.new('.env.example', [Variable.new('APP_NAME')])
         | 
| 17 | 
            +
                    linted_env = LintedEnv.new(env, dot_env_file)
         | 
| 18 | 
            +
             | 
| 19 | 
            +
                    expect(linted_env.fetch(:app_name)).to eq('myapp')
         | 
| 20 | 
            +
                  end
         | 
| 21 | 
            +
             | 
| 22 | 
            +
                  it 'raises exception for unknown variable' do
         | 
| 23 | 
            +
                    env = {'APP' => 'myapp'}
         | 
| 24 | 
            +
                    dot_env_file = DotEnvFile.new('.env.example', [])
         | 
| 25 | 
            +
                    linted_env = LintedEnv.new(env, dot_env_file)
         | 
| 26 | 
            +
             | 
| 27 | 
            +
                    expect {
         | 
| 28 | 
            +
                      linted_env.fetch('APP')
         | 
| 29 | 
            +
                    }.to raise_error(UnknownVariable)
         | 
| 30 | 
            +
                  end
         | 
| 31 | 
            +
             | 
| 32 | 
            +
                  it 'raises exception for undefined variable' do
         | 
| 33 | 
            +
                    env = {}
         | 
| 34 | 
            +
                    dot_env_file = DotEnvFile.new('.env.example', [Variable.new('APP')])
         | 
| 35 | 
            +
                    linted_env = LintedEnv.new(env, dot_env_file)
         | 
| 36 | 
            +
             | 
| 37 | 
            +
                    expect {
         | 
| 38 | 
            +
                      linted_env.fetch('APP')
         | 
| 39 | 
            +
                    }.to raise_error(MissingVariable)
         | 
| 40 | 
            +
                  end
         | 
| 41 | 
            +
             | 
| 42 | 
            +
                  it 'returns default value for undefined variable' do
         | 
| 43 | 
            +
                    env = {}
         | 
| 44 | 
            +
                    dot_env_file = DotEnvFile.new('.env.example', [Variable.new('APP')])
         | 
| 45 | 
            +
                    linted_env = LintedEnv.new(env, dot_env_file)
         | 
| 46 | 
            +
             | 
| 47 | 
            +
                    expect(linted_env.fetch('APP', 'default')).to eq('default')
         | 
| 48 | 
            +
                  end
         | 
| 49 | 
            +
             | 
| 50 | 
            +
                  it 'returns default from block for undefined variable' do
         | 
| 51 | 
            +
                    env = {}
         | 
| 52 | 
            +
                    dot_env_file = DotEnvFile.new('.env.example', [Variable.new('APP')])
         | 
| 53 | 
            +
                    linted_env = LintedEnv.new(env, dot_env_file)
         | 
| 54 | 
            +
             | 
| 55 | 
            +
                    expect(linted_env.fetch('APP') { 'default' }).to eq('default')
         | 
| 56 | 
            +
                  end
         | 
| 57 | 
            +
             | 
| 58 | 
            +
                  it 'raises exception if optional variable is used without default' do
         | 
| 59 | 
            +
                    env = {'APP' => 'myapp'}
         | 
| 60 | 
            +
                    dot_env_file = DotEnvFile.new('.env.example', [Variable.new('APP', '', true)])
         | 
| 61 | 
            +
                    linted_env = LintedEnv.new(env, dot_env_file)
         | 
| 62 | 
            +
             | 
| 63 | 
            +
                    expect {
         | 
| 64 | 
            +
                      linted_env.fetch('APP')
         | 
| 65 | 
            +
                    }.to raise_error(DefaultValueRequiredForOptionalVariable)
         | 
| 66 | 
            +
                  end
         | 
| 67 | 
            +
             | 
| 68 | 
            +
                  it 'allows to use optional variable with default value' do
         | 
| 69 | 
            +
                    env = {'APP' => 'myapp'}
         | 
| 70 | 
            +
                    dot_env_file = DotEnvFile.new('.env.example', [Variable.new('APP', '', true)])
         | 
| 71 | 
            +
                    linted_env = LintedEnv.new(env, dot_env_file)
         | 
| 72 | 
            +
             | 
| 73 | 
            +
                    expect(linted_env.fetch('APP', 'default')).to eq('myapp')
         | 
| 74 | 
            +
                  end
         | 
| 75 | 
            +
             | 
| 76 | 
            +
                  it 'allows to use optional variable with block' do
         | 
| 77 | 
            +
                    env = {'APP' => 'myapp'}
         | 
| 78 | 
            +
                    dot_env_file = DotEnvFile.new('.env.example', [Variable.new('APP', '', true)])
         | 
| 79 | 
            +
                    linted_env = LintedEnv.new(env, dot_env_file)
         | 
| 80 | 
            +
             | 
| 81 | 
            +
                    expect(linted_env.fetch('APP') { 'block' }).to eq('myapp')
         | 
| 82 | 
            +
                  end
         | 
| 83 | 
            +
                end
         | 
| 84 | 
            +
              end
         | 
| 85 | 
            +
            end
         | 
| @@ -0,0 +1,109 @@ | |
| 1 | 
            +
            require 'spec_helper'
         | 
| 2 | 
            +
            require 'fileutils'
         | 
| 3 | 
            +
             | 
| 4 | 
            +
            describe EnvLint do
         | 
| 5 | 
            +
              SANDBOX_DIR = File.join(File.dirname(__FILE__), 'tmp')
         | 
| 6 | 
            +
             | 
| 7 | 
            +
              around do |example|
         | 
| 8 | 
            +
                clean_up_sandbox
         | 
| 9 | 
            +
                in_sandbox { example.run }
         | 
| 10 | 
            +
              end
         | 
| 11 | 
            +
             | 
| 12 | 
            +
              describe '.verify_hash' do
         | 
| 13 | 
            +
                it 'passes when hash is complete' do
         | 
| 14 | 
            +
                  File.write('.env.example', <<-END)
         | 
| 15 | 
            +
                    APP=myapp
         | 
| 16 | 
            +
                  END
         | 
| 17 | 
            +
             | 
| 18 | 
            +
                  expect {
         | 
| 19 | 
            +
                    EnvLint.verify_hash('.env.example', {'APP' => 'some name'})
         | 
| 20 | 
            +
                  }.not_to raise_error
         | 
| 21 | 
            +
                end
         | 
| 22 | 
            +
             | 
| 23 | 
            +
                it 'fails when hash is missing non optional variable' do
         | 
| 24 | 
            +
                  File.write('.env.example', <<-END)
         | 
| 25 | 
            +
                    APP=myapp
         | 
| 26 | 
            +
                  END
         | 
| 27 | 
            +
             | 
| 28 | 
            +
                  expect {
         | 
| 29 | 
            +
                    EnvLint.verify_hash('.env.example', {'OTHER' => 'other'})
         | 
| 30 | 
            +
                  }.to raise_error(EnvLint::MissingVariables)
         | 
| 31 | 
            +
                end
         | 
| 32 | 
            +
              end
         | 
| 33 | 
            +
             | 
| 34 | 
            +
              describe '.verify_args' do
         | 
| 35 | 
            +
                it 'passes when all args are known' do
         | 
| 36 | 
            +
                  File.write('.env.example', <<-END)
         | 
| 37 | 
            +
                    APP=myapp
         | 
| 38 | 
            +
                    OTHER=other
         | 
| 39 | 
            +
                  END
         | 
| 40 | 
            +
             | 
| 41 | 
            +
                  expect {
         | 
| 42 | 
            +
                    EnvLint.verify_args('.env.example', ['APP=name'])
         | 
| 43 | 
            +
                  }.not_to raise_error
         | 
| 44 | 
            +
                end
         | 
| 45 | 
            +
             | 
| 46 | 
            +
                it 'fails when there are unknown args' do
         | 
| 47 | 
            +
                  File.write('.env.example', <<-END)
         | 
| 48 | 
            +
                    APP=myapp
         | 
| 49 | 
            +
                  END
         | 
| 50 | 
            +
             | 
| 51 | 
            +
                  expect {
         | 
| 52 | 
            +
                    EnvLint.verify_args('.env.example', ['OTHER=name'])
         | 
| 53 | 
            +
                  }.to raise_error(EnvLint::UnknownVariables)
         | 
| 54 | 
            +
                end
         | 
| 55 | 
            +
              end
         | 
| 56 | 
            +
             | 
| 57 | 
            +
              describe '.verify_export_output' do
         | 
| 58 | 
            +
                it 'passes when the env is complete' do
         | 
| 59 | 
            +
                  File.write('.env.example', <<-END)
         | 
| 60 | 
            +
                    APP=myapp
         | 
| 61 | 
            +
                  END
         | 
| 62 | 
            +
                  export_output = <<-END
         | 
| 63 | 
            +
                    declare -x APP="myapp"
         | 
| 64 | 
            +
                    declare -x OTHER="other"
         | 
| 65 | 
            +
                  END
         | 
| 66 | 
            +
             | 
| 67 | 
            +
                  expect {
         | 
| 68 | 
            +
                    EnvLint.verify_export_output('.env.example', export_output)
         | 
| 69 | 
            +
                  }.not_to raise_error
         | 
| 70 | 
            +
                end
         | 
| 71 | 
            +
             | 
| 72 | 
            +
                it 'fails when the env is missing non optional variable' do
         | 
| 73 | 
            +
                  File.write('.env.example', <<-END)
         | 
| 74 | 
            +
                    APP=myapp
         | 
| 75 | 
            +
                  END
         | 
| 76 | 
            +
                  export_output = <<-END
         | 
| 77 | 
            +
                    declare -x OTHER="other"
         | 
| 78 | 
            +
                  END
         | 
| 79 | 
            +
             | 
| 80 | 
            +
                  expect {
         | 
| 81 | 
            +
                    EnvLint.verify_export_output('.env.example', export_output)
         | 
| 82 | 
            +
                  }.to raise_error(EnvLint::MissingVariables)
         | 
| 83 | 
            +
                end
         | 
| 84 | 
            +
              end
         | 
| 85 | 
            +
             | 
| 86 | 
            +
              describe EnvLint::LintedEnv do
         | 
| 87 | 
            +
                it 'allows to read defined variables from ENV' do
         | 
| 88 | 
            +
                  File.write('.env.example', <<-END)
         | 
| 89 | 
            +
                    APP=myapp
         | 
| 90 | 
            +
                  END
         | 
| 91 | 
            +
                  allow(ENV).to receive(:fetch).with('APP').and_return('value')
         | 
| 92 | 
            +
             | 
| 93 | 
            +
                  linted_env = EnvLint::LintedEnv.from_file('.env.example')
         | 
| 94 | 
            +
             | 
| 95 | 
            +
                  expect(linted_env.fetch(:app)).to eq('value')
         | 
| 96 | 
            +
                end
         | 
| 97 | 
            +
              end
         | 
| 98 | 
            +
             | 
| 99 | 
            +
              private
         | 
| 100 | 
            +
             | 
| 101 | 
            +
              def clean_up_sandbox
         | 
| 102 | 
            +
                FileUtils.rm_rf(SANDBOX_DIR)
         | 
| 103 | 
            +
                FileUtils.mkdir_p(SANDBOX_DIR)
         | 
| 104 | 
            +
              end
         | 
| 105 | 
            +
             | 
| 106 | 
            +
              def in_sandbox(&block)
         | 
| 107 | 
            +
                Dir.chdir(SANDBOX_DIR, &block)
         | 
| 108 | 
            +
              end
         | 
| 109 | 
            +
            end
         | 
    
        data/spec/spec_helper.rb
    ADDED
    
    
    
        metadata
    ADDED
    
    | @@ -0,0 +1,146 @@ | |
| 1 | 
            +
            --- !ruby/object:Gem::Specification
         | 
| 2 | 
            +
            name: env_lint
         | 
| 3 | 
            +
            version: !ruby/object:Gem::Version
         | 
| 4 | 
            +
              version: 0.0.1
         | 
| 5 | 
            +
            platform: ruby
         | 
| 6 | 
            +
            authors:
         | 
| 7 | 
            +
            - Tim Fischbach
         | 
| 8 | 
            +
            autorequire: 
         | 
| 9 | 
            +
            bindir: bin
         | 
| 10 | 
            +
            cert_chain: []
         | 
| 11 | 
            +
            date: 2014-02-19 00:00:00.000000000 Z
         | 
| 12 | 
            +
            dependencies:
         | 
| 13 | 
            +
            - !ruby/object:Gem::Dependency
         | 
| 14 | 
            +
              name: formatador
         | 
| 15 | 
            +
              requirement: !ruby/object:Gem::Requirement
         | 
| 16 | 
            +
                requirements:
         | 
| 17 | 
            +
                - - '>='
         | 
| 18 | 
            +
                  - !ruby/object:Gem::Version
         | 
| 19 | 
            +
                    version: '0'
         | 
| 20 | 
            +
              type: :runtime
         | 
| 21 | 
            +
              prerelease: false
         | 
| 22 | 
            +
              version_requirements: !ruby/object:Gem::Requirement
         | 
| 23 | 
            +
                requirements:
         | 
| 24 | 
            +
                - - '>='
         | 
| 25 | 
            +
                  - !ruby/object:Gem::Version
         | 
| 26 | 
            +
                    version: '0'
         | 
| 27 | 
            +
            - !ruby/object:Gem::Dependency
         | 
| 28 | 
            +
              name: capistrano
         | 
| 29 | 
            +
              requirement: !ruby/object:Gem::Requirement
         | 
| 30 | 
            +
                requirements:
         | 
| 31 | 
            +
                - - ~>
         | 
| 32 | 
            +
                  - !ruby/object:Gem::Version
         | 
| 33 | 
            +
                    version: '2.9'
         | 
| 34 | 
            +
              type: :runtime
         | 
| 35 | 
            +
              prerelease: false
         | 
| 36 | 
            +
              version_requirements: !ruby/object:Gem::Requirement
         | 
| 37 | 
            +
                requirements:
         | 
| 38 | 
            +
                - - ~>
         | 
| 39 | 
            +
                  - !ruby/object:Gem::Version
         | 
| 40 | 
            +
                    version: '2.9'
         | 
| 41 | 
            +
            - !ruby/object:Gem::Dependency
         | 
| 42 | 
            +
              name: rspec
         | 
| 43 | 
            +
              requirement: !ruby/object:Gem::Requirement
         | 
| 44 | 
            +
                requirements:
         | 
| 45 | 
            +
                - - '='
         | 
| 46 | 
            +
                  - !ruby/object:Gem::Version
         | 
| 47 | 
            +
                    version: 3.0.0.beta1
         | 
| 48 | 
            +
              type: :development
         | 
| 49 | 
            +
              prerelease: false
         | 
| 50 | 
            +
              version_requirements: !ruby/object:Gem::Requirement
         | 
| 51 | 
            +
                requirements:
         | 
| 52 | 
            +
                - - '='
         | 
| 53 | 
            +
                  - !ruby/object:Gem::Version
         | 
| 54 | 
            +
                    version: 3.0.0.beta1
         | 
| 55 | 
            +
            - !ruby/object:Gem::Dependency
         | 
| 56 | 
            +
              name: bundler
         | 
| 57 | 
            +
              requirement: !ruby/object:Gem::Requirement
         | 
| 58 | 
            +
                requirements:
         | 
| 59 | 
            +
                - - ~>
         | 
| 60 | 
            +
                  - !ruby/object:Gem::Version
         | 
| 61 | 
            +
                    version: '1.3'
         | 
| 62 | 
            +
              type: :development
         | 
| 63 | 
            +
              prerelease: false
         | 
| 64 | 
            +
              version_requirements: !ruby/object:Gem::Requirement
         | 
| 65 | 
            +
                requirements:
         | 
| 66 | 
            +
                - - ~>
         | 
| 67 | 
            +
                  - !ruby/object:Gem::Version
         | 
| 68 | 
            +
                    version: '1.3'
         | 
| 69 | 
            +
            - !ruby/object:Gem::Dependency
         | 
| 70 | 
            +
              name: rake
         | 
| 71 | 
            +
              requirement: !ruby/object:Gem::Requirement
         | 
| 72 | 
            +
                requirements:
         | 
| 73 | 
            +
                - - '>='
         | 
| 74 | 
            +
                  - !ruby/object:Gem::Version
         | 
| 75 | 
            +
                    version: '0'
         | 
| 76 | 
            +
              type: :development
         | 
| 77 | 
            +
              prerelease: false
         | 
| 78 | 
            +
              version_requirements: !ruby/object:Gem::Requirement
         | 
| 79 | 
            +
                requirements:
         | 
| 80 | 
            +
                - - '>='
         | 
| 81 | 
            +
                  - !ruby/object:Gem::Version
         | 
| 82 | 
            +
                    version: '0'
         | 
| 83 | 
            +
            description: 
         | 
| 84 | 
            +
            email:
         | 
| 85 | 
            +
            - mail@timfischbach.de
         | 
| 86 | 
            +
            executables: []
         | 
| 87 | 
            +
            extensions: []
         | 
| 88 | 
            +
            extra_rdoc_files: []
         | 
| 89 | 
            +
            files:
         | 
| 90 | 
            +
            - .gitignore
         | 
| 91 | 
            +
            - .travis.yml
         | 
| 92 | 
            +
            - Gemfile
         | 
| 93 | 
            +
            - LICENSE.txt
         | 
| 94 | 
            +
            - README.md
         | 
| 95 | 
            +
            - Rakefile
         | 
| 96 | 
            +
            - env_lint.gemspec
         | 
| 97 | 
            +
            - lib/env_lint.rb
         | 
| 98 | 
            +
            - lib/env_lint/capistrano.rb
         | 
| 99 | 
            +
            - lib/env_lint/dot_env_file.rb
         | 
| 100 | 
            +
            - lib/env_lint/dot_env_parser.rb
         | 
| 101 | 
            +
            - lib/env_lint/env_key_parser.rb
         | 
| 102 | 
            +
            - lib/env_lint/errors.rb
         | 
| 103 | 
            +
            - lib/env_lint/formatter.rb
         | 
| 104 | 
            +
            - lib/env_lint/linted_env.rb
         | 
| 105 | 
            +
            - lib/env_lint/tasks.rb
         | 
| 106 | 
            +
            - lib/env_lint/variable.rb
         | 
| 107 | 
            +
            - lib/env_lint/version.rb
         | 
| 108 | 
            +
            - spec/env_lint/capistrano_spec.rb
         | 
| 109 | 
            +
            - spec/env_lint/dot_env_file_spec.rb
         | 
| 110 | 
            +
            - spec/env_lint/dot_env_parser_spec.rb
         | 
| 111 | 
            +
            - spec/env_lint/env_key_parser_spec.rb
         | 
| 112 | 
            +
            - spec/env_lint/linted_env_spec.rb
         | 
| 113 | 
            +
            - spec/integration_spec.rb
         | 
| 114 | 
            +
            - spec/spec_helper.rb
         | 
| 115 | 
            +
            homepage: ''
         | 
| 116 | 
            +
            licenses:
         | 
| 117 | 
            +
            - MIT
         | 
| 118 | 
            +
            metadata: {}
         | 
| 119 | 
            +
            post_install_message: 
         | 
| 120 | 
            +
            rdoc_options: []
         | 
| 121 | 
            +
            require_paths:
         | 
| 122 | 
            +
            - lib
         | 
| 123 | 
            +
            required_ruby_version: !ruby/object:Gem::Requirement
         | 
| 124 | 
            +
              requirements:
         | 
| 125 | 
            +
              - - '>='
         | 
| 126 | 
            +
                - !ruby/object:Gem::Version
         | 
| 127 | 
            +
                  version: '0'
         | 
| 128 | 
            +
            required_rubygems_version: !ruby/object:Gem::Requirement
         | 
| 129 | 
            +
              requirements:
         | 
| 130 | 
            +
              - - '>='
         | 
| 131 | 
            +
                - !ruby/object:Gem::Version
         | 
| 132 | 
            +
                  version: '0'
         | 
| 133 | 
            +
            requirements: []
         | 
| 134 | 
            +
            rubyforge_project: 
         | 
| 135 | 
            +
            rubygems_version: 2.2.2
         | 
| 136 | 
            +
            signing_key: 
         | 
| 137 | 
            +
            specification_version: 4
         | 
| 138 | 
            +
            summary: Lint the environment according by .env.example
         | 
| 139 | 
            +
            test_files:
         | 
| 140 | 
            +
            - spec/env_lint/capistrano_spec.rb
         | 
| 141 | 
            +
            - spec/env_lint/dot_env_file_spec.rb
         | 
| 142 | 
            +
            - spec/env_lint/dot_env_parser_spec.rb
         | 
| 143 | 
            +
            - spec/env_lint/env_key_parser_spec.rb
         | 
| 144 | 
            +
            - spec/env_lint/linted_env_spec.rb
         | 
| 145 | 
            +
            - spec/integration_spec.rb
         | 
| 146 | 
            +
            - spec/spec_helper.rb
         |