diff --git a/.gitignore b/.gitignore index f535252..eb6050b 100644 --- a/.gitignore +++ b/.gitignore @@ -38,6 +38,7 @@ obj/ # Misc .bin/ +.release/ *.log *.graphml -coverage.db* +coverage.db* \ No newline at end of file diff --git a/.pkgmeta b/.pkgmeta index 33ccf2d..93192e5 100644 --- a/.pkgmeta +++ b/.pkgmeta @@ -2,23 +2,29 @@ package-as: Grichelde enable-nolib-creation: no -externals: - libs/LibStub: https://repos.wowace.com/wow/libstub/tags/1.0 - libs/LibDataBroker: https://github.com/tekkub/libdatabroker-1-1 - libs/LibDBIcon: https://repos.curseforge.com/wow/libdbicon-1-0/trunk/LibDBIcon-1.0 - libs/CallbackHandler-1.0: https://repos.wowace.com/wow/callbackhandler/trunk/CallbackHandler-1.0 - libs/AceAddon-3.0: https://repos.wowace.com/wow/ace3/trunk/AceAddon-3.0 - libs/AceConfig-3.0: https://repos.wowace.com/wow/ace3/trunk/AceConfig-3.0 - libs/AceConsole-3.0: https://repos.wowace.com/wow/ace3/trunk/AceConsole-3.0 - libs/AceDB-3.0: https://repos.wowace.com/wow/ace3/trunk/AceDB-3.0 - libs/AceDBOptions-3.0: https://repos.wowace.com/wow/ace3/trunk/AceDBOptions-3.0 - libs/AceEvent-3.0: https://repos.wowace.com/wow/ace3/trunk/AceEvent-3.0 - libs/AceGUI-3.0: https://repos.wowace.com/wow/ace3/trunk/AceGUI-3.0 - libs/AceHook-3.0: https://repos.wowace.com/wow/ace3/trunk/AceHook-3.0 - libs/AceLocale-3.0: https://repos.wowace.com/wow/ace3/trunk/AceLocale-3.0 +embedded-libraries: + Libs/LibStub: https://repos.wowace.com/wow/libstub/tags/1.0 + curse-slug: libstub + Libs/LibDataBroker: https://github.com/tekkub/libdatabroker-1-1 + curse-slug: libdatabroker-1-1 + Libs/LibDBIcon: https://repos.curseforge.com/wow/libdbicon-1-0/trunk/LibDBIcon-1.0 + curse-slug: libdbicon-1-0 + Libs/CallbackHandler-1.0: https://repos.wowace.com/wow/callbackhandler/trunk/CallbackHandler-1.0 + curse-slug: callbackhandler + Libs/AceAddon-3.0: https://repos.wowace.com/wow/ace3/trunk/AceAddon-3.0 + curse-slug: ace3 + Libs/AceConfig-3.0: https://repos.wowace.com/wow/ace3/trunk/AceConfig-3.0 + Libs/AceConsole-3.0: https://repos.wowace.com/wow/ace3/trunk/AceConsole-3.0 + Libs/AceDB-3.0: https://repos.wowace.com/wow/ace3/trunk/AceDB-3.0 + Libs/AceDBOptions-3.0: https://repos.wowace.com/wow/ace3/trunk/AceDBOptions-3.0 + Libs/AceEvent-3.0: https://repos.wowace.com/wow/ace3/trunk/AceEvent-3.0 + Libs/AceGUI-3.0: https://repos.wowace.com/wow/ace3/trunk/AceGUI-3.0 + Libs/AceHook-3.0: https://repos.wowace.com/wow/ace3/trunk/AceHook-3.0 + Libs/AceLocale-3.0: https://repos.wowace.com/wow/ace3/trunk/AceLocale-3.0 ignore: - twitch + - GricheldeTest.lua manual-changelog: filename: CHANGELOG.md diff --git a/.release/README.md b/.release/README.md new file mode 100644 index 0000000..63330c1 --- /dev/null +++ b/.release/README.md @@ -0,0 +1,311 @@ +# release.sh + +__release.sh__ generates an addon zip file from a Git, SVN, or Mercurial +checkout. + +__release.sh__ works by creating a new project directory (*.release* by +default), copying files from the checkout into the project directory, checking +out external repositories then copying their files into the project directory, +then moves subdirectories into the project root. The project directory is then +zipped to create a distributable addon zip file which can also be uploaded to +CurseForge, WoWInterface, Wago, and GitHub (as a release). + +__release.sh__ assumes that tags (Git annotated tags and SVN tags) are named for +the version numbers for the project. It will identify if the HEAD is tagged and +use that as the current version number. It will search back through parent +commits for the previous tag and generate a changelog containing the commits +since that tag. + +## Building with GitHub Actions + +For a full example workflow, please check out the [wiki page](https://github.com/BigWigsMods/packager/wiki/GitHub-Actions-workflow). + +### Example using [options](#Usage) + +```yaml +- uses: BigWigsMods/packager@v1 + with: + args: -g classic -m .pkgmeta-classic +``` + +## Customizing the build + +__release.sh__ uses the TOC file to determine the package name for the project. +You can also set the CurseForge project id (`-p`), the WoWInterface addon +id (`-w`) or the Wago project id (`-a`) by adding the following to the TOC file: + +```toc +## X-Curse-Project-ID: 1234 +## X-WoWI-ID: 5678 +## X-Wago-ID: he54k6bL +``` + +Your CurseForge project id can be found on the addon page in the "About Project" +side box. + +Your WoWInterface addon id is in the url for the addon, eg, the "5678" +in . + +Your Wago project id can be found on the developer dashboard. + +### The PackageMeta file + +__release.sh__ can read a __.pkgmeta__ file and supports the following +directives. See the [wiki page](https://github.com/BigWigsMods/packager/wiki/Preparing-the-PackageMeta-File) +for more info. + +- *externals* (Git, SVN, and Mercurial) Caveats: An external's .pkgmeta is only + parsed for ignore and externals will not have localization keywords replaced. +- *ignore* +- *changelog-title* +- *manual-changelog* +- *move-folders* +- *package-as* +- *enable-nolib-creation* (defaults to no) Caveats: nolib packages will only be + uploaded to GitHub and attached to a release. Unlike with the CurseForge + packager, manually uploaded nolib packages will not be used by the client when + users have enabled downloading libraries separately. +- *tools-used* +- *required-dependencies* +- *optional-dependencies* +- *embedded-libraries* Note: All fetched externals will be marked as embedded, + overriding any manually set relations in the pkgmeta. + +You can also use a few directives for WoWInterface uploading. + +- *wowi-archive-previous* : `yes|no` (defaults to yes) Archive the previous + release. +- *wowi-create-changelog* : `yes|no` (defaults to yes) Generate a changelog + using BBCode that will be set when uploading. A manual changelog will always + be used instead if set in the .pkgmeta. +- *wowi-convert-changelog* : `yes|no` (defaults to yes) Convert a manual + changelog in Markdown format to BBCode if you have [pandoc](http://pandoc.org/) + installed; otherwise, the manual changelog will be used as-is. If set to `no` + when using a generated changelog, Markdown will be used instead of BBCode. + **Note**: Markdown support is experimental and needs to be requested on a + per-project basis. + +### String replacements + +__release.sh__ supports the following repository substitution keywords when +copying the files from the checkout into the project directory. See the +[wiki page](https://github.com/BigWigsMods/packager/wiki/Repository-Keyword-Substitutions) +for more info. + +- *@[localization](https://github.com/BigWigsMods/packager/wiki/Localization-Substitution)(locale="locale", format="format", ...)@* + - *escape-non-ascii* + - *handle-unlocalized* + - *handle-subnamespaces="concat"* + - *key* + - *namespace* + - *same-key-is-true* + - *table-name* +- *@file-revision@* +- *@project-revision@* +- *@file-hash@* +- *@project-hash@* +- *@file-abbreviated-hash@* +- *@project-abbreviated-hash@* +- *@file-author@* +- *@project-author@* +- *@file-date-iso@* +- *@project-date-iso@* +- *@file-date-integer@* +- *@project-date-integer@* +- *@file-timestamp@* +- *@project-timestamp@* +- *@project-version@* + +### Build type keywords + +Specific keywords used in a comment at the start (`@keyword@`) and end +(`@end-keyword@`) of a block of code can be used to conditionally run that code +based on the build type. If the build type does not match, the block of code +is comment out so line numbers do not change. + +Supported keywords and when the code block will run: + +- `alpha`: in untagged builds. +- `debug`: never. Code will only run when using an unpackaged source. +- `do-not-package`: never. Same as `debug` except removed from the packaged + file. +- `no-lib-strip`: _(not supported in Lua files)_ in any build other than a + *nolib* build. +- `retail`,`version-retail`,`version-classic`,`version-bcc`: based on game + version. + +`do-not-package` is a bit special. Everything between the tags, including the +tags themselves, will always be removed from the packaged file. This will cause +the line numbers of subsequent lines to change, which can result in bug report +line numbers not matching the source code. The typical usage is at the end of +Lua files surrounding debugging functions and other code that end users should +never see or execute. + +All keywords except `do-not-package` can be prefixed with `non-` to inverse the +logic. When doing this, the keywords should start and end a **block comment** +as shown below. + +More examples are available on the [wiki page](https://github.com/BigWigsMods/packager/wiki/Repository-Keyword-Substitutions#debug-replacements). + +#### In Lua files + +`--@keyword@` and `--@end-keyword@` +turn into `--[===[@keyword` and `--@end-keyword]===]`. + +`--[===[@non-keyword@` and `--@end-non-keyword@]===]` +turn into `--@non-keyword@` and `--@end-non-keyword@`. + +#### In XML files + +**Note:** XML doesn't allow nested comments so make sure not to nest keywords. +If you need to nest keywords, you can do so in the TOC instead. + +`` and `` +turn into ``. + +`` +turn into `` and ``. + +#### In TOC files + +The lines with `#@keyword@` and `#@end-keyword@` get removed, as well as every +line in-between. + +The lines with `#@non-keyword@` and `#@end-non-keyword@` get removed, as well as +removing a '# ' at the beginning of each line in-between. + +### Changing the file name + +__release.sh__ uses the file name template `"{package-name}-{project-version}{nolib}{classic}"` +for the addon zip file. This can be changed with the `-n` switch (`release.sh +-n "{package-name}-{project-version}"`). + +These tokens are always replaced with their value: + +- `{package-name}` +- `{project-revision}` +- `{project-hash}` +- `{project-abbreviated-hash}` +- `{project-author}` +- `{project-date-iso}` +- `{project-date-integer}` +- `{project-timestamp}` +- `{project-version}` +- `{game-type}` +- `{release-type}` + +These tokens are "flags" and are conditionally shown prefixed with a dash based +on the build type: + +- `{alpha}` +- `{beta}` +- `{nolib}` +- `{classic}` + +`{classic}` has some additional magic: + +1. It will show as the non-retail build type, so either `-classic` or `-bcc`. +2. It will not be shown if "-classic" or "-bcc" is in the project version. +3. If it is included in the file name (it is by default) and #2 does not apply, + it will also be appended to the file label (i.e., the name shown on + CurseForge). + +## Building for multiple game versions + +__release.sh__ needs to know what version of World of Warcraft the package is +targeting. This is normally automatically detected using the `## Interface:` +line of the addon's TOC file. + +If your addon supports both retail and classic in the same branch, you can use +multiple `## Interface-Type:` lines in your TOC file. Only one `## Interface:` +line will be included in the packaged TOC file based on the targeted game +version. + +```toc +## Interface: 90005 +## Interface-Retail: 90005 +## Interface-Classic: 11306 +## Interface-BCC: 20501 +``` + +You specify what version of the game you're targeting with the `-g` switch. You +can use a specific version (`release.sh -g 1.13.6`) or you can use the game type +(`release.sh -g classic`). Using a game type will set the game version based on +the appropriate TOC `## Interface` value. + +You can also set multiple specific versions as a comma delimited list using the +`-g` switch (`release.sh -g 1.13.6,2.5.1,9.0.5`). This will still only build +one package, with the the last version listed used as the target version for +the build. + +**Setting multiple versions is not recommended!** The addon will always be +marked "Out of date" in-game for versions that do not match the TOC interface +value for the last version set. So even if you don't need any special file +processing, it will always be best to run the packager multiple times so the TOC +interface value is correct for each game version. + +## Building locally + +The recommended way to include __release.sh__ in a project is to: + +1. Create a *.release* subdirectory in your top-level checkout. +2. Copy __release.sh__ into the *.release* directory. +3. Ignore the *.release* subdirectory in __.gitignore__. +4. Run __release.sh__. + +## Usage + +```text +Usage: release.sh [options] + -c Skip copying files into the package directory. + -d Skip uploading. + -e Skip checkout of external repositories. + -l Skip @localization@ keyword replacement. + -L Only do @localization@ keyword replacement (skip upload to CurseForge). + -o Keep existing package directory, overwriting its contents. + -s Create a stripped-down "nolib" package. + -u Use Unix line-endings. + -z Skip zip file creation. + -t topdir Set top-level directory of checkout. + -r releasedir Set directory containing the package directory. Defaults to "$topdir/.release". + -p curse-id Set the project id used on CurseForge for localization and uploading. (Use 0 to unset the TOC value) + -w wowi-id Set the addon id used on WoWInterface for uploading. (Use 0 to unset the TOC value) + -a wago-id Set the project id used on Wago Addons for uploading. (Use 0 to unset the TOC value) + -g game-version Set the game version to use for uploading. + -m pkgmeta.yaml Set the pkgmeta file to use. + -n package-name Set the package zip file name. Use "-n help" for more info. +``` + +### Uploading + +__release.sh__ uses following environment variables for uploading: + +- `CF_API_KEY` - a [CurseForge API token](https://wow.curseforge.com/account/api-tokens), + required for the CurseForge API to fetch localization and upload files. +- `WOWI_API_TOKEN` - a [WoWInterface API token](https://www.wowinterface.com/downloads/filecpl.php?action=apitokens), + required for uploading to WoWInterface. +- `WAGO_API_TOKEN` - a [Wago Addons API token](https://addons.wago.io/account/apikeys), + required for uploading to Wago Addons. +- `GITHUB_OAUTH` - a [GitHub personal access token](https://github.com/settings/tokens), + required for uploading to GitHub. + +__release.sh__ will attempt to load environment variables from a `.env` file in +the topdir or current working directory. You can also edit __release.sh__ and +enter the tokens near the top of the file. + +### Dependencies + +__release.sh__ is mostly POSIX-compatible, so it should run in any Unix-like +environment provided the following are available: + +- bash >= 4.3 +- awk +- sed +- curl +- zip +- version control software as needed: + - git >= 2.13.0 + - subversion >= 1.7.0 + - mercurial >= 3.9.0 (pre-3.9 will have issues with [secure connections](https://www.mercurial-scm.org/wiki/SecureConnections)) +- [jq](https://stedolan.github.io/jq/download/) >= 1.5 (when uploading) +- [pandoc](https://pandoc.org/installing.html) >= 1.19.2 (optional) diff --git a/.release/release.sh b/.release/release.sh new file mode 100644 index 0000000..0d15c1a --- /dev/null +++ b/.release/release.sh @@ -0,0 +1,2647 @@ +#!/usr/bin/env bash + +# release.sh generates an addon zip file from a Git, SVN, or Mercurial checkout. +# +# This is free and unencumbered software released into the public domain. +# +# Anyone is free to copy, modify, publish, use, compile, sell, or +# distribute this software, either in source code form or as a compiled +# binary, for any purpose, commercial or non-commercial, and by any +# means. +# +# In jurisdictions that recognize copyright laws, the author or authors +# of this software dedicate any and all copyright interest in the +# software to the public domain. We make this dedication for the benefit +# of the public at large and to the detriment of our heirs and +# successors. We intend this dedication to be an overt act of +# relinquishment in perpetuity of all present and future rights to this +# software under copyright law. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +# IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR +# OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, +# ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +# OTHER DEALINGS IN THE SOFTWARE. +# +# For more information, please refer to + +## USER OPTIONS + +# Secrets for uploading +cf_token= +github_token= +wowi_token= +wago_token= + +# Variables set via command-line options +slug= +addonid= +wagoid= +topdir= +releasedir= +overwrite= +nolib= +line_ending="dos" +skip_copying= +skip_externals= +skip_localization= +skip_zipfile= +skip_upload= +skip_cf_upload= +pkgmeta_file= +game_version= +game_type= +file_type= +file_name="{package-name}-{project-version}{nolib}{classic}" + +wowi_markup="bbcode" + +## END USER OPTIONS + +if [[ ${BASH_VERSINFO[0]} -lt 4 ]] || [[ ${BASH_VERSINFO[0]} -eq 4 && ${BASH_VERSINFO[1]} -lt 3 ]]; then + echo "ERROR! bash version 4.3 or above is required. Your version is ${BASH_VERSION}." >&2 + exit 1 +fi + +# Game versions for uploading +declare -A game_flavors=( ["retail"]="mainline" ["classic"]="classic" ["bcc"]="bcc" ) +declare -A game_versions +toc_version= + +# Script return code +exit_code=0 + +# Escape a string for use in sed substitutions. +escape_substr() { + local s="$1" + s=${s//\\/\\\\} + s=${s//\//\\/} + s=${s//&/\\&} + echo "$s" +} + +# File name templating +filename_filter() { + local classic alpha beta invalid + [ -n "$skip_invalid" ] && invalid="&" || invalid="_" + if [[ "$game_type" != "retail" ]] && [[ "$game_type" != "classic" || "${si_project_version,,}" != *"-classic"* ]] && [[ "$game_type" != "bcc" || "${si_project_version,,}" != *"-bcc"* ]]; then + # only append the game type if the tag doesn't include it + classic="-$game_type" + fi + [ "$file_type" == "alpha" ] && alpha="-alpha" + [ "$file_type" == "beta" ] && beta="-beta" + sed \ + -e "s/{package-name}/$( escape_substr "$package" )/g" \ + -e "s/{project-revision}/$si_project_revision/g" \ + -e "s/{project-hash}/$si_project_hash/g" \ + -e "s/{project-abbreviated-hash}/$si_project_abbreviated_hash/g" \ + -e "s/{project-author}/$( escape_substr "$si_project_author" )/g" \ + -e "s/{project-date-iso}/$si_project_date_iso/g" \ + -e "s/{project-date-integer}/$si_project_date_integer/g" \ + -e "s/{project-timestamp}/$si_project_timestamp/g" \ + -e "s/{project-version}/$( escape_substr "$si_project_version" )/g" \ + -e "s/{game-type}/${game_type}/g" \ + -e "s/{release-type}/${file_type}/g" \ + -e "s/{alpha}/${alpha}/g" \ + -e "s/{beta}/${beta}/g" \ + -e "s/{nolib}/${nolib:+-nolib}/g" \ + -e "s/{classic}/${classic}/g" \ + -e "s/\([^A-Za-z0-9._-]\)/${invalid}/g" \ + <<< "$1" +} + +toc_filter() { + local keyword="$1" + local remove="$2" + if [ -z "$remove" ]; then + # "active" build type: remove comments (keep content), remove non-blocks (remove all) + sed \ + -e "/#@\(end-\)\{0,1\}${keyword}@/d" \ + -e "/#@non-${keyword}@/,/#@end-non-${keyword}@/d" + else + # "non" build type: remove blocks (remove content), uncomment non-blocks (remove tags) + sed \ + -e "/#@${keyword}@/,/#@end-${keyword}@/d" \ + -e "/#@non-${keyword}@/,/#@end-non-${keyword}@/s/^#[[:blank:]]\{1,\}//" \ + -e "/#@\(end-\)\{0,1\}non-${keyword}@/d" + fi +} + + +# Process command-line options +usage() { + cat <<-'EOF' >&2 + Usage: release.sh [options] + -c Skip copying files into the package directory. + -d Skip uploading. + -e Skip checkout of external repositories. + -l Skip @localization@ keyword replacement. + -L Only do @localization@ keyword replacement (skip upload to CurseForge). + -o Keep existing package directory, overwriting its contents. + -s Create a stripped-down "nolib" package. + -u Use Unix line-endings. + -z Skip zip file creation. + -t topdir Set top-level directory of checkout. + -r releasedir Set directory containing the package directory. Defaults to "$topdir/.release". + -p curse-id Set the project id used on CurseForge for localization and uploading. (Use 0 to unset the TOC value) + -w wowi-id Set the addon id used on WoWInterface for uploading. (Use 0 to unset the TOC value) + -a wago-id Set the project id used on Wago Addons for uploading. (Use 0 to unset the TOC value) + -g game-version Set the game version to use for uploading. + -m pkgmeta.yaml Set the pkgmeta file to use. + -n package-name Set the package zip file name. Use "-n help" for more info. + EOF +} + +OPTIND=1 +while getopts ":celLzusop:dw:a:r:t:g:m:n:" opt; do + case $opt in + c) skip_copying="true" ;; # Skip copying files into the package directory + z) skip_zipfile="true" ;; # Skip creating a zip file + e) skip_externals="true" ;; # Skip checkout of external repositories + l) skip_localization="true" ;; # Skip @localization@ keyword replacement + L) skip_cf_upload="true" ;; # Skip uploading to CurseForge + d) skip_upload="true" ;; # Skip uploading + u) line_ending="unix" ;; # Use LF instead of CRLF as the line ending for all text files + o) overwrite="true" ;; # Don't delete existing directories in the release directory + p) slug="$OPTARG" ;; # Set CurseForge project id + w) addonid="$OPTARG" ;; # Set WoWInterface addon id + a) wagoid="$OPTARG" ;; # Set Wago Addons project id + r) releasedir="$OPTARG" ;; # Set the release directory + t) # Set the top-level directory of the checkout + if [ ! -d "$OPTARG" ]; then + echo "Invalid argument for option \"-t\" - Directory \"$OPTARG\" does not exist." >&2 + usage + exit 1 + fi + topdir="$OPTARG" + ;; + s) # Create a nolib package without externals + nolib="true" + skip_externals="true" + ;; + g) # Set the game type or version + OPTARG="${OPTARG,,}" + case "$OPTARG" in + retail|classic|bcc) game_type="$OPTARG" ;; # game_version from toc + mainline) game_type="retail" ;; + bc) + echo "Invalid argument for option \"-g\" ($OPTARG)" >&2 + echo "" >&2 + echo "The \"bc\" game type has been changed to \"bcc\" to match Blizzard." >&2 + echo "This affects TOC interface lines (Interface-BC -> Interface-BCC) and" >&2 + echo "build keywords (version-bc -> version-bcc)." >&2 + echo "" >&2 + exit 1 + ;; + *) + # Set game version (x.y.z) + # Build game type set from the last value if a list + IFS=',' read -ra V <<< "$OPTARG" + for i in "${V[@]}"; do + if [[ ! "$i" =~ ^([0-9]+)\.([0-9]+)\.([0-9]+)[a-z]?$ ]]; then + echo "Invalid argument for option \"-g\" ($i)" >&2 + usage + exit 1 + fi + if [[ ${BASH_REMATCH[1]} == "1" && ${BASH_REMATCH[2]} == "13" ]]; then + game_type="classic" + elif [[ ${BASH_REMATCH[1]} == "2" && ${BASH_REMATCH[2]} == "5" ]]; then + game_type="bcc" + else + game_type="retail" + fi + # Only one version per game type is allowed + if [ -n "${game_versions[$game_type]}" ]; then + echo "Invalid argument for option \"-g\" ($i) - Only one version per game type is supported." >&2 + usage + exit 1 + fi + game_versions[$game_type]="$i" + done + game_version="$OPTARG" + esac + ;; + m) # Set the pkgmeta file + if [ ! -f "$OPTARG" ]; then + echo "Invalid argument for option \"-m\" - File \"$OPTARG\" does not exist." >&2 + usage + exit 1 + fi + pkgmeta_file="$OPTARG" + ;; + n) # Set the package file name + if [ "$OPTARG" = "help" ]; then + cat <<-'EOF' >&2 + Set the package zip file name. There are several string substitutions you can + use to include version control and build type infomation in the file name. + + The default file name is "{package-name}-{project-version}{nolib}{classic}". + + Tokens: {package-name}{project-revision}{project-hash}{project-abbreviated-hash} + {project-author}{project-date-iso}{project-date-integer}{project-timestamp} + {project-version}{game-type}{release-type} + + Flags: {alpha}{beta}{nolib}{classic} + + Tokens are always replaced with their value. Flags are shown prefixed with a dash + depending on the build type. + EOF + exit 0 + fi + file_name="$OPTARG" + if skip_invalid=true filename_filter "$file_name" | grep -q '[{}]'; then + tokens=$( skip_invalid=true filename_filter "$file_name" | sed -e '/^[^{]*{\|}[^{]*{\|}[^{]*/s//}{/g' -e 's/^}\({.*}\){$/\1/' ) + echo "Invalid argument for option \"-n\" - Invalid substitutions: $tokens" >&2 + exit 1 + fi + ;; + :) + echo "Option \"-$OPTARG\" requires an argument." >&2 + usage + exit 1 + ;; + \?) + if [ "$OPTARG" = "?" ] || [ "$OPTARG" = "h" ]; then + usage + exit 0 + fi + echo "Unknown option \"-$OPTARG\"" >&2 + usage + exit 1 + ;; + esac +done +shift $((OPTIND - 1)) + +# Set $topdir to top-level directory of the checkout. +if [ -z "$topdir" ]; then + dir=$( pwd ) + if [ -d "$dir/.git" ] || [ -d "$dir/.svn" ] || [ -d "$dir/.hg" ]; then + topdir=. + else + dir=${dir%/*} + topdir=".." + while [ -n "$dir" ]; do + if [ -d "$topdir/.git" ] || [ -d "$topdir/.svn" ] || [ -d "$topdir/.hg" ]; then + break + fi + dir=${dir%/*} + topdir="$topdir/.." + done + if [ ! -d "$topdir/.git" ] && [ ! -d "$topdir/.svn" ] && [ ! -d "$topdir/.hg" ]; then + echo "No Git, SVN, or Hg checkout found." >&2 + exit 1 + fi + fi +fi + +# Handle folding sections in CI logs +start_group() { echo "$1"; } +end_group() { echo; } + +# Check for Travis CI +if [ -n "$TRAVIS" ]; then + # Don't run the packager for pull requests + if [ "$TRAVIS_PULL_REQUEST" != "false" ]; then + echo "Not packaging pull request." + exit 0 + fi + if [ -z "$TRAVIS_TAG" ]; then + # Don't run the packager if there is a tag pending + check_tag=$( git -C "$topdir" tag --points-at HEAD ) + if [ -n "$check_tag" ]; then + echo "Found future tag \"${check_tag}\", not packaging." + exit 0 + fi + # Only package master, classic, or develop + if [ "$TRAVIS_BRANCH" != "master" ] && [ "$TRAVIS_BRANCH" != "classic" ] && [ "$TRAVIS_BRANCH" != "develop" ]; then + echo "Not packaging \"${TRAVIS_BRANCH}\"." + exit 0 + fi + fi + # https://github.com/travis-ci/travis-build/tree/master/lib/travis/build/bash + start_group() { + echo -en "travis_fold:start:$2\\r\033[0K" + # release_timer_id="$(printf %08x $((RANDOM * RANDOM)))" + # release_timer_start_time="$(date -u +%s%N)" + # echo -en "travis_time:start:${release_timer_id}\\r\033[0K" + echo "$1" + } + end_group() { + # local release_timer_end_time="$(date -u +%s%N)" + # local duration=$((release_timer_end_time - release_timer_start_time)) + # echo -en "\\ntravis_time:end:${release_timer_id}:start=${release_timer_start_time},finish=${release_timer_end_time},duration=${duration}\\r\033[0K" + echo -en "travis_fold:end:$1\\r\033[0K" + } +fi + +# Check for GitHub Actions +if [ -n "$GITHUB_ACTIONS" ]; then + # Prevent duplicate builds + if [[ "$GITHUB_REF" == "refs/heads"* ]]; then + check_tag=$( git -C "$topdir" tag --points-at HEAD ) + if [ -n "$check_tag" ]; then + echo "Found future tag \"${check_tag}\", not packaging." + exit 0 + fi + fi + start_group() { echo "##[group]$1"; } + end_group() { echo "##[endgroup]"; } +fi +unset check_tag + +# Load secrets +if [ -f "$topdir/.env" ]; then + # shellcheck disable=1090 + . "$topdir/.env" +elif [ -f ".env" ]; then + . ".env" +fi +[ -z "$cf_token" ] && cf_token=$CF_API_KEY +[ -z "$github_token" ] && github_token=$GITHUB_OAUTH +[ -z "$wowi_token" ] && wowi_token=$WOWI_API_TOKEN +[ -z "$wago_token" ] && wago_token=$WAGO_API_TOKEN + +# Set $releasedir to the directory which will contain the generated addon zipfile. +if [ -z "$releasedir" ]; then + releasedir="$topdir/.release" +fi + +# Set $basedir to the basename of the checkout directory. +basedir=$( cd "$topdir" && pwd ) +case $basedir in +/*/*) + basedir=${basedir##/*/} + ;; +/*) + basedir=${basedir##/} + ;; +esac + +# Set $repository_type to "git" or "svn" or "hg". +repository_type= +if [ -d "$topdir/.git" ]; then + repository_type=git +elif [ -d "$topdir/.svn" ]; then + repository_type=svn +elif [ -d "$topdir/.hg" ]; then + repository_type=hg +else + echo "No Git, SVN, or Hg checkout found in \"$topdir\"." >&2 + exit 1 +fi + +# $releasedir must be an absolute path or inside $topdir. +case $releasedir in +/*) ;; +$topdir/*) ;; +*) + echo "The release directory \"$releasedir\" must be an absolute path or inside \"$topdir\"." >&2 + exit 1 + ;; +esac + +# Create the staging directory. +mkdir -p "$releasedir" 2>/dev/null || { + echo "Unable to create the release directory \"$releasedir\"." >&2 + exit 1 +} + +# Expand $topdir and $releasedir to their absolute paths for string comparisons later. +topdir=$( cd "$topdir" && pwd ) +releasedir=$( cd "$releasedir" && pwd ) + +### +### set_info_ returns the following information: +### +si_repo_type= # "git" or "svn" or "hg" +si_repo_dir= # the checkout directory +si_repo_url= # the checkout url +si_tag= # tag for HEAD +si_previous_tag= # previous tag +si_previous_revision= # [SVN|Hg] revision number for previous tag + +si_project_revision= # Turns into the highest revision of the entire project in integer form, e.g. 1234, for SVN. Turns into the commit count for the project's hash for Git. +si_project_hash= # [Git|Hg] Turns into the hash of the entire project in hex form. e.g. 106c634df4b3dd4691bf24e148a23e9af35165ea +si_project_abbreviated_hash= # [Git|Hg] Turns into the abbreviated hash of the entire project in hex form. e.g. 106c63f +si_project_author= # Turns into the last author of the entire project. e.g. ckknight +si_project_date_iso= # Turns into the last changed date (by UTC) of the entire project in ISO 8601. e.g. 2008-05-01T12:34:56Z +si_project_date_integer= # Turns into the last changed date (by UTC) of the entire project in a readable integer fashion. e.g. 2008050123456 +si_project_timestamp= # Turns into the last changed date (by UTC) of the entire project in POSIX timestamp. e.g. 1209663296 +si_project_version= # Turns into an approximate version of the project. The tag name if on a tag, otherwise it's up to the repo. SVN returns something like "r1234", Git returns something like "v0.1-873fc1" + +si_file_revision= # Turns into the current revision of the file in integer form, e.g. 1234, for SVN. Turns into the commit count for the file's hash for Git. +si_file_hash= # Turns into the hash of the file in hex form. e.g. 106c634df4b3dd4691bf24e148a23e9af35165ea +si_file_abbreviated_hash= # Turns into the abbreviated hash of the file in hex form. e.g. 106c63 +si_file_author= # Turns into the last author of the file. e.g. ckknight +si_file_date_iso= # Turns into the last changed date (by UTC) of the file in ISO 8601. e.g. 2008-05-01T12:34:56Z +si_file_date_integer= # Turns into the last changed date (by UTC) of the file in a readable integer fashion. e.g. 20080501123456 +si_file_timestamp= # Turns into the last changed date (by UTC) of the file in POSIX timestamp. e.g. 1209663296 + +# SVN date helper function +strtotime() { + local value="$1" # datetime string + local format="$2" # strptime string + if [[ "${OSTYPE,,}" == *"darwin"* ]]; then # bsd + date -j -f "$format" "$value" "+%s" 2>/dev/null + else # gnu + date -d "$value" +%s 2>/dev/null + fi +} + +set_info_git() { + si_repo_dir="$1" + si_repo_type="git" + si_repo_url=$( git -C "$si_repo_dir" remote get-url origin 2>/dev/null | sed -e 's/^git@\(.*\):/https:\/\/\1\//' ) + if [ -z "$si_repo_url" ]; then # no origin so grab the first fetch url + si_repo_url=$( git -C "$si_repo_dir" remote -v | awk '/(fetch)/ { print $2; exit }' | sed -e 's/^git@\(.*\):/https:\/\/\1\//' ) + fi + + # Populate filter vars. + si_project_hash=$( git -C "$si_repo_dir" show --no-patch --format="%H" 2>/dev/null ) + si_project_abbreviated_hash=$( git -C "$si_repo_dir" show --no-patch --abbrev=7 --format="%h" 2>/dev/null ) + si_project_author=$( git -C "$si_repo_dir" show --no-patch --format="%an" 2>/dev/null ) + si_project_timestamp=$( git -C "$si_repo_dir" show --no-patch --format="%at" 2>/dev/null ) + si_project_date_iso=$( TZ='' printf "%(%Y-%m-%dT%H:%M:%SZ)T" "$si_project_timestamp" ) + si_project_date_integer=$( TZ='' printf "%(%Y%m%d%H%M%S)T" "$si_project_timestamp" ) + # XXX --depth limits rev-list :\ [ ! -s "$(git rev-parse --git-dir)/shallow" ] || git fetch --unshallow --no-tags + si_project_revision=$( git -C "$si_repo_dir" rev-list --count "$si_project_hash" 2>/dev/null ) + + # Get the tag for the HEAD. + si_previous_tag= + si_previous_revision= + _si_tag=$( git -C "$si_repo_dir" describe --tags --always --abbrev=7 2>/dev/null ) + si_tag=$( git -C "$si_repo_dir" describe --tags --always --abbrev=0 2>/dev/null ) + # Set $si_project_version to the version number of HEAD. May be empty if there are no commits. + si_project_version=$si_tag + # The HEAD is not tagged if the HEAD is several commits past the most recent tag. + if [ "$si_tag" = "$si_project_hash" ]; then + # --abbrev=0 expands out the full sha if there was no previous tag + si_project_version=$_si_tag + si_previous_tag= + si_tag= + elif [ "$_si_tag" != "$si_tag" ]; then + # not on a tag + si_project_version=$( git -C "$si_repo_dir" describe --tags --abbrev=7 --exclude="*[Aa][Ll][Pp][Hh][Aa]*" 2>/dev/null ) + si_previous_tag=$( git -C "$si_repo_dir" describe --tags --abbrev=0 --exclude="*[Aa][Ll][Pp][Hh][Aa]*" 2>/dev/null ) + si_tag= + else # we're on a tag, just jump back one commit + if [[ ${si_tag,,} != *"beta"* && ${si_tag,,} != *"alpha"* ]]; then + # full release, ignore beta tags + si_previous_tag=$( git -C "$si_repo_dir" describe --tags --abbrev=0 --exclude="*[Aa][Ll][Pp][Hh][Aa]*" --exclude="*[Bb][Ee][Tt][Aa]*" HEAD~ 2>/dev/null ) + else + si_previous_tag=$( git -C "$si_repo_dir" describe --tags --abbrev=0 --exclude="*[Aa][Ll][Pp][Hh][Aa]*" HEAD~ 2>/dev/null ) + fi + fi +} + +set_info_svn() { + si_repo_dir="$1" + si_repo_type="svn" + + # Temporary file to hold results of "svn info". + _si_svninfo="${si_repo_dir}/.svn/release_sh_svninfo" + svn info -r BASE "$si_repo_dir" 2>/dev/null > "$_si_svninfo" + + if [ -s "$_si_svninfo" ]; then + _si_root=$( awk '/^Repository Root:/ { print $3; exit }' < "$_si_svninfo" ) + _si_url=$( awk '/^URL:/ { print $2; exit }' < "$_si_svninfo" ) + _si_revision=$( awk '/^Last Changed Rev:/ { print $NF; exit }' < "$_si_svninfo" ) + si_repo_url=$_si_root + + case ${_si_url#${_si_root}/} in + tags/*) + # Extract the tag from the URL. + si_tag=${_si_url#${_si_root}/tags/} + si_tag=${si_tag%%/*} + si_project_revision="$_si_revision" + ;; + *) + # Check if the latest tag matches the working copy revision (/trunk checkout instead of /tags) + _si_tag_line=$( svn log --verbose --limit 1 "$_si_root/tags" 2>/dev/null | awk '/^ A/ { print $0; exit }' ) + _si_tag=$( echo "$_si_tag_line" | awk '/^ A/ { print $2 }' | awk -F/ '{ print $NF }' ) + _si_tag_from_revision=$( echo "$_si_tag_line" | sed -e 's/^.*:\([0-9]\{1,\}\)).*$/\1/' ) # (from /project/trunk:N) + + if [ "$_si_tag_from_revision" = "$_si_revision" ]; then + si_tag="$_si_tag" + si_project_revision=$( svn info "$_si_root/tags/$si_tag" 2>/dev/null | awk '/^Last Changed Rev:/ { print $NF; exit }' ) + else + # Set $si_project_revision to the highest revision of the project at the checkout path + si_project_revision=$( svn info --recursive "$si_repo_dir" 2>/dev/null | awk '/^Last Changed Rev:/ { print $NF }' | sort -nr | head -n1 ) + fi + ;; + esac + + if [ -n "$si_tag" ]; then + si_project_version="$si_tag" + else + si_project_version="r$si_project_revision" + fi + + # Get the previous tag and it's revision + _si_limit=$((si_project_revision - 1)) + _si_tag=$( svn log --verbose --limit 1 "$_si_root/tags" -r $_si_limit:1 2>/dev/null | awk '/^ A/ { print $0; exit }' | awk '/^ A/ { print $2 }' | awk -F/ '{ print $NF }' ) + if [ -n "$_si_tag" ]; then + si_previous_tag="$_si_tag" + si_previous_revision=$( svn info "$_si_root/tags/$_si_tag" 2>/dev/null | awk '/^Last Changed Rev:/ { print $NF; exit }' ) + fi + + # Populate filter vars. + si_project_author=$( awk '/^Last Changed Author:/ { print $0; exit }' < "$_si_svninfo" | cut -d" " -f4- ) + _si_timestamp=$( awk '/^Last Changed Date:/ { print $4,$5; exit }' < "$_si_svninfo" ) + si_project_timestamp=$( strtotime "$_si_timestamp" "%F %T" ) + si_project_date_iso=$( TZ='' printf "%(%Y-%m-%dT%H:%M:%SZ)T" "$si_project_timestamp" ) + si_project_date_integer=$( TZ='' printf "%(%Y%m%d%H%M%S)T" "$si_project_timestamp" ) + # SVN repositories have no project hash. + si_project_hash= + si_project_abbreviated_hash= + + rm -f "$_si_svninfo" 2>/dev/null + fi +} + +set_info_hg() { + si_repo_dir="$1" + si_repo_type="hg" + si_repo_url=$( hg --cwd "$si_repo_dir" paths -q default ) + if [ -z "$si_repo_url" ]; then # no default so grab the first path + si_repo_url=$( hg --cwd "$si_repo_dir" paths | awk '{ print $3; exit }' ) + fi + + # Populate filter vars. + si_project_hash=$( hg --cwd "$si_repo_dir" log -r . --template '{node}' 2>/dev/null ) + si_project_abbreviated_hash=$( hg --cwd "$si_repo_dir" log -r . --template '{node|short}' 2>/dev/null ) + si_project_author=$( hg --cwd "$si_repo_dir" log -r . --template '{author}' 2>/dev/null ) + si_project_timestamp=$( hg --cwd "$si_repo_dir" log -r . --template '{date}' 2>/dev/null | cut -d. -f1 ) + si_project_date_iso=$( TZ='' printf "%(%Y-%m-%dT%H:%M:%SZ)T" "$si_project_timestamp" ) + si_project_date_integer=$( TZ='' printf "%(%Y%m%d%H%M%S)T" "$si_project_timestamp" ) + si_project_revision=$( hg --cwd "$si_repo_dir" log -r . --template '{rev}' 2>/dev/null ) + + # Get tag info + si_tag= + # I'm just muddling through revsets, so there is probably a better way to do this + # Ignore tag commits, so v1.0-1 will package as v1.0 + if [ "$( hg --cwd "$si_repo_dir" log -r '.-filelog(.hgtags)' --template '{rev}' 2>/dev/null )" == "" ]; then + _si_tip=$( hg --cwd "$si_repo_dir" log -r 'last(parents(.))' --template '{rev}' 2>/dev/null ) + else + _si_tip=$( hg --cwd "$si_repo_dir" log -r . --template '{rev}' 2>/dev/null ) + fi + si_previous_tag=$( hg --cwd "$si_repo_dir" log -r "$_si_tip" --template '{latesttag}' 2>/dev/null ) + # si_project_version=$( hg --cwd "$si_repo_dir" log -r "$_si_tip" --template "{ ifeq(changessincelatesttag, 0, latesttag, '{latesttag}-{changessincelatesttag}-m{node|short}') }" 2>/dev/null ) # git style + si_project_version=$( hg --cwd "$si_repo_dir" log -r "$_si_tip" --template "{ ifeq(changessincelatesttag, 0, latesttag, 'r{rev}') }" 2>/dev/null ) # svn style + if [ "$si_previous_tag" = "$si_project_version" ]; then + # we're on a tag + si_tag=$si_previous_tag + si_previous_tag=$( hg --cwd "$si_repo_dir" log -r "last(parents($_si_tip))" --template '{latesttag}' 2>/dev/null ) + fi + si_previous_revision=$( hg --cwd "$si_repo_dir" log -r "$si_previous_tag" --template '{rev}' 2>/dev/null ) +} + +set_info_file() { + if [ "$si_repo_type" = "git" ]; then + _si_file=${1#si_repo_dir} # need the path relative to the checkout + # Populate filter vars from the last commit the file was included in. + si_file_hash=$( git -C "$si_repo_dir" log --max-count=1 --format="%H" "$_si_file" 2>/dev/null ) + si_file_abbreviated_hash=$( git -C "$si_repo_dir" log --max-count=1 --abbrev=7 --format="%h" "$_si_file" 2>/dev/null ) + si_file_author=$( git -C "$si_repo_dir" log --max-count=1 --format="%an" "$_si_file" 2>/dev/null ) + si_file_timestamp=$( git -C "$si_repo_dir" log --max-count=1 --format="%at" "$_si_file" 2>/dev/null ) + si_file_date_iso=$( TZ='' printf "%(%Y-%m-%dT%H:%M:%SZ)T" "$si_file_timestamp" ) + si_file_date_integer=$( TZ='' printf "%(%Y%m%d%H%M%S)T" "$si_file_timestamp" ) + si_file_revision=$( git -C "$si_repo_dir" rev-list --count "$si_file_hash" 2>/dev/null ) # XXX checkout depth affects rev-list, see set_info_git + elif [ "$si_repo_type" = "svn" ]; then + _si_file="$1" + # Temporary file to hold results of "svn info". + _sif_svninfo="${si_repo_dir}/.svn/release_sh_svnfinfo" + svn info "$_si_file" 2>/dev/null > "$_sif_svninfo" + if [ -s "$_sif_svninfo" ]; then + # Populate filter vars. + si_file_revision=$( awk '/^Last Changed Rev:/ { print $NF; exit }' < "$_sif_svninfo" ) + si_file_author=$( awk '/^Last Changed Author:/ { print $0; exit }' < "$_sif_svninfo" | cut -d" " -f4- ) + _si_timestamp=$( awk '/^Last Changed Date:/ { print $4,$5,$6; exit }' < "$_sif_svninfo" ) + si_file_timestamp=$( strtotime "$_si_timestamp" "%F %T %z" ) + si_file_date_iso=$( TZ='' printf "%(%Y-%m-%dT%H:%M:%SZ)T" "$si_file_timestamp" ) + si_file_date_integer=$( TZ='' printf "%(%Y%m%d%H%M%S)T" "$si_file_timestamp" ) + # SVN repositories have no project hash. + si_file_hash= + si_file_abbreviated_hash= + + rm -f "$_sif_svninfo" 2>/dev/null + fi + elif [ "$si_repo_type" = "hg" ]; then + _si_file=${1#si_repo_dir} # need the path relative to the checkout + # Populate filter vars. + si_file_hash=$( hg --cwd "$si_repo_dir" log --limit 1 --template '{node}' "$_si_file" 2>/dev/null ) + si_file_abbreviated_hash=$( hg --cwd "$si_repo_dir" log --limit 1 --template '{node|short}' "$_si_file" 2>/dev/null ) + si_file_author=$( hg --cwd "$si_repo_dir" log --limit 1 --template '{author}' "$_si_file" 2>/dev/null ) + si_file_timestamp=$( hg --cwd "$si_repo_dir" log --limit 1 --template '{date}' "$_si_file" 2>/dev/null | cut -d. -f1 ) + si_file_date_iso=$( TZ='' printf "%(%Y-%m-%dT%H:%M:%SZ)T" "$si_file_timestamp" ) + si_file_date_integer=$( TZ='' printf "%(%Y%m%d%H%M%S)T" "$si_file_timestamp" ) + si_file_revision=$( hg --cwd "$si_repo_dir" log --limit 1 --template '{rev}' "$_si_file" 2>/dev/null ) + fi +} + +# Set some version info about the project +case $repository_type in +git) set_info_git "$topdir" ;; +svn) set_info_svn "$topdir" ;; +hg) set_info_hg "$topdir" ;; +esac + +tag=$si_tag +project_version=$si_project_version +previous_version=$si_previous_tag +project_hash=$si_project_hash +project_revision=$si_project_revision +previous_revision=$si_previous_revision +project_timestamp=$si_project_timestamp +project_github_url= +project_github_slug= +if [[ "$si_repo_url" == "https://github.com"* ]]; then + project_github_url=${si_repo_url%.git} + project_github_slug=${project_github_url#https://github.com/} +fi +project_site= + +# Bare carriage-return character. +carriage_return=$( printf "\r" ) + +# Returns 0 if $1 matches one of the colon-separated patterns in $2. +match_pattern() { + _mp_file=$1 + _mp_list="$2:" + while [ -n "$_mp_list" ]; do + _mp_pattern=${_mp_list%%:*} + _mp_list=${_mp_list#*:} + # shellcheck disable=2254 + case $_mp_file in + $_mp_pattern) + return 0 + ;; + esac + done + return 1 +} + +# Simple .pkgmeta YAML processor. +yaml_keyvalue() { + yaml_key=${1%%:*} + yaml_value=${1#$yaml_key:} + yaml_value=${yaml_value#"${yaml_value%%[! ]*}"} # trim leading whitespace + yaml_value=${yaml_value#[\'\"]} # trim leading quotes + yaml_value=${yaml_value%[\'\"]} # trim trailing quotes +} + +yaml_listitem() { + yaml_item=${1#-} + yaml_item=${yaml_item#"${yaml_item%%[! ]*}"} # trim leading whitespace +} + +### +### Process .pkgmeta to set variables used later in the script. +### + +if [ -z "$pkgmeta_file" ]; then + pkgmeta_file="$topdir/.pkgmeta" + # CurseForge allows this so check for it + if [ ! -f "$pkgmeta_file" ] && [ -f "$topdir/pkgmeta.yaml" ]; then + pkgmeta_file="$topdir/pkgmeta.yaml" + fi +fi + +# Variables set via .pkgmeta. +package= +manual_changelog= +changelog= +changelog_markup="text" +enable_nolib_creation= +ignore= +contents= +nolib_exclude= +wowi_gen_changelog="true" +wowi_archive="true" +wowi_convert_changelog="true" +declare -A relations=() + +parse_ignore() { + pkgmeta="$1" + [ -f "$pkgmeta" ] || return 1 + + checkpath="$topdir" # paths are relative to the topdir + copypath="" + if [ "$2" != "" ]; then + checkpath=$( dirname "$pkgmeta" ) + copypath="$2/" + fi + + yaml_eof= + while [ -z "$yaml_eof" ]; do + IFS='' read -r yaml_line || yaml_eof="true" + # Skip commented out lines. + if [[ $yaml_line =~ ^[[:space:]]*\# ]]; then + continue + fi + # Strip any trailing CR character. + yaml_line=${yaml_line%$carriage_return} + + case $yaml_line in + [!\ ]*:*) + # Split $yaml_line into a $yaml_key, $yaml_value pair. + yaml_keyvalue "$yaml_line" + # Set the $pkgmeta_phase for stateful processing. + pkgmeta_phase=$yaml_key + ;; + [\ ]*"- "*) + yaml_line=${yaml_line#"${yaml_line%%[! ]*}"} # trim leading whitespace + # Get the YAML list item. + yaml_listitem "$yaml_line" + if [ "$pkgmeta_phase" = "ignore" ]; then + pattern=$yaml_item + if [ -d "$checkpath/$pattern" ]; then + pattern="$copypath$pattern/*" + elif [ ! -f "$checkpath/$pattern" ]; then + # doesn't exist so match both a file and a path + pattern="$copypath$pattern:$copypath$pattern/*" + else + pattern="$copypath$pattern" + fi + if [ -z "$ignore" ]; then + ignore="$pattern" + else + ignore="$ignore:$pattern" + fi + fi + ;; + esac + done < "$pkgmeta" +} + +if [ -f "$pkgmeta_file" ]; then + if grep -q $'^[ ]*\t\+[[:blank:]]*[[:graph:]]' "$pkgmeta_file"; then + # Try to cut down on some troubleshooting pain. + echo "ERROR! Your pkgmeta file contains a leading tab. Only spaces are allowed for indentation in YAML files." >&2 + grep -n $'^[ ]*\t\+[[:blank:]]*[[:graph:]]' "$pkgmeta_file" | sed $'s/\t/^I/g' + exit 1 + fi + + yaml_eof= + while [ -z "$yaml_eof" ]; do + IFS='' read -r yaml_line || yaml_eof="true" + # Skip commented out lines. + if [[ $yaml_line =~ ^[[:space:]]*\# ]]; then + continue + fi + # Strip any trailing CR character. + yaml_line=${yaml_line%$carriage_return} + + case $yaml_line in + [!\ ]*:*) + # Split $yaml_line into a $yaml_key, $yaml_value pair. + yaml_keyvalue "$yaml_line" + # Set the $pkgmeta_phase for stateful processing. + pkgmeta_phase=$yaml_key + + case $yaml_key in + enable-nolib-creation) + if [ "$yaml_value" = "yes" ]; then + enable_nolib_creation="true" + fi + ;; + manual-changelog) + changelog=$yaml_value + manual_changelog="true" + ;; + changelog-title) + project="$yaml_value" + ;; + package-as) + package=$yaml_value + ;; + wowi-create-changelog) + if [ "$yaml_value" = "no" ]; then + wowi_gen_changelog= + fi + ;; + wowi-convert-changelog) + if [ "$yaml_value" = "no" ]; then + wowi_convert_changelog= + fi + ;; + wowi-archive-previous) + if [ "$yaml_value" = "no" ]; then + wowi_archive= + fi + ;; + esac + ;; + " "*) + yaml_line=${yaml_line#"${yaml_line%%[! ]*}"} # trim leading whitespace + case $yaml_line in + "- "*) + # Get the YAML list item. + yaml_listitem "$yaml_line" + case $pkgmeta_phase in + ignore) + pattern=$yaml_item + if [ -d "$topdir/$pattern" ]; then + pattern="$pattern/*" + elif [ ! -f "$topdir/$pattern" ]; then + # doesn't exist so match both a file and a path + pattern="$pattern:$pattern/*" + fi + if [ -z "$ignore" ]; then + ignore="$pattern" + else + ignore="$ignore:$pattern" + fi + ;; + tools-used) + relations["$yaml_item"]="tool" + ;; + required-dependencies) + relations["$yaml_item"]="requiredDependency" + ;; + optional-dependencies) + relations["$yaml_item"]="optionalDependency" + ;; + embedded-libraries) + relations["$yaml_item"]="embeddedLibrary" + ;; + esac + ;; + *:*) + # Split $yaml_line into a $yaml_key, $yaml_value pair. + yaml_keyvalue "$yaml_line" + case $pkgmeta_phase in + manual-changelog) + case $yaml_key in + filename) + changelog=$yaml_value + manual_changelog="true" + ;; + markup-type) + if [ "$yaml_value" = "markdown" ] || [ "$yaml_value" = "html" ]; then + changelog_markup=$yaml_value + else + changelog_markup="text" + fi + ;; + esac + ;; + esac + ;; + esac + ;; + esac + done < "$pkgmeta_file" +fi + +# Add untracked/ignored files to the ignore list +if [ "$repository_type" = "git" ]; then + OLDIFS=$IFS + IFS=$'\n' + for _vcs_ignore in $( git -C "$topdir" ls-files --others --directory ); do + if [ -d "$topdir/$_vcs_ignore" ]; then + _vcs_ignore="$_vcs_ignore*" + fi + if [ -z "$ignore" ]; then + ignore="$_vcs_ignore" + else + ignore="$ignore:$_vcs_ignore" + fi + done + IFS=$OLDIFS +elif [ "$repository_type" = "svn" ]; then + # svn always being difficult. + OLDIFS=$IFS + IFS=$'\n' + for _vcs_ignore in $( cd "$topdir" && svn status --no-ignore --ignore-externals | awk '/^[?IX]/' | cut -c9- | tr '\\' '/' ); do + if [ -d "$topdir/$_vcs_ignore" ]; then + _vcs_ignore="$_vcs_ignore/*" + fi + if [ -z "$ignore" ]; then + ignore="$_vcs_ignore" + else + ignore="$ignore:$_vcs_ignore" + fi + done + IFS=$OLDIFS +elif [ "$repository_type" = "hg" ]; then + _vcs_ignore=$( hg --cwd "$topdir" status --ignored --unknown --no-status --print0 | tr '\0' ':' ) + if [ -n "$_vcs_ignore" ]; then + _vcs_ignore=${_vcs_ignore:0:-1} + if [ -z "$ignore" ]; then + ignore="$_vcs_ignore" + else + ignore="$ignore:$_vcs_ignore" + fi + fi +fi + +### +### Process TOC file +### + +# Set the package name from a TOC file name +if [[ -z "$package" ]]; then + package=$( cd "$topdir" && find *.toc -maxdepth 0 2>/dev/null | head -n1 ) + if [[ -z "$package" ]]; then + echo "Could not find an addon TOC file. In another directory? Set 'package-as' in .pkgmeta" >&2 + exit 1 + fi + package=${package%.toc} + if [[ $package =~ ^(.*)-(Mainline|Classic|BCC)$ ]]; then + package="${BASH_REMATCH[1]}" + fi +fi + +toc_path="$package.toc" + +# Handle having the main addon in a sub dir +if [[ ! -f "$topdir/$toc_path" && -f "$topdir/$package/$toc_path" ]]; then + toc_path="$package/$toc_path" +fi + +if [[ ! -f "$topdir/$toc_path" ]]; then + echo "Could not find an addon TOC file. In another directory? Make sure it matches the 'package-as' in .pkgmeta" >&2 + exit 1 +fi + +# Get the interface version for setting the upload version. +toc_file=$( + # remove bom and cr and apply some non-version toc filters + [ "$file_type" != "alpha" ] && _tf_alpha="true" + sed -e $'1s/^\xEF\xBB\xBF//' -e $'s/\r//g' "$topdir/$toc_path" | toc_filter alpha ${_tf_alpha} | toc_filter debug true +) +root_toc_version=$( awk '/^## Interface:/ { print $NF; exit }' <<< "$toc_file" ) +toc_version="$root_toc_version" +if [[ -n "$toc_version" && -z "$game_type" ]]; then + # toc -> game type + case $toc_version in + 113*) game_type="classic" ;; + 205*) game_type="bcc" ;; + *) game_type="retail" + esac +else + # game type -> toc + game_type_toc_version=$( awk 'tolower($0) ~ /^## interface-'${game_type:-retail}':/ { print $NF; exit }' <<< "$toc_file" ) + if [[ -z "$game_type" ]]; then + # default to retail + game_type="retail" + elif [[ -n "$game_type_toc_version" ]]; then + # use the game version value if set + toc_version="$game_type_toc_version" + fi + # Check for other interface lines + if [[ -z "$toc_version" ]] || \ + [[ "$game_type" == "classic" && "$toc_version" != "113"* ]] || \ + [[ "$game_type" == "bcc" && "$toc_version" != "205"* ]] || \ + [[ "$game_type" == "retail" && ("$toc_version" == "113"* || "$toc_version" == "205"*) ]] + then + toc_version="$game_type_toc_version" + if [[ -z "$toc_version" ]]; then + # Check @non-@ blocks + case $game_type in + classic) toc_version=$( sed -n '/@non-[-a-z]*@/,/@end-non-[-a-z]*@/{//b;p}' <<< "$toc_file" | awk '/#[[:blank:]]*## Interface:[[:blank:]]*(113)/ { print $NF; exit }' ) ;; + bcc) toc_version=$( sed -n '/@non-[-a-z]*@/,/@end-non-[-a-z]*@/{//b;p}' <<< "$toc_file" | awk '/#[[:blank:]]*## Interface:[[:blank:]]*(205)/ { print $NF; exit }' ) ;; + esac + # This becomes the actual interface version after string replacements + root_toc_version="$toc_version" + fi + fi + if [[ -z "$toc_version" ]]; then + echo "Addon TOC interface version is not compatible with the game version \"${game_type}\" or was not found." >&2 + exit 1 + fi + if [[ "${toc_version,,}" == "incompatible" ]]; then + echo "Addon TOC interface version is set as incompatible for game version \"${game_type}\"." >&2 + exit 1 + fi +fi +if [ -z "$game_version" ]; then + printf -v game_version "%d.%d.%d" ${toc_version:0:1} ${toc_version:1:2} ${toc_version:3:2} 2>/dev/null || { + echo "Addon TOC interface version \"${toc_version}\" is invalid." >&2 + exit 1 + } + game_versions[$game_type]="$game_version" +fi + +# Get the title of the project for using in the changelog. +if [ -z "$project" ]; then + project=$( awk '/^## Title:/ { print $0; exit }' <<< "$toc_file" | sed -e 's/|c[0-9A-Fa-f]\{8\}//g' -e 's/|r//g' -e 's/|T[^|]*|t//g' -e 's/## Title[[:space:]]*:[[:space:]]*\(.*\)/\1/' -e 's/[[:space:]]*$//' ) +fi +# Grab CurseForge ID and WoWI ID from the TOC file if not set by the script. +if [ -z "$slug" ]; then + slug=$( awk '/^## X-Curse-Project-ID:/ { print $NF; exit }' <<< "$toc_file" ) +fi +if [ -z "$addonid" ]; then + addonid=$( awk '/^## X-WoWI-ID:/ { print $NF; exit }' <<< "$toc_file" ) +fi +if [ -z "$wagoid" ]; then + wagoid=$( awk '/^## X-Wago-ID:/ { print $NF; exit }' <<< "$toc_file" ) +fi +unset toc_file + +# unset project ids if they are set to 0 +[ "$slug" = "0" ] && slug= +[ "$addonid" = "0" ] && addonid= +[ "$wagoid" = "0" ] && wagoid= + +# Automatic file type detection based on CurseForge rules +# 1) Untagged commits will be marked as an alpha. +# 2) Tagged commits will be marked as a release with the following exceptions: +# - If the tag contains the word "alpha", it will be marked as an alpha file. +# - If instead the tag contains the word "beta", it will be marked as a beta file. +if [ -n "$tag" ]; then + if [[ "${tag,,}" == *"alpha"* ]]; then + file_type="alpha" + elif [[ "${tag,,}" == *"beta"* ]]; then + file_type="beta" + else + file_type="release" + fi +else + file_type="alpha" +fi + +echo +echo "Packaging $package" +if [ -n "$project_version" ]; then + echo "Current version: $project_version" +fi +if [ -n "$previous_version" ]; then + echo "Previous version: $previous_version" +fi +( + [ "$game_type" = "retail" ] && retail="retail" || retail="non-retail version-${game_type}" + [ "$file_type" = "alpha" ] && alpha="alpha" || alpha="non-alpha" + echo "Build type: ${retail} ${alpha} non-debug${nolib:+ nolib}" + echo "Game version: ${game_version}" + echo +) +if [[ "$slug" =~ ^[0-9]+$ ]]; then + project_site="https://wow.curseforge.com" + echo "CurseForge ID: $slug${cf_token:+ [token set]}" +fi +if [ -n "$addonid" ]; then + echo "WoWInterface ID: $addonid${wowi_token:+ [token set]}" +fi +if [ -n "$wagoid" ]; then + echo "Wago ID: $wagoid${wago_token:+ [token set]}" +fi +if [ -n "$project_github_slug" ]; then + echo "GitHub: $project_github_slug${github_token:+ [token set]}" +fi +if [ -n "$project_site" ] || [ -n "$addonid" ] || [ -n "$wagoid" ] || [ -n "$project_github_slug" ]; then + echo +fi +echo "Checkout directory: $topdir" +echo "Release directory: $releasedir" +echo + +# Set $pkgdir to the path of the package directory inside $releasedir. +pkgdir="$releasedir/$package" +if [ -d "$pkgdir" ] && [ -z "$overwrite" ]; then + #echo "Removing previous package directory: $pkgdir" + rm -fr "$pkgdir" +fi +if [ ! -d "$pkgdir" ]; then + mkdir -p "$pkgdir" +fi + +# Set the contents of the addon zipfile. +contents="$package" + +### +### Create filters for pass-through processing of files to replace repository keywords. +### + +# Filter for simple repository keyword replacement. +vcs_filter() { + sed \ + -e "s/@project-revision@/$si_project_revision/g" \ + -e "s/@project-hash@/$si_project_hash/g" \ + -e "s/@project-abbreviated-hash@/$si_project_abbreviated_hash/g" \ + -e "s/@project-author@/$( escape_substr "$si_project_author" )/g" \ + -e "s/@project-date-iso@/$si_project_date_iso/g" \ + -e "s/@project-date-integer@/$si_project_date_integer/g" \ + -e "s/@project-timestamp@/$si_project_timestamp/g" \ + -e "s/@project-version@/$( escape_substr "$si_project_version" )/g" \ + -e "s/@file-revision@/$si_file_revision/g" \ + -e "s/@file-hash@/$si_file_hash/g" \ + -e "s/@file-abbreviated-hash@/$si_file_abbreviated_hash/g" \ + -e "s/@file-author@/$( escape_substr "$si_file_author" )/g" \ + -e "s/@file-date-iso@/$si_file_date_iso/g" \ + -e "s/@file-date-integer@/$si_file_date_integer/g" \ + -e "s/@file-timestamp@/$si_file_timestamp/g" +} + +# Find URL of localization api. +set_localization_url() { + localization_url= + if [ -n "$slug" ] && [ -n "$cf_token" ] && [ -n "$project_site" ]; then + localization_url="${project_site}/api/projects/$slug/localization/export" + fi + if [ -z "$localization_url" ] && find "$topdir" -path '*/.*' -prune -o -name "*.lua" -print0 | xargs -0 grep -q "@localization"; then + echo "Skipping localization! Missing CurseForge API token and/or project id is invalid." + echo + fi +} + +# Filter to handle @localization@ repository keyword replacement. +# https://authors.curseforge.com/knowledge-base/projects/531-localization-substitutions/ +declare -A unlocalized_values=( ["english"]="ShowPrimary" ["comment"]="ShowPrimaryAsComment" ["blank"]="ShowBlankAsComment" ["ignore"]="Ignore" ) +localization_filter() { + _ul_eof= + while [ -z "$_ul_eof" ]; do + IFS='' read -r _ul_line || _ul_eof="true" + # Strip any trailing CR character. + _ul_line=${_ul_line%$carriage_return} + case $_ul_line in + *@localization\(*\)@*) + _ul_lang= + _ul_namespace= + _ul_singlekey= + _ul_tablename="L" + # Get the prefix of the line before the comment. + _ul_prefix=${_ul_line%%@localization(*} + _ul_prefix=${_ul_prefix%%--*} + # Strip everything but the localization parameters. + _ul_params=${_ul_line#*@localization(} + _ul_params=${_ul_params%)@} + # Sanitize the params a bit. (namespaces are restricted to [a-zA-Z0-9_], separated by [./:]) + _ul_params=${_ul_params// /} + _ul_params=${_ul_params//,/, } + # Pull the locale language first (mainly for warnings). + _ul_lang="enUS" + if [[ $_ul_params == *"locale=\""* ]]; then + _ul_lang=${_ul_params##*locale=\"} + _ul_lang=${_ul_lang:0:4} + _ul_lang=${_ul_lang%%\"*} + else + echo " Warning! No locale set, using enUS." >&2 + fi + # Generate a URL parameter string from the localization parameters. + # https://authors.curseforge.com/knowledge-base/projects/529-api + _ul_url_params="" + set -- ${_ul_params} + for _ul_param; do + _ul_key=${_ul_param%%=*} + _ul_value=${_ul_param#*=} + _ul_value=${_ul_value%,*} + _ul_value=${_ul_value#*\"} + _ul_value=${_ul_value%\"*} + case ${_ul_key} in + escape-non-ascii) + if [ "$_ul_value" = "true" ]; then + _ul_url_params="${_ul_url_params}&escape-non-ascii-characters=true" + fi + ;; + format) + if [ "$_ul_value" = "lua_table" ]; then + _ul_url_params="${_ul_url_params}&export-type=Table" + fi + ;; + handle-unlocalized) + if [ "$_ul_value" != "english" ] && [ -n "${unlocalized_values[$_ul_value]}" ]; then + _ul_url_params="${_ul_url_params}&unlocalized=${unlocalized_values[$_ul_value]}" + fi + ;; + handle-subnamespaces) + if [ "$_ul_value" = "concat" ]; then # concat with / + _ul_url_params="${_ul_url_params}&concatenante-subnamespaces=true" + elif [ "$_ul_value" = "subtable" ]; then + echo " ($_ul_lang) Warning! ${_ul_key}=\"${_ul_value}\" is not supported. Include each full subnamespace, comma delimited." >&2 + fi + ;; + key) + # _ul_params was stripped of spaces, so reparse the line for the key + _ul_singlekey=${_ul_line#*@localization(} + _ul_singlekey=${_ul_singlekey#*key=\"} + _ul_singlekey=${_ul_singlekey%%\",*} + _ul_singlekey=${_ul_singlekey%%\")@*} + ;; + locale) + _ul_lang=$_ul_value + ;; + namespace) + # reparse to get all namespaces if multiple + _ul_namespace=${_ul_params##*namespace=\"} + _ul_namespace=${_ul_namespace%%\"*} + _ul_namespace=${_ul_namespace//, /,} + _ul_url_params="${_ul_url_params}&namespaces=${_ul_namespace}" + _ul_namespace="/${_ul_namespace}" + ;; + namespace-delimiter) + if [ "$_ul_value" != "/" ]; then + echo " ($_ul_lang) Warning! ${_ul_key}=\"${_ul_value}\" is not supported." >&2 + fi + ;; + prefix-values) + echo " ($_ul_lang) Warning! \"${_ul_key}\" is not supported." >&2 + ;; + same-key-is-true) + if [ "$_ul_value" = "true" ]; then + _ul_url_params="${_ul_url_params}&true-if-value-equals-key=true" + fi + ;; + table-name) + if [ "$_ul_value" != "L" ]; then + _ul_tablename="$_ul_value" + _ul_url_params="${_ul_url_params}&table-name=${_ul_value}" + fi + ;; + esac + done + + if [ -z "$_cdt_localization" ] || [ -z "$localization_url" ]; then + echo " Skipping localization (${_ul_lang}${_ul_namespace})" >&2 + + # If the line isn't a TOC entry, print anything before the keyword. + if [[ $_ul_line != "## "* ]]; then + if [ -n "$_ul_eof" ]; then + echo -n "$_ul_prefix" + else + echo "$_ul_prefix" + fi + fi + else + _ul_url="${localization_url}?lang=${_ul_lang}${_ul_url_params}" + echo " Adding ${_ul_lang}${_ul_namespace}" >&2 + + if [ -z "$_ul_singlekey" ]; then + # Write text that preceded the substitution. + echo -n "$_ul_prefix" + + # Fetch the localization data, but don't output anything if there is an error. + curl -s -H "x-api-token: $cf_token" "${_ul_url}" | awk -v url="$_ul_url" '/^{"error/ { o=" Error! "$0"\n "url; print o >"/dev/stderr"; exit 1 } /"/dev/stderr"; exit 1 } /^'"$_ul_tablename"' = '"$_ul_tablename"' or \{\}/ { next } { print }' + + # Insert a trailing blank line to match CF packager. + if [ -z "$_ul_eof" ]; then + echo "" + fi + else + # Parse out a single phrase. This is kind of expensive, but caching would be way too much effort to optimize for what is basically an edge case. + _ul_value=$( curl -s -H "x-api-token: $cf_token" "${_ul_url}" | awk -v url="$_ul_url" '/^{"error/ { o=" Error! "$0"\n "url; print o >"/dev/stderr"; exit 1 } /"/dev/stderr"; exit 1 } { print }' | sed -n '/L\["'"$_ul_singlekey"'"\]/p' | sed 's/^.* = "\(.*\)"/\1/' ) + if [ -n "$_ul_value" ] && [ "$_ul_value" != "$_ul_singlekey" ]; then + # The result is different from the base value so print out the line. + echo "${_ul_prefix}${_ul_value}${_ul_line##*)@}" + fi + fi + fi + ;; + *) + if [ -n "$_ul_eof" ]; then + echo -n "$_ul_line" + else + echo "$_ul_line" + fi + esac + done +} + +lua_filter() { + local level + case $1 in + alpha) level="=" ;; + debug) level="==" ;; + retail|version-*) level="====" ;; + *) level="===" + esac + sed \ + -e "s/--@$1@/--[${level}[@$1@/g" \ + -e "s/--@end-$1@/--@end-$1@]${level}]/g" \ + -e "s/--\[===\[@non-$1@/--@non-$1@/g" \ + -e "s/--@end-non-$1@\]===\]/--@end-non-$1@/g" +} + +toc_interface_filter() { + # Always remove BOM so ^ works + if [ "$root_toc_version" != "$toc_version" ]; then + # toc version isn't what is set in the toc file + if [ -n "$root_toc_version" ]; then # rewrite + sed -e $'1s/^\xEF\xBB\xBF//' -e 's/^## Interface:.*$/## Interface: '"$toc_version"'/' -e '/^## Interface-/d' + else # add + sed -e $'1s/^\xEF\xBB\xBF//' -e '1i\ +## Interface: '"$toc_version" -e '/^## Interface-/d' + fi + else # cleanup + sed -e $'1s/^\xEF\xBB\xBF//' -e '/^## Interface-/d' + fi +} + +xml_filter() { + sed \ + -e "s///@end-$1@-->/g" \ + -e "s//g" \ + -e "s/@end-non-$1@-->//g" +} + +do_not_package_filter() { + case $1 in + lua) sed '/--@do-not-package@/,/--@end-do-not-package@/d' ;; + toc) sed '/#@do-not-package@/,/#@end-do-not-package@/d' ;; + xml) sed '//,//d' ;; + esac +} + +line_ending_filter() { + local _lef_eof _lef_line + while [ -z "$_lef_eof" ]; do + IFS='' read -r _lef_line || _lef_eof="true" + # Strip any trailing CR character. + _lef_line=${_lef_line%$carriage_return} + if [ -n "$_lef_eof" ]; then + # Preserve EOF not preceded by newlines. + echo -n "$_lef_line" + else + case $line_ending in + dos) printf "%s\r\n" "$_lef_line" ;; # Terminate lines with CR LF. + unix) printf "%s\n" "$_lef_line" ;; # Terminate lines with LF. + esac + fi + done +} + +### +### Copy files from the working directory into the package directory. +### + +# Copy of the contents of the source directory into the destination directory. +# Dotfiles and any files matching the ignore pattern are skipped. Copied files +# are subject to repository keyword replacement. +# +copy_directory_tree() { + _cdt_alpha= + _cdt_debug= + _cdt_ignored_patterns= + _cdt_localization= + _cdt_nolib= + _cdt_do_not_package= + _cdt_unchanged_patterns= + _cdt_classic= + _cdt_external= + OPTIND=1 + while getopts :adi:lnpu:c:e _cdt_opt "$@"; do + # shellcheck disable=2220 + case $_cdt_opt in + a) _cdt_alpha="true" ;; + d) _cdt_debug="true" ;; + i) _cdt_ignored_patterns=$OPTARG ;; + l) _cdt_localization="true" + set_localization_url + ;; + n) _cdt_nolib="true" ;; + p) _cdt_do_not_package="true" ;; + u) _cdt_unchanged_patterns=$OPTARG ;; + c) _cdt_classic=$OPTARG ;; + e) _cdt_external="true" ;; + esac + done + shift $((OPTIND - 1)) + _cdt_srcdir=$1 + _cdt_destdir=$2 + + if [ -z "$_cdt_external" ]; then + start_group "Copying files into ${_cdt_destdir#$topdir/}:" "copy" + else # don't nest groups + echo "Copying files into ${_cdt_destdir#$topdir/}:" + fi + if [ ! -d "$_cdt_destdir" ]; then + mkdir -p "$_cdt_destdir" + fi + # Create a "find" command to list all of the files in the current directory, minus any ones we need to prune. + _cdt_find_cmd="find ." + # Prune everything that begins with a dot except for the current directory ".". + _cdt_find_cmd+=" \( -name \".*\" -a \! -name \".\" \) -prune" + # Prune the destination directory if it is a subdirectory of the source directory. + _cdt_dest_subdir=${_cdt_destdir#${_cdt_srcdir}/} + case $_cdt_dest_subdir in + /*) ;; + *) _cdt_find_cmd+=" -o -path \"./$_cdt_dest_subdir\" -prune" ;; + esac + # Print the filename, but suppress the current directory ".". + _cdt_find_cmd+=" -o \! -name \".\" -print" + ( cd "$_cdt_srcdir" && eval "$_cdt_find_cmd" ) | while read -r file; do + file=${file#./} + if [ -f "$_cdt_srcdir/$file" ]; then + # Check if the file should be ignored. + skip_copy= + # Prefix external files with the relative pkgdir path + _cdt_check_file=$file + if [ -n "${_cdt_destdir#$pkgdir}" ]; then + _cdt_check_file="${_cdt_destdir#$pkgdir/}/$file" + fi + # Skip files matching the colon-separated "ignored" shell wildcard patterns. + if [ -z "$skip_copy" ] && match_pattern "$_cdt_check_file" "$_cdt_ignored_patterns"; then + skip_copy="true" + fi + # Never skip files that match the colon-separated "unchanged" shell wildcard patterns. + unchanged= + if [ -n "$skip_copy" ] && match_pattern "$file" "$_cdt_unchanged_patterns"; then + skip_copy= + unchanged="true" + fi + # Copy unskipped files into $_cdt_destdir. + if [ -n "$skip_copy" ]; then + echo " Ignoring: $file" + else + dir=${file%/*} + if [ "$dir" != "$file" ]; then + mkdir -p "$_cdt_destdir/$dir" + fi + # Check if the file matches a pattern for keyword replacement. + if [ -n "$unchanged" ] || ! match_pattern "$file" "*.lua:*.md:*.toc:*.txt:*.xml"; then + echo " Copying: $file (unchanged)" + cp "$_cdt_srcdir/$file" "$_cdt_destdir/$dir" + else + # Set the filters for replacement based on file extension. + _cdt_filters="vcs_filter" + case $file in + *.lua) + [ -n "$_cdt_do_not_package" ] && _cdt_filters+="|do_not_package_filter lua" + [ -n "$_cdt_alpha" ] && _cdt_filters+="|lua_filter alpha" + [ -n "$_cdt_debug" ] && _cdt_filters+="|lua_filter debug" + if [ -n "$_cdt_classic" ]; then + _cdt_filters+="|lua_filter retail" + _cdt_filters+="|lua_filter version-retail" + [ "$_cdt_classic" = "classic" ] && _cdt_filters+="|lua_filter version-bcc" + [ "$_cdt_classic" = "bcc" ] && _cdt_filters+="|lua_filter version-classic" + else + _cdt_filters+="|lua_filter version-classic" + _cdt_filters+="|lua_filter version-bcc" + fi + [ -n "$_cdt_localization" ] && _cdt_filters+="|localization_filter" + ;; + *.xml) + [ -n "$_cdt_do_not_package" ] && _cdt_filters+="|do_not_package_filter xml" + [ -n "$_cdt_nolib" ] && _cdt_filters+="|xml_filter no-lib-strip" + [ -n "$_cdt_alpha" ] && _cdt_filters+="|xml_filter alpha" + [ -n "$_cdt_debug" ] && _cdt_filters+="|xml_filter debug" + if [ -n "$_cdt_classic" ]; then + _cdt_filters+="|xml_filter retail" + _cdt_filters+="|xml_filter version-retail" + [ "$_cdt_classic" = "classic" ] && _cdt_filters+="|xml_filter version-bcc" + [ "$_cdt_classic" = "bcc" ] && _cdt_filters+="|xml_filter version-classic" + else + _cdt_filters+="|xml_filter version-classic" + _cdt_filters+="|xml_filter version-bcc" + fi + ;; + *.toc) + _cdt_filters+="|do_not_package_filter toc" + [ -n "$_cdt_nolib" ] && _cdt_filters+="|toc_filter no-lib-strip true" # leave the tokens in the file normally + _cdt_filters+="|toc_filter alpha ${_cdt_alpha}" + _cdt_filters+="|toc_filter debug ${_cdt_debug}" + _cdt_filters+="|toc_filter retail ${_cdt_classic:+true}" + _cdt_filters+="|toc_filter version-retail ${_cdt_classic:+true}" + _cdt_filters+="|toc_filter version-classic $([[ -z "$_cdt_classic" || "$_cdt_classic" == "bcc" ]] && echo "true")" + _cdt_filters+="|toc_filter version-bcc $([[ -z "$_cdt_classic" || "$_cdt_classic" == "classic" ]] && echo "true")" + [[ -z "$_cdt_external" && ! $file =~ -(Mainline|Classic|BCC).toc$ ]] && _cdt_filters+="|toc_interface_filter" + [ -n "$_cdt_localization" ] && _cdt_filters+="|localization_filter" + ;; + esac + + # Set the filter for normalizing line endings. + _cdt_filters+="|line_ending_filter" + + # Set version control values for the file. + set_info_file "$_cdt_srcdir/$file" + + echo " Copying: $file" + eval < "$_cdt_srcdir/$file" "$_cdt_filters" > "$_cdt_destdir/$file" + fi + fi + fi + done + if [ -z "$_external_dir" ]; then + end_group "copy" + fi +} + +if [ -z "$skip_copying" ]; then + cdt_args="-dp" + [ "$file_type" != "alpha" ] && cdt_args+="a" + [ -z "$skip_localization" ] && cdt_args+="l" + [ -n "$nolib" ] && cdt_args+="n" + [ "$game_type" != "retail" ] && cdt_args+=" -c $game_type" + [ -n "$ignore" ] && cdt_args+=" -i \"$ignore\"" + [ -n "$changelog" ] && cdt_args+=" -u \"$changelog\"" + eval copy_directory_tree "$cdt_args" "\"$topdir\"" "\"$pkgdir\"" +fi + +# Reset ignore and parse pkgmeta ignores again to handle ignoring external paths +ignore= +parse_ignore "$pkgmeta_file" + +### +### Process .pkgmeta again to perform any pre-move-folders actions. +### + +retry() { + local result=0 + local count=1 + while [[ "$count" -le 3 ]]; do + [[ "$result" -ne 0 ]] && { + echo -e "\033[01;31mRetrying (${count}/3)\033[0m" >&2 + } + "$@" && { result=0 && break; } || result="$?" + count="$((count + 1))" + sleep 3 + done + return "$result" +} + +# Checkout the external into a ".checkout" subdirectory of the final directory. +checkout_external() { + _external_dir=$1 + _external_uri=$2 + _external_tag=$3 + _external_type=$4 + # shellcheck disable=2034 + _external_slug=$5 # unused until we can easily fetch the project id + _external_checkout_type=$6 + + _cqe_checkout_dir="$pkgdir/$_external_dir/.checkout" + mkdir -p "$_cqe_checkout_dir" + if [ "$_external_type" = "git" ]; then + if [ -z "$_external_tag" ]; then + echo "Fetching latest version of external $_external_uri" + retry git clone -q --depth 1 "$_external_uri" "$_cqe_checkout_dir" || return 1 + elif [ "$_external_tag" != "latest" ]; then + echo "Fetching $_external_checkout_type \"$_external_tag\" from external $_external_uri" + if [ "$_external_checkout_type" = "commit" ]; then + retry git clone -q "$_external_uri" "$_cqe_checkout_dir" || return 1 + git -C "$_cqe_checkout_dir" checkout -q "$_external_tag" || return 1 + else + git -c advice.detachedHead=false clone -q --depth 1 --branch "$_external_tag" "$_external_uri" "$_cqe_checkout_dir" || return 1 + fi + else # [ "$_external_tag" = "latest" ]; then + retry git clone -q --depth 50 "$_external_uri" "$_cqe_checkout_dir" || return 1 + _external_tag=$( git -C "$_cqe_checkout_dir" for-each-ref refs/tags --sort=-creatordate --format=%\(refname:short\) --count=1 ) + if [ -n "$_external_tag" ]; then + echo "Fetching tag \"$_external_tag\" from external $_external_uri" + git -C "$_cqe_checkout_dir" checkout -q "$_external_tag" || return 1 + else + echo "Fetching latest version of external $_external_uri" + fi + fi + + # pull submodules + git -C "$_cqe_checkout_dir" submodule -q update --init --recursive || return 1 + + set_info_git "$_cqe_checkout_dir" + echo "Checked out $( git -C "$_cqe_checkout_dir" describe --always --tags --abbrev=7 --long )" #$si_project_abbreviated_hash + elif [ "$_external_type" = "svn" ]; then + if [[ $external_uri == *"/trunk" ]]; then + _cqe_svn_trunk_url=$_external_uri + _cqe_svn_subdir= + else + _cqe_svn_trunk_url="${_external_uri%/trunk/*}/trunk" + _cqe_svn_subdir=${_external_uri#${_cqe_svn_trunk_url}/} + fi + + if [ -z "$_external_tag" ]; then + echo "Fetching latest version of external $_external_uri" + retry svn checkout -q "$_external_uri" "$_cqe_checkout_dir" || return 1 + else + _cqe_svn_tag_url="${_cqe_svn_trunk_url%/trunk}/tags" + if [ "$_external_tag" = "latest" ]; then + _external_tag=$( svn log --verbose --limit 1 "$_cqe_svn_tag_url" 2>/dev/null | awk '/^ A \/tags\// { print $2; exit }' | awk -F/ '{ print $3 }' ) + if [ -z "$_external_tag" ]; then + _external_tag="latest" + fi + fi + if [ "$_external_tag" = "latest" ]; then + echo "No tags found in $_cqe_svn_tag_url" + echo "Fetching latest version of external $_external_uri" + retry svn checkout -q "$_external_uri" "$_cqe_checkout_dir" || return 1 + else + _cqe_external_uri="${_cqe_svn_tag_url}/$_external_tag" + if [ -n "$_cqe_svn_subdir" ]; then + _cqe_external_uri="${_cqe_external_uri}/$_cqe_svn_subdir" + fi + echo "Fetching tag \"$_external_tag\" from external $_cqe_external_uri" + retry svn checkout -q "$_cqe_external_uri" "$_cqe_checkout_dir" || return 1 + fi + fi + set_info_svn "$_cqe_checkout_dir" + echo "Checked out r$si_project_revision" + elif [ "$_external_type" = "hg" ]; then + if [ -z "$_external_tag" ]; then + echo "Fetching latest version of external $_external_uri" + retry hg clone -q "$_external_uri" "$_cqe_checkout_dir" || return 1 + elif [ "$_external_tag" != "latest" ]; then + echo "Fetching $_external_checkout_type \"$_external_tag\" from external $_external_uri" + retry hg clone -q --updaterev "$_external_tag" "$_external_uri" "$_cqe_checkout_dir" || return 1 + else # [ "$_external_tag" = "latest" ]; then + retry hg clone -q "$_external_uri" "$_cqe_checkout_dir" || return 1 + _external_tag=$( hg --cwd "$_cqe_checkout_dir" log -r . --template '{latesttag}' ) + if [ -n "$_external_tag" ]; then + echo "Fetching tag \"$_external_tag\" from external $_external_uri" + hg --cwd "$_cqe_checkout_dir" update -q "$_external_tag" + else + echo "Fetching latest version of external $_external_uri" + fi + fi + set_info_hg "$_cqe_checkout_dir" + echo "Checked out r$si_project_revision" + else + echo "Unknown external: $_external_uri" >&2 + return 1 + fi + # Copy the checkout into the proper external directory. + ( + cd "$_cqe_checkout_dir" || return 1 + # Set the slug for external localization, if needed. + # Note: We don't actually do localization since we need the project id and + # the only way to convert slug->id would be to scrape the project page :\ + slug= #$_external_slug + project_site= + if [[ "$_external_uri" == *"wowace.com"* || "$_external_uri" == *"curseforge.com"* ]]; then + project_site="https://wow.curseforge.com" + fi + # If a .pkgmeta file is present, process it for an "ignore" list. + parse_ignore "$_cqe_checkout_dir/.pkgmeta" "$_external_dir" + copy_directory_tree -dnpe -i "$ignore" "$_cqe_checkout_dir" "$pkgdir/$_external_dir" + ) + # Remove the ".checkout" subdirectory containing the full checkout. + if [ -d "$_cqe_checkout_dir" ]; then + rm -fr "$_cqe_checkout_dir" + fi +} + +external_pids=() + +external_dir= +external_uri= +external_tag= +external_type= +external_slug= +external_checkout_type= +process_external() { + if [ -n "$external_dir" ] && [ -n "$external_uri" ] && [ -z "$skip_externals" ]; then + # convert old curse repo urls + case $external_uri in + *git.curseforge.com*|*git.wowace.com*) + external_type="git" + # git://git.curseforge.com/wow/$slug/mainline.git -> https://repos.curseforge.com/wow/$slug + external_uri=${external_uri%/mainline.git} + external_uri="https://repos${external_uri#*://git}" + ;; + *svn.curseforge.com*|*svn.wowace.com*) + external_type="svn" + # svn://svn.curseforge.com/wow/$slug/mainline/trunk -> https://repos.curseforge.com/wow/$slug/trunk + external_uri=${external_uri/\/mainline/} + external_uri="https://repos${external_uri#*://svn}" + ;; + *hg.curseforge.com*|*hg.wowace.com*) + external_type="hg" + # http://hg.curseforge.com/wow/$slug/mainline -> https://repos.curseforge.com/wow/$slug + external_uri=${external_uri%/mainline} + external_uri="https://repos${external_uri#*://hg}" + ;; + svn:*) + # just in case + external_type="svn" + ;; + *) + if [ -z "$external_type" ]; then + external_type="git" + fi + ;; + esac + + if [[ $external_uri == "https://repos.curseforge.com/wow/"* || $external_uri == "https://repos.wowace.com/wow/"* ]]; then + if [ -z "$external_slug" ]; then + external_slug=${external_uri#*/wow/} + external_slug=${external_slug%%/*} + fi + + # check if the repo is svn + _svn_path=${external_uri#*/wow/$external_slug/} + if [[ "$_svn_path" == "trunk"* ]]; then + external_type="svn" + elif [[ "$_svn_path" == "tags/"* ]]; then + external_type="svn" + # change the tag path into the trunk path and use the tag var so it gets logged as a tag + external_tag=${_svn_path#tags/} + external_tag=${external_tag%%/*} + external_uri="${external_uri%/tags*}/trunk${_svn_path#tags/$external_tag}" + fi + fi + + if [ -n "$external_slug" ]; then + relations["$external_slug"]="embeddedLibrary" + fi + + echo "Fetching external: $external_dir" + checkout_external "$external_dir" "$external_uri" "$external_tag" "$external_type" "$external_slug" "$external_checkout_type" &> "$releasedir/.$BASHPID.externalout" & + external_pids+=($!) + fi + external_dir= + external_uri= + external_tag= + external_type= + external_slug= + external_checkout_type= +} + +# Don't leave extra files around if exited early +kill_externals() { + rm -f "$releasedir"/.*.externalout + kill 0 +} +trap kill_externals INT + +if [ -z "$skip_externals" ] && [ -f "$pkgmeta_file" ]; then + yaml_eof= + while [ -z "$yaml_eof" ]; do + IFS='' read -r yaml_line || yaml_eof="true" + # Skip commented out lines. + if [[ $yaml_line =~ ^[[:space:]]*\# ]]; then + continue + fi + # Strip any trailing CR character. + yaml_line=${yaml_line%$carriage_return} + + case $yaml_line in + [!\ ]*:*) + # Started a new section, so checkout any queued externals. + process_external + # Split $yaml_line into a $yaml_key, $yaml_value pair. + yaml_keyvalue "$yaml_line" + # Set the $pkgmeta_phase for stateful processing. + pkgmeta_phase=$yaml_key + ;; + " "*) + yaml_line=${yaml_line#"${yaml_line%%[! ]*}"} # trim leading whitespace + case $yaml_line in + "- "*) + ;; + *:*) + # Split $yaml_line into a $yaml_key, $yaml_value pair. + yaml_keyvalue "$yaml_line" + case $pkgmeta_phase in + externals) + case $yaml_key in + url) external_uri=$yaml_value ;; + tag) + external_tag=$yaml_value + external_checkout_type=$yaml_key + ;; + branch) + external_tag=$yaml_value + external_checkout_type=$yaml_key + ;; + commit) + external_tag=$yaml_value + external_checkout_type=$yaml_key + ;; + type) external_type=$yaml_value ;; + curse-slug) external_slug=$yaml_value ;; + *) + # Started a new external, so checkout any queued externals. + process_external + + external_dir=$yaml_key + nolib_exclude="$nolib_exclude $pkgdir/$external_dir/*" + if [ -n "$yaml_value" ]; then + external_uri=$yaml_value + # Immediately checkout this fully-specified external. + process_external + fi + ;; + esac + ;; + esac + ;; + esac + ;; + esac + done < "$pkgmeta_file" + # Reached end of file, so checkout any remaining queued externals. + process_external + + if [ ${#external_pids[*]} -gt 0 ]; then + echo + echo "Waiting for externals to finish..." + echo + + while [ ${#external_pids[*]} -gt 0 ]; do + wait -n + for i in ${!external_pids[*]}; do + pid=${external_pids[i]} + if ! kill -0 $pid 2>/dev/null; then + _external_output="$releasedir/.$pid.externalout" + if ! wait $pid; then + _external_error=1 + # wrap each line with a bright red color code + awk '{ printf "\033[01;31m%s\033[0m\n", $0 }' "$_external_output" + echo + else + start_group "$( head -n1 "$_external_output" )" "external.$pid" + tail -n+2 "$_external_output" + end_group "external.$pid" + fi + rm -f "$_external_output" 2>/dev/null + unset 'external_pids[i]' + fi + done + done + + if [ -n "$_external_error" ]; then + echo + echo "There was an error fetching externals :(" >&2 + exit 1 + fi + fi +fi + +# Restore the signal handlers +trap - INT + +### +### Create the changelog of commits since the previous release tag. +### + +if [ -z "$project" ]; then + project="$package" +fi + +# Create a changelog in the package directory if the source directory does +# not contain a manual changelog. +if [ -n "$manual_changelog" ] && [ -f "$topdir/$changelog" ]; then + start_group "Using manual changelog at $changelog" "changelog" + head -n7 "$topdir/$changelog" + [ "$( wc -l < "$topdir/$changelog" )" -gt 7 ] && echo "..." + end_group "changelog" + + # Convert Markdown to BBCode (with HTML as an intermediary) for sending to WoWInterface + # Requires pandoc (http://pandoc.org/) + if [ "$changelog_markup" = "markdown" ] && [ -n "$wowi_convert_changelog" ] && hash pandoc &>/dev/null; then + wowi_changelog="$releasedir/WOWI-$project_version-CHANGELOG.txt" + pandoc -f commonmark -t html "$topdir/$changelog" | sed \ + -e 's/<\(\/\)\?\(b\|i\|u\)>/[\1\2]/g' \ + -e 's/<\(\/\)\?em>/[\1i]/g' \ + -e 's/<\(\/\)\?strong>/[\1b]/g' \ + -e 's/]*>/[list]/g' -e 's/]*>/[list="1"]/g' \ + -e 's/<\/[ou]l>/[\/list]\n/g' \ + -e 's/
  • /[*]/g' -e 's/

  • /[*]/g' -e 's/<\/p><\/li>//g' -e 's/<\/li>//g' \ + -e 's/\[\*\]\[ \] /[*]☐ /g' -e 's/\[\*\]\[[xX]\] /[*]☒ /g' \ + -e 's/]*>/[size="6"]/g' -e 's/]*>/[size="5"]/g' -e 's/]*>/[size="4"]/g' \ + -e 's/]*>/[size="3"]/g' -e 's/]*>/[size="3"]/g' -e 's/]*>/[size="3"]/g' \ + -e 's/<\/h[1-6]>/[\/size]\n/g' \ + -e 's/
    /[quote]/g' -e 's/<\/blockquote>/[\/quote]\n/g' \ + -e 's/
    ]*>
    /[highlight="lua"]/g' -e 's/<\/code><\/pre><\/div>/[\/highlight]\n/g' \
    +			-e 's/
    /[code]/g' -e 's/<\/code><\/pre>/[\/code]\n/g' \
    +			-e 's//[font="monospace"]/g' -e 's/<\/code>/[\/font]/g' \
    +			-e 's/]*>/[url="\1"]/g' -e 's/<\/a>/\[\/url]/g' \
    +			-e 's/]*>/[img]\1[\/img]/g' \
    +			-e 's/
    /_____________________________________________________________________________\n/g' \ + -e 's/<\/p>/\n/g' \ + -e '/^<[^>]\+>$/d' -e 's/<[^>]\+>//g' \ + -e 's/"/"/g' \ + -e 's/&/&/g' \ + -e 's/<//g' \ + -e "s/'/'/g" \ + | line_ending_filter > "$wowi_changelog" + fi +else + if [ -n "$manual_changelog" ]; then + echo "Warning! Could not find a manual changelog at $topdir/$changelog" + manual_changelog= + fi + changelog="CHANGELOG.md" + changelog_markup="markdown" + + if [ -n "$wowi_gen_changelog" ] && [ -z "$wowi_convert_changelog" ]; then + wowi_markup="markdown" + fi + + start_group "Generating changelog of commits into $changelog" "changelog" + + _changelog_range= + if [ "$repository_type" = "git" ]; then + changelog_url= + changelog_version= + changelog_previous="[Previous Releases](${project_github_url}/releases)" + changelog_url_wowi= + changelog_version_wowi= + changelog_previous_wowi="[url=${project_github_url}/releases]Previous Releases[/url]" + if [ -z "$previous_version" ] && [ -z "$tag" ]; then + # no range, show all commits up to ours + changelog_url="[Full Changelog](${project_github_url}/commits/${project_hash})" + changelog_version="[${project_version}](${project_github_url}/tree/${project_hash})" + changelog_url_wowi="[url=${project_github_url}/commits/${project_hash}]Full Changelog[/url]" + changelog_version_wowi="[url=${project_github_url}/tree/${project_hash}]${project_version}[/url]" + _changelog_range="$project_hash" + elif [ -z "$previous_version" ] && [ -n "$tag" ]; then + # first tag, show all commits upto it + changelog_url="[Full Changelog](${project_github_url}/commits/${tag})" + changelog_version="[${project_version}](${project_github_url}/tree/${tag})" + changelog_url_wowi="[url=${project_github_url}/commits/${tag}]Full Changelog[/url]" + changelog_version_wowi="[url=${project_github_url}/tree/${tag}]${project_version}[/url]" + _changelog_range="$tag" + elif [ -n "$previous_version" ] && [ -z "$tag" ]; then + # compare between last tag and our commit + changelog_url="[Full Changelog](${project_github_url}/compare/${previous_version}...${project_hash})" + changelog_version="[$project_version](${project_github_url}/tree/${project_hash})" + changelog_url_wowi="[url=${project_github_url}/compare/${previous_version}...${project_hash}]Full Changelog[/url]" + changelog_version_wowi="[url=${project_github_url}/tree/${project_hash}]${project_version}[/url]" + _changelog_range="$previous_version..$project_hash" + elif [ -n "$previous_version" ] && [ -n "$tag" ]; then + # compare between last tag and our tag + changelog_url="[Full Changelog](${project_github_url}/compare/${previous_version}...${tag})" + changelog_version="[$project_version](${project_github_url}/tree/${tag})" + changelog_url_wowi="[url=${project_github_url}/compare/${previous_version}...${tag}]Full Changelog[/url]" + changelog_version_wowi="[url=${project_github_url}/tree/${tag}]${project_version}[/url]" + _changelog_range="$previous_version..$tag" + fi + # lazy way out + if [ -z "$project_github_url" ]; then + changelog_url= + changelog_version=$project_version + changelog_previous= + changelog_url_wowi= + changelog_version_wowi="[color=orange]${project_version}[/color]" + changelog_previous_wowi= + elif [ -z "$github_token" ]; then + # not creating releases :( + changelog_previous= + changelog_previous_wowi= + fi + changelog_date=$( TZ='' printf "%(%Y-%m-%d)T" "$project_timestamp" ) + + cat <<- EOF | line_ending_filter > "$pkgdir/$changelog" + # $project + + ## $changelog_version ($changelog_date) + $changelog_url $changelog_previous + + EOF + git -C "$topdir" log "$_changelog_range" --pretty=format:"###%B" \ + | sed -e 's/^/ /g' -e 's/^ *$//g' -e 's/^ ###/- /g' -e 's/$/ /' \ + -e 's/\([a-zA-Z0-9]\)_\([a-zA-Z0-9]\)/\1\\_\2/g' \ + -e 's/\[ci skip\]//g' -e 's/\[skip ci\]//g' \ + -e '/git-svn-id:/d' -e '/^[[:space:]]*This reverts commit [0-9a-f]\{40\}\.[[:space:]]*$/d' \ + -e '/^[[:space:]]*$/d' \ + | line_ending_filter >> "$pkgdir/$changelog" + + # WoWI uses BBCode, generate something usable to post to the site + # the file is deleted on successful upload + if [ -n "$addonid" ] && [ -n "$tag" ] && [ -n "$wowi_gen_changelog" ] && [ "$wowi_markup" = "bbcode" ]; then + wowi_changelog="$releasedir/WOWI-$project_version-CHANGELOG.txt" + cat <<- EOF | line_ending_filter > "$wowi_changelog" + [size=5]${project}[/size] + [size=4]${changelog_version_wowi} (${changelog_date})[/size] + ${changelog_url_wowi} ${changelog_previous_wowi} + [list] + EOF + git -C "$topdir" log "$_changelog_range" --pretty=format:"###%B" \ + | sed -e 's/^/ /g' -e 's/^ *$//g' -e 's/^ ###/[*]/g' \ + -e 's/\[ci skip\]//g' -e 's/\[skip ci\]//g' \ + -e '/git-svn-id:/d' -e '/^[[:space:]]*This reverts commit [0-9a-f]\{40\}\.[[:space:]]*$/d' \ + -e '/^[[:space:]]*$/d' \ + | line_ending_filter >> "$wowi_changelog" + echo "[/list]" | line_ending_filter >> "$wowi_changelog" + fi + + elif [ "$repository_type" = "svn" ]; then + if [ -n "$previous_revision" ]; then + _changelog_range="-r$project_revision:$previous_revision" + else + _changelog_range="-rHEAD:1" + fi + changelog_date=$( TZ='' printf "%(%Y-%m-%d)T" "$project_timestamp" ) + + cat <<- EOF | line_ending_filter > "$pkgdir/$changelog" + # $project + + ## $project_version ($changelog_date) + + EOF + svn log "$topdir" "$_changelog_range" --xml \ + | awk '//,/<\/msg>/' \ + | sed -e 's//###/g' -e 's/<\/msg>//g' \ + -e 's/^/ /g' -e 's/^ *$//g' -e 's/^ ###/- /g' -e 's/$/ /' \ + -e 's/\([a-zA-Z0-9]\)_\([a-zA-Z0-9]\)/\1\\_\2/g' \ + -e 's/\[ci skip\]//g' -e 's/\[skip ci\]//g' \ + -e '/^[[:space:]]*$/d' \ + | line_ending_filter >> "$pkgdir/$changelog" + + # WoWI uses BBCode, generate something usable to post to the site + # the file is deleted on successful upload + if [ -n "$addonid" ] && [ -n "$tag" ] && [ -n "$wowi_gen_changelog" ] && [ "$wowi_markup" = "bbcode" ]; then + wowi_changelog="$releasedir/WOWI-$project_version-CHANGELOG.txt" + cat <<- EOF | line_ending_filter > "$wowi_changelog" + [size=5]${project}[/size] + [size=4][color=orange]${project_version}[/color] (${changelog_date})[/size] + + [list] + EOF + svn log "$topdir" "$_changelog_range" --xml \ + | awk '//,/<\/msg>/' \ + | sed -e 's//###/g' -e 's/<\/msg>//g' \ + -e 's/^/ /g' -e 's/^ *$//g' -e 's/^ ###/[*]/g' \ + -e 's/\[ci skip\]//g' -e 's/\[skip ci\]//g' \ + -e '/^[[:space:]]*$/d' \ + | line_ending_filter >> "$wowi_changelog" + echo "[/list]" | line_ending_filter >> "$wowi_changelog" + fi + + elif [ "$repository_type" = "hg" ]; then + if [ -n "$previous_revision" ]; then + _changelog_range="::$project_revision - ::$previous_revision - filelog(.hgtags)" + else + _changelog_range="." + fi + changelog_date=$( TZ='' printf "%(%Y-%m-%d)T" "$project_timestamp" ) + + cat <<- EOF | line_ending_filter > "$pkgdir/$changelog" + # $project + + ## $project_version ($changelog_date) + + EOF + hg --cwd "$topdir" log -r "$_changelog_range" --template '- {fill(desc|strip, 76, "", " ")}\n' | line_ending_filter >> "$pkgdir/$changelog" + + # WoWI uses BBCode, generate something usable to post to the site + # the file is deleted on successful upload + if [ -n "$addonid" ] && [ -n "$tag" ] && [ -n "$wowi_gen_changelog" ] && [ "$wowi_markup" = "bbcode" ]; then + wowi_changelog="$releasedir/WOWI-$project_version-CHANGELOG.txt" + cat <<- EOF | line_ending_filter > "$wowi_changelog" + [size=5]${project}[/size] + [size=4][color=orange]${project_version}[/color] (${changelog_date})[/size] + + [list] + EOF + hg --cwd "$topdir" log "$_changelog_range" --template '[*]{desc|strip|escape}\n' | line_ending_filter >> "$wowi_changelog" + echo "[/list]" | line_ending_filter >> "$wowi_changelog" + fi + fi + + echo "$(<"$pkgdir/$changelog")" + end_group "changelog" +fi + +### +### Process .pkgmeta to perform move-folders actions. +### + +if [ -f "$pkgmeta_file" ]; then + yaml_eof= + while [ -z "$yaml_eof" ]; do + IFS='' read -r yaml_line || yaml_eof="true" + # Skip commented out lines. + if [[ $yaml_line =~ ^[[:space:]]*\# ]]; then + continue + fi + # Strip any trailing CR character. + yaml_line=${yaml_line%$carriage_return} + + case $yaml_line in + [!\ ]*:*) + # Split $yaml_line into a $yaml_key, $yaml_value pair. + yaml_keyvalue "$yaml_line" + # Set the $pkgmeta_phase for stateful processing. + pkgmeta_phase=$yaml_key + ;; + " "*) + yaml_line=${yaml_line#"${yaml_line%%[! ]*}"} # trim leading whitespace + case $yaml_line in + "- "*) + ;; + *:*) + # Split $yaml_line into a $yaml_key, $yaml_value pair. + yaml_keyvalue "$yaml_line" + case $pkgmeta_phase in + move-folders) + srcdir="$releasedir/$yaml_key" + destdir="$releasedir/$yaml_value" + if [[ -d "$destdir" && -z "$overwrite" && "$srcdir" != "$destdir/"* ]]; then + rm -fr "$destdir" + fi + if [ -d "$srcdir" ]; then + if [ ! -d "$destdir" ]; then + mkdir -p "$destdir" + fi + echo "Moving $yaml_key to $yaml_value" + mv -f "$srcdir"/* "$destdir" && rm -fr "$srcdir" + contents="$contents $yaml_value" + # Check to see if the base source directory is empty + _mf_basedir=${srcdir%$(basename "$yaml_key")} + if [ ! "$( ls -A "$_mf_basedir" )" ]; then + echo "Removing empty directory ${_mf_basedir#$releasedir/}" + rm -fr "$_mf_basedir" + fi + fi + # update external dir + nolib_exclude=${nolib_exclude//$srcdir/$destdir} + ;; + esac + ;; + esac + ;; + esac + done < "$pkgmeta_file" + if [ -n "$srcdir" ]; then + echo + fi +fi + +### +### Create the final zipfile for the addon. +### + +if [ -z "$skip_zipfile" ]; then + archive_version="$project_version" + archive_name="$( filename_filter "$file_name" ).zip" + archive_label="$archive_version" + if [[ "${file_name}" == *"{game-type}"* ]] || [[ "$game_type" != "retail" && "${file_name}" == *"{classic}"* ]]; then + # append the game-type for clarity + archive_label="$archive_version-$game_type" + if [[ "$game_type" == "classic" && "${project_version,,}" == *"-classic"* ]] || [[ "$game_type" == "bcc" && "${project_version,,}" == *"-bcc"* ]]; then + # this is mostly for BigWigs projects that tag classic separately (eg, v10-classic) + # to prevent the extra -classic without changing all our workflows + archive_label="$archive_version" + fi + fi + archive="$releasedir/$archive_name" + + if [ -n "$GITHUB_ACTIONS" ]; then + echo "::set-output name=archive_path::${archive}" + fi + + nolib_archive_version="${project_version}-nolib" + nolib_archive_name="$( nolib=true filename_filter "$file_name" ).zip" + if [ "$archive_name" = "$nolib_archive_name" ]; then + # someone didn't include {nolib} and they're forcing nolib creation + nolib_archive_name="${nolib_archive_name#.zip}-nolib.zip" + fi + nolib_archive_label="${archive_label}-nolib" + nolib_archive="$releasedir/$nolib_archive_name" + + if [ -n "$nolib" ]; then + archive_version="$nolib_archive_version" + archive_name="$nolib_archive_name" + archive_label="$nolib_archive_label" + archive="$nolib_archive" + nolib_archive= + fi + + start_group "Creating archive: $archive_name" "archive" + if [ -f "$archive" ]; then + rm -f "$archive" + fi + #( cd "$releasedir" && zip -X -r "$archive" $contents ) + ( cd "$releasedir" && 7z a -bso0 -sns- -r "$archive" $contents ) + + if [ ! -f "$archive" ]; then + exit 1 + fi + end_group "archive" + + # Create nolib version of the zipfile + if [ -n "$enable_nolib_creation" ] && [ -z "$nolib" ] && [ -n "$nolib_exclude" ]; then + # run the nolib_filter + find "$pkgdir" -type f \( -name "*.xml" -o -name "*.toc" \) -print | while read -r file; do + case $file in + *.toc) _filter="toc_filter no-lib-strip true" ;; + *.xml) _filter="xml_filter no-lib-strip" ;; + esac + $_filter < "$file" > "$file.tmp" && mv "$file.tmp" "$file" + done + + # make the exclude paths relative to the release directory + nolib_exclude=${nolib_exclude//$releasedir\//} + + start_group "Creating no-lib archive: $nolib_archive_name" "archive.nolib" + if [ -f "$nolib_archive" ]; then + rm -f "$nolib_archive" + fi + # set noglob so each nolib_exclude path gets quoted instead of expanded + ( set -f; cd "$releasedir" && zip -X -r -q "$nolib_archive" $contents -x $nolib_exclude ) + + if [ ! -f "$nolib_archive" ]; then + exit_code=1 + fi + end_group "archive.nolib" + fi + + ### + ### Deploy the zipfile. + ### + + upload_curseforge=$( [[ -z "$skip_upload" && -z "$skip_cf_upload" && -n "$slug" && -n "$cf_token" && -n "$project_site" ]] && echo true ) + upload_wowinterface=$( [[ -z "$skip_upload" && -n "$tag" && -n "$addonid" && -n "$wowi_token" ]] && echo true ) + upload_wago=$( [[ -z "$skip_upload" && -n "$wagoid" && -n "$wago_token" ]] && echo true ) + upload_github=$( [[ -z "$skip_upload" && -n "$tag" && -n "$project_github_slug" && -n "$github_token" ]] && echo true ) + + if [[ -n "$upload_curseforge" || -n "$upload_wowinterface" || -n "$upload_github" || -n "$upload_wago" ]] && ! hash jq &>/dev/null; then + echo "Skipping upload because \"jq\" was not found." + echo + upload_curseforge= + upload_wowinterface= + upload_wago= + upload_github= + exit_code=1 + fi + + if [ -n "$upload_curseforge" ]; then + _cf_versions=$( curl -s -H "x-api-token: $cf_token" $project_site/api/game/versions ) + if [ -n "$_cf_versions" ]; then + _cf_game_version="$game_version" + if [ -n "$_cf_game_version" ]; then + _cf_game_version_id=$( echo "$_cf_versions" | jq -c --argjson v "[\"${game_version//,/\",\"}\"]" 'map(select(.name as $x | $v | index($x)) | .id) | select(length > 0)' 2>/dev/null ) + if [ -n "$_cf_game_version_id" ]; then + # and now the reverse, since an invalid version will just be dropped + _cf_game_version=$( echo "$_cf_versions" | jq -r --argjson v "$_cf_game_version_id" 'map(select(.id as $x | $v | index($x)) | .name) | join(",")' 2>/dev/null ) + fi + fi + if [ -z "$_cf_game_version_id" ]; then + case $game_type in + retail) _cf_game_type_id=517 ;; + classic) _cf_game_type_id=67408 ;; + bcc) _cf_game_type_id=73246 ;; + esac + _cf_game_version_id=$( echo "$_cf_versions" | jq -c --argjson v "$_cf_game_type_id" 'map(select(.gameVersionTypeID == $v)) | max_by(.id) | [.id]' 2>/dev/null ) + _cf_game_version=$( echo "$_cf_versions" | jq -r --argjson v "$_cf_game_type_id" 'map(select(.gameVersionTypeID == $v)) | max_by(.id) | .name' 2>/dev/null ) + fi + fi + if [ -z "$_cf_game_version_id" ]; then + echo "Error fetching game version info from $project_site/api/game/versions" + echo + echo "Skipping upload to CurseForge." + echo + upload_curseforge= + exit_code=1 + fi + fi + + # Upload to CurseForge. + if [ -n "$upload_curseforge" ]; then + _cf_payload=$( cat <<-EOF + { + "displayName": "$archive_label", + "gameVersions": $_cf_game_version_id, + "releaseType": "$file_type", + "changelog": $( jq --slurp --raw-input '.' < "$pkgdir/$changelog" ), + "changelogType": "$changelog_markup" + } + EOF + ) + _cf_payload_relations= + for i in "${!relations[@]}"; do + _cf_payload_relations="$_cf_payload_relations{\"slug\":\"$i\",\"type\":\"${relations[$i]}\"}," + done + if [[ -n $_cf_payload_relations ]]; then + _cf_payload_relations="{\"relations\":{\"projects\":[${_cf_payload_relations%,}]}}" + _cf_payload=$( echo "$_cf_payload $_cf_payload_relations" | jq -s -c '.[0] * .[1]' ) + fi + + echo "Uploading $archive_name ($_cf_game_version $file_type) to $project_site/projects/$slug" + resultfile="$releasedir/cf_result.json" + result=$( echo "$_cf_payload" | curl -sS --retry 3 --retry-delay 10 \ + -w "%{http_code}" -o "$resultfile" \ + -H "x-api-token: $cf_token" \ + -F "metadata=<-" \ + -F "file=@$archive" \ + "$project_site/api/projects/$slug/upload-file" + ) && { + case $result in + 200) echo "Success!" ;; + 302) + echo "Error! ($result)" + # don't need to ouput the redirect page + exit_code=1 + ;; + 404) + echo "Error! No project for \"$slug\" found." + exit_code=1 + ;; + *) + echo "Error! ($result)" + if [ -s "$resultfile" ]; then + echo "$(<"$resultfile")" + fi + exit_code=1 + ;; + esac + } || { + exit_code=1 + } + echo + + rm -f "$resultfile" 2>/dev/null + fi + + if [ -n "$upload_wowinterface" ]; then + _wowi_game_version= + _wowi_versions=$( curl -s -H "x-api-token: $wowi_token" https://api.wowinterface.com/addons/compatible.json ) + if [ -n "$_wowi_versions" ]; then + # Multiple versions, match on game version + if [[ "$game_version" == *","* ]]; then + _wowi_game_version=$( echo "$_wowi_versions" | jq -r --argjson v "[\"${game_version//,/\",\"}\"]" 'map(select(.id as $x | $v | index($x)) | .id) | join(",")' 2>/dev/null ) + fi + # TOC matching + if [ -z "$_wowi_game_version" ]; then + _wowi_game_version=$( echo "$_wowi_versions" | jq -r --arg toc "$toc_version" '.[] | select(.interface == $toc and .default == true) | .id' 2>/dev/null ) + fi + if [ -z "$_wowi_game_version" ]; then + _wowi_game_version=$( echo "$_wowi_versions" | jq -r --arg toc "$toc_version" 'map(select(.interface == $toc))[0] | .id // empty' 2>/dev/null ) + fi + # Handle delayed support (probably don't really need this anymore) + if [ -z "$_wowi_game_version" ] && [ "$game_type" != "retail" ]; then + _wowi_game_version=$( echo "$_wowi_versions" | jq -r --arg toc $((toc_version - 1)) '.[] | select(.interface == $toc) | .id' 2>/dev/null ) + fi + if [ -z "$_wowi_game_version" ]; then + _wowi_game_version=$( echo "$_wowi_versions" | jq -r '.[] | select(.default == true) | .id' 2>/dev/null ) + fi + fi + if [ -z "$_wowi_game_version" ]; then + echo "Error fetching game version info from https://api.wowinterface.com/addons/compatible.json" + echo + echo "Skipping upload to WoWInterface." + echo + upload_wowinterface= + exit_code=1 + fi + fi + + # Upload tags to WoWInterface. + if [ -n "$upload_wowinterface" ]; then + _wowi_args=() + if [ -f "$wowi_changelog" ]; then + _wowi_args+=("-F changelog=<$wowi_changelog") + elif [ -n "$manual_changelog" ] || [ "$wowi_markup" = "markdown" ]; then + _wowi_args+=("-F changelog=<$pkgdir/$changelog") + fi + if [ -z "$wowi_archive" ]; then + _wowi_args+=("-F archive=No") + fi + + echo "Uploading $archive_name ($_wowi_game_version) to https://www.wowinterface.com/downloads/info$addonid" + resultfile="$releasedir/wi_result.json" + result=$( curl -sS --retry 3 --retry-delay 10 \ + -w "%{http_code}" -o "$resultfile" \ + -H "x-api-token: $wowi_token" \ + -F "id=$addonid" \ + -F "version=$archive_version" \ + -F "compatible=$_wowi_game_version" \ + "${_wowi_args[@]}" \ + -F "updatefile=@$archive" \ + "https://api.wowinterface.com/addons/update" + ) && { + case $result in + 202) + echo "Success!" + if [ -f "$wowi_changelog" ]; then + rm -f "$wowi_changelog" 2>/dev/null + fi + ;; + 401) + echo "Error! No addon for id \"$addonid\" found or you do not have permission to upload files." + exit_code=1 + ;; + 403) + echo "Error! Incorrect api key or you do not have permission to upload files." + exit_code=1 + ;; + *) + echo "Error! ($result)" + if [ -s "$resultfile" ]; then + echo "$(<"$resultfile")" + fi + exit_code=1 + ;; + esac + } || { + exit_code=1 + } + echo + + rm -f "$resultfile" 2>/dev/null + fi + + # Upload to Wago + if [ -n "$upload_wago" ] ; then + _wago_support_property="" + for type in "${!game_versions[@]}"; do + if [[ "$type" == "bcc" ]]; then + _wago_support_property+="\"supported_bc_patch\": \"${game_versions[$type]}\", " + else + _wago_support_property+="\"supported_${type}_patch\": \"${game_versions[$type]}\", " + fi + done + + _wago_stability="$file_type" + if [ "$file_type" = "release" ]; then + _wago_stability="stable" + fi + + _wago_payload=$( cat <<-EOF + { + "label": "$archive_label", + $_wago_support_property + "stability": "$_wago_stability", + "changelog": $( jq --slurp --raw-input '.' < "$pkgdir/$changelog" ) + } + EOF + ) + + echo "Uploading $archive_name ($game_version $file_type) to Wago" + resultfile="$releasedir/wago_result.json" + result=$( echo "$_wago_payload" | curl -sS --retry 3 --retry-delay 10 \ + -w "%{http_code}" -o "$resultfile" \ + -H "authorization: Bearer $wago_token" \ + -H "accept: application/json" \ + -F "metadata=<-" \ + -F "file=@$archive" \ + "https://addons.wago.io/api/projects/$wagoid/version" + ) && { + case $result in + 200|201) echo "Success!" ;; + 302) + echo "Error! ($result)" + # don't need to ouput the redirect page + exit_code=1 + ;; + 404) + echo "Error! No Wago project for id \"$wagoid\" found." + exit_code=1 + ;; + *) + echo "Error! ($result)" + if [ -s "$resultfile" ]; then + echo "$(<"$resultfile")" + fi + exit_code=1 + ;; + esac + } || { + exit_code=1 + } + echo + + rm -f "$resultfile" 2>/dev/null + fi + + # Create a GitHub Release for tags and upload the zipfile as an asset. + if [ -n "$upload_github" ]; then + upload_github_asset() { + _ghf_release_id=$1 + _ghf_file_name=$2 + _ghf_file_path=$3 + _ghf_resultfile="$releasedir/gh_asset_result.json" + _ghf_content_type="application/${_ghf_file_name##*.}" # zip or json + + # check if an asset exists and delete it (editing a release) + asset_id=$( curl -sS \ + -H "Accept: application/vnd.github.v3+json" \ + -H "Authorization: token $github_token" \ + "https://api.github.com/repos/$project_github_slug/releases/$_ghf_release_id/assets" \ + | jq --arg file "$_ghf_file_name" '.[] | select(.name? == $file) | .id' + ) + if [ -n "$asset_id" ]; then + curl -s \ + -X DELETE \ + -H "Accept: application/vnd.github.v3+json" \ + -H "Authorization: token $github_token" \ + "https://api.github.com/repos/$project_github_slug/releases/assets/$asset_id" &>/dev/null + fi + + echo -n "Uploading $_ghf_file_name... " + result=$( curl -sS --retry 3 --retry-delay 10 \ + -w "%{http_code}" -o "$_ghf_resultfile" \ + -H "Accept: application/vnd.github.v3+json" \ + -H "Authorization: token $github_token" \ + -H "Content-Type: $_ghf_content_type" \ + --data-binary "@$_ghf_file_path" \ + "https://uploads.github.com/repos/$project_github_slug/releases/$_ghf_release_id/assets?name=$_ghf_file_name" + ) && { + if [ "$result" = "201" ]; then + echo "Success!" + else + echo "Error ($result)" + if [ -s "$_ghf_resultfile" ]; then + echo "$(<"$_ghf_resultfile")" + fi + exit_code=1 + fi + } || { + exit_code=1 + } + + rm -f "$_ghf_resultfile" 2>/dev/null + return 0 + } + + _gh_metadata='{ "filename": "'"$archive_name"'", "nolib": false, "metadata": [' + for type in "${!game_versions[@]}"; do + _gh_metadata+='{ "flavor": "'"${game_flavors[$type]}"'", "interface": '"$toc_version"' },' + done + _gh_metadata=${_gh_metadata%,} + _gh_metadata+='] }' + if [ -f "$nolib_archive" ]; then + _gh_metadata+=',{ "filename": "'"$nolib_archive_name"'", "nolib": true, "metadata": [' + for type in "${!game_versions[@]}"; do + _gh_metadata+='{ "flavor": "'"${game_flavors[$type]}"'", "interface": '"$toc_version"' },' + done + _gh_metadata=${_gh_metadata%,} + _gh_metadata+='] }' + fi + _gh_metadata='{ "releases": ['"$_gh_metadata"'] }' + + versionfile="$releasedir/release.json" + jq -c '.' <<< "$_gh_metadata" > "$versionfile" || echo "There was an error creating release.json" >&2 + + _gh_payload=$( cat <<-EOF + { + "tag_name": "$tag", + "name": "$tag", + "body": $( jq --slurp --raw-input '.' < "$pkgdir/$changelog" ), + "draft": false, + "prerelease": $( [[ "$file_type" != "release" ]] && echo true || echo false ) + } + EOF + ) + resultfile="$releasedir/gh_result.json" + + release_id=$( curl -sS \ + -H "Accept: application/vnd.github.v3+json" \ + -H "Authorization: token $github_token" \ + "https://api.github.com/repos/$project_github_slug/releases/tags/$tag" \ + | jq '.id // empty' + ) + if [ -n "$release_id" ]; then + echo "Updating GitHub release: https://github.com/$project_github_slug/releases/tag/$tag" + _gh_release_url="-X PATCH https://api.github.com/repos/$project_github_slug/releases/$release_id" + + # combine version info + _gh_metadata_url=$( curl -sS \ + -H "Accept: application/vnd.github.v3+json" \ + -H "Authorization: token $github_token" \ + "https://api.github.com/repos/$project_github_slug/releases/$release_id/assets" \ + | jq -r '.[] | select(.name? == "release.json") | .url // empty' + ) + if [ -n "$_gh_metadata_url" ]; then + _gh_previous_metadata=$( curl -sSL --fail \ + -H "Accept: application/octet-stream" \ + -H "Authorization: token $github_token" \ + "$_gh_metadata_url" + ) && { + jq -sc '.[0].releases + .[1].releases | unique_by(.filename) | { releases: [.[]] }' <<< "${_gh_previous_metadata} ${_gh_metadata}" > "$versionfile" + } || { + echo "Warning: Unable to update release.json ($?)" + } + fi + else + echo "Creating GitHub release: https://github.com/$project_github_slug/releases/tag/$tag" + _gh_release_url="https://api.github.com/repos/$project_github_slug/releases" + fi + result=$( echo "$_gh_payload" | curl -sS --retry 3 --retry-delay 10 \ + -w "%{http_code}" -o "$resultfile" \ + -H "Accept: application/vnd.github.v3+json" \ + -H "Authorization: token $github_token" \ + -d @- \ + $_gh_release_url + ) && { + if [ "$result" = "200" ] || [ "$result" = "201" ]; then # edited || created + if [ -z "$release_id" ]; then + release_id=$( jq '.id' < "$resultfile" ) + fi + upload_github_asset "$release_id" "$archive_name" "$archive" + if [ -f "$nolib_archive" ]; then + upload_github_asset "$release_id" "$nolib_archive_name" "$nolib_archive" + fi + if [ -s "$versionfile" ]; then + upload_github_asset "$release_id" "release.json" "$versionfile" + fi + else + echo "Error! ($result)" + if [ -s "$resultfile" ]; then + echo "$(<"$resultfile")" + fi + exit_code=1 + fi + } || { + exit_code=1 + } + + rm -f "$resultfile" 2>/dev/null + [ -z "$CI" ] && rm -f "$versionfile" 2>/dev/null + echo + fi +fi + +# All done. + +echo +echo "Packaging complete." +echo + +exit $exit_code diff --git a/.retail.toc b/.retail.toc deleted file mode 100644 index f406399..0000000 --- a/.retail.toc +++ /dev/null @@ -1,3 +0,0 @@ -## Interface: 90005 - -## X-Build: Retail \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md index 3be984c..1a1b02c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,10 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## Version 1.1.2 - 2021-05-23 +### Changed +- adopted to BigWigs release script to better support different game packages + ## Version 1.1.1 - 2021-04-22 ### Changed - bumped versions for WoW Classic and Shadowlands diff --git a/Grichelde.lua b/Grichelde.lua index 62bfe76..356eba3 100644 --- a/Grichelde.lua +++ b/Grichelde.lua @@ -21,8 +21,9 @@ local _G = _G -- initialize addon local Grichelde = LibStub("AceAddon-3.0"):NewAddon(AddonTable, AddonName, "AceConsole-3.0", "AceEvent-3.0", "AceHook-3.0") Grichelde.version = GetAddOnMetadata(AddonName, "Version") -Grichelde.build = GetAddOnMetadata(AddonName, "X-Build") or "Experimental" -Grichelde.classic = _G.WOW_PROJECT_ID == _G.WOW_PROJECT_CLASSIC +Grichelde.build = GetAddOnMetadata(AddonName, "X-Build") or "Development" +--Grichelde.era = _G.WOW_PROJECT_ID == _G.WOW_PROJECT_CLASSIC +--Grichelde.bcc = _G.WOW_PROJECT_ID == _G.WOW_PROJECT_BURNING_CRUSADE_CLASSIC Grichelde.logLevel = 0 -- cannot reference Grichelde.LOG_LEVELs here as they are loaded afterwards -- publish to global env diff --git a/Grichelde.toc b/Grichelde.toc index 8be0334..00b4cfd 100644 --- a/Grichelde.toc +++ b/Grichelde.toc @@ -1,13 +1,38 @@ ## Interface: 11307 +## Interface-Classic: 11307 +## Interface-BCC: 20501 +## Interface-Retail: 90005 ## Title: Grichelde ## Notes: Replaces characters of your chat input line before sending. ## Notes-de: Ersetzt eingegebene Zeichen in der Chat-Zeile vor dem Versenden. -## Version: 1.1.1 +#@debug@ +## Version: 1.1.2 +#@end-debug@ +#@non-debug@ +# ## Version: @project-version@ +#@end-non-debug@ ## Author: Teilzeit-Jedi ## eMail: tj@teilzeit-jedi.de +#@version-classic@ ## X-Build: Classic +## X-Compatible: 20501 +## X-Compatible: 90005 +#@end-version-classic@ +#@non-version-classic@ +#@version-bcc@ +# ## X-Build: BCC +# ## X-Compatible: 11307 +# ## X-Compatible: 90005 +#@end-version-bcc@ +#@version-retail@ +# ## X-Build: Retail +# ## X-Compatible: 11307 +# ## X-Compatible: 20501 +#@end-version-retail@ +#@end-non-version-classic@ + ## X-Curse-Project-ID: 385480 ## X-License: GPLv3 ## X-Category: Chat/Communication @@ -17,7 +42,21 @@ ## OptionalDeps: LibStub, CallbackHandler, Ace3, LibDataBroker, LibDBIcon ## SavedVariables: GricheldeDB -libs.xml +#@no-lib-strip@ +Libs\LibStub\LibStub.lua +Libs\CallbackHandler-1.0\CallbackHandler-1.0.xml +Libs\AceAddon-3.0\AceAddon-3.0.xml +Libs\AceLocale-3.0\AceLocale-3.0.xml +Libs\AceEvent-3.0\AceEvent-3.0.xml +Libs\AceHook-3.0\AceHook-3.0.xml +Libs\AceConsole-3.0\AceConsole-3.0.xml +Libs\AceDB-3.0\AceDB-3.0.xml +Libs\AceDBOptions-3.0\AceDBOptions-3.0.xml +Libs\AceGUI-3.0\AceGUI-3.0.xml +Libs\AceConfig-3.0\AceConfig-3.0.xml +Libs\LibDataBroker-1.1\LibDataBroker-1.1.lua +Libs\LibDBIcon-1.0\LibDBIcon-1.0.lua +#@end-no-lib-strip@ Grichelde.lua GricheldeConstants.lua @@ -31,4 +70,6 @@ GricheldeOptions.lua GricheldeMinimap.lua GricheldeChat.lua -GricheldeTest.lua \ No newline at end of file +#@do-not-package@ +GricheldeTest.lua +#@end-do-not-package@ \ No newline at end of file diff --git a/GricheldeUtils.lua b/GricheldeUtils.lua index 472a987..e9ee072 100644 --- a/GricheldeUtils.lua +++ b/GricheldeUtils.lua @@ -84,12 +84,6 @@ function Grichelde:SplitOnLastMatch(text, delimPattern, start) return left, right or text end ---- Splits at last word of a text line -function Grichelde:TestMatch(text, pattern) - local pos1, pos2, left, right = find(text, pattern, 1) - self:DebugPrint("TestMatch : pos1: %d, pos2: %d, left: %s, right: %s", pos1, pos2, left, right) -end - function Grichelde:Format(message, ...) if (message == nil) then return "" diff --git a/libs.xml b/libs.xml deleted file mode 100644 index dc0c907..0000000 --- a/libs.xml +++ /dev/null @@ -1,17 +0,0 @@ - - -