releasinator 0.1.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,529 @@
1
+ require 'rubygems'
2
+ require 'bundler/setup'
3
+ require 'rake'
4
+ require 'colorize'
5
+ require 'json'
6
+ require 'tempfile'
7
+ require_relative '../command_processor'
8
+ require_relative '../config_hash'
9
+ require_relative '../copy_file'
10
+ require_relative '../current_release'
11
+ require_relative '../downstream_repo'
12
+ require_relative '../publisher'
13
+ require_relative '../validator'
14
+
15
+ include Releasinator
16
+
17
+ DOWNSTREAM_REPOS = "downstream_repos"
18
+
19
+ def get_base_dir
20
+ if @releasinator_config.has_key?(:base_docs_dir)
21
+ @releasinator_config[:base_docs_dir]
22
+ else
23
+ '.'
24
+ end
25
+ end
26
+
27
+ def use_git_flow()
28
+ return @releasinator_config[:use_git_flow] if @releasinator_config.has_key? :use_git_flow
29
+ false
30
+ end
31
+
32
+ def full_file_sync(options)
33
+ return options[:full_file_sync] if options.has_key? :full_file_sync
34
+ false
35
+ end
36
+
37
+ def release_to_github(options)
38
+ return options[:release_to_github] if options.has_key? :release_to_github
39
+ false
40
+ end
41
+
42
+ def git_tag(new_tag, changelog)
43
+ confirm_tag_overwrite(new_tag)
44
+ puts "tagging with changelog: \n\n#{@currentRelease.changelog}\n".yellow
45
+ changelog_tempfile = Tempfile.new("#{new_tag}.changelog")
46
+ changelog_tempfile.write(changelog)
47
+ changelog_tempfile.close
48
+ # include changelog in annotated tag
49
+ CommandProcessor.command("git tag -a -f #{new_tag} -F #{changelog_tempfile.path}")
50
+ changelog_tempfile.unlink
51
+ end
52
+
53
+ desc "read and validate the config, adding one if not found"
54
+ task :config do
55
+ @releasinator_config = ConfigHash.new(verbose == true, Rake.application.options.trace == true)
56
+ @validator = Validator.new(@releasinator_config)
57
+ @validator.validate_config
58
+ end
59
+
60
+ namespace :validate do
61
+
62
+ desc "validate that git is the correct version"
63
+ task :git_version => :config do
64
+ @validator.validate_git_version
65
+ end
66
+
67
+ desc "validate that git reports no untracked, unstaged, or uncommitted changes"
68
+ task :git => :config do
69
+ @validator.validate_clean_git
70
+ end
71
+
72
+ desc "validate current branch matches the latest on the server and follows naming conventions"
73
+ task :branch => [:config, :changelog] do
74
+ current_branch = GitUtil.get_current_branch()
75
+ @validator.validate_matches_branch(current_branch)
76
+ if use_git_flow()
77
+ expected_release_branch = "release/#{@currentRelease.release}"
78
+ abort("git flow expects the current branch to be either 'develop' or 'release/#{@currentRelease.release}'. Current branch is '#{current_branch}'".red) unless current_branch == expected_release_branch || current_branch == "develop"
79
+ else
80
+ abort("non-git flow expects releases to come from the master branch. Current branch is '#{current_branch}'".red) unless current_branch == "master"
81
+ end
82
+ end
83
+
84
+ desc "validate the presence README.md, renaming a similar file if found"
85
+ task :readme => :config do
86
+ @validator.validate_exist(get_base_dir(), "README.md", DOWNSTREAM_REPOS)
87
+ end
88
+
89
+ desc "validate the presence LICENSE, renaming a similar file if found - also validates that its referenced from README.md"
90
+ task :license => :config do
91
+ @validator.validate_exist(get_base_dir(), "LICENSE", DOWNSTREAM_REPOS)
92
+ @validator.validate_referenced_in_readme(get_base_dir(), "LICENSE")
93
+ end
94
+
95
+ desc "validate the presence CONTRIBUTING.md, renaming a similar file if found - also validates that its referenced from README.md"
96
+ task :contributing => :config do
97
+ @validator.validate_exist(get_base_dir(), "CONTRIBUTING.md", DOWNSTREAM_REPOS)
98
+ @validator.validate_referenced_in_readme(get_base_dir(), "CONTRIBUTING.md")
99
+ end
100
+
101
+ desc "validate the presence .github/ISSUE_TEMPLATE.md"
102
+ task :issue_template => :config do
103
+ @validator.validate_exist(get_base_dir(), ".github/ISSUE_TEMPLATE.md", DOWNSTREAM_REPOS)
104
+ end
105
+
106
+ desc "validate the presence, formatting, and semver sequence of CHANGELOG.md"
107
+ task :changelog => :config do
108
+ @currentRelease = @validator.validate_changelog(get_base_dir(), DOWNSTREAM_REPOS)
109
+ end
110
+
111
+ desc "validate the presence of .gitignore, adding it and any appropriate releasinator lines if necessary"
112
+ task :gitignore => :config do
113
+ @validator.validate_gitignore("#{DOWNSTREAM_REPOS}/", @releasinator_config.has_key?(:downstream_repos))
114
+ end
115
+
116
+ desc "validate all submodules are on the latest origin/master versions"
117
+ task :submodules => :config do
118
+ @validator.validate_submodules
119
+ end
120
+
121
+ desc "validate the current user can push to local repo"
122
+ task :github_permissions_local => [:config] do
123
+ @validator.validate_github_permissions(GitUtil.repo_url)
124
+ end
125
+
126
+ desc "validate the current user can push to downstream repos"
127
+ task :github_permissions_downstream, [:downstream_repo_index] => [:config] do |t, args|
128
+ if @releasinator_config.has_key?(:downstream_repos)
129
+ get_downstream_repos(args[:downstream_repo_index]).each do |downstream_repo, index|
130
+ @validator.validate_github_permissions(downstream_repo.url)
131
+ end
132
+ else
133
+ Printer.success("Not validating permissions of downstream repos. None found.")
134
+ end
135
+ end
136
+
137
+ desc "run any configatron.custom_validation_methods"
138
+ task :custom => :config do
139
+ if @releasinator_config.has_key?(:custom_validation_methods)
140
+ @releasinator_config[:custom_validation_methods].each do |validate_method|
141
+ validate_method.call
142
+ end
143
+ Printer.success("All configatron.custom_validation_methods succeeded.")
144
+ else
145
+ Printer.success("No configatron.custom_validation_methods found.")
146
+ end
147
+ end
148
+
149
+ desc "validate all"
150
+ task :all =>
151
+ [
152
+ :git_version,
153
+ :gitignore,
154
+ :git,
155
+ :branch,
156
+ :submodules,
157
+ :readme,
158
+ :changelog,
159
+ :license,
160
+ :contributing,
161
+ :issue_template,
162
+ :github_permissions_local,
163
+ :github_permissions_downstream,
164
+ :custom
165
+ ] do
166
+ Printer.success("All validations passed.")
167
+ end
168
+ end
169
+
170
+ task :default => :release
171
+
172
+ desc "release all"
173
+ task :release => [:"validate:all",:"local:build",:"pm:all",:"downstream:all",:"local:push",:"docs:all"] do
174
+ Printer.success("Done releasing.")
175
+ end
176
+
177
+ def confirm_tag_overwrite(new_tag)
178
+ tag_results = CommandProcessor.command('git tag -l')
179
+ tag_results.split.each do |existing_tag|
180
+ if existing_tag == new_tag
181
+ Printer.check_proceed("Tag #{existing_tag} already present. Overwrite tag #{existing_tag}?", "Tag #{existing_tag} not overwritten.")
182
+ end
183
+ end
184
+ end
185
+
186
+ desc "iterate over the prerelease_checklist_items, asking the user if each is done"
187
+ task :prerelease_checklist => :config do
188
+ @releasinator_config[:prerelease_checklist_items].each do |prerelease_item|
189
+ Printer.check_proceed("#{prerelease_item}", "Then no release for you!")
190
+ end
191
+ end
192
+
193
+ namespace :local do
194
+ desc "ask user whether to proceed with release"
195
+ task :confirm do
196
+ Printer.check_proceed("You're about to release #{@currentRelease.release}!", "Then no release for you!")
197
+ end
198
+
199
+ desc "change branch for git flow, if using git flow"
200
+ task :prepare => [:config, :"validate:changelog"] do
201
+ if use_git_flow()
202
+ CommandProcessor.command("git checkout -b release/#{@currentRelease.release} develop") unless GitUtil.get_current_branch() != "develop"
203
+ end
204
+ end
205
+
206
+ desc "tag the local repo"
207
+ task :tag => :config do
208
+ git_tag(@currentRelease.release, @currentRelease.changelog)
209
+ end
210
+
211
+ desc "build the local repo"
212
+ task :build => [:config, :"validate:changelog", :prerelease_checklist, :confirm, :prepare, :tag] do
213
+ puts "building #{@currentRelease.release}" if @releasinator_config[:verbose]
214
+ @releasinator_config[:build_method].call
215
+ if @releasinator_config.has_key? :post_build_methods
216
+ @releasinator_config[:post_build_methods].each do |post_build_method|
217
+ post_build_method.call(@currentRelease.release)
218
+ end
219
+ end
220
+ end
221
+
222
+ desc "run the git flow branch magic (if configured) and push local to remote"
223
+ task :push => :config do
224
+ if use_git_flow()
225
+ CommandProcessor.command("git checkout master")
226
+ CommandProcessor.command("git pull")
227
+ CommandProcessor.command("git merge --no-ff release/#{@currentRelease.release}")
228
+ GitUtil.delete_branch "release/#{@currentRelease.release}"
229
+ CommandProcessor.command("git checkout develop")
230
+ CommandProcessor.command("git merge master")
231
+ CommandProcessor.command("git push origin develop --tags")
232
+ end
233
+ CommandProcessor.command("git push origin master --tags")
234
+ if @releasinator_config[:release_to_github]
235
+ Publisher.new(@releasinator_config).publish_draft(GitUtil.repo_url, @currentRelease)
236
+ end
237
+ end
238
+ end
239
+
240
+ namespace :pm do
241
+ desc "publish and wait for package manager"
242
+ task :all => [:publish, :wait]
243
+
244
+ desc "call configured publish_to_package_manager_method"
245
+ task :publish => [:config, :"validate:changelog"] do
246
+ @releasinator_config[:publish_to_package_manager_method].call(@currentRelease.release)
247
+ end
248
+
249
+ desc "call configured wait_for_package_manager_method"
250
+ task :wait => [:config, :"validate:changelog"] do
251
+ @releasinator_config[:wait_for_package_manager_method].call(@currentRelease.release)
252
+ end
253
+ end
254
+
255
+ def copy_the_file(root_dir, copy_file, version=nil)
256
+ Dir.mkdir(copy_file.target_dir) unless File.exists?(copy_file.target_dir)
257
+ # use __VERSION__ to auto-substitute the version in any input param
258
+ source_file_name = copy_file.source_file.gsub("__VERSION__", "#{version}")
259
+ target_dir_name = copy_file.target_dir.gsub("__VERSION__", "#{version}")
260
+ destination_file_name = copy_file.target_name.gsub("__VERSION__", "#{version}")
261
+ CommandProcessor.command("cp -R #{root_dir}/#{source_file_name} #{target_dir_name}/#{destination_file_name}")
262
+ end
263
+
264
+ def get_new_branch_name(new_branch_name, version)
265
+ new_branch_name.gsub("__VERSION__", "#{version}")
266
+ end
267
+
268
+ def reset_repo(branch_name)
269
+ # resets the repo to a clean state
270
+ GitUtil.checkout(branch_name)
271
+ CommandProcessor.command("git fetch origin --prune --recurse-submodules -j9")
272
+ CommandProcessor.command("git reset --hard origin/#{branch_name}")
273
+ CommandProcessor.command("git clean -x -d -f")
274
+ end
275
+
276
+ def get_downstream_repos(downstream_repo_index)
277
+ repos_to_iterate_over = {}
278
+ if downstream_repo_index
279
+ index = Integer(downstream_repo_index) rescue false
280
+ if !index
281
+ Printer.fail("downstream_repo_index:#{downstream_repo_index} not a valid integer")
282
+ abort()
283
+ end
284
+ downstream_repo_max_index = @releasinator_config[:downstream_repos].size - 1
285
+ if index < 0
286
+ Printer.fail("Index out of bounds downstream_repo_index: #{index} < 0")
287
+ abort()
288
+ end
289
+ if index > downstream_repo_max_index
290
+ Printer.fail("Index out of bounds downstream_repo_index: #{index} >= #{downstream_repo_max_index}")
291
+ abort()
292
+ end
293
+ # keep original index for printing
294
+ repos_to_iterate_over[@releasinator_config[:downstream_repos][index]] = index
295
+ else
296
+ @releasinator_config[:downstream_repos].each_with_index do |downstream_repo, index|
297
+ repos_to_iterate_over[downstream_repo] = index
298
+ end
299
+ end
300
+ repos_to_iterate_over
301
+ end
302
+
303
+ namespace :downstream do
304
+ desc "build, package, and push all downstream repos"
305
+ task :all => [:reset,:prepare,:build,:package,:push] do
306
+ Printer.success("Done with all downstream tasks.")
307
+ end
308
+
309
+ desc "reset the downstream repos to their starting state"
310
+ task :reset, [:downstream_repo_index] => [:config, :"validate:changelog"] do |t, args|
311
+ if @releasinator_config.has_key?(:downstream_repos)
312
+ get_downstream_repos(args[:downstream_repo_index]).each do |downstream_repo, index|
313
+ puts "resetting downstream_repo[#{index}]: #{downstream_repo.url}" if @releasinator_config[:verbose]
314
+ Dir.mkdir(DOWNSTREAM_REPOS) unless File.exists?(DOWNSTREAM_REPOS)
315
+ Dir.chdir(DOWNSTREAM_REPOS) do
316
+ CommandProcessor.command("git clone --origin origin #{downstream_repo.url} #{downstream_repo.name}") unless File.exists?(downstream_repo.name)
317
+
318
+ Dir.chdir(downstream_repo.name) do
319
+ reset_repo(downstream_repo.branch)
320
+
321
+ if downstream_repo.options.has_key? :new_branch_name
322
+ new_branch_name = get_new_branch_name(downstream_repo.options[:new_branch_name], @currentRelease.release)
323
+ GitUtil.delete_branch new_branch_name
324
+ end
325
+ end
326
+ end
327
+ end
328
+ Printer.success("Done resetting downstream repos.")
329
+ else
330
+ Printer.success("Not resetting downstream repos. None found.")
331
+ end
332
+ end
333
+
334
+ desc "prepare downstream release, copying files from base_docs_dir and any other configured files"
335
+ task :prepare, [:downstream_repo_index] => [:config, :"validate:changelog", :reset] do |t, args|
336
+ if @releasinator_config.has_key?(:downstream_repos)
337
+ get_downstream_repos(args[:downstream_repo_index]).each do |downstream_repo, index|
338
+ puts "preparing downstream_repo[#{index}]: #{downstream_repo.url}" if @releasinator_config[:verbose]
339
+
340
+ root_dir = Dir.pwd.strip
341
+ copy_from_dir = root_dir + "/" + get_base_dir()
342
+
343
+ Dir.chdir(DOWNSTREAM_REPOS) do
344
+ Dir.chdir(downstream_repo.name) do
345
+
346
+ if downstream_repo.options.has_key? :new_branch_name
347
+ new_branch_name = get_new_branch_name(downstream_repo.options[:new_branch_name], @currentRelease.release)
348
+ CommandProcessor.command("git checkout -b #{new_branch_name}")
349
+ end
350
+
351
+ if full_file_sync(downstream_repo.options)
352
+ # remove old everything
353
+ CommandProcessor.command("rm -rf *")
354
+
355
+ # update all sdk files
356
+ CommandProcessor.command("rsync -av --exclude='#{DOWNSTREAM_REPOS}' --exclude='.git/' #{copy_from_dir}/* .")
357
+ CommandProcessor.command("rsync -av --exclude='#{DOWNSTREAM_REPOS}' --exclude='.git/' #{copy_from_dir}/.[!.]* .")
358
+ end
359
+
360
+ # copy custom files
361
+ if downstream_repo.options.has_key? :files_to_copy
362
+ downstream_repo.options[:files_to_copy].each do |copy_file|
363
+ copy_the_file(root_dir, copy_file, @currentRelease.release)
364
+ end
365
+ end
366
+
367
+ if downstream_repo.options.has_key? :post_copy_methods
368
+ downstream_repo.options[:post_copy_methods].each do |method|
369
+ method.call(@currentRelease.release)
370
+ end
371
+ end
372
+
373
+ if GitUtil.is_clean_git?
374
+ Printer.fail("Nothing changed in #{downstream_repo.name}!")
375
+ abort()
376
+ end
377
+ # add everything to git and commit
378
+ CommandProcessor.command("git add .")
379
+ CommandProcessor.command("git add -u .")
380
+ if downstream_repo.options.has_key? :new_branch_name
381
+ commit_message = "Update #{@releasinator_config[:product_name]} to #{@currentRelease.release}"
382
+ else
383
+ commit_message = "Release #{@currentRelease.release}"
384
+ end
385
+ CommandProcessor.command("git commit -am \"#{commit_message}\"")
386
+ end
387
+ end
388
+ end
389
+ Printer.success("Done preparing downstream repos.")
390
+ else
391
+ Printer.success("Not preparing downstream repos. None found.")
392
+ end
393
+ end
394
+
395
+ desc "call all build_methods for each downstream repo"
396
+ task :build, [:downstream_repo_index] => :config do |t, args|
397
+ if @releasinator_config.has_key?(:downstream_repos)
398
+ get_downstream_repos(args[:downstream_repo_index]).each do |downstream_repo, index|
399
+ puts "building downstream_repo[#{index}]: #{downstream_repo.url}" if @releasinator_config[:verbose]
400
+ Dir.chdir(DOWNSTREAM_REPOS) do
401
+ Dir.chdir(downstream_repo.name) do
402
+ # build any files to verify release
403
+ if downstream_repo.options.has_key? :build_methods
404
+ downstream_repo.options[:build_methods].each do |method|
405
+ method.call
406
+ end
407
+ end
408
+ end
409
+ end
410
+ end
411
+ Printer.success("Done building downstream repos.")
412
+ else
413
+ Printer.success("Not building downstream repos. None found.")
414
+ end
415
+ end
416
+
417
+ desc "tag all non-branch downstream repos"
418
+ task :package, [:downstream_repo_index] => [:config,:"validate:changelog"] do |t, args|
419
+ if @releasinator_config.has_key?(:downstream_repos)
420
+ get_downstream_repos(args[:downstream_repo_index]).each do |downstream_repo, index|
421
+ puts "packaging downstream_repo[#{index}]: #{downstream_repo.url}" if @releasinator_config[:verbose]
422
+ Dir.chdir(DOWNSTREAM_REPOS) do
423
+ Dir.chdir(downstream_repo.name) do
424
+ # don't tag those where new branches are created
425
+ git_tag(@currentRelease.release, @currentRelease.changelog) unless downstream_repo.options.has_key? :new_branch_name
426
+ end
427
+ end
428
+ end
429
+ Printer.success("Done packaging downstream repos.")
430
+ else
431
+ Printer.success("Not packaging downstream repos. None found.")
432
+ end
433
+ end
434
+
435
+ desc "push tags and creates draft release, or pushes branch and creates pull request, depending on the presence of new_branch_name"
436
+ task :push, [:downstream_repo_index] => [:config,:"validate:changelog"] do |t, args|
437
+ if @releasinator_config.has_key?(:downstream_repos)
438
+ get_downstream_repos(args[:downstream_repo_index]).each do |downstream_repo, index|
439
+ puts "pushing downstream_repo[#{index}]: #{downstream_repo.url}" if @releasinator_config[:verbose]
440
+ Dir.chdir(DOWNSTREAM_REPOS) do
441
+ Dir.chdir(downstream_repo.name) do
442
+ if downstream_repo.options.has_key? :new_branch_name
443
+ new_branch_name = get_new_branch_name(downstream_repo.options[:new_branch_name], @currentRelease.release)
444
+ CommandProcessor.command("git push -u origin #{new_branch_name}")
445
+ Publisher.new(@releasinator_config).publish_pull_request(downstream_repo.url, @currentRelease, @releasinator_config[:product_name], downstream_repo.branch, new_branch_name)
446
+ else
447
+ CommandProcessor.command("git push origin master --tags")
448
+ Publisher.new(@releasinator_config).publish_draft(downstream_repo.url, @currentRelease) unless ! release_to_github(downstream_repo.options)
449
+ end
450
+ end
451
+ end
452
+ end
453
+ Printer.success("Done pushing downstream repos.")
454
+ else
455
+ Printer.success("Not pushing downstream repos. None found.")
456
+ end
457
+ end
458
+ end
459
+
460
+ namespace :docs do
461
+ desc "build, copy, and push docs to gh-pages branch"
462
+ task :all => [:build, :package, :push]
463
+
464
+ desc "build docs"
465
+ task :build => [:config] do
466
+ if @releasinator_config.has_key?(:doc_build_method)
467
+ @releasinator_config[:doc_build_method].call
468
+ Printer.success("doc_build_method done.")
469
+ else
470
+ Printer.success("No doc_build_method found.")
471
+ end
472
+ end
473
+
474
+ desc "copy and commit docs to gh-pages branch"
475
+ task :package => [:config,:"validate:changelog"] do
476
+ if @releasinator_config.has_key?(:doc_files_to_copy)
477
+ root_dir = Dir.pwd.strip
478
+
479
+ doc_target_dir = "."
480
+ doc_target_dir = @releasinator_config[:doc_target_dir] if @releasinator_config.has_key?(:doc_target_dir)
481
+
482
+ Dir.chdir(doc_target_dir) do
483
+ current_branch = GitUtil.get_current_branch()
484
+
485
+ GitUtil.init_gh_pages()
486
+ reset_repo("gh-pages")
487
+ @releasinator_config[:doc_files_to_copy].each do |copy_file|
488
+ copy_the_file(root_dir, copy_file)
489
+ end
490
+
491
+ CommandProcessor.command("git add .")
492
+ CommandProcessor.command("git commit -m \"Update docs for release #{@currentRelease.release}\"")
493
+
494
+ # switch back to previous branch
495
+ CommandProcessor.command("git checkout #{current_branch}")
496
+ end
497
+ Printer.success("Doc files copied.")
498
+ else
499
+ Printer.success("No doc_files_to_copy found.")
500
+ end
501
+ end
502
+
503
+ desc "push gh-pages branch"
504
+ task :push => [:config] do
505
+ if @releasinator_config.has_key?(:doc_build_method)
506
+ doc_target_dir = "."
507
+ doc_target_dir = @releasinator_config[:doc_target_dir] if @releasinator_config.has_key?(:doc_target_dir)
508
+
509
+ Dir.chdir(doc_target_dir) do
510
+ current_branch = GitUtil.get_current_branch()
511
+ CommandProcessor.command("git checkout gh-pages")
512
+ CommandProcessor.command("git push origin gh-pages")
513
+ # switch back to previous branch
514
+ CommandProcessor.command("git checkout #{current_branch}")
515
+ end
516
+ Printer.success("Docs pushed.")
517
+ else
518
+ Printer.success("No docs pushed.")
519
+ end
520
+ end
521
+ end
522
+
523
+ def replace_string(filepath, string_to_replace, new_string)
524
+ IO.write(filepath,
525
+ File.open(filepath) do |file|
526
+ file.read.gsub(string_to_replace, new_string)
527
+ end
528
+ )
529
+ end