diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 82ba718..2157170 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -3,6 +3,10 @@ on: push: branches: - master + # Skip when the release workflow already handles branch splitting + paths-ignore: + - 'CHANGELOG.md' + - '.github/workflows/release.yml' jobs: split-upm: name: split upm branch diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..29faf7d --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,331 @@ +name: Release + +on: + push: + branches: + - master + workflow_dispatch: + inputs: + version_bump: + description: 'Version bump type' + required: true + default: 'patch' + type: choice + options: + - patch + - minor + - major + release_notes: + description: 'Additional release notes (optional)' + required: false + type: string + +permissions: + contents: write + +jobs: + release: + name: Create Release + runs-on: ubuntu-latest + outputs: + new_version: ${{ steps.version.outputs.new_version }} + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + token: ${{ secrets.GITHUB_TOKEN }} + + - name: Get current version + id: current + run: | + CURRENT=$(grep -oP '"version":\s*"\K[^"]+' src/Proyecto26.RestClient/package.json) + echo "version=$CURRENT" >> $GITHUB_OUTPUT + echo "Current version: $CURRENT" + + - name: Determine version bump from commits + id: bump + run: | + if [ "${{ github.event_name }}" = "workflow_dispatch" ]; then + echo "type=${{ inputs.version_bump }}" >> $GITHUB_OUTPUT + echo "Manual bump: ${{ inputs.version_bump }}" + else + LAST_TAG=$(git describe --tags --abbrev=0 2>/dev/null || echo "") + if [ -z "$LAST_TAG" ]; then + RANGE="HEAD" + else + RANGE="${LAST_TAG}..HEAD" + fi + COMMITS=$(git log $RANGE --pretty=format:"%s" 2>/dev/null || echo "") + if echo "$COMMITS" | grep -qiE "^(feat|feature)(\(.+\))?!:|BREAKING CHANGE"; then + echo "type=major" >> $GITHUB_OUTPUT + elif echo "$COMMITS" | grep -qiE "^(feat|feature)(\(.+\))?:"; then + echo "type=minor" >> $GITHUB_OUTPUT + else + echo "type=patch" >> $GITHUB_OUTPUT + fi + fi + + - name: Calculate new version + id: version + run: | + IFS='.' read -r MAJOR MINOR PATCH <<< "${{ steps.current.outputs.version }}" + case "${{ steps.bump.outputs.type }}" in + major) MAJOR=$((MAJOR + 1)); MINOR=0; PATCH=0 ;; + minor) MINOR=$((MINOR + 1)); PATCH=0 ;; + patch) PATCH=$((PATCH + 1)) ;; + esac + NEW_VERSION="${MAJOR}.${MINOR}.${PATCH}" + echo "new_version=$NEW_VERSION" >> $GITHUB_OUTPUT + echo "New version: $NEW_VERSION" + + - name: Generate changelog entry + id: changelog + run: | + LAST_TAG=$(git describe --tags --abbrev=0 2>/dev/null || echo "") + if [ -z "$LAST_TAG" ]; then RANGE="HEAD"; else RANGE="${LAST_TAG}..HEAD"; fi + + TODAY=$(date +%Y-%m-%d) + NEW_VERSION="${{ steps.version.outputs.new_version }}" + ADDED="" CHANGED="" FIXED="" REMOVED="" + + while IFS= read -r line; do + HASH=$(echo "$line" | cut -d'|' -f1) + MSG=$(echo "$line" | cut -d'|' -f2-) + SHORT_HASH=$(echo "$HASH" | cut -c1-7) + PR_NUM=$(echo "$MSG" | grep -oP '\(#\K[0-9]+(?=\))' | head -1) + + if [ -n "$PR_NUM" ]; then + ATTR="([#${PR_NUM}](https://github.com/proyecto26/RestClient/pull/${PR_NUM}))" + else + ATTR="([${SHORT_HASH}](https://github.com/proyecto26/RestClient/commit/${HASH}))" + fi + + CLEAN_MSG=$(echo "$MSG" | sed -E 's/^(feat|fix|perf|refactor|chore|docs|style|test|build|ci)(\([^)]*\))?(!)?:\s*//') + CLEAN_MSG=$(echo "$CLEAN_MSG" | sed -E 's/\s*\(#[0-9]+\)\s*$//') + + if echo "$MSG" | grep -qiE "^(feat|feature)(\(.+\))?:"; then + ADDED="${ADDED}- ${CLEAN_MSG} ${ATTR}.\n" + elif echo "$MSG" | grep -qiE "^fix(\(.+\))?:"; then + FIXED="${FIXED}- ${CLEAN_MSG} ${ATTR}.\n" + elif echo "$MSG" | grep -qiE "^(perf|refactor|chore|docs|style|build|ci)(\(.+\))?:"; then + CHANGED="${CHANGED}- ${CLEAN_MSG} ${ATTR}.\n" + elif echo "$MSG" | grep -qiE "^(revert)(\(.+\))?:"; then + REMOVED="${REMOVED}- ${CLEAN_MSG} ${ATTR}.\n" + else + CHANGED="${CHANGED}- ${CLEAN_MSG} ${ATTR}.\n" + fi + done < <(git log $RANGE --pretty=format:"%H|%s" --no-merges 2>/dev/null) + + if [ -n "${{ inputs.release_notes }}" ]; then + ADDED="${ADDED}- ${{ inputs.release_notes }}\n" + fi + + BODY="" + [ -n "$ADDED" ] && BODY="${BODY}### Added\n${ADDED}\n" + [ -n "$CHANGED" ] && BODY="${BODY}### Changed\n${CHANGED}\n" + [ -n "$FIXED" ] && BODY="${BODY}### Fixed\n${FIXED}\n" + [ -n "$REMOVED" ] && BODY="${BODY}### Removed\n${REMOVED}\n" + [ -z "$BODY" ] && BODY="### Changed\n- Release version ${NEW_VERSION}.\n" + + echo -e "$BODY" > /tmp/release_body.md + + # Update CHANGELOG.md + PREV_VERSION="${{ steps.current.outputs.version }}" + HEADER="## [${NEW_VERSION}] - ${TODAY}" + LINK="[${NEW_VERSION}]: https://github.com/proyecto26/RestClient/compare/v${PREV_VERSION}...v${NEW_VERSION}" + sed -i "/^## \[Unreleased\]/a\\\\n${HEADER}\\n" CHANGELOG.md + sed -i "/^## \[${NEW_VERSION}\]/r /tmp/release_body.md" CHANGELOG.md + sed -i "s|\[Unreleased\]: .*|[Unreleased]: https://github.com/proyecto26/RestClient/compare/v${NEW_VERSION}...HEAD|" CHANGELOG.md + sed -i "/^\[Unreleased\]:/a ${LINK}" CHANGELOG.md + + - name: Update version in all files + run: | + VERSION="${{ steps.version.outputs.new_version }}" + sed -i 's/"version": "[^"]*"/"version": "'"$VERSION"'"/' src/Proyecto26.RestClient/package.json + sed -i 's/[^<]*'"$VERSION"'[^<]*'"$VERSION"' "$STAGING/$GUID/pathname" + } + + add_folder_meta() { + local meta="$1" unity_path="$2" + GUID=$(grep 'guid:' "$meta" | awk '{print $2}') + [ -z "$GUID" ] && return + mkdir -p "$STAGING/$GUID" + cp "$meta" "$STAGING/$GUID/asset.meta" + echo "$unity_path" > "$STAGING/$GUID/pathname" + } + + # Add source files (.cs, .asmdef, package.json) — exclude build system files + for f in $(find "$PKG_SRC" -type f \( -name "*.cs" -o -name "*.asmdef" -o -name "package.json" \) \ + ! -path "*/Properties/*" ! -path "*/bin/*" ! -path "*/obj/*"); do + META="${f}.meta" + if [ -f "$META" ]; then + REL_PATH="${f#$PKG_SRC/}" + add_asset "$f" "$META" "$UNITY_ROOT/$REL_PATH" + fi + done + + # Add folder .meta files (e.g., Helpers.meta) + if [ -f "$PKG_SRC/Helpers.meta" ]; then + add_folder_meta "$PKG_SRC/Helpers.meta" "$UNITY_ROOT/Helpers" + fi + + # Create the .unitypackage + cd "$STAGING" + ASSET_COUNT=$(ls -d */ 2>/dev/null | wc -l) + tar -czf "/tmp/Proyecto26.RestClient-${VERSION}.unitypackage" */ + echo "Built .unitypackage with ${ASSET_COUNT} assets" + + - name: Create GitHub Release + uses: softprops/action-gh-release@v2 + with: + tag_name: v${{ steps.version.outputs.new_version }} + name: Release ${{ steps.version.outputs.new_version }} + body_path: /tmp/release_body.md + draft: false + prerelease: false + files: | + /tmp/Proyecto26.RestClient-${{ steps.version.outputs.new_version }}.unitypackage + + publish-nuget: + name: Publish to NuGet + needs: release + runs-on: ubuntu-latest + if: needs.release.outputs.new_version != '' + steps: + - uses: actions/checkout@v4 + with: + ref: master + + - name: Pull latest (includes version bump commit) + run: git pull origin master + + # Use nuget CLI with .nuspec file (old-style .csproj is not compatible with dotnet pack) + - name: Install NuGet CLI + run: | + sudo apt-get update && sudo apt-get install -y nuget + + - name: Build NuGet package + run: | + cd src/Proyecto26.RestClient + nuget pack Proyecto26.RestClient.nuspec -Version ${{ needs.release.outputs.new_version }} -OutputDirectory ../../artifacts + + - name: Publish to NuGet + run: | + nuget push artifacts/*.nupkg \ + -ApiKey ${{ secrets.NUGET_API_KEY }} \ + -Source https://api.nuget.org/v3/index.json \ + -SkipDuplicate + env: + NUGET_API_KEY: ${{ secrets.NUGET_API_KEY }} + + split-branches: + name: Split UPM & NuGet branches + needs: release + runs-on: ubuntu-latest + if: needs.release.outputs.new_version != '' + steps: + - uses: actions/checkout@v4 + with: + ref: master + fetch-depth: 0 + + - name: Pull latest + run: git pull origin master + + - name: Split UPM branch + run: | + git branch -d upm &> /dev/null || echo "upm branch not found" + git mv "$PKG_ROOT" "src/$UPM_ROOT" + git mv "src/$UPM_ROOT" . + git mv demo/Assets demo/Samples~ + git mv demo/Samples~ $UPM_ROOT + git mv doc Docs + git mv Docs $UPM_ROOT + git mv LICENSE $UPM_ROOT + git mv README.md $UPM_ROOT + git mv SECURITY.md $UPM_ROOT + git mv CHANGELOG.md $UPM_ROOT + git mv CONTRIBUTING.md $UPM_ROOT + git config --global user.name 'github-actions[bot]' + git config --global user.email 'github-actions[bot]@users.noreply.github.com' + git add "$UPM_ROOT" + git commit -m "create release folder" + git subtree split -P "$UPM_ROOT" -b upm + git checkout upm + # Remove non-Unity files that cause missing .meta warnings (fixes #219) + if [[ -d "Samples~/Packages" ]]; then + git rm -rf Samples~/Packages + git rm Samples~/Packages.meta --ignore-unmatch + git rm Samples~/packages.config + git rm Samples~/packages.config.meta --ignore-unmatch + fi + if [[ -d "Properties" ]]; then + git rm -rf Properties + git rm Properties.meta --ignore-unmatch + git rm Proyecto26.RestClient.csproj + git rm Proyecto26.RestClient.csproj.meta --ignore-unmatch + git rm Proyecto26.RestClient.nuspec + git rm Proyecto26.RestClient.nuspec.meta --ignore-unmatch + git rm packages.config + git rm packages.config.meta --ignore-unmatch + fi + git commit -am "fix: clean UPM branch (remove non-Unity files)" + git push -u origin upm --force + # Tag UPM branch for OpenUPM version detection + git tag "upm/${{ needs.release.outputs.new_version }}" + git push origin "upm/${{ needs.release.outputs.new_version }}" + env: + PKG_ROOT: src/Proyecto26.RestClient + UPM_ROOT: Release + + - name: Split NuGet branch + run: | + git checkout master + git pull origin master + git branch -d nuget &> /dev/null || echo "nuget branch not found" + git mv LICENSE $NUGET_ROOT + git mv README.md $NUGET_ROOT + git mv SECURITY.md $NUGET_ROOT + git mv CHANGELOG.md $NUGET_ROOT + git mv CONTRIBUTING.md $NUGET_ROOT + git config --global user.name 'github-actions[bot]' + git config --global user.email 'github-actions[bot]@users.noreply.github.com' + git subtree split -P "$NUGET_ROOT" -b nuget + git checkout nuget + git commit -am "fix: src => root" --allow-empty + git push -u origin nuget --force + env: + NUGET_ROOT: src diff --git a/CHANGELOG.md b/CHANGELOG.md index 68d69e6..57fd1f3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,14 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +## [2.6.3] - 2025-11-22 + +### Fixed +- Fixed **ArgumentNullException** when calling `Abort()` or accessing properties on completed requests ([#103](https://github.com/proyecto26/RestClient/issues/103)). +- Enhanced disposal detection for UnityWebRequest objects to prevent crashes during cleanup scenarios. +- Added proper exception handling for all RequestHelper properties that access disposed UnityWebRequest instances. +- Improved thread safety for request cancellation in Unity's OnDestroy patterns. + ## [2.6.2] - 2021-12-26 ### Added diff --git a/README.md b/README.md index 17f1a48..361f6ee 100644 --- a/README.md +++ b/README.md @@ -338,20 +338,18 @@ This project exists thanks to all the people who contribute. [[Contribute](CONTR ### Collaborators -[jdnichollsc](https://github.com/jdnichollsc) | [diegoossa](https://github.com/diegoossa) | [nasdull](https://github.com/nasdull) | -:---: | :---: | :---: | -[Juan Nicholls](mailto:jdnichollsc@hotmail.com) | [Diego Ossa](mailto:diegoossa@gmail.com) | [Nasdull](mailto:nasdull@hotmail.com) | +[jdnichollsc](https://github.com/jdnichollsc) | [diegoossa](https://github.com/diegoossa) | [nasdull](https://github.com/nasdull) | [nasdull](https://github.com/maifeeulasad) | +:---: | :---: | :---: | :---: | +[Juan Nicholls](mailto:jdnichollsc@hotmail.com) | [Diego Ossa](mailto:diegoossa@gmail.com) | [Nasdull](mailto:nasdull@hotmail.com) | [Maifee Ul Asad](mailto:maifeeulasad@gmail.com) | ## Supporting 🍻 I believe in Unicorns 🦄 Support [me](http://www.paypal.me/jdnichollsc/2), if you do too. -Donate **Ethereum**, **ADA**, **BNB**, **SHIBA**, **USDT**, **DOGE**: +Donate **Ethereum**, **ADA**, **BNB**, **SHIBA**, **USDT/USDC**, **DOGE**, etc: -![Wallet address](https://user-images.githubusercontent.com/2154886/123501719-84bf1900-d60c-11eb-882c-98a499cea323.png) - -> Wallet address: 0x3F9fA8021B43ACe578C2352861Cf335449F33427 +> Wallet address: jdnichollsc.eth Please let us know your contributions! 🙏 @@ -364,6 +362,12 @@ The maintainers of RestClient for Unity and thousands of other packages are work ## Security contact information 🚨 To report a security vulnerability, please use the [Tidelift security contact](https://tidelift.com/security). Tidelift will coordinate the fix and disclosure. +## Who is using RestClient ⚔️ +We are battle tested. There is a list of our usage in production: + - mainware. Ref: https://github.com/proyecto26/RestClient/issues/138#issuecomment-725552712 + - virsabi. Ref: https://github.com/proyecto26/RestClient/issues/138#issuecomment-863222899 + - type3studio. Ref: https://github.com/proyecto26/RestClient/issues/138#issuecomment-1415945883 + ## License ⚖️ This repository is available under the [MIT License](https://github.com/proyecto26/RestClient/blob/develop/LICENSE). diff --git a/src/.gitignore b/src/.gitignore index 1c2b85e..46d7d72 100644 --- a/src/.gitignore +++ b/src/.gitignore @@ -278,6 +278,13 @@ paket-files/ __pycache__/ *.pyc +# Unity stub DLLs (should be referenced from Unity installation, not committed) +dlls/ + +# Build artifacts (generated during CI/CD) +artifacts/ +*.unitypackage + # Cake - Uncomment if you are using it # tools/** # !tools/packages.config diff --git a/src/Proyecto26.RestClient/Helpers/RequestHelperExtension.cs b/src/Proyecto26.RestClient/Helpers/RequestHelperExtension.cs index 7017a74..fadc6c3 100644 --- a/src/Proyecto26.RestClient/Helpers/RequestHelperExtension.cs +++ b/src/Proyecto26.RestClient/Helpers/RequestHelperExtension.cs @@ -20,7 +20,15 @@ public float UploadProgress float progress = 0; if (this.Request != null) { - progress = this.Request.uploadProgress; + try + { + progress = this.Request.uploadProgress; + } + catch (ArgumentNullException) + { + // Request was disposed - return 0 progress + progress = 0; + } } return progress; } @@ -36,7 +44,15 @@ public ulong UploadedBytes ulong bytes = 0; if (this.Request != null) { - bytes = this.Request.uploadedBytes; + try + { + bytes = this.Request.uploadedBytes; + } + catch (ArgumentNullException) + { + // Request was disposed - return 0 bytes + bytes = 0; + } } return bytes; } @@ -52,7 +68,15 @@ public float DownloadProgress float progress = 0; if (this.Request != null) { - progress = this.Request.downloadProgress; + try + { + progress = this.Request.downloadProgress; + } + catch (ArgumentNullException) + { + // Request was disposed - return 0 progress + progress = 0; + } } return progress; } @@ -68,7 +92,15 @@ public ulong DownloadedBytes ulong bytes = 0; if (this.Request != null) { - bytes = this.Request.downloadedBytes; + try + { + bytes = this.Request.downloadedBytes; + } + catch (ArgumentNullException) + { + // Request was disposed - return 0 bytes + bytes = 0; + } } return bytes; } @@ -81,10 +113,18 @@ public ulong DownloadedBytes /// The name of the header. public string GetHeader(string name) { - string headerValue; + string headerValue = null; if (this.Request != null) { - headerValue = this.Request.GetRequestHeader(name); + try + { + headerValue = this.Request.GetRequestHeader(name); + } + catch (ArgumentNullException) + { + // Request was disposed - fall back to headers dictionary + this.Headers.TryGetValue(name, out headerValue); + } } else { @@ -131,6 +171,12 @@ public void Abort() this.Request.Abort(); } } + catch (ArgumentNullException) + { + // Request was already disposed by UnityWebRequest's using block - this is expected behavior + // No need to log this as it's a normal part of the request lifecycle + HttpBase.DebugLog(this.EnableDebug, "Request was already disposed; abort skipped.", true); + } catch (Exception error) { HttpBase.DebugLog(this.EnableDebug, error.Message, true); } diff --git a/src/Proyecto26.RestClient/Properties/AssemblyInfo.cs b/src/Proyecto26.RestClient/Properties/AssemblyInfo.cs index 5f14f0a..72a986b 100644 --- a/src/Proyecto26.RestClient/Properties/AssemblyInfo.cs +++ b/src/Proyecto26.RestClient/Properties/AssemblyInfo.cs @@ -17,7 +17,7 @@ // The form "{Major}.{Minor}.*" will automatically update the build and revision, // and "{Major}.{Minor}.{Build}.*" will update just the revision. -[assembly: AssemblyVersion ("2.6.2")] +[assembly: AssemblyVersion ("2.6.3")] // The following attributes are used to specify the signing key for the assembly, // if desired. See the Mono documentation for more information about signing. diff --git a/src/Proyecto26.RestClient/Proyecto26.RestClient.csproj b/src/Proyecto26.RestClient/Proyecto26.RestClient.csproj index 9c255c1..edecf9f 100644 --- a/src/Proyecto26.RestClient/Proyecto26.RestClient.csproj +++ b/src/Proyecto26.RestClient/Proyecto26.RestClient.csproj @@ -9,7 +9,7 @@ Proyecto26.RestClient v3.5 Proyecto26.RestClient - 2.6.2 + 2.6.3 jdnichollsc https://github.com/proyecto26/RestClient/blob/master/img/icono.png?raw=true https://github.com/proyecto26/RestClient/blob/master/LICENSE diff --git a/src/Proyecto26.RestClient/Proyecto26.RestClient.nuspec b/src/Proyecto26.RestClient/Proyecto26.RestClient.nuspec index 0a35221..4e9a26f 100644 --- a/src/Proyecto26.RestClient/Proyecto26.RestClient.nuspec +++ b/src/Proyecto26.RestClient/Proyecto26.RestClient.nuspec @@ -2,7 +2,7 @@ Proyecto26.RestClient - 2.6.2 + 2.6.3 RestClient for Unity Juan David Nicholls Cardona jdnichollsc diff --git a/src/Proyecto26.RestClient/RestClient.cs b/src/Proyecto26.RestClient/RestClient.cs index 82b0da5..2fbdd04 100644 --- a/src/Proyecto26.RestClient/RestClient.cs +++ b/src/Proyecto26.RestClient/RestClient.cs @@ -23,7 +23,7 @@ public static System.Version Version get { if (_version == null) { - _version = new System.Version("2.6.2"); + _version = new System.Version("2.6.3"); } return _version; } diff --git a/src/Proyecto26.RestClient/RestClientPromise.cs b/src/Proyecto26.RestClient/RestClientPromise.cs index b0e92f2..84ff3ae 100644 --- a/src/Proyecto26.RestClient/RestClientPromise.cs +++ b/src/Proyecto26.RestClient/RestClientPromise.cs @@ -381,7 +381,7 @@ public static IPromise Delete(RequestHelper options) /// A string containing the URL to which the request is sent. public static IPromise Head(string url) { - return Delete(new RequestHelper { Uri = url }); + return Head(new RequestHelper { Uri = url }); } /// diff --git a/src/Proyecto26.RestClient/package.json b/src/Proyecto26.RestClient/package.json index 29f6577..e71dc6f 100644 --- a/src/Proyecto26.RestClient/package.json +++ b/src/Proyecto26.RestClient/package.json @@ -1,6 +1,6 @@ { "name": "com.proyecto26.restclient", - "version": "2.6.2", + "version": "2.6.3", "displayName": "RestClient for Unity", "description": "Simple HTTP and REST client for Unity based on Promises, also support Callbacks!", "unity": "2017.1",