diff --git a/nix/packages/hive-forge-tools.nix b/nix/packages/hive-forge-tools.nix index 84c3c37..a8f2eee 100644 --- a/nix/packages/hive-forge-tools.nix +++ b/nix/packages/hive-forge-tools.nix @@ -71,6 +71,34 @@ pkgs.writeShellApplication { fi } + # Pick a body string from --body, --body-file (with `-` meaning + # stdin), or piped stdin. Exactly one source — passing both + # `--body` and `--body-file` errors out so the caller is + # forced to pick. Shared between pr-create / issue-create / + # issue-edit so the body-input surface stays consistent across + # body-accepting verbs (#382). Falling all the way through + # (no flag, no piped stdin) yields empty; callers that + # require a body check after this returns. + resolve_body() { + local _body="$1" + local _file="$2" + if [ -n "$_body" ] && [ -n "$_file" ]; then + echo "hive-forge: --body and --body-file are mutually exclusive" >&2 + exit 1 + fi + if [ -n "$_file" ]; then + if [ "$_file" = "-" ]; then + cat + else + cat "$_file" + fi + elif [ -n "$_body" ]; then + printf '%s' "$_body" + elif [ ! -t 0 ]; then + cat + fi + } + cmd_view() { # view [repo] # Dump title + body + all comments for an issue or PR. @@ -322,23 +350,29 @@ pkgs.writeShellApplication { } cmd_pr_create() { - # pr-create --title --head <branch> [--base <base>] [--body <body>] [--draft] [repo] + # pr-create --title <title> --head <branch> [--base <base>] + # [--body <body> | --body-file <path>] [--draft] [repo] # Create a pull request. Prints the PR URL on success. - local _title="" _head="" _base="main" _body="" _draft="false" _repo="$HIVE_FORGE_REPO" + # Body sources (priority): --body, --body-file <path> (use `-` + # for stdin), piped stdin (when neither flag is set). Closes + # #382. + local _title="" _head="" _base="main" _body="" _body_file="" _draft="false" _repo="$HIVE_FORGE_REPO" while [ $# -gt 0 ]; do case "$1" in - --title) _title="$2"; shift 2 ;; - --head) _head="$2"; shift 2 ;; - --base) _base="$2"; shift 2 ;; - --body) _body="$2"; shift 2 ;; - --draft) _draft="true"; shift ;; - *) _repo="$1"; shift ;; + --title) _title="$2"; shift 2 ;; + --head) _head="$2"; shift 2 ;; + --base) _base="$2"; shift 2 ;; + --body) _body="$2"; shift 2 ;; + --body-file) _body_file="$2"; shift 2 ;; + --draft) _draft="true"; shift ;; + *) _repo="$1"; shift ;; esac done if [ -z "$_title" ] || [ -z "$_head" ]; then - echo "usage: hive-forge pr-create --title <title> --head <branch> [--base <base>] [--body <body>] [--draft] [repo]" >&2 + echo "usage: hive-forge pr-create --title <title> --head <branch> [--base <base>] [--body <body> | --body-file <path>] [--draft] [repo]" >&2 exit 1 fi + _body=$(resolve_body "$_body" "$_body_file") local _payload _payload=$(jq -n \ --arg title "$_title" \ @@ -352,21 +386,27 @@ pkgs.writeShellApplication { } cmd_issue_create() { - # issue-create --title <title> [--body <body>] [--assignee <user>] [repo] + # issue-create --title <title> [--body <body> | --body-file <path>] + # [--assignee <user>] [repo] # Create an issue. Prints the issue URL on success. - local _title="" _body="" _assignee="" _repo="$HIVE_FORGE_REPO" + # Body sources (priority): --body, --body-file <path> (use `-` + # for stdin), piped stdin (when neither flag is set). Closes + # #382. + local _title="" _body="" _body_file="" _assignee="" _repo="$HIVE_FORGE_REPO" while [ $# -gt 0 ]; do case "$1" in - --title) _title="$2"; shift 2 ;; - --body) _body="$2"; shift 2 ;; - --assignee) _assignee="$2"; shift 2 ;; - *) _repo="$1"; shift ;; + --title) _title="$2"; shift 2 ;; + --body) _body="$2"; shift 2 ;; + --body-file) _body_file="$2"; shift 2 ;; + --assignee) _assignee="$2"; shift 2 ;; + *) _repo="$1"; shift ;; esac done if [ -z "$_title" ]; then - echo "usage: hive-forge issue-create --title <title> [--body <body>] [--assignee <user>] [repo]" >&2 + echo "usage: hive-forge issue-create --title <title> [--body <body> | --body-file <path>] [--assignee <user>] [repo]" >&2 exit 1 fi + _body=$(resolve_body "$_body" "$_body_file") local _payload if [ -n "$_assignee" ]; then _payload=$(jq -n --arg t "$_title" --arg b "$_body" --arg a "$_assignee" \ @@ -379,20 +419,28 @@ pkgs.writeShellApplication { } cmd_issue_edit() { - # issue-edit <number> [--title <title>] [--body <body>] [--state open|closed] [--milestone <id>] [repo] - # Edit an issue's title, body, state, or milestone. Only provided fields are changed. - if [ $# -lt 1 ]; then echo "usage: hive-forge issue-edit <number> [--title <title>] [--body <body>] [--state open|closed] [--milestone <id>] [repo]" >&2; exit 1; fi + # issue-edit <number> [--title <title>] [--body <body> | --body-file <path>] + # [--state open|closed] [--milestone <id>] [repo] + # Edit an issue's title, body, state, or milestone. Only + # provided fields are changed. Body sources for #382 parity: + # --body, --body-file <path> (use `-` for stdin), or piped + # stdin (only when neither --body nor --body-file is set; the + # partial-update contract still treats unset → "leave body + # alone", so piping nothing is a no-op rather than a clobber). + if [ $# -lt 1 ]; then echo "usage: hive-forge issue-edit <number> [--title <title>] [--body <body> | --body-file <path>] [--state open|closed] [--milestone <id>] [repo]" >&2; exit 1; fi local _n="$1"; shift - local _title="" _body="" _state="" _milestone="" _repo="$HIVE_FORGE_REPO" + local _title="" _body="" _body_file="" _state="" _milestone="" _repo="$HIVE_FORGE_REPO" while [ $# -gt 0 ]; do case "$1" in --title) _title="$2"; shift 2 ;; --body) _body="$2"; shift 2 ;; + --body-file) _body_file="$2"; shift 2 ;; --state) _state="$2"; shift 2 ;; --milestone) _milestone="$2"; shift 2 ;; *) _repo="$1"; shift ;; esac done + _body=$(resolve_body "$_body" "$_body_file") local _payload _payload=$(jq -n \ --arg title "$_title" \