Skip to content

feat(wizard): add Publish step — generate & upload first SBOMs locally - #281

Open
vpetersson wants to merge 2 commits into
masterfrom
feat/wizard-publish-step
Open

feat(wizard): add Publish step — generate & upload first SBOMs locally#281
vpetersson wants to merge 2 commits into
masterfrom
feat/wizard-publish-step

Conversation

@vpetersson

Copy link
Copy Markdown
Contributor

Summary

Expands the wizard with a final Publish step (step 9, between Apply and Done) that generates and uploads the first SBOMs right from the wizard, instead of waiting for the first CI run.

After Apply has created the components and written sboms.yml, the new screen lists one planned run per (lockfile, format) — the same matrix rows the emitted workflow runs in CI — and offers Publish now / Skip / Back. Each run executes the real pipeline as a subprocess, streaming its output live into the screen's log, and the Done screen gains a per-run published/failed panel.

Design decisions

  • Subprocess, not in-processrun_pipeline calls sys.exit on failure, reconfigures global logging, and mutates process-wide audit state, none of which can happen inside the Textual app. A subprocess also exercises exactly the code path CI will run. Known pipeline env vars are scrubbed from the inherited environment so a stray DOCKER_IMAGE in the user's shell can't hijack a run.
  • Credentials — CI authenticates via OIDC or the repo secret; the local run reuses the session token the user already entered on the Authenticate screen. No extra setup.
  • Naming parity — the matrix-row logic is factored out of ci_emitter._matrix_block into a shared matrix_rows() helper, so local runs and the CI job agree byte-for-byte on component IDs, output filenames, and duplicate-name slug disambiguation.
  • Best-effort and optional — the step waits for an explicit button press (runs can take minutes), a failed run never blocks the remaining ones or the wizard (CI retries on the next push, and the UI says so), and Skip leaves the Done screen exactly as it was before this feature. --dry-run previews the runs without spawning anything.
  • Versioning — locally published SBOMs are stamped with the short git SHA, matching the workflow's non-tag fallback. PRODUCT_RELEASE is deliberately not set locally so onboarding never cuts a surprise product release named after a commit SHA.
  • Output hygiene — generated files go to a kept temp dir (surfaced on the Done screen), never the working tree; the subprocess runs with cwd=repo_root so repo-relative lockfile paths and sbomify.json augmentation resolve the same way they do in CI.

Test plan

  • New tests/test_wizard_publish.py: matrix-row parity, env building + scrubbing, dry-run, per-run failure isolation, spawn-error handling, real _stream_subprocess round-trip, Done-panel text variants.
  • Two new Textual pilot tests: Publish screen composes, Skip → Done with no outcomes, Publish → worker → Continue → Done.
  • Full suite: 2,440 passed, ruff clean. New runner module at 100% coverage, new screen at 81%.

🤖 Generated with Claude Code

Add a ninth wizard step between Apply and Done that runs the real
pipeline locally for each planned (lockfile, format) matrix row, so the
user leaves the wizard with their first SBOMs already on sbomify
instead of waiting for the first CI run.

- Each row runs as a subprocess of the wizard's interpreter with an env
  block mirroring the emitted workflow's (COMPONENT_ID, LOCK_FILE,
  UPLOAD/AUGMENT/ENRICH, SBOM_FORMAT, API_BASE_URL), using the wizard
  session token in place of OIDC / the repo secret. Known pipeline env
  vars are scrubbed from the inherited environment first.
- matrix_rows() is factored out of ci_emitter._matrix_block so local
  runs and the CI job agree byte-for-byte on component IDs, output
  filenames, and duplicate-name slug disambiguation.
- Best-effort and optional: explicit Publish/Skip buttons, a failed run
  never blocks the rest (CI retries on the next push), Skip leaves Done
  unchanged, and --dry-run previews without spawning anything.
- Local SBOMs are stamped with the short git SHA (the workflow's
  non-tag fallback); PRODUCT_RELEASE is deliberately not set so
  onboarding never cuts a release named after a commit SHA.
- Generated files go to a kept temp dir surfaced on the Done screen,
  which now renders a per-run published/failed/dry-run panel.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings July 16, 2026 14:04
Comment thread sbomify_action/cli/wizard/publish.py Dismissed

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds an optional Publish step to the CLI wizard to locally generate and upload initial SBOMs immediately after Apply, using the same matrix-row planning logic as the emitted CI workflow and streaming subprocess output into the TUI.

Changes:

  • Introduces a new Publish screen + orchestration module to run the pipeline as subprocesses and record per-run outcomes.
  • Refactors CI matrix planning into a shared matrix_rows() helper and MatrixRow dataclass for byte-for-byte parity between local publish and CI.
  • Extends Done screen and wizard state to surface publish outcomes and (when applicable) the retained publish output directory; adds comprehensive new test coverage.

Reviewed changes

Copilot reviewed 13 out of 13 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
tests/test_wizard_textual.py Adds Textual pilot tests for Publish step navigation and worker completion flow.
tests/test_wizard_publish.py Adds unit tests for matrix parity, env building/scrubbing, subprocess streaming, run isolation, and Done/Publish text rendering.
sbomify_action/cli/wizard/styles.tcss Adds layout rules so the Publish log can stream large subprocess output without breaking the screen layout.
sbomify_action/cli/wizard/state.py Adds PublishOutcome and new state fields for publish outcomes/output dir; clears them on re-apply.
sbomify_action/cli/wizard/screens/welcome.py Updates the step list to include the new optional Publish step.
sbomify_action/cli/wizard/screens/publish.py New screen implementing the optional local publish UX with worker-thread subprocess streaming.
sbomify_action/cli/wizard/screens/help.py Updates help text to include the Publish step.
sbomify_action/cli/wizard/screens/done.py Adds an optional “Published SBOMs” panel driven by recorded publish outcomes.
sbomify_action/cli/wizard/screens/apply.py Routes Apply completion to the new Publish screen instead of Done.
sbomify_action/cli/wizard/screens/_base.py Increments wizard total steps to include Publish.
sbomify_action/cli/wizard/publish.py New subprocess-based publish runner (run_publish) + helpers for env building, versioning, and streaming output.
sbomify_action/cli/wizard/ci_emitter.py Factors matrix row computation into shared matrix_rows() returning MatrixRow objects; YAML rendering consumes it.
sbomify_action/cli/wizard/app.py Updates high-level wizard step documentation to include Publish + Done.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +206 to +213
state.publish_outcomes = []
output_dir = Path(tempfile.mkdtemp(prefix="sbomify-wizard-publish-"))
state.publish_output_dir = output_dir
runs = build_publish_runs(state, output_dir)

if not runs:
log("warning", "Nothing to publish — no components were applied.")
return True
Comment on lines +190 to +192
for outcome in state.publish_outcomes:
label = f"{outcome.rel_path} [#5E5E5E]({outcome.sbom_format})[/]"
if state.is_dry_run:
- Coerce the session token to str for the subprocess env: the client's
  token field is typed str | None, though the wizard always has one; an
  empty TOKEN fails the subprocess's own config validation loudly.
- Type _rows() as list[MatrixRow].
- Suppress dangerous-subprocess-use-audit on the pipeline Popen with the
  repo's standard annotation — list-form, shell=False, fixed executable
  (sys.executable -c <constant>).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings July 16, 2026 14:10

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 13 out of 13 changed files in this pull request and generated 2 comments.

Comment on lines +190 to +192
for outcome in state.publish_outcomes:
label = f"{outcome.rel_path} [#5E5E5E]({outcome.sbom_format})[/]"
if state.is_dry_run:
Comment on lines +207 to +211
if event.button.id == "skip":
# Explicitly not publishing — leave publish_outcomes empty so
# the Done screen renders exactly as it did pre-publish-step.
self._continue_to_done()
return
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants