Class: Inquirex::Tools::Publisher
- Inherits:
-
Object
- Object
- Inquirex::Tools::Publisher
- Defined in:
- lib/inquirex/tools/publisher.rb
Overview
Publishes every package in the family by running each one's own
just publish in its own directory.
How a package reaches its registry stays that package's business: the
gems build a .gem and gem push it, the npm packages run bun or yarn.
This class only decides the order, supplies the one thing a package
cannot work out for itself — a fresh 2FA code — and stops at the first
failure.
A TOTP is single-use. Reading 1Password once per gem inside a 30-second window returns the same six digits every time, and RubyGems rejects the second push as a replay. So a code is read per gem and the run waits for it to actually rotate before moving on, which is why publishing four gems takes minutes rather than seconds. The npm packages read their own code and are called with no argument.
Order comes from Workspace::LOCKSTEP, so adding a package to the family adds it to the release. That order puts the core gem first, which matters: inquirex-llm and inquirex-tty depend on it and cannot resolve until it is on RubyGems.
Constant Summary collapse
- ROTATE_TIMEOUT =
How long to wait for a TOTP to rotate before giving up and using what 1Password last returned. Codes rotate every 30 seconds; 90 covers a window boundary landing badly without hanging a release indefinitely.
90- ROTATE_INTERVAL =
Seconds between polls of 1Password while waiting for a new code.
2- DEFAULT_RUNNER =
Shells out with the child's stdout and stderr left attached, so a failing package's own output is what the operator sees.
lambda { |dir, argv| Dir.chdir(dir) { system("just", *argv) } }
- DEFAULT_OTP_READER =
Reads the current RubyGems TOTP.
opresolves the account from OP_ACCOUNT, whichjust publish-allexports by decrypting .env.encrypted. Returns nil when unavailable, so a release without 1Password degrades togem pushprompting rather than failing. lambda { ref = "op://open-source-repos/ruby-gems/one-time password?attribute=otp" code = `op read #{ref.inspect} 2>/dev/null`.strip code.empty? ? nil : code }
- DEFAULT_PUBLISHED_CHECKER =
Asks the registry whether a version is already public.
Returns nil rather than false when the answer cannot be obtained — no network, a 500, a timeout. The caller treats nil as "attempt it": the registry itself refuses a duplicate, so guessing "not published" and trying is safe, whereas guessing "published" would silently skip a package that never shipped.
lambda { |name, kind, version| url = case kind when :gem then "https://rubygems.org/api/v2/rubygems/#{name}/versions/#{version}.json" when :npm then "https://registry.npmjs.org/#{name}/#{version}" end begin uri = URI.parse(url) response = Net::HTTP.start(uri.host, uri.port, use_ssl: true, open_timeout: 5, read_timeout: 5) do |http| http.head(uri.request_uri) end case response when Net::HTTPSuccess then true when Net::HTTPNotFound then false end rescue StandardError nil end }
Instance Method Summary collapse
-
#call(dry_run: false, args: [], force: false) ⇒ Boolean
Publishes every lockstep package, or prints what it would publish.
-
#initialize(workspace: Workspace.new, out: $stdout, runner: DEFAULT_RUNNER, otp_reader: DEFAULT_OTP_READER, published_checker: DEFAULT_PUBLISHED_CHECKER) ⇒ Publisher
constructor
A new instance of Publisher.
Constructor Details
#initialize(workspace: Workspace.new, out: $stdout, runner: DEFAULT_RUNNER, otp_reader: DEFAULT_OTP_READER, published_checker: DEFAULT_PUBLISHED_CHECKER) ⇒ Publisher
Returns a new instance of Publisher.
95 96 97 98 99 100 101 102 103 104 105 |
# File 'lib/inquirex/tools/publisher.rb', line 95 def initialize(workspace: Workspace.new, out: $stdout, runner: DEFAULT_RUNNER, otp_reader: DEFAULT_OTP_READER, published_checker: DEFAULT_PUBLISHED_CHECKER) @workspace = workspace @out = out @runner = runner @otp_reader = otp_reader @published_checker = published_checker end |
Instance Method Details
#call(dry_run: false, args: [], force: false) ⇒ Boolean
Publishes every lockstep package, or prints what it would publish.
Idempotent: a package whose current version is already on its registry is skipped, so re-running after a mid-release failure picks up exactly where it stopped instead of throwing six rejections at two registries.
118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 |
# File 'lib/inquirex/tools/publisher.rb', line 118 def call(dry_run: false, args: [], force: false) out.puts(dry_run ? "DRY RUN — these commands would run, in this order:" : "Publishing the family:") last_otp = nil published = 0 skipped = 0 Workspace::LOCKSTEP.each do |name, kind| dir = File.join(workspace.root, name) raise Error, "no checkout for #{name} at #{dir}" unless Dir.exist?(dir) version = workspace.version_of(name) if !force && version && published_checker.call(name, kind, version) row(name, kind, "already #{version} on #{registry(kind)} — skipping".dark) skipped += 1 next end argv = ["publish", *args] if dry_run row(name, kind, "just #{argv.join(" ")}#{" (#{version})" if version}") published += 1 next end # Only the gems take a code: each npm package reads its own. if kind == :gem last_otp = fresh_otp(last_otp) argv += [last_otp] if last_otp end out.puts out.puts "━━ #{name} (#{kind}) ━━━━━━━━━━━━━━━━━━━━━━━━━━━━" return false unless run(name, dir, argv) published += 1 end out.puts out.puts summary(dry_run:, published:, skipped:) true end |