Class: Inquirex::Tools::Releaser

Inherits:
Object
  • Object
show all
Defined in:
lib/inquirex/tools/releaser.rb

Overview

Cuts a GitHub release for every package in the family by running each one's own just release in its own directory.

The counterpart to publishing: that puts packages on RubyGems and npm, this puts a vX.Y.Z tag and a GitHub release against the commit that shipped. Every package already owns a release recipe — it tags, force-pushes the tag, and recreates the GitHub release with generated notes — so how a repository releases stays that repository's business. What this adds is the order, the guards, and one command instead of six.

Unlike publishing, a release is reversible: a tag can be moved and a GitHub release deleted, which is exactly what each recipe does on every run. That is why this is safe to re-run, and why a mid-run failure can be fixed and the command simply issued again.

The guards exist because the recipe tags HEAD. Releasing from a dirty tree tags a commit that does not match what was tested; releasing from a feature branch tags work that was never merged. Both produce a v0.9.5 pointing somewhere nobody can reproduce, and neither is fixed by re-running — so they are refused rather than warned about.

Examples:

Preview a release without tagging anything

Inquirex::Tools::Releaser.new.call(dry_run: true)

Constant Summary collapse

RELEASE_BRANCH =

The only branch a release may be cut from. A tag on anything else points at work that never went through review.

"main"
DEFAULT_RUNNER =

Shells out with the child's stdout and stderr left attached, so a failing package's own gh output is what the operator sees.

lambda { |dir, argv|
  Dir.chdir(dir) { system("just", *argv) }
}

Instance Method Summary collapse

Constructor Details

#initialize(workspace: Workspace.new, out: $stdout, runner: DEFAULT_RUNNER) ⇒ Releaser

Returns a new instance of Releaser.

Parameters:

  • workspace (Workspace) (defaults to: Workspace.new)

    the ecosystem checkout to release from

  • out (IO) (defaults to: $stdout)

    where report output goes

  • runner (#call) (defaults to: DEFAULT_RUNNER)

    invoked with (dir, argv) to run a command; the default shells out. Injected so specs can assert what would run without creating a tag or a GitHub release.



47
48
49
50
51
# File 'lib/inquirex/tools/releaser.rb', line 47

def initialize(workspace: Workspace.new, out: $stdout, runner: DEFAULT_RUNNER)
  @workspace = workspace
  @out = out
  @runner = runner
end

Instance Method Details

#call(dry_run: false, force: false, args: []) ⇒ Boolean

Creates a GitHub release for every lockstep package.

Stops at the first package that fails or is refused, and says so. The packages after it are left alone rather than half-tagged.

Parameters:

  • dry_run (Boolean) (defaults to: false)

    print what would run; tag nothing

  • force (Boolean) (defaults to: false)

    release despite a dirty tree or a non-main branch

  • args (Array<String>) (defaults to: [])

    extra arguments forwarded to just release

Returns:

  • (Boolean)

    true when every package released or was printed



62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
# File 'lib/inquirex/tools/releaser.rb', line 62

def call(dry_run: false, force: false, args: [])
  out.puts(dry_run ? "DRY RUN — these releases would be created:" : "Creating GitHub releases for the family:")

  released = 0

  Workspace::LOCKSTEP.each_key do |name|
    dir = File.join(workspace.root, name)
    raise Error, "no checkout for #{name} at #{dir}" unless Dir.exist?(dir)

    version = workspace.version_of(name)

    blocker = force ? nil : blocker_for(name)
    if blocker
      row(name, version, blocker.red)
      return refused(name)
    end

    argv = ["release", *args]
    if dry_run
      row(name, version, "just #{argv.join(" ")}")
      released += 1
      next
    end

    out.puts
    out.puts "━━ #{name} v#{version} ━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
    return false unless run(name, dir, argv)

    released += 1
  end

  out.puts
  out.puts summary(dry_run:, released:)
  true
end