abak-flow 0.3.2 → 1.0.0

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