toys-release 0.1.1 → 0.2.2

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.
@@ -2,7 +2,7 @@
2
2
 
3
3
  require "yaml"
4
4
 
5
- require_relative "semver"
5
+ require "toys/release/semver"
6
6
 
7
7
  module Toys
8
8
  module Release
@@ -14,6 +14,7 @@ module Toys
14
14
  ScopeInfo = ::Struct.new(:semver, :header)
15
15
 
16
16
  ##
17
+ # @private
17
18
  # Create a CommitTagSettings from either a tag name string (which will
18
19
  # default to patch releases) or a hash with fields.
19
20
  #
@@ -160,6 +161,7 @@ module Toys
160
161
  #
161
162
  class ComponentSettings
162
163
  ##
164
+ # @private
163
165
  # Create a ComponentSettings from input data structures
164
166
  #
165
167
  # @param info [Hash] Nested hash input
@@ -299,13 +301,134 @@ module Toys
299
301
  end
300
302
 
301
303
  ##
304
+ # Configuration of input settings for a step.
305
+ # An input declares a dependency on a step, and copies any files output by
306
+ # that dependency.
307
+ #
308
+ class InputSettings
309
+ ##
310
+ # @private
311
+ # Construct input settings
312
+ #
313
+ # @param info [Hash,String] Config data
314
+ #
315
+ def initialize(info)
316
+ @step_name = @dest = @source_path = @dest_path = nil
317
+ case info
318
+ when ::String
319
+ @step_name = info
320
+ @dest = "component"
321
+ when ::Hash
322
+ @step_name = info["name"]
323
+ @dest = info.fetch("dest", "component")
324
+ @dest = "none" if @dest == false
325
+ @source_path = info["source_path"]
326
+ @dest_path = info["dest_path"]
327
+ end
328
+ end
329
+
330
+ ##
331
+ # @return [String] Name of the step to copy data from.
332
+ #
333
+ attr_reader :step_name
334
+
335
+ ##
336
+ # @return [String,false] Where to copy data to. Possible values are
337
+ # "component", "repo_root", "output", "temp", and "none". If "none",
338
+ # no copying is performed and this input declares a dependency only.
339
+ #
340
+ attr_reader :dest
341
+
342
+ ##
343
+ # @return [String,nil] Path in the source to copy from. Can be a path to
344
+ # a file or a directory. If nil, copy everything from the input.
345
+ #
346
+ attr_reader :source_path
347
+
348
+ ##
349
+ # @return [String,nil] Path in the destination to copy to, relative to
350
+ # the destination. If nil, uses the source path.
351
+ #
352
+ attr_reader :dest_path
353
+
354
+ ##
355
+ # @return [Hash] the hash representation
356
+ #
357
+ def to_h
358
+ {
359
+ "name" => step_name,
360
+ "dest" => dest,
361
+ "source_path" => source_path,
362
+ "dest_path" => dest_path,
363
+ }
364
+ end
365
+ end
366
+
367
+ ##
368
+ # Configuration of output info for a step.
369
+ # An output automatically copies files from the repo directory to this
370
+ # step's output where they can be imported by another step.
371
+ #
372
+ class OutputSettings
373
+ ##
374
+ # @private
375
+ # Construct output settings
376
+ #
377
+ # @param info [Hash,String] Config data
378
+ #
379
+ def initialize(info)
380
+ @source = @source_path = @dest_path = nil
381
+ case info
382
+ when ::String
383
+ @source_path = info
384
+ @source = "component"
385
+ when ::Hash
386
+ @source = info.fetch("source", "component")
387
+ @source_path = info["source_path"]
388
+ @dest_path = info["dest_path"]
389
+ end
390
+ end
391
+
392
+ ##
393
+ # @return [String] Where to copy data from. Possible values are
394
+ # "component", "repo_root", and "temp".
395
+ #
396
+ attr_reader :source
397
+
398
+ ##
399
+ # @return [String,nil] Path to copy from, relative to the source. Can be
400
+ # a file or a directory. If nil, copy everything in the source.
401
+ #
402
+ attr_reader :source_path
403
+
404
+ ##
405
+ # @return [String,nil] Path in the step's output to copy to.
406
+ # If nil, uses the source path.
407
+ #
408
+ attr_reader :dest_path
409
+
410
+ ##
411
+ # @return [Hash] the hash representation
412
+ #
413
+ def to_h
414
+ {
415
+ "source" => source,
416
+ "source_path" => source_path,
417
+ "dest_path" => dest_path,
418
+ }
419
+ end
420
+ end
421
+
422
+ ##
423
+ # @private
302
424
  # Configuration of a step
303
425
  #
304
426
  class StepSettings
305
- def initialize(name, type, options)
306
- @name = name
307
- @type = type
308
- @options = options
427
+ ##
428
+ # Create a StepSettings
429
+ #
430
+ def initialize(info)
431
+ from_h(info.dup)
309
432
  end
310
433
 
311
434
  ##
@@ -318,18 +441,62 @@ module Toys
318
441
  #
319
442
  attr_reader :type
320
443
 
444
+ ##
445
+ # @return [boolean] Whether this step is explicitly requested
446
+ #
447
+ def requested?
448
+ @requested
449
+ end
450
+
451
+ ##
452
+ # @return [Array<InputSettings>] Inputs for this step
453
+ #
454
+ attr_reader :inputs
455
+
456
+ ##
457
+ # @return [Array<OutputSettings>] Extra outputs for this step
458
+ #
459
+ attr_reader :outputs
460
+
321
461
  ##
322
462
  # @return [Hash{String=>Object}] Options for this step
323
463
  #
324
464
  attr_reader :options
325
465
 
466
+ ##
467
+ # @return [Hash] the hash representation
468
+ #
469
+ def to_h
470
+ {
471
+ "name" => name,
472
+ "type" => type,
473
+ "run" => requested?,
474
+ "inputs" => inputs.map(&:to_h),
475
+ "outputs" => outputs.map(&:to_h),
476
+ }.merge(RepoSettings.deep_copy(options))
477
+ end
478
+
326
479
  ##
327
480
  # Make a deep copy
328
481
  #
329
482
  # @return [StepSettings] A deep copy
330
483
  #
331
484
  def deep_copy
332
- StepSettings.new(name, type, RepoSettings.deep_copy(options))
485
+ StepSettings.new(to_h)
486
+ end
487
+
488
+ ##
489
+ # @private
490
+ # Initialize the step from the given hash.
491
+ # The hash will be deconstructed in place.
492
+ #
493
+ def from_h(info)
494
+ @type = info.delete("type") || info["name"] || "noop"
495
+ @name = info.delete("name") || "_anon_#{@type}_#{object_id}"
496
+ @requested = info.delete("run") ? true : false
497
+ @inputs = Array(info.delete("inputs")).map { |input_info| InputSettings.new(input_info) }
498
+ @outputs = Array(info.delete("outputs")).map { |output_info| OutputSettings.new(output_info) }
499
+ @options = info
333
500
  end
334
501
  end
335
502
 
@@ -368,6 +535,7 @@ module Toys
368
535
  end
369
536
 
370
537
  ##
538
+ # @private
371
539
  # Create a repo configuration object.
372
540
  #
373
541
  # @param info [Hash] Configuration hash read from JSON.
@@ -378,7 +546,6 @@ module Toys
378
546
  @default_component_name = nil
379
547
  read_global_info(info)
380
548
  read_label_info(info)
381
- read_commit_lint_info(info)
382
549
  read_commit_tag_info(info)
383
550
  read_default_step_info(info)
384
551
  read_component_info(info)
@@ -452,18 +619,6 @@ module Toys
452
619
  #
453
620
  attr_reader :gh_pages_enabled
454
621
 
455
- ##
456
- # @return [Array<String>] The merge strategies allowed when linting
457
- # commit messages.
458
- #
459
- attr_reader :commit_lint_merge
460
-
461
- ##
462
- # @return [Array<String>] The allowed conventional commit types when
463
- # linting commit messages.
464
- #
465
- attr_reader :commit_lint_allowed_types
466
-
467
622
  ##
468
623
  # @return [Hash{String=>CommitTagSettings}] The conventional commit types
469
624
  # recognized as release-triggering, along with the type of change they
@@ -535,21 +690,6 @@ module Toys
535
690
  @enable_release_automation
536
691
  end
537
692
 
538
- ##
539
- # @return [boolean] Whether conventional commit linting errors should fail
540
- # GitHub checks.
541
- #
542
- def commit_lint_fail_checks?
543
- @commit_lint_fail_checks
544
- end
545
-
546
- ##
547
- # @return [boolean] Whether to perform conventional commit linting.
548
- #
549
- def commit_lint_active?
550
- @commit_lint_active
551
- end
552
-
553
693
  ##
554
694
  # @return [Array<String>] A list of all component names.
555
695
  #
@@ -587,18 +727,7 @@ module Toys
587
727
 
588
728
  # @private
589
729
  def read_steps(info)
590
- steps = []
591
- info.each do |step_info|
592
- step_info = step_info.dup
593
- name = step_info.delete("name")
594
- type = step_info.delete("type")
595
- if type
596
- steps << StepSettings.new(name, type, step_info)
597
- else
598
- @errors << "No step type provided for step #{name.inspect}"
599
- end
600
- end
601
- steps
730
+ info.map { |step_info| StepSettings.new(step_info) }
602
731
  end
603
732
 
604
733
  # @private
@@ -610,14 +739,15 @@ module Toys
610
739
  steps.each do |step|
611
740
  next if (mod_name && step.name != mod_name) || (mod_type && step.type != mod_type)
612
741
  count += 1
613
- opts = step.options
742
+ modified_info = step.to_h
614
743
  mod_data.each do |key, value|
615
744
  if value.nil?
616
- opts.delete(key)
745
+ modified_info.delete(key)
617
746
  else
618
- opts[key] = value
747
+ modified_info[key] = value
619
748
  end
620
749
  end
750
+ step.from_h(modified_info)
621
751
  end
622
752
  if count.zero?
623
753
  @errors << "Unable to find step to modify for name=#{mod_name.inspect} and type=#{mod_type.inspect}."
@@ -628,14 +758,60 @@ module Toys
628
758
 
629
759
  # @private
630
760
  def prepend_steps(steps, info)
631
- pre_steps = read_steps(info)
632
- pre_steps + steps
761
+ before = []
762
+ insert = []
763
+ after = steps
764
+ case info
765
+ when ::Hash
766
+ if (before_name = info["before"])
767
+ before_index = steps.find_index { |step| step.name == before_name }
768
+ if before_index
769
+ before = steps[...before_index]
770
+ after = steps[before_index..]
771
+ else
772
+ @errors << "Unable to find step named #{before_name} in prepend_steps.before"
773
+ end
774
+ end
775
+ if (steps_info = info["steps"]).is_a?(::Array)
776
+ insert = read_steps(steps_info)
777
+ else
778
+ @errors << "steps expected in prepend_steps"
779
+ end
780
+ when ::Array
781
+ insert = read_steps(info)
782
+ else
783
+ @errors << "prepend_steps expected a hash or array"
784
+ end
785
+ before + insert + after
633
786
  end
634
787
 
635
788
  # @private
636
789
  def append_steps(steps, info)
637
- post_steps = read_steps(info)
638
- steps + post_steps
790
+ before = steps
791
+ insert = []
792
+ after = []
793
+ case info
794
+ when ::Hash
795
+ if (after_name = info["after"])
796
+ after_index = steps.find_index { |step| step.name == after_name }
797
+ if after_index
798
+ before = steps[..after_index]
799
+ after = steps[(after_index + 1)..]
800
+ else
801
+ @errors << "Unable to find step named #{after_name} in append_steps.after"
802
+ end
803
+ end
804
+ if (steps_info = info["steps"]).is_a?(::Array)
805
+ insert = read_steps(steps_info)
806
+ else
807
+ @errors << "steps expected in append_steps"
808
+ end
809
+ when ::Array
810
+ insert = read_steps(info)
811
+ else
812
+ @errors << "append_steps expected a hash or array"
813
+ end
814
+ before + insert + after
639
815
  end
640
816
 
641
817
  # @private
@@ -656,57 +832,29 @@ module Toys
656
832
  DEFAULT_MAIN_BRAMCH = "main"
657
833
  private_constant :DEFAULT_MAIN_BRAMCH
658
834
 
659
- DEFAULT_RELEASE_COMMIT_TAGS = [
660
- {
661
- "tag" => "feat",
662
- "header" => "ADDED",
663
- "semver" => "minor",
664
- }.freeze,
665
- {
666
- "tag" => "fix",
667
- "header" => "FIXED",
668
- }.freeze,
669
- "docs",
670
- ].freeze
835
+ DEFAULT_RELEASE_COMMIT_TAGS = ::YAML.load(<<~STRING) # rubocop:disable Security/YAMLLoad
836
+ - tag: feat
837
+ header: ADDED
838
+ semver: minor
839
+ - tag: fix
840
+ header: FIXED
841
+ - docs
842
+ STRING
671
843
  private_constant :DEFAULT_RELEASE_COMMIT_TAGS
672
844
 
673
- DEFAULT_STEPS = {
674
- "component" => [
675
- {
676
- "name" => "github-release",
677
- "type" => "GitHubRelease",
678
- }.freeze,
679
- ].freeze,
680
- "gem" => [
681
- {
682
- "name" => "bundle",
683
- "type" => "Bundle",
684
- }.freeze,
685
- {
686
- "name" => "build-gem",
687
- "type" => "BuildGem",
688
- }.freeze,
689
- {
690
- "name" => "build-yard",
691
- "type" => "BuildYard",
692
- "require_gh_pages_enabled" => true,
693
- }.freeze,
694
- {
695
- "name" => "github-release",
696
- "type" => "GitHubRelease",
697
- }.freeze,
698
- {
699
- "name" => "release-gem",
700
- "type" => "ReleaseGem",
701
- "input" => "build-gem",
702
- }.freeze,
703
- {
704
- "name" => "push-gh-pages",
705
- "type" => "PushGhPages",
706
- "input" => "build-yard",
707
- }.freeze,
708
- ].freeze,
709
- }.freeze
845
+ DEFAULT_STEPS = ::YAML.load(<<~STRING) # rubocop:disable Security/YAMLLoad
846
+ component:
847
+ - name: release_github
848
+ gem:
849
+ - name: bundle
850
+ - name: build_gem
851
+ - name: build_yard
852
+ - name: release_github
853
+ - name: release_gem
854
+ source: build_gem
855
+ - name: push_gh_pages
856
+ source: build_yard
857
+ STRING
710
858
  private_constant :DEFAULT_STEPS
711
859
 
712
860
  DEFAULT_BREAKING_CHANGE_HEADER = "BREAKING CHANGE"
@@ -750,18 +898,6 @@ module Toys
750
898
  @release_complete_label = info["release_complete_label"] || DEFAULT_RELEASE_COMPLETE_LABEL
751
899
  end
752
900
 
753
- def read_commit_lint_info(info)
754
- info = info["commit_lint"]
755
- @commit_lint_active = !info.nil?
756
- info = {} unless info.is_a?(::Hash)
757
- @commit_lint_fail_checks = info["fail_checks"] ? true : false
758
- @commit_lint_merge = Array(info["merge"] || ["squash", "merge", "rebase"])
759
- @commit_lint_allowed_types = info["allowed_types"]
760
- if @commit_lint_allowed_types
761
- @commit_lint_allowed_types = Array(@commit_lint_allowed_types).map(&:downcase)
762
- end
763
- end
764
-
765
901
  def read_commit_tag_info(info)
766
902
  @release_commit_tags = read_commit_tag_info_set(info["release_commit_tags"] || DEFAULT_RELEASE_COMMIT_TAGS)
767
903
  info["modify_release_commit_tags"]&.each do |tag, data|
@@ -3,11 +3,11 @@
3
3
  require "base64"
4
4
  require "fileutils"
5
5
  require "json"
6
- require "tmpdir"
7
6
  require "yaml"
8
7
 
9
- require_relative "component"
10
- require_relative "pull_request"
8
+ require "toys/release/artifact_dir"
9
+ require "toys/release/component"
10
+ require "toys/release/pull_request"
11
11
 
12
12
  module Toys
13
13
  module Release
@@ -493,8 +493,7 @@ module Toys
493
493
  ::FileUtils.remove_entry(dir, true)
494
494
  ::FileUtils.mkdir_p(dir)
495
495
  else
496
- dir = ::Dir.mktmpdir
497
- at_exit { ::FileUtils.remove_entry(dir, true) }
496
+ dir = ArtifactDir.new(auto_cleanup: true).get
498
497
  end
499
498
  dir
500
499
  end
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative "semver"
3
+ require "toys/release/semver"
4
4
 
5
5
  module Toys
6
6
  module Release