Skip to content

Add safe attachment bundle output #3

Description

@joewalker

Add these options to bz-show:

--output-dir DIRECTORY      Create a bug directory beneath DIRECTORY
--download-attachments      Download eligible attachments into that directory

--download-attachments must require --output-dir. --output and
--output-dir must be mutually exclusive. Requiring an explicit directory
prevents a read-looking command from unexpectedly writing files into the
current directory.

The layout should be deterministic:

DIRECTORY/
  bug-1234567/
    bug-1234567.md
    attachment-890123-original-name.txt

Prefixing the attachment ID prevents collisions between attachments with the
same submitted filename. Do not download obsolete attachments or
text/x-phabricator-request pseudo-attachments. Continue listing all attachment
metadata in the Markdown summary so omitted downloads remain visible.

Implement with Red/Green TDD

  1. Extend argument-parsing tests with valid --output-dir and
    --download-attachments cases, plus errors for incompatible or incomplete
    option combinations.
  2. Add focused tests for an attachment filename function. It must:
    • discard directory components from both slash styles;
    • replace control characters and unsafe separators;
    • handle empty names, . and ..;
    • preserve a useful extension where possible;
    • prefix the numeric attachment ID; and
    • always return a single filename, never a path.
  3. Add focused tests for strict base64 decoding. Reject malformed base64 rather
    than writing partial or surprising bytes.
  4. Add an injectable filesystem boundary for runShowCommand, similar to the
    existing CliIo boundary, or extract a documented bundle writer whose
    behavior can be tested using directories under cache/tmp.
  5. Create the output directories with user-only permissions where the platform
    supports them. Write the Markdown and attachments with mode 0o600, matching
    the existing private Markdown output behavior.
  6. Decide overwrite behavior before performing any writes. The initial version
    should fail if an attachment target already exists. It may update the
    deterministic Markdown summary only after all attachment target paths have
    been validated, so a filename conflict cannot leave a partially updated
    bundle.
  7. Fetch eligible attachments one at a time through Bugzilla.attachment(id).
    Decode and write each body, then verify the decoded byte length against the
    metadata size when Bugzilla supplies a nonzero size. Treat a mismatch as a
    failed download and report the attachment ID and filename.
  8. Write status messages to stderr, keeping stdout available for Markdown and
    machine-readable output. Never print attachment bodies, base64 data, or API
    keys.
  9. If one download fails, stop and return a nonzero exit code. Report which
    files were already written; do not delete pre-existing user files or attempt
    an unsafe rollback.
  10. Update showCommandHelp and README.md with the layout, filtering rules,
    permissions, overwrite behavior, and an example invocation.
  11. Add end-to-end runShowCommand tests covering a successful bundle, skipped
    obsolete and Phabricator attachments, duplicate submitted filenames,
    malformed base64, size mismatch, an existing destination, and a failed
    attachment API request.
  12. Run pnpm test src/__test__/cli-show.test.ts, then pnpm tsc.

Acceptance criteria:

  • No attachment body is fetched unless --download-attachments is present.
  • Every generated path remains beneath the selected bug directory.
  • Existing attachment files are never silently overwritten.
  • Downloaded bytes match Bugzilla's decoded attachment data and remain
    compressed if the attachment was compressed.
  • A normal bz-show invocation remains stdout-only and creates no files.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions