feat(wizard): add Publish step — generate & upload first SBOMs locally - #281
Open
vpetersson wants to merge 2 commits into
Open
feat(wizard): add Publish step — generate & upload first SBOMs locally#281vpetersson wants to merge 2 commits into
vpetersson wants to merge 2 commits into
Conversation
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>
Contributor
There was a problem hiding this comment.
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 andMatrixRowdataclass 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>
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 |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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
run_pipelinecallssys.exiton 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 strayDOCKER_IMAGEin the user's shell can't hijack a run.ci_emitter._matrix_blockinto a sharedmatrix_rows()helper, so local runs and the CI job agree byte-for-byte on component IDs, output filenames, and duplicate-name slug disambiguation.--dry-runpreviews the runs without spawning anything.PRODUCT_RELEASEis deliberately not set locally so onboarding never cuts a surprise product release named after a commit SHA.cwd=repo_rootso repo-relative lockfile paths andsbomify.jsonaugmentation resolve the same way they do in CI.Test plan
tests/test_wizard_publish.py: matrix-row parity, env building + scrubbing, dry-run, per-run failure isolation, spawn-error handling, real_stream_subprocessround-trip, Done-panel text variants.🤖 Generated with Claude Code