mirror of
https://github.com/shadowsocks/ShadowsocksX-NG.git
synced 2026-11-08 10:24:51 +00:00
242 lines
12 KiB
YAML
242 lines
12 KiB
YAML
# https://taskfile.dev/
|
|
version: '3'
|
|
|
|
vars:
|
|
PUBLISH_TAG: 'v{{trimPrefix "v" (default "" .MARKETING_VERSION)}}'
|
|
PUBLISH_DMG: '{{.TASKFILE_DIR}}/dist/ShadowsocksX-NG2-{{default "" .MARKETING_VERSION}}-{{default "" .CURRENT_PROJECT_VERSION}}-arm64.dmg'
|
|
PUBLISH_MERGE_MESSAGE: 'chore(release): merge {{.PUBLISH_TAG}} into master'
|
|
|
|
tasks:
|
|
publish:
|
|
desc: Push a release tag, merge develop into master, and open a GitHub draft with the packaged DMG
|
|
dir: '{{.TASKFILE_DIR}}'
|
|
requires:
|
|
vars: [MARKETING_VERSION, CURRENT_PROJECT_VERSION]
|
|
vars:
|
|
GIT_COMMON_DIR:
|
|
sh: git rev-parse --git-common-dir
|
|
preconditions:
|
|
- sh: '{{regexMatch .RELEASE_VERSION_PATTERN .MARKETING_VERSION}}'
|
|
msg: 'MARKETING_VERSION must be a release version, such as 2.1.0 or v2.1.0-rc.1.'
|
|
- sh: '{{regexMatch "^[0-9]+(\\.[0-9]+){0,2}$" .CURRENT_PROJECT_VERSION}}'
|
|
msg: 'CURRENT_PROJECT_VERSION must have one to three numeric components.'
|
|
- sh: 'test -f {{.PUBLISH_DMG | shellQuote}} && test ! -L {{.PUBLISH_DMG | shellQuote}}'
|
|
msg: 'Run package with exactly the same version parameters first.'
|
|
- sh: '{{if default "" .RELEASE_NOTES_FILE}}test -f {{.RELEASE_NOTES_FILE | shellQuote}} && test -r {{.RELEASE_NOTES_FILE | shellQuote}}{{else}}true{{end}}'
|
|
msg: 'RELEASE_NOTES_FILE must name a readable file.'
|
|
- sh: 'test "$(git branch --show-current)" = develop && test -z "$(git status --porcelain)"'
|
|
msg: 'Run publish from a clean develop branch, including no untracked files.'
|
|
- sh: |
|
|
for state in MERGE_HEAD CHERRY_PICK_HEAD REVERT_HEAD rebase-merge rebase-apply; do
|
|
test ! -e "$(git rev-parse --git-path "$state")" || exit 1
|
|
done
|
|
msg: 'Finish or abort the existing Git operation before publishing.'
|
|
cmds:
|
|
- for: [git, gh, shasum, cmp]
|
|
cmd: 'command -v {{.ITEM}} > /dev/null'
|
|
- 'mkdir {{printf "%s/ng2-publish.lock" .GIT_COMMON_DIR | shellQuote}}'
|
|
- defer: 'rmdir {{printf "%s/ng2-publish.lock" .GIT_COMMON_DIR | shellQuote}}'
|
|
- git fetch --no-tags origin +refs/heads/develop:refs/remotes/origin/develop +refs/heads/master:refs/remotes/origin/master
|
|
- task: publish:inspect-git
|
|
|
|
publish:inspect-git:
|
|
internal: true
|
|
dir: '{{.TASKFILE_DIR}}'
|
|
vars:
|
|
COMMIT:
|
|
sh: git rev-parse refs/heads/develop
|
|
MASTER:
|
|
sh: git rev-parse refs/remotes/origin/master
|
|
FETCH_URL:
|
|
sh: git remote get-url origin
|
|
PUSH_URL:
|
|
sh: git remote get-url --push --all origin
|
|
REPO: '{{.FETCH_URL | trimPrefix "https://" | trimPrefix "ssh://git@" | trimPrefix "git@" | replace ":" "/" | trimSuffix ".git" | lower}}'
|
|
PUSH_REPO: '{{.PUSH_URL | trimPrefix "https://" | trimPrefix "ssh://git@" | trimPrefix "git@" | replace ":" "/" | trimSuffix ".git" | lower}}'
|
|
REMOTE_TAGS:
|
|
sh: git ls-remote --tags origin {{printf "refs/tags/%s" .PUBLISH_TAG | shellQuote}} {{printf "refs/tags/%s^{}" .PUBLISH_TAG | shellQuote}}
|
|
# Annotated tags have a final peeled line; lightweight tags have only one line.
|
|
REMOTE_TAG: '{{regexFind "^[0-9a-f]+" (last (splitList "\n" .REMOTE_TAGS))}}'
|
|
WORKTREES:
|
|
sh: git worktree list --porcelain
|
|
STATE:
|
|
map:
|
|
commit: '{{.COMMIT}}'
|
|
remote_tag: '{{.REMOTE_TAG}}'
|
|
repo: '{{.REPO}}'
|
|
preconditions:
|
|
- sh: '{{and (regexMatch "^[A-Za-z0-9.-]+/[A-Za-z0-9_.-]+/[A-Za-z0-9_.-]+$" .REPO) (eq .REPO .PUSH_REPO)}}'
|
|
msg: 'origin fetch and push URLs must identify the same single GitHub repository.'
|
|
- sh: 'test {{.COMMIT | shellQuote}} = "$(git rev-parse refs/remotes/origin/develop)"'
|
|
msg: 'develop must match origin/develop; synchronize it before publishing.'
|
|
- sh: '! git show-ref --verify --quiet {{printf "refs/tags/%s" .PUBLISH_TAG | shellQuote}} || test "$(git rev-parse {{printf "refs/tags/%s^{commit}" .PUBLISH_TAG | shellQuote}})" = {{.COMMIT | shellQuote}}'
|
|
msg: 'The local release tag points to a different commit.'
|
|
- sh: '{{or (empty .REMOTE_TAG) (eq .REMOTE_TAG .COMMIT)}}'
|
|
msg: 'The remote release tag points to a different commit.'
|
|
- sh: |
|
|
if git show-ref --verify --quiet refs/heads/master; then
|
|
test "$(git rev-parse master)" = {{.MASTER | shellQuote}} || {
|
|
test "$(git show -s --format=%P master)" = {{printf "%s %s" .MASTER .COMMIT | shellQuote}} &&
|
|
test "$(git show -s --format=%B master)" = {{.PUBLISH_MERGE_MESSAGE | shellQuote}}
|
|
}
|
|
fi
|
|
msg: 'Local master must match origin/master or be the pending release merge from a failed push.'
|
|
- sh: '{{not (regexMatch "(?m)^branch refs/heads/master$" .WORKTREES)}}'
|
|
msg: 'master is checked out in another worktree; release it before publishing.'
|
|
cmds:
|
|
- gh auth status --hostname {{first (splitList "/" .REPO) | shellQuote}}
|
|
- task: publish:inspect-release
|
|
vars:
|
|
STATE:
|
|
ref: .STATE
|
|
|
|
publish:inspect-release:
|
|
internal: true
|
|
dir: '{{.TASKFILE_DIR}}'
|
|
vars:
|
|
RELEASE_PAGES_JSON:
|
|
sh: >-
|
|
gh api --hostname {{first (splitList "/" .STATE.repo) | shellQuote}} --paginate --slurp
|
|
{{printf "repos/%s/releases?per_page=100" (join "/" (rest (splitList "/" .STATE.repo))) | shellQuote}}
|
|
RELEASE_JSON: |
|
|
{{- $release := dict -}}
|
|
{{- range $page := mustFromJson .RELEASE_PAGES_JSON -}}
|
|
{{- range $page -}}
|
|
{{- if eq .tag_name $.PUBLISH_TAG -}}
|
|
{{- if $release -}}{{fail "Duplicate releases"}}{{- end -}}
|
|
{{- $release = . -}}
|
|
{{- end -}}
|
|
{{- end -}}
|
|
{{- end -}}
|
|
{{- $dmgs := list -}}
|
|
{{- range $release.assets -}}
|
|
{{- if hasSuffix ".dmg" (lower .name) -}}
|
|
{{- $dmgs = append $dmgs . -}}
|
|
{{- end -}}
|
|
{{- end -}}
|
|
{{- $_ := set $release "dmgs" $dmgs -}}
|
|
{{- mustToJson $release -}}
|
|
RELEASE:
|
|
ref: mustFromJson .RELEASE_JSON
|
|
ASSET:
|
|
ref: first .RELEASE.dmgs
|
|
DMG_CHECKSUM:
|
|
sh: shasum -a 256 {{.PUBLISH_DMG | shellQuote}}
|
|
preconditions:
|
|
# Existing tags determine the released commit; target_commitish is only used to create new tags.
|
|
- sh: '{{or (not .RELEASE.tag_name) (eq .STATE.remote_tag .STATE.commit) (eq .RELEASE.target_commitish .STATE.commit)}}'
|
|
msg: 'The existing release targets a different commit.'
|
|
- sh: '{{or (not .RELEASE.tag_name) (eq .RELEASE.prerelease (contains "-" .MARKETING_VERSION))}}'
|
|
msg: 'The existing release has a different prerelease flag.'
|
|
- sh: '{{le (len .RELEASE.dmgs) 1}}'
|
|
msg: 'The existing release contains conflicting DMG assets.'
|
|
- sh: '{{or (not .ASSET) (eq .ASSET.name (base .PUBLISH_DMG))}}'
|
|
msg: 'The existing release contains a different DMG.'
|
|
- sh: '{{or (not .ASSET) (eq .ASSET.state "uploaded")}}'
|
|
msg: 'The existing DMG upload is incomplete; resolve it manually.'
|
|
- sh: '{{or (not .RELEASE.tag_name) .RELEASE.draft (not (empty .ASSET))}}'
|
|
msg: 'The published release is missing the expected DMG.'
|
|
- sh: '{{if and .RELEASE.tag_name (not .RELEASE.draft)}}test {{.STATE.remote_tag | shellQuote}} = {{.STATE.commit | shellQuote}} && git merge-base --is-ancestor {{.STATE.commit | shellQuote}} origin/master{{else}}true{{end}}'
|
|
msg: 'The published release has incomplete Git state; resolve it manually.'
|
|
cmds:
|
|
- task: publish:verify-asset
|
|
if: '{{not (empty .ASSET)}}'
|
|
vars: &release-context
|
|
STATE:
|
|
ref: .STATE
|
|
RELEASE:
|
|
ref: .RELEASE
|
|
ASSET:
|
|
ref: .ASSET
|
|
DMG_CHECKSUM: '{{.DMG_CHECKSUM}}'
|
|
- task: '{{if and (not .RELEASE.tag_name) (not (default "" .RELEASE_NOTES_FILE))}}publish:confirm-generated-notes{{else}}publish:execute{{end}}'
|
|
vars: *release-context
|
|
|
|
publish:verify-asset:
|
|
internal: true
|
|
dir: '{{.TASKFILE_DIR}}'
|
|
vars:
|
|
DOWNLOAD_DIR:
|
|
sh: mktemp -d "${TMPDIR:-/tmp}/ng2-release-verify.XXXXXX"
|
|
cmds:
|
|
- defer: 'rm -rf {{.DOWNLOAD_DIR | shellQuote}}'
|
|
- gh release download {{.PUBLISH_TAG | shellQuote}} --repo {{.STATE.repo | shellQuote}} --pattern {{base .PUBLISH_DMG | shellQuote}} --dir {{.DOWNLOAD_DIR | shellQuote}}
|
|
- cmp {{.PUBLISH_DMG | shellQuote}} {{printf "%s/%s" .DOWNLOAD_DIR (base .PUBLISH_DMG) | shellQuote}}
|
|
|
|
publish:confirm-generated-notes:
|
|
internal: true
|
|
prompt: 'No RELEASE_NOTES_FILE supplied. Use GitHub-generated notes and proceed with tag/master pushes?'
|
|
cmds:
|
|
- task: publish:execute
|
|
vars: *release-context
|
|
|
|
publish:execute:
|
|
internal: true
|
|
dir: '{{.TASKFILE_DIR}}'
|
|
cmds:
|
|
- task: publish:tag
|
|
vars: *release-context
|
|
- task: publish:master
|
|
vars: *release-context
|
|
- 'printf "%s\n" {{.DMG_CHECKSUM | shellQuote}} | shasum -a 256 --check'
|
|
- task: publish:create-draft
|
|
vars: *release-context
|
|
- task: publish:upload
|
|
vars: *release-context
|
|
- cmd: 'printf "Release already published and matches this package: %s\n" {{.PUBLISH_TAG | shellQuote}}'
|
|
if: '{{and .RELEASE.tag_name (not .RELEASE.draft)}}'
|
|
- gh release view {{.PUBLISH_TAG | shellQuote}} --repo {{.STATE.repo | shellQuote}} --web
|
|
|
|
publish:tag:
|
|
internal: true
|
|
dir: '{{.TASKFILE_DIR}}'
|
|
status:
|
|
- '{{not (empty .STATE.remote_tag)}}'
|
|
cmds:
|
|
- cmd: git tag -a {{.PUBLISH_TAG | shellQuote}} {{.STATE.commit | shellQuote}} -m {{printf "Release %s" .PUBLISH_TAG | shellQuote}}
|
|
if: '! git show-ref --verify --quiet {{printf "refs/tags/%s" .PUBLISH_TAG | shellQuote}}'
|
|
- git push origin {{printf "refs/tags/%s:refs/tags/%s" .PUBLISH_TAG .PUBLISH_TAG | shellQuote}}
|
|
|
|
publish:master:
|
|
internal: true
|
|
dir: '{{.TASKFILE_DIR}}'
|
|
status:
|
|
- git merge-base --is-ancestor {{.STATE.commit | shellQuote}} origin/master
|
|
cmds:
|
|
- defer: |
|
|
if [ "$(git branch --show-current)" = master ]; then
|
|
if [ -z "$(git status --porcelain)" ] && [ ! -e "$(git rev-parse --git-path MERGE_HEAD)" ]; then
|
|
git switch develop
|
|
else
|
|
printf '%s\n' 'Merge state retained on master. Resolve it or run git merge --abort, then git switch develop.' >&2
|
|
fi
|
|
fi
|
|
- cmd: git switch --track -c master origin/master
|
|
if: '! git show-ref --verify --quiet refs/heads/master'
|
|
- cmd: git switch master
|
|
if: 'test "$(git branch --show-current)" != master'
|
|
- cmd: git merge --no-ff --no-edit -m {{.PUBLISH_MERGE_MESSAGE | shellQuote}} {{.STATE.commit | shellQuote}}
|
|
if: '! git merge-base --is-ancestor {{.STATE.commit | shellQuote}} master'
|
|
- git push origin refs/heads/master:refs/heads/master
|
|
- git switch develop
|
|
|
|
publish:create-draft:
|
|
internal: true
|
|
dir: '{{.TASKFILE_DIR}}'
|
|
status:
|
|
- '{{not (empty .RELEASE.tag_name)}}'
|
|
cmds:
|
|
- >-
|
|
gh release create {{.PUBLISH_TAG | shellQuote}} --repo {{.STATE.repo | shellQuote}}
|
|
--draft --verify-tag --target {{.STATE.commit | shellQuote}} --title {{.PUBLISH_TAG | shellQuote}}
|
|
{{if contains "-" .MARKETING_VERSION}}--prerelease{{end}}
|
|
{{if default "" .RELEASE_NOTES_FILE}}--notes-file {{.RELEASE_NOTES_FILE | shellQuote}}{{else}}--generate-notes{{end}}
|
|
|
|
publish:upload:
|
|
internal: true
|
|
dir: '{{.TASKFILE_DIR}}'
|
|
status:
|
|
- '{{not (empty .ASSET)}}'
|
|
cmds:
|
|
- gh release upload {{.PUBLISH_TAG | shellQuote}} {{.PUBLISH_DMG | shellQuote}} --repo {{.STATE.repo | shellQuote}}
|