gem_enforcer 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 259683929871f4f3ba6aabed1d4730837860fd2fe0c983f19baf735da1be2edc
4
+ data.tar.gz: 962e62bd2f891d48b4abf50efe0e44fbb4d43938ef745189c075c5753ca86faf
5
+ SHA512:
6
+ metadata.gz: 86b8b4f2755007c7f1973c2dc844066b4060cc0eb1729906cdabcbd3ad91d17eed1274be1bc3c26a01d6c5459ed660cd547657409b25b844b45d7dc75919e8e0
7
+ data.tar.gz: debf1f1abd7668a5a36413ee1156b69bba9b0a8d0cb0f4bfd16801c5db74cb58d66e3175a5b1e92d21d1df216a8cebe7da9a0b3db996152aaafef450304d2a4d
@@ -0,0 +1,29 @@
1
+ version: 2.1
2
+
3
+ orbs:
4
+ ruby: circleci/ruby@1.0
5
+ node: circleci/node@2
6
+ cst: cst/framework@1
7
+
8
+ workflows:
9
+ version: 2
10
+ yeet-le-jobs:
11
+ jobs:
12
+ - cst/enforce-gem-version-bump
13
+ - cst/rspec-ruby:
14
+ rspec-system-args: "SIMPLE_COV_RUN=true"
15
+ cc-report-collect-ruby: "3.2.5"
16
+ matrix:
17
+ parameters:
18
+ ruby-version: ["3.2.5", "3.3.5"]
19
+ alias: required-matrix-tests
20
+ name: test-ruby<< matrix.ruby-version >>
21
+ - cst/publish-gem:
22
+ publish-git: true
23
+ publish-default-gem: true
24
+ requires:
25
+ - required-matrix-tests
26
+ filters:
27
+ branches:
28
+ only:
29
+ - main
data/.gitignore ADDED
@@ -0,0 +1,16 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /_yardoc/
4
+ /coverage/
5
+ /doc/
6
+ /pkg/
7
+ /spec/reports/
8
+ /spec/examples.txt*
9
+ /tmp/
10
+ .DS_Store
11
+
12
+ # rspec failure tracking
13
+ .rspec_status
14
+
15
+ # generated gems
16
+ *.gem
data/.rspec ADDED
@@ -0,0 +1,3 @@
1
+ --format documentation
2
+ --color
3
+ --require spec_helper
data/.ruby-version ADDED
@@ -0,0 +1 @@
1
+ 3.3.4
data/CHANGELOG.md ADDED
@@ -0,0 +1,23 @@
1
+ # Changelog
2
+
3
+ All notable changes to this project will be documented in this file.
4
+
5
+ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6
+ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
+
8
+ ## [Unreleased]
9
+
10
+ ## [X.y.z] - YYYY-MM-DD
11
+
12
+ ### Added
13
+
14
+ ### Changed
15
+ - Changed feature 1
16
+
17
+ ### Removed
18
+ - Removed feature 1
19
+
20
+ ### Fixed
21
+ - Bug fix 1
22
+
23
+ [//]: # (Added, Changed, Removed, Fixed are the possible headers for each changlog version)
data/CODEOWNERS ADDED
@@ -0,0 +1 @@
1
+ * @matt-taylor
data/Dockerfile ADDED
@@ -0,0 +1,15 @@
1
+ FROM ruby:3.2.5
2
+ RUN cd /tmp && curl -L --output ghr.tar.gz https://github.com/tcnksm/ghr/releases/download/v0.12.0/ghr_v0.12.0_linux_amd64.tar.gz && \
3
+ tar -xzvf ghr.tar.gz && chmod +x ghr_v0.12.0_linux_amd64/ghr && mv ghr_v0.12.0_linux_amd64/ghr /usr/local/bin/ghr && rm -rf /tmp/*
4
+
5
+ WORKDIR /gem
6
+ COPY Gemfile /gem/Gemfile
7
+
8
+ COPY gem_enforcer.gemspec /gem/gem_enforcer.gemspec
9
+ COPY lib/gem_enforcer/version.rb /gem/lib/gem_enforcer/version.rb
10
+
11
+
12
+ RUN gem update --system && gem install bundler && bundle install --jobs=3 --retry=3 && \
13
+ rm -rf /usr/local/bundle/cache
14
+
15
+ COPY . /gem
data/Gemfile ADDED
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ source "https://rubygems.org"
4
+
5
+ gemspec
6
+
7
+ gem "faraday", "< 2.6"
8
+ gem 'activesupport'
9
+ gem 'faker'
10
+ gem 'pry'
11
+ gem 'rspec', '~> 3.0'
12
+ gem 'rspec_junit_formatter'
13
+ gem 'simplecov', require: false, group: :test
data/Gemfile.lock ADDED
@@ -0,0 +1,101 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ gem_enforcer (0.0.1)
5
+ class_composer (>= 1.0)
6
+ faraday
7
+ octokit
8
+
9
+ GEM
10
+ remote: https://rubygems.org/
11
+ specs:
12
+ activesupport (7.1.4)
13
+ base64
14
+ bigdecimal
15
+ concurrent-ruby (~> 1.0, >= 1.0.2)
16
+ connection_pool (>= 2.2.5)
17
+ drb
18
+ i18n (>= 1.6, < 2)
19
+ minitest (>= 5.1)
20
+ mutex_m
21
+ tzinfo (~> 2.0)
22
+ addressable (2.8.7)
23
+ public_suffix (>= 2.0.2, < 7.0)
24
+ base64 (0.2.0)
25
+ bigdecimal (3.1.8)
26
+ byebug (11.1.3)
27
+ class_composer (1.0.2)
28
+ coderay (1.1.3)
29
+ concurrent-ruby (1.3.4)
30
+ connection_pool (2.4.1)
31
+ diff-lcs (1.5.1)
32
+ docile (1.4.1)
33
+ drb (2.2.1)
34
+ faker (3.4.2)
35
+ i18n (>= 1.8.11, < 2)
36
+ faraday (2.5.2)
37
+ faraday-net_http (>= 2.0, < 3.1)
38
+ ruby2_keywords (>= 0.0.4)
39
+ faraday-net_http (3.0.2)
40
+ i18n (1.14.5)
41
+ concurrent-ruby (~> 1.0)
42
+ method_source (1.1.0)
43
+ minitest (5.25.1)
44
+ mutex_m (0.2.0)
45
+ octokit (9.1.0)
46
+ faraday (>= 1, < 3)
47
+ sawyer (~> 0.9)
48
+ pry (0.14.2)
49
+ coderay (~> 1.1)
50
+ method_source (~> 1.0)
51
+ pry-byebug (3.10.1)
52
+ byebug (~> 11.0)
53
+ pry (>= 0.13, < 0.15)
54
+ public_suffix (6.0.1)
55
+ rake (12.3.3)
56
+ rspec (3.13.0)
57
+ rspec-core (~> 3.13.0)
58
+ rspec-expectations (~> 3.13.0)
59
+ rspec-mocks (~> 3.13.0)
60
+ rspec-core (3.13.1)
61
+ rspec-support (~> 3.13.0)
62
+ rspec-expectations (3.13.3)
63
+ diff-lcs (>= 1.2.0, < 2.0)
64
+ rspec-support (~> 3.13.0)
65
+ rspec-mocks (3.13.1)
66
+ diff-lcs (>= 1.2.0, < 2.0)
67
+ rspec-support (~> 3.13.0)
68
+ rspec-support (3.13.1)
69
+ rspec_junit_formatter (0.6.0)
70
+ rspec-core (>= 2, < 4, != 2.12.0)
71
+ ruby2_keywords (0.0.5)
72
+ sawyer (0.9.2)
73
+ addressable (>= 2.3.5)
74
+ faraday (>= 0.17.3, < 3)
75
+ simplecov (0.22.0)
76
+ docile (~> 1.1)
77
+ simplecov-html (~> 0.11)
78
+ simplecov_json_formatter (~> 0.1)
79
+ simplecov-html (0.13.0)
80
+ simplecov_json_formatter (0.1.4)
81
+ tzinfo (2.0.6)
82
+ concurrent-ruby (~> 1.0)
83
+
84
+ PLATFORMS
85
+ aarch64-linux
86
+ ruby
87
+
88
+ DEPENDENCIES
89
+ activesupport
90
+ faker
91
+ faraday (< 2.6)
92
+ gem_enforcer!
93
+ pry
94
+ pry-byebug
95
+ rake (~> 12.0)
96
+ rspec (~> 3.0)
97
+ rspec_junit_formatter
98
+ simplecov
99
+
100
+ BUNDLED WITH
101
+ 2.5.18
data/Makefile ADDED
@@ -0,0 +1,21 @@
1
+ .PHONY: bash build bundle rspec
2
+
3
+ APP_NAME?=gem_enforcer
4
+
5
+ build: #: Build the containers that we'll need
6
+ docker-compose build --pull
7
+
8
+ bash: #: Get a bash prompt on the core container
9
+ docker-compose run --rm -e RAILS_ENV=development $(APP_NAME) bash
10
+
11
+ bash_test: #: Get a test bash prompt on the core container
12
+ docker-compose run --rm -e RAILS_ENV=test $(APP_NAME) bash
13
+
14
+ down: #: Bring down the service -- Destroys everything in redis and all containers
15
+ docker-compose down
16
+
17
+ clean: #: Clean up stopped/exited containers
18
+ docker-compose rm -f
19
+
20
+ bundle: #: install gems for Dummy App with
21
+ docker-compose run --rm $(APP_NAME) bundle install
data/README.md ADDED
@@ -0,0 +1,33 @@
1
+ # GemEnforcer
2
+
3
+ `GemEnforcer` is
4
+
5
+ ## Installation
6
+
7
+ Add this line to your application's Gemfile:
8
+
9
+ ```ruby
10
+ gem 'gem_enforcer'
11
+ ```
12
+
13
+ And then execute:
14
+
15
+ $ bundle install
16
+
17
+ Or install it yourself as:
18
+
19
+ $ gem install gem_enforcer
20
+
21
+
22
+ ## Development
23
+
24
+ This gem can be developed against local machine or while using docker. Simpleified Docker commands can be found in the `Makefile` or execute `make help`
25
+
26
+ ## Contributing
27
+
28
+ This gem welcomes contribution.
29
+
30
+ Bug reports and pull requests are welcome on GitHub at
31
+ https://github.com/matt-taylor/json_schematize.
32
+
33
+
data/bin/console ADDED
@@ -0,0 +1,11 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "bundler/setup"
4
+ require "gem_enforcer"
5
+
6
+ # You can add fixtures and/or initialization code here to make experimenting
7
+ # with your gem easier.
8
+
9
+ require "pry"
10
+ GemEnforcer::Setup.validate_yml!
11
+ Pry.start
@@ -0,0 +1,15 @@
1
+ services:
2
+ gem_enforcer:
3
+ command: tail -f /dev/null
4
+ build:
5
+ context: .
6
+ dockerfile: ./Dockerfile
7
+ volumes:
8
+ - .:/gem
9
+ - ..:/local
10
+ - bundle-cache:/usr/local/bundle:delegated
11
+ environment:
12
+ GITHUB_TOKEN: ${GITHUB_TOKEN}
13
+
14
+ volumes:
15
+ bundle-cache:
@@ -0,0 +1,40 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "lib/gem_enforcer/version"
4
+
5
+ Gem::Specification.new do |spec|
6
+ spec.name = "gem_enforcer"
7
+ spec.version = GemEnforcer::VERSION
8
+ spec.authors = ["Matt Taylor"]
9
+ spec.email = ["mattius.taylor@gmail.com"]
10
+
11
+ spec.summary = "Long form of the description"
12
+ spec.description = "Provide the ability to validate targetted gems are up to date before executing commands"
13
+ spec.homepage = "https://github.com/matt-taylor/gem_enforcer"
14
+ spec.license = "MIT"
15
+
16
+ spec.required_ruby_version = Gem::Requirement.new(">= 3.2")
17
+
18
+ spec.metadata = {
19
+ "homepage_uri" => spec.homepage,
20
+ "source_code_uri" => spec.homepage,
21
+ }
22
+
23
+ # Specify which files should be added to the gem when it is released.
24
+ # The `git ls-files -z` loads the files in the RubyGem that have been added into git.
25
+ spec.files = Dir.chdir(File.expand_path(__dir__)) do
26
+ %x(git ls-files -z).split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
27
+ end
28
+ spec.bindir = "exe"
29
+ spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
30
+ spec.require_paths = ["lib"]
31
+
32
+ spec.add_dependency "class_composer", ">= 1.0"
33
+ spec.add_dependency "faraday"
34
+ spec.add_dependency "octokit"
35
+
36
+ spec.add_development_dependency "pry-byebug"
37
+ spec.add_development_dependency "rake", "~> 12.0"
38
+ spec.add_development_dependency "rspec", "~> 3.0"
39
+ spec.add_development_dependency "simplecov", "~> 0.17.0"
40
+ end
data/gem_enforcer.yml ADDED
@@ -0,0 +1,61 @@
1
+ ---
2
+ invalid_config:
3
+ log_level: error
4
+ behavior: exit
5
+ gems:
6
+ rails:
7
+ on_failure:
8
+ log_level: warn # on failure, log a warn message
9
+ behavior: exit
10
+ version_threshold:
11
+ minor: 3 # within the last 3 minor releases
12
+ server: true # when set to true, defaults to https://rubygems.org
13
+
14
+ shoryuken:
15
+ on_failure:
16
+ log_level: info # on failure, log a info message
17
+ behavior: skip
18
+ version_threshold:
19
+ # Must be within the most current version released
20
+ # If above is true, must be within the last 2 minor releases
21
+ # If above is true, must be within the last 3 patches released
22
+ major: 0 # within the current major version
23
+ minor: 2 # within the last 3 minor releases
24
+ patch: 3 # within the last 3 patches released
25
+ server: true # when set to true, defaults to https://rubygems.org
26
+
27
+ redis:
28
+ on_failure:
29
+ log_level: error
30
+ behavior: raise # on failure, log error and exit(1)
31
+ enforce_insync: true # Ensure gem is up to date
32
+ server:
33
+ source: https://rubygems.org
34
+
35
+ faraday:
36
+ on_failure:
37
+ log_level: error
38
+ behavior: exit # on failure, log error and exit(1)
39
+ enforce_insync: true
40
+ # version_threshold:
41
+
42
+ # For most gems, this is effectively the same as `enforce_insync: true`
43
+ # major: 0
44
+ # minor: 6
45
+ # patch: 0
46
+ server:
47
+ source: https://rubygems.org
48
+
49
+ custom_gem:
50
+ enforce_insync: true
51
+ server: true
52
+ sidekiq:
53
+ version_threshold:
54
+ releases: 5 # within the last 5 releases
55
+ git:
56
+ owner: sidekiq # Github root page for the gem
57
+
58
+
59
+
60
+
61
+
@@ -0,0 +1,37 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "class_composer"
4
+ require "logger"
5
+ require "gem_enforcer/errors"
6
+
7
+ module GemEnforcer
8
+ class Configuration
9
+ include ClassComposer::Generator
10
+
11
+ GITHUB_ACCESS_TOKEN = Proc.new do
12
+ token = ENV.fetch("GITHUB_TOKEN") do
13
+ ENV.fetch("BUNDLE_GITHUB__COM") do
14
+ raise GemEnforcer::Error, "Expected access token in `GITHUB_TOKEN` or `BUNDLE_GITHUB__COM`"
15
+ end
16
+ end
17
+ if token.end_with?(":x-oauth-basic")
18
+ token.split(":x-oauth-basic")[0]
19
+ else
20
+ token
21
+ end
22
+ end
23
+
24
+ DEFAULT_YAML_PATH = Proc.new do
25
+ if defined?(Rails)
26
+ "#{Rails.root}/config/gem_enforcer.yml"
27
+ else
28
+ "/gem/gem_enforcer.yml"
29
+ end
30
+ end
31
+
32
+ add_composer :github_access_token, allowed: String, default: GITHUB_ACCESS_TOKEN.()
33
+ add_composer :yml_config_path, allowed: String, default: DEFAULT_YAML_PATH.()
34
+ add_composer :raise_on_invalid_config, allowed: [TrueClass, FalseClass], default: true
35
+ add_composer :logger, allowed: [Logger, (TTY::Logger if defined?(TTY::Logger))].compact, default: Logger.new(STDOUT)
36
+ end
37
+ end
@@ -0,0 +1,6 @@
1
+ # frozen_string_literal: true
2
+
3
+ module GemEnforcer
4
+ class Error < StandardError; end
5
+ class ValidationError < Error; end
6
+ end
@@ -0,0 +1,35 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "faraday"
4
+
5
+ module GemEnforcer
6
+ module Retrieve
7
+ class GemServer
8
+ def initialize(source: DEFAULT_SOURCE)
9
+ @source = source
10
+ end
11
+
12
+ def gem_versions(name:)
13
+ raw_gem_versions = raw_server_versions.select { _1.match?(/^#{name} /) }
14
+ return [] if raw_gem_versions.nil? || raw_gem_versions.empty?
15
+
16
+ versions = raw_gem_versions.map do |metadata|
17
+ raw_name, raw_version_list, _sha = metadata.split(" ")
18
+ next if raw_name != name
19
+
20
+ raw_version_list.split(",").map { Gem::Version.new(_1) }
21
+ end.flatten.compact.uniq
22
+
23
+ versions
24
+ end
25
+
26
+ # expensive call .. do this once gem source
27
+ def raw_server_versions
28
+ @server_versions ||= begin
29
+ api_call = Faraday.new(url: @source).get("versions")
30
+ api_call.body.split("\n")
31
+ end
32
+ end
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,28 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "octokit"
4
+
5
+ module GemEnforcer
6
+ module Retrieve
7
+ class GitTag
8
+ def initialize(owner:, access_token: GemEnforcer.github_access_token)
9
+ @access_token = access_token
10
+ @owner = owner
11
+ end
12
+
13
+ def gem_versions(name:)
14
+ repo_name = "#{@owner}/#{name}"
15
+ releases = client.releases(repo_name, { per_page: 50 })
16
+ while next_page_href = client.last_response.rels[:next]&.href
17
+ releases.concat(client.get(next_page_href, { per_page: 50 }))
18
+ end
19
+
20
+ releases.map { Gem::Version.new(_1.tag_name.gsub(/.*?(?=\d*\.)/im, "")) }.compact
21
+ end
22
+
23
+ def client
24
+ @client ||= ::Octokit::Client.new(access_token: @access_token)
25
+ end
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,28 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "gem_enforcer/retrieve/gem_server"
4
+ require "gem_enforcer/retrieve/git_tag"
5
+
6
+ module GemEnforcer
7
+ module Retrieve
8
+ module_function
9
+
10
+ def server_retrieval_by_source(source:)
11
+ @server_retrieval_by_source ||= {}
12
+
13
+ return @server_retrieval_by_source[source] if @server_retrieval_by_source[source]
14
+
15
+ @server_retrieval_by_source[source] = GemServer.new(source: source)
16
+ @server_retrieval_by_source[source]
17
+ end
18
+
19
+ def github_retrieval_by_owner(owner:)
20
+ @github_retrieval_by_owner ||= {}
21
+
22
+ return @github_retrieval_by_owner[owner] if @github_retrieval_by_owner[owner]
23
+
24
+ @github_retrieval_by_owner[owner] = GitTag.new(owner: owner)
25
+ @github_retrieval_by_owner[owner]
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,58 @@
1
+ # frozen_string_literal: true
2
+
3
+ module GemEnforcer
4
+ module Setup
5
+ module Helper
6
+ module OnFailure
7
+ ALLOWED_ON_FAILURE = [:raise, :exit, DEFAULT_BEHAVIOR = :none]
8
+ DEFAULT_LOG_LEVEL = :error
9
+
10
+ def validate_on_failure
11
+ on_failure = params["on_failure"]
12
+ if on_failure.nil?
13
+ @on_failure_log_level = DEFAULT_LOG_LEVEL
14
+ @on_failure_behavior = DEFAULT_BEHAVIOR
15
+ return true
16
+ end
17
+
18
+ if Hash === on_failure
19
+ @on_failure_log_level = on_failure.fetch("log_level", DEFAULT_LOG_LEVEL)
20
+ behavior = on_failure.fetch("behavior", DEFAULT_BEHAVIOR).to_sym rescue nil
21
+ if ALLOWED_ON_FAILURE.include?(behavior)
22
+ @on_failure_behavior = behavior
23
+ return true
24
+ else
25
+ errors << "on_failure.behavior: Expected behavior to be in #{ALLOWED_ON_FAILURE}"
26
+ return false
27
+ end
28
+ end
29
+
30
+ errors << "on_failure: Expected value hash with :behavior and/or :log_level keys"
31
+ false
32
+ end
33
+
34
+ def on_failure_default_message
35
+ message = params.dig("on_failure", "message") rescue nil
36
+ return message if message
37
+
38
+ version_default_message
39
+ end
40
+
41
+ def on_failure_behavior(msg:)
42
+
43
+ end
44
+
45
+ def execute_on_failure!(behavior:, msg: on_failure_default_message)
46
+ GemEnforcer.logger.public_send(@on_failure_log_level, "Validation failed for #{gem_name}. Current Version is #{current_version}. #{msg}")
47
+
48
+ case @on_failure_behavior.to_sym
49
+ when :raise
50
+ raise ValidationError, "Validation failed for #{gem_name}. Current Version is #{current_version}. #{msg}"
51
+ when :exit
52
+ Kernel.exit(1)
53
+ end
54
+ end
55
+ end
56
+ end
57
+ end
58
+ end
@@ -0,0 +1,60 @@
1
+ # frozen_string_literal: true
2
+
3
+ module GemEnforcer
4
+ module Setup
5
+ module Helper
6
+ module Retrieval
7
+ def validate_retrieval
8
+ if params["server"]
9
+ @retrieval_method = :server
10
+ server_result = _server_validate
11
+ @factory_version_list = Retrieve.server_retrieval_by_source(source: @retrieval_source)
12
+
13
+ server_result
14
+ elsif params["git"]
15
+ @retrieval_method = :git
16
+ git_result = _git_validate
17
+ @factory_version_list = Retrieve.github_retrieval_by_owner(owner: @retrieval_owner)
18
+
19
+ git_result
20
+ else
21
+ errors << "retrieval: Missing retrieval type. Expected `server` or `git`"
22
+ false
23
+ end
24
+ end
25
+
26
+ def retrieve_version_list
27
+ @factory_version_list.gem_versions(name: @gem_name).sort
28
+ end
29
+
30
+ def _server_validate
31
+ server = params.dig("server")
32
+
33
+ if server == true
34
+ @retrieval_source = GemEnforcer::DEFAULT_SERVER_SOURCE
35
+ return true
36
+ end
37
+
38
+ if Hash === server && server["source"]
39
+ @retrieval_source = server["source"]
40
+ return true
41
+ end
42
+
43
+ errors << "retrieval.server: Missing source"
44
+ false
45
+ end
46
+
47
+ def _git_validate
48
+ git = params.dig("git")
49
+ if Hash === git && git["owner"]
50
+ @retrieval_owner = git["owner"]
51
+ return true
52
+ end
53
+
54
+ errors << "retrieval.git: Missing owner"
55
+ false
56
+ end
57
+ end
58
+ end
59
+ end
60
+ end
@@ -0,0 +1,151 @@
1
+ # frozen_string_literal: true
2
+
3
+ module GemEnforcer
4
+ module Setup
5
+ module Helper
6
+ module Version
7
+ ALLOWED_VERSION_REALEASE = :releases
8
+ ALLOWED_VERSION_SEMVER = [:major, :minor, :patch]
9
+ ALLOWED_VERSION_THRESHOLD_KEYS = [ALLOWED_VERSION_REALEASE, *ALLOWED_VERSION_SEMVER]
10
+
11
+ def version_default_message
12
+ @default_version_message
13
+ end
14
+
15
+ def validate_version
16
+ if params.keys.include?("enforce_insync") && params.keys.include?("version_threshold")
17
+ errors << "version: Must only contain `enforce_insync` or `version_threshold`"
18
+ return false
19
+ end
20
+
21
+ if params["enforce_insync"]
22
+ @version_type = :enforce_insync
23
+ @default_version_message = "Version must be the most recently released version."
24
+ return true
25
+ end
26
+
27
+ @version_type = :version_threshold
28
+ version_threshold_keys = params["version_threshold"]&.keys || []
29
+ if version_threshold_keys.sort == [ALLOWED_VERSION_REALEASE.to_s].sort
30
+ @version_threshold = :releases
31
+ @default_version_message = "Version must be within #{params["version_threshold"]["releases"]} of the most recently released versions"
32
+ _releases_validate
33
+ elsif ALLOWED_VERSION_THRESHOLD_KEYS.any? { version_threshold_keys.include?(_1.to_s) }
34
+ @version_threshold = :semver
35
+ message = []
36
+ message << "#{params["version_threshold"]["major"]} major versions" if params["version_threshold"]["major"]
37
+ message << "#{params["version_threshold"]["minor"]} minor versions" if params["version_threshold"]["minor"]
38
+ message << "#{params["version_threshold"]["patch"]} patch versions" if params["version_threshold"]["patch"]
39
+ @default_version_message = "Version must be within #{message.join(" and ")} of the most recent release."
40
+ _semver_validate
41
+ else
42
+ errors << "version.version_threshold: Expected keys to contain [#{ALLOWED_VERSION_REALEASE}] or #{ALLOWED_VERSION_SEMVER}"
43
+ false
44
+ end
45
+ end
46
+
47
+ def _semver_validate
48
+ boolean = true
49
+ if major = params["version_threshold"]["major"]
50
+ unless Integer === major
51
+ boolean = false
52
+ errors << "version.version_threshold.major: Expected value to be an Integer"
53
+ end
54
+ end
55
+
56
+ if minor = params["version_threshold"]["minor"]
57
+ unless Integer === minor
58
+ boolean = false
59
+ errors << "version.version_threshold.minor: Expected value to be an Integer"
60
+ end
61
+ end
62
+
63
+ if patch = params["version_threshold"]["patch"]
64
+ unless Integer === patch
65
+ boolean = false
66
+ errors << "version.version_threshold.patch: Expected value to be an Integer"
67
+ end
68
+ end
69
+
70
+ boolean
71
+ end
72
+
73
+ def _releases_validate
74
+ release_count = params["version_threshold"]["releases"]
75
+ unless Integer === release_count
76
+ errors << "version.version_threshold.releases: Expected value to be an Integer"
77
+ return false
78
+ end
79
+
80
+ true
81
+ end
82
+
83
+ def version_execute?(version_list:)
84
+ if @version_type == :enforce_insync
85
+ __validate_enforce_insync?(version_list:)
86
+ else
87
+ if @version_threshold == :releases
88
+ __validate_version_threshold_releases?(version_list:)
89
+ else
90
+ __validate_version_threshold_semver?(version_list:)
91
+ end
92
+ end
93
+ end
94
+
95
+ def __validate_version_threshold_semver?(version_list:)
96
+ if max_major_versions_behind = params.dig("version_threshold", "major")
97
+ return false unless __threshold_semver_distance(type: :major, number: current_version.segments[0], list: version_list.map { _1.segments[0] }, threshold: max_major_versions_behind)
98
+ end
99
+
100
+ if max_minor_versions_behind = params.dig("version_threshold", "minor")
101
+ # Select only the minor versions that match the major version
102
+ current_major_version = current_version.segments[0]
103
+ minor_version_check_list = version_list.select { _1.segments[0] == current_major_version }.map { _1.segments[1] }
104
+ return false unless __threshold_semver_distance(type: :minor, number: current_version.segments[1], list: minor_version_check_list, threshold: max_minor_versions_behind)
105
+ end
106
+
107
+ if max_patch_versions_behind = params.dig("version_threshold", "patch")
108
+ # Select only the patch versions that match the major version
109
+ current_major_minor_version = current_version.segments[0..1]
110
+ patch_version_check_list = version_list.select { _1.segments[0..1] == current_major_minor_version }.map { _1.segments[2] }
111
+ return false unless __threshold_semver_distance(type: :patch, number: current_version.segments[2], list: patch_version_check_list, threshold: max_patch_versions_behind)
112
+ end
113
+
114
+ true
115
+ end
116
+
117
+ def __threshold_semver_distance(type:, number:, list:, threshold:)
118
+ # remove duplicates ans sort in highest to lowest number
119
+ uniq_list = list.uniq.sort.reverse
120
+
121
+ # get the position in the sorted array
122
+ position_in_sorted_array = uniq_list.find_index(number)
123
+
124
+ # if position is less than or equal to the threshold, we are good
125
+ # otherwise, it is out of compliance
126
+ return true if position_in_sorted_array <= threshold
127
+
128
+ @default_version_message += " Failed to match #{type} version threshold"
129
+
130
+ false
131
+ end
132
+
133
+ def __validate_version_threshold_releases?(version_list:)
134
+ releases_behind = params.dig("version_threshold", "releases").to_i
135
+
136
+ min_version_allowed = version_list[-releases_behind]
137
+ current_version >= min_version_allowed
138
+ end
139
+
140
+ def __validate_enforce_insync?(version_list:)
141
+ max_version = version_list.max
142
+ return true if current_version >= max_version
143
+
144
+ @default_version_message += " Please upgrade to at least v#{max_version}"
145
+
146
+ false
147
+ end
148
+ end
149
+ end
150
+ end
151
+ end
@@ -0,0 +1,64 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "yaml"
4
+
5
+ require "gem_enforcer/setup/helper/retrieval"
6
+ require "gem_enforcer/setup/helper/on_failure"
7
+ require "gem_enforcer/setup/helper/version"
8
+
9
+ module GemEnforcer
10
+ module Setup
11
+ class Validate
12
+ attr_reader :gem_name, :params, :validation_status
13
+
14
+ include Helper::Retrieval
15
+ include Helper::OnFailure
16
+ include Helper::Version
17
+
18
+ def initialize(name:, **params)
19
+ @params = params
20
+ @gem_name = name
21
+
22
+ @validation_status = validate!
23
+ end
24
+
25
+ # Allow behavior to be overridden if desired
26
+ def run_validation!(behavior: nil)
27
+ unless validation_status
28
+ raise Error, "Unable to run validation with invalid config."
29
+ end
30
+
31
+ return true if current_version.nil?
32
+
33
+ return true if version_execute?(version_list: retrieve_version_list)
34
+
35
+ execute_on_failure!(behavior: behavior)
36
+ false
37
+ end
38
+
39
+ def current_version
40
+ Gem.loaded_specs[gem_name]&.version
41
+ end
42
+
43
+ def error_status
44
+ return nil if errors.empty?
45
+
46
+ errors.map { "#{gem_name}.#{_1}" }
47
+ end
48
+
49
+ private
50
+
51
+ def errors
52
+ @errors ||= []
53
+ end
54
+
55
+ def validate!
56
+ boolean = validate_retrieval
57
+ boolean &= validate_on_failure
58
+ boolean &= validate_version
59
+
60
+ boolean
61
+ end
62
+ end
63
+ end
64
+ end
@@ -0,0 +1,41 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "yaml"
4
+ require "gem_enforcer/setup/validate"
5
+
6
+ module GemEnforcer
7
+ module Setup
8
+ module_function
9
+
10
+ def validate_yml!
11
+ errors = config_yml["gems"].map do |name, metadata|
12
+ validator = Validate.new(name: name, **metadata)
13
+ validations << validator
14
+
15
+ validator.error_status
16
+ end.compact
17
+ return true if errors.empty?
18
+
19
+ log_level = config_yml.dig("invalid_config", "log_level") || "error"
20
+ behavior = config_yml.dig("invalid_config", "behavior") || "exit"
21
+ end
22
+
23
+ def validations
24
+ @validations ||= []
25
+ end
26
+
27
+ def run_validations!(behavior: nil)
28
+ validations.each { _1.run_validation!(behavior: behavior) }
29
+ end
30
+
31
+ def config_yml
32
+ @read_config_yml ||= begin
33
+ path = GemEnforcer.configuration.yml_config_path
34
+ file = File.read(path)
35
+ erb = ERB.new(file)
36
+
37
+ YAML.load(erb.result)
38
+ end
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module GemEnforcer
4
+ VERSION = "0.0.1"
5
+ end
@@ -0,0 +1,32 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "gem_enforcer/configuration"
4
+ require "gem_enforcer/errors"
5
+ require "gem_enforcer/retrieve"
6
+ require "gem_enforcer/setup"
7
+ require "gem_enforcer/version"
8
+
9
+ module GemEnforcer
10
+ DEFAULT_SERVER_SOURCE = "https://rubygems.org"
11
+ def self.configure
12
+ yield configuration if block_given?
13
+ end
14
+
15
+ def self.configuration
16
+ @configuration ||= GemEnforcer::Configuration.new
17
+ end
18
+
19
+ def self.configuration=(object)
20
+ raise ConfigError, "Expected configuration to be a GemEnforcer::Configuration" unless object.is_a?(GemEnforcer::Configuration)
21
+
22
+ @configuration = object
23
+ end
24
+
25
+ def self.github_access_token
26
+ configuration.github_access_token
27
+ end
28
+
29
+ def self.logger
30
+ configuration.logger
31
+ end
32
+ end
metadata ADDED
@@ -0,0 +1,171 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: gem_enforcer
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - Matt Taylor
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2024-09-11 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: class_composer
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: '1.0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: '1.0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: faraday
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: octokit
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: pry-byebug
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ">="
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ">="
67
+ - !ruby/object:Gem::Version
68
+ version: '0'
69
+ - !ruby/object:Gem::Dependency
70
+ name: rake
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - "~>"
74
+ - !ruby/object:Gem::Version
75
+ version: '12.0'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - "~>"
81
+ - !ruby/object:Gem::Version
82
+ version: '12.0'
83
+ - !ruby/object:Gem::Dependency
84
+ name: rspec
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - "~>"
88
+ - !ruby/object:Gem::Version
89
+ version: '3.0'
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - "~>"
95
+ - !ruby/object:Gem::Version
96
+ version: '3.0'
97
+ - !ruby/object:Gem::Dependency
98
+ name: simplecov
99
+ requirement: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - "~>"
102
+ - !ruby/object:Gem::Version
103
+ version: 0.17.0
104
+ type: :development
105
+ prerelease: false
106
+ version_requirements: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - "~>"
109
+ - !ruby/object:Gem::Version
110
+ version: 0.17.0
111
+ description: Provide the ability to validate targetted gems are up to date before
112
+ executing commands
113
+ email:
114
+ - mattius.taylor@gmail.com
115
+ executables: []
116
+ extensions: []
117
+ extra_rdoc_files: []
118
+ files:
119
+ - ".circleci/config.yml"
120
+ - ".gitignore"
121
+ - ".rspec"
122
+ - ".ruby-version"
123
+ - CHANGELOG.md
124
+ - CODEOWNERS
125
+ - Dockerfile
126
+ - Gemfile
127
+ - Gemfile.lock
128
+ - Makefile
129
+ - README.md
130
+ - bin/console
131
+ - docker-compose.yml
132
+ - gem_enforcer.gemspec
133
+ - gem_enforcer.yml
134
+ - lib/gem_enforcer.rb
135
+ - lib/gem_enforcer/configuration.rb
136
+ - lib/gem_enforcer/errors.rb
137
+ - lib/gem_enforcer/retrieve.rb
138
+ - lib/gem_enforcer/retrieve/gem_server.rb
139
+ - lib/gem_enforcer/retrieve/git_tag.rb
140
+ - lib/gem_enforcer/setup.rb
141
+ - lib/gem_enforcer/setup/helper/on_failure.rb
142
+ - lib/gem_enforcer/setup/helper/retrieval.rb
143
+ - lib/gem_enforcer/setup/helper/version.rb
144
+ - lib/gem_enforcer/setup/validate.rb
145
+ - lib/gem_enforcer/version.rb
146
+ homepage: https://github.com/matt-taylor/gem_enforcer
147
+ licenses:
148
+ - MIT
149
+ metadata:
150
+ homepage_uri: https://github.com/matt-taylor/gem_enforcer
151
+ source_code_uri: https://github.com/matt-taylor/gem_enforcer
152
+ post_install_message:
153
+ rdoc_options: []
154
+ require_paths:
155
+ - lib
156
+ required_ruby_version: !ruby/object:Gem::Requirement
157
+ requirements:
158
+ - - ">="
159
+ - !ruby/object:Gem::Version
160
+ version: '3.2'
161
+ required_rubygems_version: !ruby/object:Gem::Requirement
162
+ requirements:
163
+ - - ">="
164
+ - !ruby/object:Gem::Version
165
+ version: '0'
166
+ requirements: []
167
+ rubygems_version: 3.5.9
168
+ signing_key:
169
+ specification_version: 4
170
+ summary: Long form of the description
171
+ test_files: []