├── .github └── workflows │ └── test.yml ├── .gitignore ├── LICENSE ├── README.md └── gh-project /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | on: push 2 | 3 | jobs: 4 | test: 5 | runs-on: ubuntu-latest 6 | steps: 7 | - uses: actions/checkout@v2 8 | - uses: actions/setup-node@v2 9 | - run: shellcheck gh-project 10 | - run: npx prettier --check . 11 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | *.iml 3 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 rethab 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # gh project 2 | 3 | ℹ️ contributions welcome 4 | 5 | A [GitHub CLI](https://cli.github.com/) extension to work with projects. 6 | 7 | ## Installation 8 | 9 | Make sure you have at least version 2 of the GitHub CLI installed. 10 | 11 | Install this extension with `gh extension install rethab/gh-project`. 12 | 13 | ## Synopsis 14 | 15 | ```bash 16 | Work with GitHub Projects 17 | 18 | USAGE 19 | gh project [flags] 20 | 21 | CORE COMMANDS 22 | list List projects 23 | list-columns List columns in project 24 | list-cards List cards in a column 25 | create-card Create a new issue and add it as a card to a column 26 | move-card Move card to a different column or within a column 27 | 28 | SHOW COMMAND HELP AND USAGE 29 | $ gh project --help 30 | 31 | INHERITED FLAGS 32 | --help Show help for command 33 | 34 | EXAMPLES 35 | $ gh project list 36 | ``` 37 | -------------------------------------------------------------------------------- /gh-project: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -e 3 | 4 | cmd=$1 5 | shift 6 | 7 | list_projects() { 8 | local path=$1 9 | TEMPLATE='{{tablerow "ID" "Name" "URL"}}{{range .}}{{tablerow .id .name .html_url}}{{end}}' 10 | exec gh api --preview inertia "${path}/projects" --template="$TEMPLATE" 11 | } 12 | 13 | list_projects_help() { 14 | echo "List projects from an organisation, a user or a repository" 15 | echo "" 16 | echo "Without flags, projects from the current repository are listed" 17 | echo "" 18 | echo "USAGE" 19 | echo " gh project list [flags]" 20 | echo "" 21 | echo "FLAGS" 22 | printf " --org organisation\tList projects from organisation\n" 23 | printf " -u, --user user\tList projects from user\n" 24 | printf " -o, --owner owner\tList projects from owner (requires repository)\n" 25 | printf " -r, --repository repository\tList projects from repository (requires owner)\n" 26 | echo "" 27 | echo "INHERITED FLAGS" 28 | echo " --help Show help for command" 29 | echo "" 30 | echo "EXAMPLES" 31 | echo " $ gh project list" 32 | echo " $ gh project list --org my-corporation" 33 | echo " $ gh project list --owner rethab --repository gh-project" 34 | } 35 | 36 | list_columns() { 37 | local project_id="$1" 38 | TEMPLATE='{{tablerow "ID" "Name" }}{{range .}}{{tablerow .id .name }}{{end}}' 39 | exec gh api --preview inertia "projects/${project_id}/columns" --template="$TEMPLATE" 40 | } 41 | 42 | list_columns_help() { 43 | echo "List columns in a project" 44 | echo "" 45 | echo "USAGE" 46 | echo " gh project list-columns [flags]" 47 | echo "" 48 | echo "FLAGS" 49 | printf " -p, --project id\tId of the project. Use the \"list\" command to show projects\n" 50 | echo "" 51 | echo "INHERITED FLAGS" 52 | echo " --help Show help for command" 53 | echo "" 54 | echo "EXAMPLES" 55 | echo " $ gh project list-columns --project 12789786" 56 | } 57 | 58 | list_cards() { 59 | local column_id="$1" 60 | shift 61 | 62 | TEMPLATE='{{tablerow "ID" "Note" }}{{range .}}{{tablerow .id .note }}{{end}}' 63 | exec gh api "/projects/columns/${column_id}/cards" --template="$TEMPLATE" 64 | } 65 | 66 | list_cards_help() { 67 | echo "List cards in a column" 68 | echo "" 69 | echo "USAGE" 70 | echo " gh project list-cards [flags]" 71 | echo "" 72 | echo "FLAGS" 73 | printf " -c, --column id\tColumn from which to list cards. Use \"list-column\" to show columns\n" 74 | echo "" 75 | echo "INHERITED FLAGS" 76 | echo " --help Show help for command" 77 | echo "" 78 | echo "EXAMPLES" 79 | echo " $ gh project list-cards --column 1489862" 80 | } 81 | 82 | create_card() { 83 | local owner="$1" 84 | shift 85 | local column_id="$1" 86 | shift 87 | local issue_repo="$1" 88 | shift 89 | local label="$1" 90 | shift 91 | local title=$* 92 | 93 | if [[ -n $label ]]; then 94 | label="\"${label//,/\",\"}\"" 95 | fi 96 | 97 | issue_payload="{\"title\": \"$title\", \"labels\": [$label]}" 98 | issue_id=$(echo "$issue_payload" | gh api "/repos/${owner}/${issue_repo}/issues" --input - --jq '.id') 99 | 100 | content_url=$(gh api --preview inertia "/projects/columns/${column_id}/cards" -F content_id="$issue_id" -f content_type=Issue --jq '.content_url') 101 | 102 | echo "$content_url" | sed s/api.// | sed s-repos/-- 103 | } 104 | 105 | create_card_help() { 106 | echo "Create a new card in a project" 107 | echo "" 108 | echo "The content of a card is based on an issue, which is going to be created as part of this command." 109 | echo "" 110 | echo "USAGE" 111 | echo " gh project create-card [flags]" 112 | echo "" 113 | echo "FLAGS" 114 | printf " -o, --owner owner \t\tOwner/organization to create the card in. Defaults to the owner of the current repository.\n" 115 | printf " -c, --column id \t\tColumn in which to create card. Use \"list-column\" to show columns\n" 116 | printf " -r, --issue-repository name\tName of the repository in which to create the issue\n" 117 | printf " -l, --label string\t\tAdd labels by name. Separate multiple labels with a comma\n" 118 | printf " -t, --title string\t\tTitle of the card\n" 119 | echo "" 120 | echo "INHERITED FLAGS" 121 | echo " --help Show help for command" 122 | echo "" 123 | echo "EXAMPLES" 124 | echo " $ gh project create-card --column 1489862 --issue-repository backend-service --label \"help wanted\" --title \"implement new feature\"" 125 | } 126 | 127 | move_card() { 128 | local card_id="$1" 129 | local position="$2" 130 | local column_id="$3" 131 | 132 | if [ -z "$column_id" ]; then 133 | exec gh api "/projects/columns/cards/$card_id/moves" -f "position=$position" 134 | else 135 | exec gh api "/projects/columns/cards/$card_id/moves" -f "position=$position" -F "column_id=$column_id" 136 | fi 137 | } 138 | 139 | move_card_help() { 140 | echo "Move a card within a column or to a different column" 141 | echo "" 142 | echo "USAGE" 143 | echo " gh project move-card [flags]" 144 | echo "" 145 | echo "FLAGS" 146 | printf " -c, --card card_id\t\tCard to move. Use \"list-cards\" to show cards\n" 147 | printf " -p, --position position\tPosition to move the card to. Options: top, bottom, after:. Defaults to top\n" 148 | printf " --column column_id\t\tID of the column to move to. If omitted, card is moved within column\n" 149 | echo "" 150 | echo "INHERITED FLAGS" 151 | echo " --help Show help for command" 152 | echo "" 153 | echo "EXAMPLES" 154 | echo " $ gh project move-card --card 69500449 --position top --column 16122294" 155 | } 156 | 157 | 158 | show_help() { 159 | echo "Work with GitHub Projects" 160 | echo "" 161 | echo "USAGE" 162 | echo " gh project [flags]" 163 | echo "" 164 | echo "CORE COMMANDS" 165 | printf " list\t\tList projects\n" 166 | printf " list-columns\tList columns in project\n" 167 | printf " list-cards\tList cards in a column\n" 168 | printf " create-card\tCreate a new issue and add it as a card to a column\n" 169 | printf " move-card\tMove card to a different column or within a column\n" 170 | echo "" 171 | echo "SHOW COMMAND HELP AND USAGE" 172 | echo " $ gh project --help" 173 | echo "" 174 | echo "INHERITED FLAGS" 175 | echo " --help Show help for command" 176 | echo "" 177 | echo "EXAMPLES" 178 | echo " $ gh project list" 179 | } 180 | 181 | require_arg() { 182 | local flag="$1" 183 | echo "$flag requires an argument" 184 | exit 1 185 | } 186 | 187 | 188 | case "$cmd" in 189 | list) 190 | while [ "${1:-}" != "" ]; do 191 | case "$1" in 192 | -u|--user) 193 | user="$2" 194 | shift 2 || require_arg "user" 195 | ;; 196 | --org) 197 | org="$2" 198 | shift 2 || require_arg "org" 199 | ;; 200 | -o|--owner) 201 | owner="$2" 202 | shift 2 || require_arg "owner" 203 | ;; 204 | -r|--repository) 205 | repository="$2" 206 | shift 2 || require_arg "repository" 207 | ;; 208 | -h|--help) 209 | list_projects_help 210 | exit 0 211 | ;; 212 | *) 213 | echo "Unexpected argument: $1" 214 | list_projects_help 215 | exit 1 216 | ;; 217 | esac 218 | done 219 | 220 | if [[ -n "$user" ]]; then 221 | path="/users/$user" 222 | [[ -n "$org$owner$repository" ]] && { echo "invalid flags in combination with user"; exit 1; } 223 | elif [[ -n "$org" ]]; then 224 | path="/orgs/$org" 225 | [[ -n "$owner$repository" ]] && { echo "invalid flags in combination with org"; exit 1; } 226 | elif [[ -n "$owner" && -n "$repository" ]]; then 227 | path="/repos/$owner/$repository" 228 | else 229 | path="/repos/{owner}/{repo}" 230 | fi 231 | 232 | list_projects "$path" 233 | ;; 234 | list-columns) 235 | while [ "${1:-}" != "" ]; do 236 | case "$1" in 237 | -p|--project) 238 | project_id="$2" 239 | shift 2 || require_arg "project" 240 | ;; 241 | -h|--help) 242 | list_columns_help 243 | exit 0 244 | ;; 245 | *) 246 | echo "Unexpected argument: $1" 247 | list_columns_help 248 | exit 1 249 | ;; 250 | esac 251 | done 252 | [[ -z "$project_id" ]] && { echo "Missing project"; exit 1; } 253 | list_columns "$project_id" 254 | ;; 255 | list-cards) 256 | owner='{owner}' 257 | while [ "${1:-}" != "" ]; do 258 | case "$1" in 259 | -c|--column) 260 | column_id="$2" 261 | shift 2 || require_arg "column" 262 | ;; 263 | -h|--help) 264 | list_cards_help 265 | exit 0 266 | ;; 267 | *) 268 | echo "Unexpected argument: $1" 269 | list_cards_help 270 | exit 1 271 | ;; 272 | esac 273 | done 274 | 275 | [[ -z "$column_id" ]] && { echo "Missing column"; exit 1; } 276 | list_cards "$column_id" 277 | ;; 278 | create-card) 279 | owner='{owner}' 280 | labels='' 281 | while [ "${1:-}" != "" ]; do 282 | case "$1" in 283 | -o|--owner) 284 | owner="$2" 285 | shift 2 || require_arg "owner" 286 | ;; 287 | -c|--column) 288 | column_id="$2" 289 | shift 2 || require_arg "column" 290 | ;; 291 | -r|--issue-repository) 292 | issue_repository="$2" 293 | shift 2 || require_arg "repository" 294 | ;; 295 | -l|--label) 296 | labels="$2" 297 | shift 2 || require_arg "label" 298 | ;; 299 | -t|--title) 300 | title="$2" 301 | shift 2 || require_arg "title" 302 | ;; 303 | -h|--help) 304 | create_card_help 305 | exit 0 306 | ;; 307 | *) 308 | echo "Unexpected argument: $1" 309 | create_card_help 310 | exit 1 311 | ;; 312 | esac 313 | done 314 | 315 | [[ -z "$column_id" ]] && { echo "Missing column"; exit 1; } 316 | [[ -z "$issue_repository" ]] && { echo "Missing issue-repository"; exit 1; } 317 | [[ -z "$title" ]] && { echo "Missing title"; exit 1; } 318 | create_card "$owner" "$column_id" "$issue_repository" "$labels" "$title" 319 | ;; 320 | move-card) 321 | while [ "${1:-}" != "" ]; do 322 | case "$1" in 323 | -c|--card) 324 | card_id="$2" 325 | shift 2 || require_arg "card" 326 | ;; 327 | -p|--position) 328 | position="$2" 329 | shift 2 || require_arg "position" 330 | ;; 331 | --column) 332 | column_id="$2" 333 | shift 2 || require_arg "column" 334 | ;; 335 | -h|--help) 336 | move_card_help 337 | exit 0 338 | ;; 339 | *) 340 | echo "Unexpected argument: $1" 341 | show_help 342 | exit 1 343 | ;; 344 | esac 345 | done 346 | 347 | [[ -z "$card_id" ]] && { echo "Missing card"; exit 1; } 348 | [[ -z "$position" ]] && { position="top"; } 349 | move_card "$card_id" "$position" "$column_id" 350 | ;; 351 | --help) 352 | show_help 353 | ;; 354 | *) 355 | echo "Invalid command '$cmd'" 356 | show_help 357 | exit 1 358 | ;; 359 | esac 360 | --------------------------------------------------------------------------------