schmersion 1.0.0

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.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: b913b0f1449212f78614fa4d033a16ee85a839d9992b847978869c35dc4dfc29
4
+ data.tar.gz: a1452b8b38d15ce8676e20dca1ef936d783b1a66124ab7eedb054a8220cf80e9
5
+ SHA512:
6
+ metadata.gz: 380925581f98a27eb2b5fb45ab4a0e2e1b09dce184040881e8c4bf8541a71e7d74915e18cae2b09580e2454ab801e959be82ff84d3f005c9258d48079193da9e
7
+ data.tar.gz: 27720896c464ed1d871cd7841e0caa7b705135d23efe3cdc14db9644eac65b91dcae3b2588e42dabc9778f0724025189a1c3bc857afc4db3f5b8376c3744a13f
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 1.0.0
@@ -0,0 +1,20 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ $LOAD_PATH.unshift(File.expand_path('../lib', __dir__))
5
+
6
+ require 'schmersion'
7
+ require 'swamp/cli'
8
+ require 'git'
9
+ require 'colorize'
10
+
11
+ begin
12
+ cli = Swamp::CLI.new(:schmersion, version: Schmersion::VERSION)
13
+ cli.load_from_directory(File.expand_path('../cli', __dir__))
14
+ cli.dispatch(ARGV.empty? ? ['help'] : ARGV)
15
+ rescue Swamp::Error, Git::GitExecuteError, Schmersion::Error => e
16
+ warn "\e[31mError: #{e.message}\e[0m"
17
+ exit 2
18
+ rescue Interrupt
19
+ exit 3
20
+ end
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ command :help do
4
+ desc 'Display this help text'
5
+ action do |context|
6
+ puts "\e[35mWelcome to Schmersion v#{Schmersion::VERSION}\e[0m"
7
+ puts 'For documentation see https://github.com/krystal/schmersion.'
8
+ puts
9
+
10
+ puts 'The following commands are supported:'
11
+ puts
12
+ context.cli.commands.sort_by { |k, _v| k.to_s }.each do |_, command|
13
+ if command.description
14
+ puts " \e[36m#{command.name.to_s.ljust(18, ' ')}\e[0m #{command.description}"
15
+ end
16
+ end
17
+ puts
18
+ puts 'For details for the options available for each command, use the --help option.'
19
+ puts "For example 'schmersion pending --help'."
20
+ end
21
+ end
@@ -0,0 +1,49 @@
1
+ # frozen_string_literal: true
2
+
3
+ command :init do
4
+ desc 'Create a .schmersion.yml config file'
5
+
6
+ option '-f', '--force', 'Override existing hooks if they exist' do |_, options|
7
+ options[:force] = true
8
+ end
9
+
10
+ action do |context|
11
+ if File.exist?('.schmersion.yaml') && !context.options[:force]
12
+ puts 'A file already exists at .schmersion.yaml'
13
+ puts 'Use --force if you wish to re-generate it.'
14
+ exit 1
15
+ end
16
+
17
+ example = <<~EXAMPLE
18
+ # Welcome to the Schmersion config file
19
+ #
20
+ # This is an array of all the commit types that are supported. You can
21
+ # override this to define your own set as needed per-project.
22
+ #
23
+ # types: [feat, fix, style, chore, test, refactor, perf, docs, ci, build, revert]
24
+ #
25
+ # By default, you can use any scope (the word in brackets following the type).
26
+ # If you wish to restrict this, you can define a list of valid scopes.
27
+ # scopes: [auth, api, ...]
28
+ #
29
+ # A key part of the configuration is how you wish to export your CHANGELOG files.w
30
+ exports:
31
+ - name: CHANGELOG.md
32
+ formatter: markdown
33
+ options:
34
+ title: CHANGELOG
35
+ description: This file contains all the latest changes and updates to this application.
36
+ sections:
37
+ - title: Features
38
+ types: [feat]
39
+ - title: Bug Fixes
40
+ types: [fix]
41
+ #
42
+ # There are additional options you can define, check out the README for details.
43
+ # https://github.com/krystal/schmersion
44
+ EXAMPLE
45
+
46
+ File.write('.schmersion.yaml', example)
47
+ puts 'Created new .schmersion.yaml file'.green
48
+ end
49
+ end
@@ -0,0 +1,14 @@
1
+ # rubocop:disable Naming/FileName
2
+ # frozen_string_literal: true
3
+
4
+ command :'lint-prepare' do
5
+ action do |context|
6
+ require 'schmersion/repo'
7
+ require 'schmersion/linter'
8
+ repo = Schmersion::Repo.new(FileUtils.pwd)
9
+ linter = Schmersion::Linter.new(repo)
10
+ linter.prepare(context.args[0], context.args[1])
11
+ end
12
+ end
13
+
14
+ # rubocop:enable Naming/FileName
@@ -0,0 +1,28 @@
1
+ # rubocop:disable Naming/FileName
2
+ # frozen_string_literal: true
3
+
4
+ command :'lint-validate' do
5
+ action do |context|
6
+ require 'schmersion/repo'
7
+ require 'schmersion/linter'
8
+ repo = Schmersion::Repo.new(FileUtils.pwd)
9
+ linter = Schmersion::Linter.new(repo)
10
+ errors = linter.validate_file(context.args[0])
11
+ if errors.empty?
12
+ exit 0
13
+ end
14
+
15
+ puts
16
+ puts ' Schmersion Commit Linting'.cyan
17
+ puts
18
+ puts " Uh oh. There's a few things wrong with your commit message...".red
19
+ puts ' Please correct these and re-commit to continue.'.red
20
+ puts
21
+ errors.each do |error|
22
+ puts " => #{error}"
23
+ end
24
+ puts
25
+ exit 1
26
+ end
27
+ end
28
+ # rubocop:enable Naming/FileName
@@ -0,0 +1,28 @@
1
+ # frozen_string_literal: true
2
+
3
+ command :log do
4
+ desc 'Print all commits in a range nicely'
5
+
6
+ option '--from [REF]', 'Commit to start from (default: beginning of time)' do |value, options|
7
+ options[:from] = value
8
+ end
9
+
10
+ option '--to [REF]', 'Commit to finish with (default: current branch)' do |value, options|
11
+ options[:to] = value
12
+ end
13
+
14
+ action do |context|
15
+ require 'schmersion/repo'
16
+ require 'schmersion/helpers'
17
+ require 'colorize'
18
+
19
+ repo = Schmersion::Repo.new(FileUtils.pwd)
20
+
21
+ from = context.options[:from] || :start
22
+ to = context.options[:to] || repo.current_branch
23
+
24
+ commits = repo.commits(from, to, exclude_with_invalid_messages: true)
25
+
26
+ Schmersion::Helpers.print_commit_list(commits)
27
+ end
28
+ end
@@ -0,0 +1,49 @@
1
+ # frozen_string_literal: true
2
+
3
+ command :pending do
4
+ desc 'Displays commits pending'
5
+
6
+ option '--from [REF]', 'Version to start from (default: latest version)' do |value, options|
7
+ options[:from] = value
8
+ end
9
+
10
+ option '--to [REF]', 'Commit to end at (default: HEAD)' do |value, options|
11
+ options[:to] = value
12
+ end
13
+
14
+ option '--pre [PREFIX]', 'Create a pre-release version' do |value, options|
15
+ options[:pre] = value || true
16
+ end
17
+
18
+ action do |context|
19
+ require 'schmersion/repo'
20
+ require 'schmersion/helpers'
21
+
22
+ repo = Schmersion::Repo.new(FileUtils.pwd)
23
+ current_version, next_version = repo.pending_version(
24
+ from: context.options[:from],
25
+ to: context.options[:to],
26
+ version_options: {
27
+ pre: context.options[:pre]
28
+ }
29
+ )
30
+
31
+ puts
32
+ if current_version
33
+ puts 'The current published version is:'
34
+ puts
35
+ puts " #{current_version.to_s.cyan}"
36
+ puts
37
+ puts 'The next version will be:'
38
+ else
39
+ puts 'The first version will be:'
40
+ end
41
+ puts
42
+ puts " #{next_version.version.to_s.green}"
43
+ puts
44
+ puts 'The following commits will be included:'
45
+ puts
46
+ Schmersion::Helpers.print_commit_list(next_version.commits, prefix: ' ')
47
+ puts
48
+ end
49
+ end
@@ -0,0 +1,40 @@
1
+ # frozen_string_literal: true
2
+
3
+ command :release do
4
+ desc 'Create a release'
5
+
6
+ option '-r [VERSION]', 'Override the version number' do |value, options|
7
+ options[:version] = value
8
+ end
9
+
10
+ option '--dry-run', "Don't actually do anything, just show a preview" do |_, options|
11
+ options[:dry_run] = true
12
+ end
13
+
14
+ option '--pre [PREFIX]', 'Create a pre-release version' do |value, options|
15
+ options[:pre] = value || true
16
+ end
17
+
18
+ option '--skip-export', "Don't generate new CHANGELOG exports" do |_, options|
19
+ options[:skips] ||= []
20
+ options[:skips] << :export
21
+ end
22
+
23
+ option '--skip-commit', "Don't commit anything" do |_, options|
24
+ options[:skips] ||= []
25
+ options[:skips] << :commit
26
+ end
27
+
28
+ option '--skip-tag', "Don't create a tag" do |_, options|
29
+ options[:skips] ||= []
30
+ options[:skips] << :tag
31
+ end
32
+
33
+ action do |context|
34
+ require 'schmersion/repo'
35
+ require 'schmersion/releaser'
36
+ repo = Schmersion::Repo.new(FileUtils.pwd)
37
+ releaser = Schmersion::Releaser.new(repo, context.options)
38
+ releaser.release
39
+ end
40
+ end
@@ -0,0 +1,22 @@
1
+ # rubocop:disable Naming/FileName
2
+ # frozen_string_literal: true
3
+
4
+ command :'setup-linting' do
5
+ desc 'Install git hooks to lint commit messages'
6
+
7
+ option '-f', '--force', 'Override existing hooks if they exist' do |_, options|
8
+ options[:force] = true
9
+ end
10
+
11
+ action do |context|
12
+ require 'schmersion/repo'
13
+ require 'schmersion/linter'
14
+ repo = Schmersion::Repo.new(FileUtils.pwd)
15
+ linter = Schmersion::Linter.new(repo)
16
+ linter.setup(force: context.options[:force])
17
+
18
+ puts 'Installed hooks successfully'.green
19
+ end
20
+ end
21
+
22
+ # rubocop:enable Naming/FileName
@@ -0,0 +1,14 @@
1
+ # frozen_string_literal: true
2
+
3
+ command :versions do
4
+ desc 'Print a list of all versions in version order'
5
+ action do
6
+ require 'schmersion/repo'
7
+ require 'colorize'
8
+
9
+ repo = Schmersion::Repo.new(FileUtils.pwd)
10
+ repo.versions.each do |version, _|
11
+ puts version
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,4 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'schmersion/schmersion_version'
4
+ require 'schmersion/error'
@@ -0,0 +1,23 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'schmersion/message'
4
+
5
+ module Schmersion
6
+ class Commit
7
+
8
+ attr_reader :message
9
+ attr_reader :ref
10
+ attr_reader :date
11
+ attr_reader :author
12
+ attr_reader :raw_commit
13
+
14
+ def initialize(commit)
15
+ @message = Message.new(commit.message)
16
+ @ref = commit.sha
17
+ @date = commit.date
18
+ @author = commit.author.name
19
+ @raw_commit = commit
20
+ end
21
+
22
+ end
23
+ end
@@ -0,0 +1,60 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'schmersion/commit'
4
+ require 'schmersion/version_calculator'
5
+
6
+ module Schmersion
7
+ class CommitParser
8
+
9
+ MAX_COMMITS = 100_000_000
10
+
11
+ attr_reader :commits
12
+
13
+ def initialize(repo, start_commit, end_commit, **options)
14
+ @start_commit = start_commit
15
+ @end_commit = end_commit
16
+ @options = options
17
+
18
+ @commits = []
19
+
20
+ if @start_commit == :start
21
+ # If start is provided, use the first commit as the initial reference.
22
+ # Ideally this would actually start from the beginning of time and include
23
+ # this commit.
24
+ first_commit = repo.log(MAX_COMMITS).last
25
+ @start_commit = first_commit.sha
26
+ end
27
+
28
+ @raw_commits = repo.log(MAX_COMMITS).between(@start_commit, @end_commit)
29
+
30
+ parse
31
+ end
32
+
33
+ def parse
34
+ @commits = []
35
+ @raw_commits.each do |commit|
36
+ commit = Commit.new(commit)
37
+
38
+ next if skip_commit?(commit)
39
+
40
+ @commits << commit
41
+ end
42
+ @commits = @commits.reverse
43
+ @commits
44
+ end
45
+
46
+ def next_version_after(current_version, **options)
47
+ calculator = VersionCalculator.new(current_version, commits, **options)
48
+ calculator.calculate
49
+ end
50
+
51
+ def skip_commit?(commit)
52
+ # We never want to see merge commits...
53
+ return true if commit.raw_commit.parents.size > 1
54
+ return true unless commit.message.valid?
55
+
56
+ false
57
+ end
58
+
59
+ end
60
+ end
@@ -0,0 +1,67 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Schmersion
4
+ class Config
5
+
6
+ DEFAULT_TYPES = %w[feat fix style chore test refactor perf docs ci build revert].freeze
7
+
8
+ DEFAULT_VERSION_OPTIONS = {
9
+ breaking_change_not_major: false
10
+ }.freeze
11
+
12
+ DEFAULT_LINTING_OPTIONS = {
13
+ max_description_length: 60
14
+ }.freeze
15
+
16
+ def initialize(hash)
17
+ @hash = hash
18
+ end
19
+
20
+ def types
21
+ @hash['types'] || DEFAULT_TYPES
22
+ end
23
+
24
+ def valid_type?(type)
25
+ return true if types.empty?
26
+
27
+ types.include?(type.to_s)
28
+ end
29
+
30
+ def scopes
31
+ @hash['scopes'] || []
32
+ end
33
+
34
+ def valid_scope?(scope)
35
+ return true if scopes.empty?
36
+
37
+ scopes.include?(scope.to_s)
38
+ end
39
+
40
+ def exports
41
+ return [] if @hash['exports'].nil?
42
+
43
+ @hash['exports'].map { |e| create_export(e) }
44
+ end
45
+
46
+ def linting
47
+ DEFAULT_LINTING_OPTIONS.merge(@hash['linting']&.transform_keys(&:to_sym) || {})
48
+ end
49
+
50
+ def version_options
51
+ DEFAULT_VERSION_OPTIONS.merge(@hash['version_options']&.transform_keys(&:to_sym) || {})
52
+ end
53
+
54
+ private
55
+
56
+ def create_export(export)
57
+ name = export['name']
58
+ formatter = Formatters::FORMATTERS[export['formatter'].to_sym]
59
+ if formatter.nil?
60
+ raise Error, "Invalid formatter '#{export['formatter']}' for #{name}"
61
+ end
62
+
63
+ formatter.new(name, export['options'] || {})
64
+ end
65
+
66
+ end
67
+ end