diff --git a/.github/workflows/close.yml b/.github/workflows/close.yml new file mode 100644 index 00000000..41b50fa --- /dev/null +++ b/.github/workflows/close.yml @@ -0,0 +1,28 @@ +# Description: +# - Close a milestone by title or number. + +name: Milestone Close@develop +on: + workflow_dispatch: + inputs: + milestone: + required: true + description: 'Title / Number' + +# Fix GraphQL: Resource not accessible by integration (updatePullRequest) +permissions: + contents: write + +jobs: + close-milestone: + runs-on: ubuntu-latest + name: Milestone Crete + steps: + - name: Close Milestone + uses: hustcer/milestone-action@develop + with: + action: close + github-token: ${{ secrets.PAT }} + milestone: ${{ github.event.inputs.milestone }} + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/create.yml b/.github/workflows/create.yml new file mode 100644 index 00000000..401677c --- /dev/null +++ b/.github/workflows/create.yml @@ -0,0 +1,36 @@ +# Description: +# - Create a milestone by title, description and due date. + +name: Milestone Create@develop +on: + workflow_dispatch: + inputs: + title: + required: true + description: 'Milestone Title' + description: + required: false + description: 'Milestone Description' + due-on: + required: false + description: 'Milestone Due Date' + +# Fix GraphQL: Resource not accessible by integration (updatePullRequest) +permissions: + contents: write + +jobs: + create-milestone: + runs-on: ubuntu-latest + name: Milestone Crete + steps: + - name: Create Milestone + uses: hustcer/milestone-action@develop + with: + action: create + github-token: ${{ secrets.PAT }} + title: ${{ github.event.inputs.title }} + due-on: ${{ github.event.inputs.due-on }} + description: ${{ github.event.inputs.description }} + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/action.yaml b/action.yaml index 47c41fb..c9cd530 100644 --- a/action.yaml +++ b/action.yaml @@ -24,6 +24,22 @@ branding: color: 'purple' inputs: + action: + required: false + default: 'bind-pr' + description: 'The action to perform, should be bind-pr,bind-issue,create or close. Defaults to bind-pr.' + title: + required: false + default: '1.0.0' + description: 'The title of the milestone to create.' + description: + required: false + default: 'The first milestone.' + description: 'The description of the milestone to create.' + due-on: + required: false + default: '' + description: 'The due date of the milestone to create.' force: required: false default: false @@ -50,9 +66,16 @@ runs: run: | use ${{ github.action_path }}/nu/milestone.nu * let repo = '${{ github.repository }}' + let action = '${{ inputs.action }}' + let title = '${{ inputs.title }}' + let description = '${{ inputs.description }}' + let due-on = '${{ inputs.due-on }}' let token = '${{ inputs.github-token }}' + let issue = '${{ github.event.issue.number }}' let pr = '${{ github.event.pull_request.number }}' let force = '${{ inputs.force }}' | into bool let milestone = '${{ inputs.milestone }}' - milestone-update $repo --gh-token $token --pr $pr --force=$force --milestone=$milestone + let commonArgs = [$action $repo --gh-token $token --force=$force] + let createArgs = [--title=$title --description=$description --due-on=$due-on] + milestone-action ...$commonArgs ...$createArgs --pr=$pr --issue=$issue --milestone=$milestone diff --git a/nu/milestone.nu b/nu/milestone.nu index 8c1ed3c..519780b 100644 --- a/nu/milestone.nu +++ b/nu/milestone.nu @@ -5,9 +5,9 @@ # [x] Support Windows, macOS, Linux # [x] Should run on local machine or Github runners # [x] Support dry run mode -# [ ] Create milestone by title, due_on, and description -# [ ] Close milestone by title or number -# [ ] Add milestone to issue that has been fixed by a PR +# [x] Create milestone by title, due_on, and description +# [x] Close milestone by title or number +# [x] Add milestone to issue that has been fixed by a PR # Description: Scripts for Github milestone management. use common.nu [ECODE, hr-line is-installed] @@ -17,7 +17,8 @@ export-env { $env.config.color_config.leading_trailing_space_bg = { attr: n } } -export def 'milestone-update' [ +# Bind milestone to a merged PR. +export def 'milestone-bind-for-pr' [ repo: string, # Github repository name --gh-token(-t): string, # Github access token --milestone(-m): string, # Milestone name @@ -27,14 +28,14 @@ export def 'milestone-update' [ ] { check-gh if ($gh_token | is-not-empty) { $env.GH_TOKEN = $gh_token } - let IGNORED_PR_STATUS = [CLOSED OPEN] - # Could be MERGED, OPEN, CLOSED. + let IGNORED_PR_STATUS = [CLOSED] + # PR state Could be MERGED, OPEN, CLOSED. let prState = gh pr view $pr --repo $repo --json 'state' | from json | get state if ($prState in $IGNORED_PR_STATUS) { print $'PR (ansi p)($pr)(ansi reset) is in state (ansi p)($prState)(ansi reset), will be ignored.' return } - let selected = if ($milestone | is-empty) { guess-milestone $repo $pr } else { $milestone } + let selected = if ($milestone | is-empty) { guess-milestone-for-pr $repo $pr } else { $milestone } if $force { let prevMilestone = gh pr view $pr --repo $repo --json 'milestone' | from json | get milestone?.title? | default '-' let shouldRemove = $prevMilestone != $selected @@ -53,8 +54,47 @@ export def 'milestone-update' [ } } +# Bind milestone to a completed issue. +export def 'milestone-bind-for-issue' [ + repo: string, # Github repository name + --gh-token(-t): string, # Github access token + --milestone(-m): string, # Milestone name + --issue: string, # The Issue number that we want to add milestone. + --force(-f), # Force update milestone even if the milestone is already set. + --dry-run(-d), # Dry run, only print the milestone that would be set. +] { + check-gh + if ($gh_token | is-not-empty) { $env.GH_TOKEN = $gh_token } + let IGNORED_ISSUE_STATUS = [OPEN] + # Issue state Could be OPEN, CLOSED(with stateReasons: COMPLETED, NOT_PLANNED, REOPENED). + let issueState = gh issue view $issue --repo $repo --json 'state' | from json | get state + let stateReason = gh issue view $issue --repo $repo --json 'stateReason' | from json | get stateReason + let shouldIgnore = ($issueState in $IGNORED_ISSUE_STATUS) or ($stateReason == 'NOT_PLANNED') + if $shouldIgnore { + print $'Issue (ansi p)($issue)(ansi reset) is Not (ansi p)COMPLETED(ansi reset), will be ignored.' + return + } + let selected = if ($milestone | is-empty) { guess-milestone-for-issue $repo $issue } else { $milestone } + if $force { + let prevMilestone = gh issue view $issue --repo $repo --json 'milestone' | from json | get milestone?.title? | default '-' + let shouldRemove = $prevMilestone != $selected + if $dry_run and $shouldRemove { + print $'(char nl)Would remove milestone for Issue (ansi p)($issue)(ansi reset) in repository (ansi p)($repo)(ansi reset) ...' + } else if $shouldRemove { + gh issue edit $issue --repo $repo --remove-milestone + } else { + print $'(char nl)Milestone for Issue (ansi p)($issue)(ansi reset) in repo (ansi p)($repo)(ansi reset) was already set to (ansi p)($prevMilestone)(ansi reset), will be ignored.' + } + } + print $'(char nl)Setting milestone to (ansi p)($selected)(ansi reset) for Issue (ansi p)($issue)(ansi reset) in repository (ansi p)($repo)(ansi reset) ...' + # FIXME: GraphQL: Resource not accessible by integration (updatePullRequest) + if not $dry_run { + gh issue edit $issue --repo $repo --milestone $selected + } +} + # Guess milestone by the merged date of the PR and the infomation of open milestones. -def guess-milestone [repo: string, pr: string] { +def guess-milestone-for-pr [repo: string, pr: string] { # Query github open milestone list by gh let milestones = gh api -X GET $'/repos/($repo)/milestones' --paginate | from json | select number title due_on created_at html_url @@ -153,4 +193,25 @@ def check-gh [] { } } -alias main = milestone-update +# Milestone action entry point. +export def milestone-action [ + action: string, # Action to perform, could be create, close, or bind-pr. + repo: string, # Github repository name + --gh-token(-t): string, # Github access token + --milestone(-m): string, # Milestone name + --title: string, # Milestone title to create or close + --due-on(-d): string, # Milestone due date, format: yyyy/mm/dd + --description(-D): string,# Milestone description + --pr: string, # The PR number/url/branch of the PR that we want to add milestone. + --force(-f), # Force update milestone even if the milestone is already set. + --dry-run(-d), # Dry run, only print the milestone that would be set. +] { + match $action { + close => { close-milestone $repo $milestone --gh-token $gh_token }, + create => { create-milestone $repo $title --due-on $due_on -D $description -t $gh_token }, + bind-pr => { milestone-bind-for-pr $repo -t $gh_token -m $milestone --pr $pr --force=$force --dry-run=$dry_run }, + bind-issue => { milestone-bind-for-issue $repo -t $gh_token -m $milestone --issue $issue --force=$force --dry-run=$dry_run }, + } +} + +alias main = milestone-action