gem_enforcer 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 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: []