schmersion 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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