abak-flow 0.3.2 → 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.
@@ -1,221 +1,61 @@
1
- # -*- encoding: utf-8 -*-
1
+ # coding: utf-8
2
+ require "ruler"
3
+
2
4
  module Abak::Flow
3
5
  class PullRequest
4
- extend Forwardable
5
-
6
- attr_reader :repository, :request_params
7
- attr_reader :config, :validator
6
+ include Ruler
8
7
 
9
- def initialize(config, options)
10
- strategy = options.delete(:strategy) || :publish
8
+ attr_reader :errors, :link
11
9
 
12
- @repository = Hub::Commands.send(:local_repo)
13
- @request_params = OpenStruct.new(options)
14
- @config = config
10
+ def initialize(params, manager)
11
+ @manager = manager
12
+ @errors = []
15
13
 
16
- @validator = Validator.new(strategy, self)
14
+ @head = params.fetch(:head)
15
+ @base = params.fetch(:base)
16
+ @title = params.fetch(:title)
17
+ @body = params.fetch(:body)
17
18
  end
18
19
 
19
- delegate [:validate!, :valid?] => :validator
20
- delegate [:base=, :head=, :title=, :body=] => :request_params
20
+ def ready?
21
+ @errors = []
21
22
 
22
- def default_task
23
- @default_task ||= current_branch.split('/').push(nil)[1].to_s
24
- end
25
-
26
- def branch_prefix
27
- @branch_prefix ||= current_branch.include?('/') ? current_branch.split('/').first : ''
28
- end
23
+ multi_ruleset do
24
+ fact(:head_is_incorrect) { not @head.valid? }
25
+ fact(:base_is_incorrect) { not @base.valid? }
26
+ fact(:title_is_incorrect) { @title.empty? }
27
+ fact(:body_is_incorrect) { @body.empty? }
29
28
 
30
- def current_branch
31
- @current_branch ||= repository.current_branch.short_name
32
- end
33
-
34
- def from_repo
35
- @from_repo ||= begin
36
- upstream_project = repository.remote_by_name('upstream').project
37
- "#{upstream_project.owner}/#{upstream_project.name}"
38
- end
39
- end
40
- alias_method :upstream_project, :from_repo
41
-
42
- def origin_project
43
- @origin_project ||= begin
44
- origin_project = repository.remote_by_name('origin').project
45
- "#{origin_project.owner}/#{origin_project.name}"
29
+ rule([:head_is_incorrect]) { @errors << I18n.t("pull_request.errors.head_is_incorrect") }
30
+ rule([:base_is_incorrect]) { @errors << I18n.t("pull_request.errors.base_is_incorrect") }
31
+ rule([:title_is_incorrect]) { @errors << I18n.t("pull_request.errors.title_is_incorrect") }
32
+ rule([:body_is_incorrect]) { @errors << I18n.t("pull_request.errors.body_is_incorrect") }
46
33
  end
47
- end
48
-
49
- def origin_repo
50
- @origin_repo ||= repository.main_project.remote.name
51
- end
52
-
53
- def base
54
- exit unless validator.valid?
55
-
56
- branch = Abak::Flow::PullRequest.branch_by_prefix(branch_prefix)
57
34
 
58
- request_params.base || "#{repository.remote_by_name('upstream').project.owner}:#{branch}"
35
+ @errors.empty? ? true : false
59
36
  end
60
37
 
61
- def head
62
- exit unless validator.valid?
63
-
64
- request_params.head || "#{repository.repo_owner}:#{current_branch}"
65
- end
66
-
67
- def title
68
- exit unless validator.valid?
69
-
70
- request_params.title
71
- end
72
-
73
- def body
74
- exit unless validator.valid?
75
-
76
- request_params.body
77
- end
78
-
79
- def self.branch_by_prefix(prefix)
80
- {:feature => :develop, :hotfix => :master}.fetch(prefix.to_sym, '')
81
- end
82
-
83
- # TODO Вынести
84
- class Validator
85
- attr_reader :strategy, :target_object
86
- attr_reader :errors, :executed
87
-
88
- def initialize(strategy_name, target_object)
89
- @strategy = Abak::Flow::PullRequest.const_get("Strategy#{strategy_name.capitalize}".to_sym)
90
- @target_object = target_object
91
- @errors = []
92
- end
93
-
94
- def valid?
95
- return errors.empty? if executed
96
-
97
- validate!
98
- errors.empty?
99
- end
100
-
101
- protected
102
- def validate!
103
- @executed = true
104
-
105
- strategy.attributes.each do |attribute|
106
- send("validate_#{attribute}")
107
- end
108
-
109
- print_errors
110
- end
111
-
112
- def print_errors
113
- errors.each do |error|
114
- say color(error[:message], :error).to_s
115
- say color(error[:tip], :info).to_s
116
- end
117
- end
118
-
119
- def validate_api_user
120
- return if target_object.config.api_user?
121
-
122
- @errors << {
123
- :message => 'Необходимо указать своего пользователя API github',
124
- :tip => '=> https://github.com/Strech/abak-flow/blob/master/README.md'
125
- }
126
- end
127
-
128
- def validate_api_token
129
- return if target_object.config.api_token?
130
-
131
- @errors << {
132
- :message => 'Необходимо указать токен своего пользователя API github',
133
- :tip => '=> https://github.com/Strech/abak-flow/blob/master/README.md'
134
- }
135
- end
136
-
137
- def validate_origin
138
- return unless target_object.repository.remote_by_name('origin').nil?
139
-
140
- @errors << {
141
- :message => 'Необходимо настроить репозиторий origin (форк) для текущего пользователя',
142
- :tip => '=> git remote add origin https://Developer@github.com/abak-press/sample.git'
143
- }
144
- end
145
-
146
- def validate_upstream
147
- return unless target_object.repository.remote_by_name('upstream').nil?
148
-
149
- @errors << {
150
- :message => 'Необходимо настроить репозиторий upstream (главный) для текущего пользователя',
151
- :tip => '=> git remote add upstream https://Developer@github.com/abak-press/sample.git'
152
- }
153
- end
154
-
155
- def validate_title
156
- return unless target_object.request_params.title.empty?
157
-
158
- @errors << {
159
- :message => 'Пожалуйста, укажите что-нибудь для заголовка pull request, например номер вашей задачи вот так:',
160
- :tip => '=> git request publish "PC-001"'
161
- }
162
- end
163
-
164
- def validate_branch
165
- return if [:master, :develop].include?(target_object.current_branch.to_sym)
166
-
167
- @errors << {
168
- :message => 'Нельзя делать pull request из меток master или develop, попробуйде переключиться, например так:',
169
- :tip => '=> git checkout master'
170
- }
171
- end
172
-
173
- def validate_deleted_branch
174
- return unless [:master, :develop].include?(target_object.current_branch.to_sym)
175
-
176
- @errors << {
177
- :message => 'Извините, но нельзя удалить ветку develop или master',
178
- :tip => '=> git checkout feature/TASK-0001'
179
- }
180
- end
38
+ def display_name
39
+ I18n.t("pull_request.name")
181
40
  end
182
41
 
183
- class Strategy
184
- def self.attributes
185
- raise NotImplementedError
186
- end
187
- end
42
+ def publish
43
+ begin
44
+ head_with_repo = [@manager.repository.origin.owner, @head] * ':'
188
45
 
189
- class StrategyFeature < Strategy
190
- def self.attributes
191
- StrategyReadycheck.attributes | [:title, :branch]
192
- end
193
- end
46
+ response = @manager.github.create_pull_request(
47
+ @manager.repository.upstream.to_s, @base.to_s, head_with_repo, @title, @body)
194
48
 
195
- class StrategyPublish < Strategy
196
- def self.attributes
197
- StrategyReadycheck.attributes | [:title]
198
- end
199
- end
49
+ @link = response._links.html.href
200
50
 
201
- class StrategyUpdate < Strategy
202
- def self.attributes
203
- StrategyReadycheck.attributes
204
- end
205
- end
51
+ true
52
+ rescue Exception => exception
53
+ backtrace = exception.backtrace[0...10] * "\n"
206
54
 
207
- class StrategyDone < Strategy
208
- def self.attributes
209
- StrategyReadycheck.attributes | [:deleted_branch]
210
- end
211
- end
55
+ @errors = ["#{exception.message}\n\n#{backtrace}"]
212
56
 
213
- class StrategyReadycheck < Strategy
214
- def self.attributes
215
- [:origin, :upstream, :api_user, :api_token]
57
+ false
216
58
  end
217
59
  end
218
-
219
- class StrategyStatus < StrategyReadycheck; end
220
- end
221
- end
60
+ end # class PullRequest
61
+ end
@@ -0,0 +1,71 @@
1
+ # coding: utf-8
2
+ require "i18n"
3
+ require "ruler"
4
+ require "forwardable"
5
+
6
+ module Abak::Flow
7
+ class Repository
8
+ include Ruler
9
+ extend Forwardable
10
+
11
+ REMOTES = [:origin, :upstream].freeze
12
+
13
+ def_delegators :@manager, :git
14
+
15
+ attr_reader :errors
16
+
17
+ def initialize(manager)
18
+ @manager = manager
19
+ @errors = []
20
+
21
+ configure!
22
+ end
23
+
24
+ def ready?
25
+ @errors = []
26
+
27
+ multi_ruleset do
28
+ fact(:origin_not_setup) { origin.nil? }
29
+ fact(:upstream_not_setup) { upstream.nil? }
30
+
31
+ rule([:origin_not_setup]) { @errors << I18n.t("repository.errors.origin_not_setup") }
32
+ rule([:upstream_not_setup]) { @errors << I18n.t("repository.errors.upstream_not_setup") }
33
+ end
34
+
35
+ @errors.empty? ? true : false
36
+ end
37
+
38
+ def display_name
39
+ I18n.t("repository.name")
40
+ end
41
+
42
+ private
43
+ def configure!
44
+ remotes = Hash[fetch_remotes_from_git]
45
+ REMOTES.each do |name|
46
+ define_singleton_method(name) { remotes[name] }
47
+ end
48
+ end
49
+
50
+ def fetch_remotes_from_git
51
+ git.remotes.
52
+ select { |remote| REMOTES.include?(remote.name.to_sym) }.
53
+ map { |remote| create_remote(remote) }.
54
+ compact
55
+ end
56
+
57
+ def create_remote(remote)
58
+ matches = /.+.github\.com[\:|\/](?<owner>.+)\/(?<project>.+).git/.match(remote.url)
59
+
60
+ if !matches.nil? && matches.captures.length == 2
61
+ [remote.name.to_sym, Remote.new(matches[:owner], matches[:project], remote)]
62
+ end
63
+ end
64
+
65
+ class Remote < Struct.new(:owner, :project, :repo)
66
+ def to_s
67
+ "#{owner}/#{project}"
68
+ end
69
+ end
70
+ end
71
+ end
@@ -1,234 +1,124 @@
1
- # -*- encoding: utf-8 -*-
1
+ # coding: utf-8
2
+ require "commander/import"
3
+ require "ansi/code"
4
+
2
5
  module Abak::Flow
3
- program :name, 'Утилита для оформления pull request на github.com'
6
+ program :name, "Утилита для оформления pull request на github.com"
4
7
  program :version, Abak::Flow::VERSION
5
- program :description, 'Утилита, заточенная под git-flow но с использованием github.com'
8
+ program :description, "Утилита, заточенная под git-flow но с использованием github.com"
6
9
 
7
10
  default_command :help
8
- command :publish do |c|
9
- c.syntax = 'git request publish <Заголовок>'
10
- c.description = 'Оформить pull request из текущей ветки (feature -> develop, hotfix -> master)'
11
11
 
12
- # Опции нужны, если человек хочет запушить `` ветку, с именем отличным от стандарта
13
- c.option '--head STRING', String, 'Имя ветки, которую нужно принять в качестве изменений'
14
- c.option '--base STRING', String, 'Имя ветки, в которую нужно принять изменения'
12
+ command :checkup do |c|
13
+ c.syntax = "git request checkup"
14
+ c.description = "Проверить все ли настроено для работы с github и удаленными репозиториями"
15
15
 
16
16
  c.action do |args, options|
17
- jira_browse_url = 'http://jira.dev.apress.ru/browse/'
18
-
19
- config = Abak::Flow::Config.current
20
- github_client = Abak::Flow::GithubClient.connect(config)
21
- request = Abak::Flow::PullRequest.new(config, :head => options.head, :base => options.base)
22
-
23
- title = args.first.to_s.strip
24
- body = забыл какая это задача :('
25
-
26
- if request.default_task =~ /^\w+\-\d{1,}$/
27
- title = request.default_task if title.empty?
28
- body = jira_browse_url + request.default_task
29
- end
30
-
31
- request.title = title
32
- request.body = body
33
-
34
- exit unless request.valid?
35
-
36
- # Запушим текущую ветку на origin
37
- say "=> Обновляю ветку #{request.current_branch} на origin"
38
- Hub::Runner.execute('push', 'origin', request.current_branch)
39
-
40
- # Запостим pull request на upstream
41
- say '=> Делаю pull request на upstream'
42
- begin
43
- result = github_client.create_pull_request(request.from_repo, request.base, request.head, request.title, request.body)
44
- say color(result._links.html.href, :green).to_s
45
- rescue => e
46
- say color(e.message, :error).to_s
47
- say "\nПроблемы? Попробуйте заглянуть сюда:"
48
- say color('=> cписок кодов статуса ответа http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html', :info).to_s
17
+ m = Manager.new
18
+ v = Visitor.new(m.configuration, m.repository, call: :ready?, look_for: :errors)
19
+
20
+ if v.ready?
21
+ say ANSI.green { I18n.t("commands.checkup.success") }
22
+ else
23
+ say ANSI.red { I18n.t("commands.checkup.fail") }
24
+ say ANSI.yellow { v.output }
49
25
  end
50
26
  end
51
- end
52
-
53
- command :update do |c|
54
- c.syntax = 'git request update'
55
- c.description = 'Обновить ветку на удаленном (origin) репозитории'
56
-
57
- c.option '--branch STRING', String, 'Имя ветки, которую нужно обновить'
58
-
59
- c.action do |args, options|
60
- config = Abak::Flow::Config.current
61
- request = Abak::Flow::PullRequest.new(config, :strategy => :update)
62
-
63
- exit unless request.valid?
64
-
65
- # Запушим текущую ветку на origin
66
- branch = options.branch || request.current_branch
67
- say "=> Обновляю ветку #{branch} на origin"
68
- Hub::Runner.execute('push', 'origin', branch)
69
- end
70
- end
71
-
72
- command :feature do |c|
73
- c.syntax = 'git request feature <Название задачи>'
74
- c.description = 'Создать ветку для выполнения задачи. Лучше всего, если название задачи, будет ее номером из jira'
27
+ end # command :checkup
75
28
 
76
- c.action do |args, options|
77
- config = Abak::Flow::Config.current
78
-
79
- task = args.shift.to_s
29
+ command :compare do |c|
30
+ c.syntax = "git request compare"
31
+ c.description = "Сравнить свою ветку с веткой upstream репозитория"
80
32
 
81
- if task.empty?
82
- say color('Необходимо указать имя задачи, а лучше всего ее номер из jira', :error).to_s
83
- exit
84
- end
85
-
86
- unless task =~ /^\w+\-\d{1,}$/
87
- say '=> Вы приняли верное решение :)' && exit if agree("Лучше всего завести задачу с именем примерно такого формата PC-001, может попробуем заново? [y/n]:")
88
- end
89
-
90
- Hub::Runner.execute('flow', 'feature', 'start', task)
91
- end
92
- end
93
-
94
- command :hotfix do |c|
95
- c.syntax = 'git request hotfix <Название задачи>'
96
- c.description = 'Создать ветку для выполнения bugfix задачи. Лучше всего, если название задачи, будет ее номером из jira'
33
+ c.option "--base STRING", String, "Имя ветки с которой нужно сравнить"
34
+ c.option "--head STRING", String, "Имя ветки которую нужно сравнить"
97
35
 
98
36
  c.action do |args, options|
99
- config = Abak::Flow::Config.current
37
+ # TODO : Вот это дубль, хочется его как-то более красиво
38
+ m = Manager.new
39
+ v = Visitor.new(m.configuration, m.repository, call: :ready?, look_for: :errors)
100
40
 
101
- task = args.shift.to_s
41
+ unless v.ready?
42
+ say ANSI.red { I18n.t("commands.compare.fail") }
43
+ say ANSI.yellow { v.output }
102
44
 
103
- if task.empty?
104
- say color('Необходимо указать имя задачи, а лучше всего ее номер из jira', :error).to_s
105
- exit
45
+ exit 1
106
46
  end
107
47
 
108
- unless task =~ /^\w+\-\d{1,}$/
109
- say '=> Вы приняли верное решение :)' && exit if agree("Лучше всего завести задачу с именем примерно такого формата PC-001, может попробуем заново? [y/n]:")
48
+ current = m.git.current_branch
49
+ head = Branch.new(options.head || current, m)
50
+ base = Branch.new(options.base || head.pick_up_base_name, m)
51
+
52
+ if head.current?
53
+ say ANSI.white {
54
+ I18n.t("commands.compare.updating",
55
+ branch: ANSI.bold { head },
56
+ upstream: ANSI.bold { "#{m.repository.origin}" }) }
57
+
58
+ head.update
59
+ else
60
+ say ANSI.yellow {
61
+ I18n.t("commands.compare.diverging",
62
+ branch: ANSI.bold { head }) }
110
63
  end
111
64
 
112
- Hub::Runner.execute('flow', 'hotfix', 'start', task)
65
+ say ANSI.green { head.compare_link(base) }
113
66
  end
114
- end
67
+ end # command :compare
115
68
 
116
- command :done do |c|
117
- c.syntax = 'git request done'
118
- c.description = 'Завершить pull request. По умолчанию удаляются ветки как локальная (local), так и удаленная (origin)'
69
+ command :publish do |c|
70
+ c.syntax = "git request publish"
71
+ c.description = "Оформить pull request в upstream репозиторий"
119
72
 
120
- c.option '--branch STRING', String, 'Имя ветки pull request которой нужно закрыть'
121
- c.option '--all', 'Удаляет ветку в локальном репозитории и в удалнном (local + origin) (по умолчанию)'
122
- c.option '--local', 'Удаляет ветку только в локальном репозитории (local)'
123
- c.option '--origin', 'Удаляет ветку в удаленном репозитории (origin)'
73
+ c.option "--title STRING", String, "Заголовок для вашего pull request"
74
+ c.option "--body STRING", String, "Текст для вашего pull request"
75
+ c.option "--base STRING", String, "Имя ветки, в которую нужно принять изменения"
124
76
 
125
77
  c.action do |args, options|
126
- config = Abak::Flow::Config.current
127
- request = Abak::Flow::PullRequest.new(config, :strategy => :done)
128
- branch = options.branch || request.current_branch
78
+ m = Manager.new
129
79
 
130
- exit unless request.valid?
80
+ head = Branch.new(m.git.current_branch, m)
81
+ base = Branch.new(options.base || head.pick_up_base_name, m)
131
82
 
132
- type = :all
133
- if [options.local, options.origin].compact.count == 1
134
- type = options.local ? :local : :origin
135
- end
83
+ title = options.title || head.pick_up_title
84
+ body = [
85
+ options.body || (head.mappable? ? nil : I18n.t("commands.publish.nothing")),
86
+ head.pick_up_body
87
+ ].compact * "\n\n"
136
88
 
137
- warning = color('Внимание! Alarm! Danger! Achtung!', :error).to_s +
138
- "\nЕсли вы удалите ветку на удаленном (remote) репозитории, а ваш pull request еще не приняли, вы рискуете потерять проделанную работу.\nВы уверены, что хотите продолжить?"
139
- if [:all, :origin].include?(type)
140
- say '=> Вы приняли верное решение :)' && exit unless agree("#{warning} [y/n]:")
141
- end
89
+ p = PullRequest.new({base: base, head: head, title: title, body: body}, m)
90
+ v = Visitor.new(m.configuration, m.repository, p, call: :ready?, look_for: :errors)
142
91
 
143
- # TODO Проверку на наличие ветки на origin
144
- if [:all, :origin].include?(type)
145
- say "=> Удаляю ветку #{branch} на origin"
146
- Hub::Runner.execute('push', request.origin_repo, ':' + branch)
147
- end
92
+ unless v.ready?
93
+ say ANSI.red { I18n.t("commands.publish.fail") }
94
+ say ANSI.yellow { v.output }
148
95
 
149
- if [:all, :local].include?(type)
150
- say "=> Удаляю локальную ветку #{branch}"
151
- Hub::Runner.execute('checkout', 'develop')
152
- Hub::Runner.execute('branch', '-D', branch)
96
+ exit 1
153
97
  end
154
- end
155
- end
156
98
 
157
- # TODO Отрефакторить эту какашку
158
- command :readycheck do |c|
159
- c.syntax = 'git request readycheck'
160
- c.description = 'Проверить все ли настроено для работы с github и удаленным (origin) репозиторием'
99
+ say ANSI.white {
100
+ I18n.t("commands.publish.updating",
101
+ branch: ANSI.bold { head },
102
+ upstream: ANSI.bold { "#{m.repository.origin}" }) }
161
103
 
162
- c.action do |args, options|
163
- config = Abak::Flow::Config.current
164
- request = Abak::Flow::PullRequest.new(config, :strategy => :readycheck)
165
-
166
- if config.proxy?
167
- message = "== В качестве прокси будет установлено значение #{config.proxy} =="
168
- say color('=' * message.length, :info).to_s
169
- say color(message, :info).to_s
170
- say color('=' * message.length + "\n", :info).to_s
171
- end
104
+ head.update
172
105
 
173
- say color('Хм ... кажется у вас все готово к работе', :debug).to_s if request.valid?
174
- end
175
- end
176
-
177
- command :garbage do |c|
178
- c.syntax = 'git request status'
179
- c.description = 'Проверить пригодность удаленных (origin) веток и возможность их уничтожения (ветки master, develop игнорируются)'
106
+ say ANSI.white {
107
+ I18n.t("commands.publish.requesting",
108
+ branch: ANSI.bold { "#{m.repository.origin.owner}:#{head}" },
109
+ upstream: ANSI.bold { "#{m.repository.upstream.owner}:#{base}" }) }
180
110
 
181
- c.action do |args, options|
182
- config = Abak::Flow::Config.current
183
- github_client = Abak::Flow::GithubClient.connect(config)
184
- request = Abak::Flow::PullRequest.new(config, :strategy => :status)
185
-
186
- exit unless request.valid?
187
-
188
- messages = {unused: ["отсутствует в upstream репозитории", :notice],
189
- differ: ["отличается от origin репозитория", :warning],
190
- missing: ["отсутствует в локальном репозитории", :warning]}
191
-
192
- say "=> Обновляю данные о репозитории upstream"
193
- %w(origin upstream).each { |remote| Hub::Runner.execute('fetch', remote, '-p') }
194
-
195
- say "=> Загружаю список веток для origin"
196
- branches = github_client.branches(request.origin_project).
197
- reject { |branch| %w(master develop).include? branch.name }
198
-
199
- say "=> На origin найдено веток: #{branches.count}\n\n"
200
- branches.each_with_index do |branch, index|
201
- index += 1
202
-
203
- base = Abak::Flow::PullRequest.branch_by_prefix branch.name.split('/').first
204
-
205
- upstream_branch = %x(git branch -r --contain #{branch.commit.sha} | grep upstream/#{base} 2> /dev/null).strip
206
- local_sha = %x(git show #{branch.name} --format=%H --no-notes 2> /dev/null | head -n 1).strip
207
-
208
- statuses = {
209
- unused: upstream_branch.empty?,
210
- differ: !local_sha.empty? && local_sha != branch.commit.sha,
211
- missing: local_sha.empty?
212
- }
213
-
214
- unless statuses.values.inject &:|
215
- say color("#{index}) #{branch.name} → можно удалить", :debug).to_s
216
- say "\n"
217
- next
218
- end
219
-
220
- diagnoses = statuses.select { |_,bool| bool }.
221
- map { |name,_| messages[name].first }.
222
- map { |msg| "#{' ' * (index.to_s.length + 2)} ↪ #{msg}" }.
223
- join("\n")
224
-
225
- if statuses.select { |_,bool| bool }.keys == [:missing]
226
- say color("#{index}) #{branch.name} → потенциально можно удалить", :warning).to_s
227
- say "#{diagnoses}\n\n"
228
- else
229
- say "#{index}) #{branch.name}\n#{diagnoses}\n\n"
230
- end
111
+ v = Visitor.new(p, call: :publish, look_for: :errors)
112
+ if v.ready?
113
+ say ANSI.green {
114
+ I18n.t("commands.publish.success",
115
+ link: ANSI.bold { p.link }) }
116
+ else
117
+ say ANSI.red { I18n.t("commands.publish.fail") }
118
+ say ANSI.yellow { v.output }
119
+
120
+ exit 3
231
121
  end
232
122
  end
233
- end
234
- end
123
+ end # command :publish
124
+ end
@@ -1,5 +1,5 @@
1
1
  module Abak
2
2
  module Flow
3
- VERSION = '0.3.2'
3
+ VERSION = '1.0.0'
4
4
  end
5
5
  end