pbm init: ensure PocketBase binary is installed (Bootstrap orchestrator) #3

Closed
opened 2026-08-27 15:41:55 +00:00 by jared · 2 comments
Owner

Context

Interface decision from session 2026-08-27: pbm init should make the project fully ready, not just write config. Binary#path (lib/pb_mirror/binary.rb:24) already installs on demand, so init auto-install is one message-send away. pbm install stays as the explicit re-download/repair path. Decision memo accepted by the user (option A, init auto-installs, --no-install escape hatch).

Tasks

  • Introduce PbMirror::Bootstrap orchestrator composing config write, gitignore management, and binary ensure, with injected collaborators and a reporter duck
  • CLI#init becomes pure dispatch; CLI plays the reporter role (Thor say matches the duck); add --install boolean option (default true)
  • Unit tests for Bootstrap using fakes: config-written message, yellow gitignore warning relay, quiet path, binary ensured with pinned version, "PocketBase X ready." message, --no-install skip
  • Update README: init now installs the pinned binary; pbm install remains for re-download/repair

Acceptance criteria

  • Fresh project: pbm init db.sqlite writes .pbm.yml, updates .gitignore, and downloads the pinned PocketBase binary in one command
  • pbm init --no-install skips the download and says so
  • Existing gitignore warning behavior unchanged (yellow warning relayed)
  • Full suite green, rubocop clean

Proposed approach (advisory — implementation may adjust course)

POODR/99-Bottles design sketched in-session; use or revise as evidence dictates.

New lib/pb_mirror/bootstrap.rb:

module PbMirror
  # Orchestrates `pbm init`: writes the config, manages .gitignore, and
  # ensures the pinned PocketBase binary is present (Binary#path installs
  # on demand). Reports progress through an injected reporter duck.
  class Bootstrap
    def initialize(init:, reporter:, gitignore: GitignoreManager.new,
                   binary_factory: Binary, config_loader: Config.method(:load), install: true)
      @init = init
      @reporter = reporter
      @gitignore = gitignore
      @binary_factory = binary_factory
      @config_loader = config_loader
      @install = install
    end

    def call
      config_path = @init.call
      @reporter.say "Wrote #{config_path}."
      report_gitignore
      ensure_binary(config_path)
    end

    private

    def report_gitignore
      warning = @gitignore.call
      @reporter.say warning, :yellow if warning
    end

    def ensure_binary(config_path)
      return @reporter.say "Skipped PocketBase install (--no-install)." unless @install

      config = @config_loader.call(config_path)
      @binary_factory.new(version: config.pocketbase_version, override_path: config.binary_path).path
      @reporter.say "PocketBase #{config.pocketbase_version} ready."
    end
  end
end

CLI#init becomes:

method_option :install, type: :boolean, default: true
def init(db_path)
  Bootstrap.new(
    init: Init.new(db_path: db_path, config_path: options[:config], port: options[:port],
                   force: options[:force]),
    reporter: self,
    install: options[:install]
  ).call
end

Tests (test/pb_mirror/bootstrap_test.rb) use fakes for all five roles: FakeInit (#call -> ".pbm.yml"), FakeGitignore (warning or nil), FakeBinaryFactory (records received_version and path_requested), FakeReporter (captures [text, color] pairs), lambda config_loader returning a Struct(pocketbase_version, binary_path). Assert outgoing messages, not state. Known trade-off: install: boolean is a control couple; acceptable at two modes.

Origin

  • Trigger: interface review of pbm init vs pbm install after shipping gitignore management (commit c360dcd)
  • Improvised this session: none — design sketched in chat only, no code applied
  • Chain: two-command onboarding friction ← init only writes config ← DESIGN (lib/pb_mirror/cli.rb init/install split)
  • Root candidate: this ticket
## Context Interface decision from session 2026-08-27: `pbm init` should make the project fully ready, not just write config. `Binary#path` (lib/pb_mirror/binary.rb:24) already installs on demand, so init auto-install is one message-send away. `pbm install` stays as the explicit re-download/repair path. Decision memo accepted by the user (option A, init auto-installs, `--no-install` escape hatch). ## Tasks - [ ] Introduce `PbMirror::Bootstrap` orchestrator composing config write, gitignore management, and binary ensure, with injected collaborators and a reporter duck - [ ] `CLI#init` becomes pure dispatch; CLI plays the reporter role (Thor `say` matches the duck); add `--install` boolean option (default true) - [ ] Unit tests for Bootstrap using fakes: config-written message, yellow gitignore warning relay, quiet path, binary ensured with pinned version, "PocketBase X ready." message, `--no-install` skip - [ ] Update README: init now installs the pinned binary; `pbm install` remains for re-download/repair ## Acceptance criteria - [ ] Fresh project: `pbm init db.sqlite` writes .pbm.yml, updates .gitignore, and downloads the pinned PocketBase binary in one command - [ ] `pbm init --no-install` skips the download and says so - [ ] Existing gitignore warning behavior unchanged (yellow warning relayed) - [ ] Full suite green, rubocop clean ## Proposed approach (advisory — implementation may adjust course) POODR/99-Bottles design sketched in-session; use or revise as evidence dictates. New `lib/pb_mirror/bootstrap.rb`: ```ruby module PbMirror # Orchestrates `pbm init`: writes the config, manages .gitignore, and # ensures the pinned PocketBase binary is present (Binary#path installs # on demand). Reports progress through an injected reporter duck. class Bootstrap def initialize(init:, reporter:, gitignore: GitignoreManager.new, binary_factory: Binary, config_loader: Config.method(:load), install: true) @init = init @reporter = reporter @gitignore = gitignore @binary_factory = binary_factory @config_loader = config_loader @install = install end def call config_path = @init.call @reporter.say "Wrote #{config_path}." report_gitignore ensure_binary(config_path) end private def report_gitignore warning = @gitignore.call @reporter.say warning, :yellow if warning end def ensure_binary(config_path) return @reporter.say "Skipped PocketBase install (--no-install)." unless @install config = @config_loader.call(config_path) @binary_factory.new(version: config.pocketbase_version, override_path: config.binary_path).path @reporter.say "PocketBase #{config.pocketbase_version} ready." end end end ``` `CLI#init` becomes: ```ruby method_option :install, type: :boolean, default: true def init(db_path) Bootstrap.new( init: Init.new(db_path: db_path, config_path: options[:config], port: options[:port], force: options[:force]), reporter: self, install: options[:install] ).call end ``` Tests (test/pb_mirror/bootstrap_test.rb) use fakes for all five roles: FakeInit (#call -> ".pbm.yml"), FakeGitignore (warning or nil), FakeBinaryFactory (records received_version and path_requested), FakeReporter (captures [text, color] pairs), lambda config_loader returning a Struct(pocketbase_version, binary_path). Assert outgoing messages, not state. Known trade-off: `install:` boolean is a control couple; acceptable at two modes. ## Origin - Trigger: interface review of `pbm init` vs `pbm install` after shipping gitignore management (commit c360dcd) - Improvised this session: none — design sketched in chat only, no code applied - Chain: two-command onboarding friction ← init only writes config ← DESIGN (lib/pb_mirror/cli.rb init/install split) - Root candidate: this ticket
Author
Owner

Work started on branch worktree-ticket-3 via os-sdlc pipeline.

Work started on branch worktree-ticket-3 via os-sdlc pipeline.
Author
Owner

Resolution

Done: pbm init now makes the project fully ready in one command: writes .pbm.yml, manages .gitignore, and downloads the pinned PocketBase binary via a new Bootstrap orchestrator (injected collaborators, CLI as reporter duck); --no-install skips the download; pbm install remains the explicit re-download path; README updated.

Evidence: Commit 1bfa1b9 merged to main (fast-forward). Suite: 55 runs, 0 failures, 0 errors, 1 pre-existing skip. Rubocop: 29 files, no offenses. New lib/pb_mirror/bootstrap.rb + test/pb_mirror/bootstrap_test.rb (3 behavior tests via fakes); binary.rb gained rubyzip>=3 extract compat and per-process temp zip name, driven by tests. Pipeline deviations: nested-worktree cleanup; os-sdlc project.yaml was missing (ran setup-project, re-opened); repair-bound exhausted on a test-infra failure (missing minitest/mock require) so final wiring (CLI/README/require placement) was completed manually outside the pipeline after manual verification.

Follow-ups: Ticket-skeptic verdict not run (manual close after pipeline dead-end). Follow-up 1: already captured as #1 (CLI-level integration tests — init→Bootstrap wiring exercised only indirectly). Follow-up 2: file os-sdlc map issue for repair-bound exhaustion on test-infrastructure failures — captured next. No other follow-ups.

## Resolution **Done:** pbm init now makes the project fully ready in one command: writes .pbm.yml, manages .gitignore, and downloads the pinned PocketBase binary via a new Bootstrap orchestrator (injected collaborators, CLI as reporter duck); --no-install skips the download; pbm install remains the explicit re-download path; README updated. **Evidence:** Commit 1bfa1b9 merged to main (fast-forward). Suite: 55 runs, 0 failures, 0 errors, 1 pre-existing skip. Rubocop: 29 files, no offenses. New lib/pb_mirror/bootstrap.rb + test/pb_mirror/bootstrap_test.rb (3 behavior tests via fakes); binary.rb gained rubyzip>=3 extract compat and per-process temp zip name, driven by tests. Pipeline deviations: nested-worktree cleanup; os-sdlc project.yaml was missing (ran setup-project, re-opened); repair-bound exhausted on a test-infra failure (missing minitest/mock require) so final wiring (CLI/README/require placement) was completed manually outside the pipeline after manual verification. **Follow-ups:** Ticket-skeptic verdict not run (manual close after pipeline dead-end). Follow-up 1: already captured as #1 (CLI-level integration tests — init→Bootstrap wiring exercised only indirectly). Follow-up 2: file os-sdlc map issue for repair-bound exhaustion on test-infrastructure failures — captured next. No other follow-ups.
jared closed this issue 2026-08-27 16:14:22 +00:00
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
jared/pb-mirror#3
No description provided.