mirror of
https://github.com/SideStore/SideStore.git
synced 2026-02-12 16:23:32 +01:00
Compare commits
19 Commits
ny/fix/703
...
alpha
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
dc29b65bd5 | ||
|
|
1e64f50ab9 | ||
|
|
c8127fb3b9 | ||
|
|
729fca9100 | ||
|
|
c6703d66c1 | ||
|
|
2197161d55 | ||
|
|
cfaf79f878 | ||
|
|
2bea980d1f | ||
|
|
f11e27c712 | ||
|
|
b316e84f0d | ||
|
|
4668f8499b | ||
|
|
f9aedaba04 | ||
|
|
8cb3de9ab5 | ||
|
|
ca57d58219 | ||
|
|
6a56fbd206 | ||
|
|
cec3825de0 | ||
|
|
b3e99d1ae3 | ||
|
|
7243d79646 | ||
|
|
e50da6603c |
@@ -45,7 +45,7 @@ on:
|
|||||||
required: false
|
required: false
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
common:
|
serialize:
|
||||||
name: Wait for other jobs
|
name: Wait for other jobs
|
||||||
# since build cache, test-build cache, test-run cache are involved, out of order exec if serialization is on individual jobs will wreak all sorts of havoc
|
# since build cache, test-build cache, test-run cache are involved, out of order exec if serialization is on individual jobs will wreak all sorts of havoc
|
||||||
# so we serialize on the entire workflow
|
# so we serialize on the entire workflow
|
||||||
@@ -68,13 +68,13 @@ jobs:
|
|||||||
|
|
||||||
build:
|
build:
|
||||||
name: Build SideStore - ${{ inputs.release_tag }}
|
name: Build SideStore - ${{ inputs.release_tag }}
|
||||||
needs: common
|
needs: serialize
|
||||||
strategy:
|
strategy:
|
||||||
fail-fast: false
|
fail-fast: false
|
||||||
matrix:
|
matrix:
|
||||||
include:
|
include:
|
||||||
- os: 'macos-15'
|
- os: 'macos-15'
|
||||||
version: '16.1'
|
version: '16.2'
|
||||||
|
|
||||||
runs-on: ${{ matrix.os }}
|
runs-on: ${{ matrix.os }}
|
||||||
outputs:
|
outputs:
|
||||||
@@ -160,7 +160,7 @@ jobs:
|
|||||||
build_num=$(echo "${{ steps.version.outputs.version }}" | sed -E 's/.*\.([0-9]+)\+.*/\1/')
|
build_num=$(echo "${{ steps.version.outputs.version }}" | sed -E 's/.*\.([0-9]+)\+.*/\1/')
|
||||||
|
|
||||||
# Combine them into the final output
|
# Combine them into the final output
|
||||||
MARKETING_VERSION="${version}-${date}.${build_num}+${{ needs.common.outputs.short-commit }}"
|
MARKETING_VERSION="${version}-${date}.${build_num}+${{ needs.serialize.outputs.short-commit }}"
|
||||||
|
|
||||||
echo "MARKETING_VERSION=$MARKETING_VERSION" >> $GITHUB_ENV
|
echo "MARKETING_VERSION=$MARKETING_VERSION" >> $GITHUB_ENV
|
||||||
echo "MARKETING_VERSION=$MARKETING_VERSION" >> $GITHUB_OUTPUT
|
echo "MARKETING_VERSION=$MARKETING_VERSION" >> $GITHUB_OUTPUT
|
||||||
@@ -177,14 +177,32 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
xcode-version: ${{ matrix.version }}
|
xcode-version: ${{ matrix.version }}
|
||||||
|
|
||||||
- name: (Build) Cache Build
|
- name: (Build) Restore Xcode & SwiftPM Cache (Exact match)
|
||||||
uses: irgaly/xcode-cache@v1
|
id: xcode-cache-restore
|
||||||
|
uses: actions/cache/restore@v3
|
||||||
with:
|
with:
|
||||||
key: xcode-cache-deriveddata-build-${{ github.ref_name }}-${{ github.sha }}
|
path: |
|
||||||
restore-keys: xcode-cache-deriveddata-build-${{ github.ref_name }}-
|
~/Library/Developer/Xcode/DerivedData
|
||||||
swiftpm-cache-key: xcode-cache-sourcedata-build-${{ github.ref_name }}-${{ github.sha }}
|
~/Library/Caches/org.swift.swiftpm
|
||||||
swiftpm-cache-restore-keys: |
|
key: xcode-cache-build-${{ github.ref_name }}-${{ github.sha }}
|
||||||
xcode-cache-sourcedata-build-${{ github.ref_name }}-
|
|
||||||
|
- name: (Build) Restore Xcode & SwiftPM Cache (Last Available)
|
||||||
|
id: xcode-cache-restore-recent
|
||||||
|
uses: actions/cache/restore@v3
|
||||||
|
with:
|
||||||
|
path: |
|
||||||
|
~/Library/Developer/Xcode/DerivedData
|
||||||
|
~/Library/Caches/org.swift.swiftpm
|
||||||
|
key: xcode-cache-build-${{ github.ref_name }}-
|
||||||
|
|
||||||
|
# - name: (Build) Cache Build
|
||||||
|
# uses: irgaly/xcode-cache@v1.8.1
|
||||||
|
# with:
|
||||||
|
# key: xcode-cache-deriveddata-build-${{ github.ref_name }}-${{ github.sha }}
|
||||||
|
# restore-keys: xcode-cache-deriveddata-build-${{ github.ref_name }}-
|
||||||
|
# swiftpm-cache-key: xcode-cache-sourcedata-build-${{ github.ref_name }}-${{ github.sha }}
|
||||||
|
# swiftpm-cache-restore-keys: |
|
||||||
|
# xcode-cache-sourcedata-build-${{ github.ref_name }}-
|
||||||
|
|
||||||
- name: (Build) Restore Pods from Cache (Exact match)
|
- name: (Build) Restore Pods from Cache (Exact match)
|
||||||
id: pods-restore
|
id: pods-restore
|
||||||
@@ -269,6 +287,16 @@ jobs:
|
|||||||
- name: Convert to IPA
|
- name: Convert to IPA
|
||||||
run: make ipa | tee -a build/logs/build.log
|
run: make ipa | tee -a build/logs/build.log
|
||||||
|
|
||||||
|
- name: (Build) Save Xcode & SwiftPM Cache
|
||||||
|
id: cache-save
|
||||||
|
if: ${{ steps.xcode-cache-restore.outputs.cache-hit != 'true' }}
|
||||||
|
uses: actions/cache/save@v3
|
||||||
|
with:
|
||||||
|
path: |
|
||||||
|
~/Library/Developer/Xcode/DerivedData
|
||||||
|
~/Library/Caches/org.swift.swiftpm
|
||||||
|
key: xcode-cache-build-${{ github.ref_name }}-${{ github.sha }}
|
||||||
|
|
||||||
- name: (Build) List Files and Build artifacts
|
- name: (Build) List Files and Build artifacts
|
||||||
run: |
|
run: |
|
||||||
echo ">>>>>>>>> Workdir <<<<<<<<<<"
|
echo ">>>>>>>>> Workdir <<<<<<<<<<"
|
||||||
@@ -287,6 +315,10 @@ jobs:
|
|||||||
find SideStore.xcarchive -maxdepth 3 -exec ls -ld {} + || true # List contents if directory exists
|
find SideStore.xcarchive -maxdepth 3 -exec ls -ld {} + || true # List contents if directory exists
|
||||||
echo ""
|
echo ""
|
||||||
|
|
||||||
|
echo ">>>>>>>>> Xcode-Derived-Data <<<<<<<<<<"
|
||||||
|
ls -la ~/Library/Developer/Xcode/DerivedData || true # List contents if directory exists
|
||||||
|
echo ""
|
||||||
|
|
||||||
- name: Encrypt build-logs for upload
|
- name: Encrypt build-logs for upload
|
||||||
id: encrypt-build-log
|
id: encrypt-build-log
|
||||||
run: |
|
run: |
|
||||||
@@ -336,7 +368,7 @@ jobs:
|
|||||||
|
|
||||||
echo "Adding files to commit"
|
echo "Adding files to commit"
|
||||||
git add --verbose build_number.txt
|
git add --verbose build_number.txt
|
||||||
git commit -m " - updated for ${{ inputs.release_tag }} - ${{ needs.common.outputs.short-commit }} deployment" || echo "No changes to commit"
|
git commit -m " - updated for ${{ inputs.release_tag }} - ${{ needs.serialize.outputs.short-commit }} deployment" || echo "No changes to commit"
|
||||||
|
|
||||||
echo "Pushing to remote repo"
|
echo "Pushing to remote repo"
|
||||||
git push --verbose
|
git push --verbose
|
||||||
@@ -371,30 +403,31 @@ jobs:
|
|||||||
- name: Upload release-notes.md
|
- name: Upload release-notes.md
|
||||||
uses: actions/upload-artifact@v4
|
uses: actions/upload-artifact@v4
|
||||||
with:
|
with:
|
||||||
name: release-notes-${{ needs.common.outputs.short-commit }}.md
|
name: release-notes-${{ needs.serialize.outputs.short-commit }}.md
|
||||||
path: release-notes.md
|
path: release-notes.md
|
||||||
|
|
||||||
- name: Upload update_release_notes.py
|
- name: Upload update_release_notes.py
|
||||||
uses: actions/upload-artifact@v4
|
uses: actions/upload-artifact@v4
|
||||||
with:
|
with:
|
||||||
name: update_release_notes-${{ needs.common.outputs.short-commit }}.py
|
name: update_release_notes-${{ needs.serialize.outputs.short-commit }}.py
|
||||||
path: update_release_notes.py
|
path: update_release_notes.py
|
||||||
|
|
||||||
- name: Upload update_apps.py
|
- name: Upload update_apps.py
|
||||||
uses: actions/upload-artifact@v4
|
uses: actions/upload-artifact@v4
|
||||||
with:
|
with:
|
||||||
name: update_apps-${{ needs.common.outputs.short-commit }}.py
|
name: update_apps-${{ needs.serialize.outputs.short-commit }}.py
|
||||||
path: update_apps.py
|
path: update_apps.py
|
||||||
|
|
||||||
tests-build:
|
tests-build:
|
||||||
|
if: ${{ vars.ENABLE_TESTS == '1' && vars.ENABLE_TESTS_BUILD == '1' }}
|
||||||
name: Tests-Build SideStore - ${{ inputs.release_tag }}
|
name: Tests-Build SideStore - ${{ inputs.release_tag }}
|
||||||
needs: common
|
needs: serialize
|
||||||
strategy:
|
strategy:
|
||||||
fail-fast: false
|
fail-fast: false
|
||||||
matrix:
|
matrix:
|
||||||
include:
|
include:
|
||||||
- os: 'macos-15'
|
- os: 'macos-15'
|
||||||
version: '16.1'
|
version: '16.2'
|
||||||
runs-on: ${{ matrix.os }}
|
runs-on: ${{ matrix.os }}
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
@@ -410,16 +443,36 @@ jobs:
|
|||||||
- name: Setup Xcode
|
- name: Setup Xcode
|
||||||
uses: maxim-lobanov/setup-xcode@v1.6.0
|
uses: maxim-lobanov/setup-xcode@v1.6.0
|
||||||
with:
|
with:
|
||||||
xcode-version: '16.1'
|
xcode-version: '16.2'
|
||||||
|
|
||||||
- name: (Tests-Build) Cache Build
|
# - name: (Tests-Build) Cache Build
|
||||||
uses: irgaly/xcode-cache@v1
|
# uses: irgaly/xcode-cache@v1.8.1
|
||||||
|
# with:
|
||||||
|
# key: xcode-cache-deriveddata-test-${{ github.ref_name }}-${{ github.sha }}
|
||||||
|
# # tests shouldn't restore cache unless it is same build
|
||||||
|
# # restore-keys: xcode-cache-deriveddata-test-${{ github.ref_name }}-
|
||||||
|
# swiftpm-cache-key: xcode-cache-sourcedata-test-${{ github.ref_name }}-${{ github.sha }}
|
||||||
|
# swiftpm-cache-restore-keys: |
|
||||||
|
# xcode-cache-sourcedata-test-${{ github.ref_name }}-
|
||||||
|
# delete-used-deriveddata-cache: true
|
||||||
|
|
||||||
|
- name: (Tests-Build) Restore Xcode & SwiftPM Cache (Exact match)
|
||||||
|
id: xcode-cache-restore
|
||||||
|
uses: actions/cache/restore@v3
|
||||||
with:
|
with:
|
||||||
key: xcode-cache-deriveddata-test-${{ github.ref_name }}-${{ github.sha }}
|
path: |
|
||||||
restore-keys: xcode-cache-deriveddata-test-${{ github.ref_name }}-
|
~/Library/Developer/Xcode/DerivedData
|
||||||
swiftpm-cache-key: xcode-cache-sourcedata-test-${{ github.ref_name }}-${{ github.sha }}
|
~/Library/Caches/org.swift.swiftpm
|
||||||
swiftpm-cache-restore-keys: |
|
key: xcode-cache-tests-${{ github.ref_name }}-${{ github.sha }}
|
||||||
xcode-cache-sourcedata-test-${{ github.ref_name }}-
|
|
||||||
|
- name: (Tests-Build) Restore Xcode & SwiftPM Cache (Last Available)
|
||||||
|
id: xcode-cache-restore-recent
|
||||||
|
uses: actions/cache/restore@v3
|
||||||
|
with:
|
||||||
|
path: |
|
||||||
|
~/Library/Developer/Xcode/DerivedData
|
||||||
|
~/Library/Caches/org.swift.swiftpm
|
||||||
|
key: xcode-cache-tests-${{ github.ref_name }}-
|
||||||
|
|
||||||
- name: (Tests-Build) Restore Pods from Cache (Exact match)
|
- name: (Tests-Build) Restore Pods from Cache (Exact match)
|
||||||
id: pods-restore
|
id: pods-restore
|
||||||
@@ -455,6 +508,13 @@ jobs:
|
|||||||
./AltStore.xcworkspace/
|
./AltStore.xcworkspace/
|
||||||
key: pods-cache-test-${{ github.ref_name }}-${{ hashFiles('Podfile') }}
|
key: pods-cache-test-${{ github.ref_name }}-${{ hashFiles('Podfile') }}
|
||||||
|
|
||||||
|
- name: Clean Derived Data (if required)
|
||||||
|
if: ${{ vars.PERFORM_CLEAN_TESTS_BUILD == '1' }}
|
||||||
|
run: |
|
||||||
|
rm -rf ~/Library/Developer/Xcode/DerivedData/
|
||||||
|
make clean
|
||||||
|
xcodebuild clean
|
||||||
|
|
||||||
- name: (Tests-Build) Clean previous build artifacts
|
- name: (Tests-Build) Clean previous build artifacts
|
||||||
run: |
|
run: |
|
||||||
make clean
|
make clean
|
||||||
@@ -487,6 +547,16 @@ jobs:
|
|||||||
run: |
|
run: |
|
||||||
NSUnbufferedIO=YES make -B build-tests 2>&1 | tee -a build/logs/tests-build.log | xcbeautify --renderer github-actions && exit ${PIPESTATUS[0]}
|
NSUnbufferedIO=YES make -B build-tests 2>&1 | tee -a build/logs/tests-build.log | xcbeautify --renderer github-actions && exit ${PIPESTATUS[0]}
|
||||||
|
|
||||||
|
- name: (Tests-Build) Save Xcode & SwiftPM Cache
|
||||||
|
id: cache-save
|
||||||
|
if: ${{ steps.xcode-cache-restore.outputs.cache-hit != 'true' }}
|
||||||
|
uses: actions/cache/save@v3
|
||||||
|
with:
|
||||||
|
path: |
|
||||||
|
~/Library/Developer/Xcode/DerivedData
|
||||||
|
~/Library/Caches/org.swift.swiftpm
|
||||||
|
key: xcode-cache-tests-${{ github.ref_name }}-${{ github.sha }}
|
||||||
|
|
||||||
- name: (Tests-Build) List Files and Build artifacts
|
- name: (Tests-Build) List Files and Build artifacts
|
||||||
if: always()
|
if: always()
|
||||||
run: |
|
run: |
|
||||||
@@ -498,6 +568,16 @@ jobs:
|
|||||||
find build -maxdepth 3 -exec ls -ld {} + || true # List contents if directory exists
|
find build -maxdepth 3 -exec ls -ld {} + || true # List contents if directory exists
|
||||||
echo ""
|
echo ""
|
||||||
|
|
||||||
|
echo ">>>>>>>>> Xcode-Derived-Data <<<<<<<<<<"
|
||||||
|
find ~/Library/Developer/Xcode/DerivedData -maxdepth 8 -exec ls -ld {} + | grep "Build/Products" >> tests-build-deriveddata.txt || true
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
- uses: actions/upload-artifact@v4
|
||||||
|
if: always()
|
||||||
|
with:
|
||||||
|
name: tests-build-deriveddata-${{ needs.serialize.outputs.short-commit }}.txt
|
||||||
|
path: tests-build-deriveddata.txt
|
||||||
|
|
||||||
- name: Encrypt tests-build-logs for upload
|
- name: Encrypt tests-build-logs for upload
|
||||||
id: encrypt-test-log
|
id: encrypt-test-log
|
||||||
if: always()
|
if: always()
|
||||||
@@ -519,18 +599,19 @@ jobs:
|
|||||||
if: always() && steps.encrypt-test-log.outputs.encrypted == 'true'
|
if: always() && steps.encrypt-test-log.outputs.encrypted == 'true'
|
||||||
uses: actions/upload-artifact@v4
|
uses: actions/upload-artifact@v4
|
||||||
with:
|
with:
|
||||||
name: encrypted-tests-build-logs-${{ needs.common.outputs.short-commit }}.zip
|
name: encrypted-tests-build-logs-${{ needs.serialize.outputs.short-commit }}.zip
|
||||||
path: encrypted-tests-build-logs.zip
|
path: encrypted-tests-build-logs.zip
|
||||||
|
|
||||||
tests-run:
|
tests-run:
|
||||||
|
if: ${{ vars.ENABLE_TESTS == '1' && vars.ENABLE_TESTS_RUN == '1' }}
|
||||||
name: Tests-Run SideStore - ${{ inputs.release_tag }}
|
name: Tests-Run SideStore - ${{ inputs.release_tag }}
|
||||||
needs: [common, tests-build]
|
needs: [serialize, tests-build]
|
||||||
strategy:
|
strategy:
|
||||||
fail-fast: false
|
fail-fast: false
|
||||||
matrix:
|
matrix:
|
||||||
include:
|
include:
|
||||||
- os: 'macos-15'
|
- os: 'macos-15'
|
||||||
version: '16.1'
|
version: '16.2'
|
||||||
runs-on: ${{ matrix.os }}
|
runs-on: ${{ matrix.os }}
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
@@ -547,16 +628,23 @@ jobs:
|
|||||||
- name: Setup Xcode
|
- name: Setup Xcode
|
||||||
uses: maxim-lobanov/setup-xcode@v1.6.0
|
uses: maxim-lobanov/setup-xcode@v1.6.0
|
||||||
with:
|
with:
|
||||||
xcode-version: '16.1'
|
xcode-version: '16.2'
|
||||||
|
|
||||||
- name: (Tests-Run) Cache Build
|
# - name: (Tests-Run) Cache Build
|
||||||
uses: irgaly/xcode-cache@v1
|
# uses: irgaly/xcode-cache@v1.8.1
|
||||||
|
# with:
|
||||||
|
# # This comes from
|
||||||
|
# key: xcode-cache-deriveddata-test-${{ github.ref_name }}-${{ github.sha }}
|
||||||
|
# swiftpm-cache-key: xcode-cache-sourcedata-test-${{ github.ref_name }}-${{ github.sha }}
|
||||||
|
|
||||||
|
- name: (Tests-Build) Restore Xcode & SwiftPM Cache (Exact match) [from tests-build job]
|
||||||
|
id: xcode-cache-restore
|
||||||
|
uses: actions/cache/restore@v3
|
||||||
with:
|
with:
|
||||||
key: xcode-cache-deriveddata-test-${{ github.ref_name }}-${{ github.sha }}
|
path: |
|
||||||
restore-keys: xcode-cache-deriveddata-test-${{ github.ref_name }}-
|
~/Library/Developer/Xcode/DerivedData
|
||||||
swiftpm-cache-key: xcode-cache-sourcedata-test-${{ github.ref_name }}-${{ github.sha }}
|
~/Library/Caches/org.swift.swiftpm
|
||||||
swiftpm-cache-restore-keys: |
|
key: xcode-cache-tests-${{ github.ref_name }}-${{ github.sha }}
|
||||||
xcode-cache-sourcedata-test-${{ github.ref_name }}-
|
|
||||||
|
|
||||||
- name: (Tests-Run) Restore Pods from Cache (Exact match)
|
- name: (Tests-Run) Restore Pods from Cache (Exact match)
|
||||||
id: pods-restore
|
id: pods-restore
|
||||||
@@ -616,9 +704,15 @@ jobs:
|
|||||||
echo ""
|
echo ""
|
||||||
|
|
||||||
echo ">>>>>>>>> Xcode-Derived-Data <<<<<<<<<<"
|
echo ">>>>>>>>> Xcode-Derived-Data <<<<<<<<<<"
|
||||||
ls -la ~/Library/Developer/Xcode/DerivedData || true # List contents if directory exists
|
find ~/Library/Developer/Xcode/DerivedData -maxdepth 8 -exec ls -ld {} + | grep "Build/Products" >> tests-run-deriveddata.txt || true
|
||||||
echo ""
|
echo ""
|
||||||
|
|
||||||
|
- uses: actions/upload-artifact@v4
|
||||||
|
if: always()
|
||||||
|
with:
|
||||||
|
name: tests-run-deriveddata-${{ needs.serialize.outputs.short-commit }}.txt
|
||||||
|
path: tests-run-deriveddata.txt
|
||||||
|
|
||||||
# we expect simulator to have been booted by now, so exit otherwise
|
# we expect simulator to have been booted by now, so exit otherwise
|
||||||
- name: Simulator Boot Check
|
- name: Simulator Boot Check
|
||||||
run: |
|
run: |
|
||||||
@@ -676,7 +770,7 @@ jobs:
|
|||||||
if: always() && steps.encrypt-test-log.outputs.encrypted == 'true'
|
if: always() && steps.encrypt-test-log.outputs.encrypted == 'true'
|
||||||
uses: actions/upload-artifact@v4
|
uses: actions/upload-artifact@v4
|
||||||
with:
|
with:
|
||||||
name: encrypted-tests-run-logs-${{ needs.common.outputs.short-commit }}.zip
|
name: encrypted-tests-run-logs-${{ needs.serialize.outputs.short-commit }}.zip
|
||||||
path: encrypted-tests-run-logs.zip
|
path: encrypted-tests-run-logs.zip
|
||||||
|
|
||||||
- name: Print tests-recording.log contents (if exists)
|
- name: Print tests-recording.log contents (if exists)
|
||||||
@@ -706,7 +800,7 @@ jobs:
|
|||||||
if: ${{ always() && steps.check-recording.outputs.found == 'true' }}
|
if: ${{ always() && steps.check-recording.outputs.found == 'true' }}
|
||||||
uses: actions/upload-artifact@v4
|
uses: actions/upload-artifact@v4
|
||||||
with:
|
with:
|
||||||
name: tests-recording-${{ needs.common.outputs.short-commit }}.mp4
|
name: tests-recording-${{ needs.serialize.outputs.short-commit }}.mp4
|
||||||
path: tests-recording.mp4
|
path: tests-recording.mp4
|
||||||
|
|
||||||
- name: Zip test-results
|
- name: Zip test-results
|
||||||
@@ -715,14 +809,14 @@ jobs:
|
|||||||
- name: Upload Test Artifacts
|
- name: Upload Test Artifacts
|
||||||
uses: actions/upload-artifact@v4
|
uses: actions/upload-artifact@v4
|
||||||
with:
|
with:
|
||||||
name: test-results-${{ needs.common.outputs.short-commit }}.zip
|
name: test-results-${{ needs.serialize.outputs.short-commit }}.zip
|
||||||
path: test-results.zip
|
path: test-results.zip
|
||||||
|
|
||||||
deploy:
|
deploy:
|
||||||
name: Deploy SideStore - ${{ inputs.release_tag }}
|
name: Deploy SideStore - ${{ inputs.release_tag }}
|
||||||
runs-on: macos-15
|
runs-on: macos-15
|
||||||
# needs: [common, build]
|
# needs: [serialize, build]
|
||||||
needs: [common, build, tests-build, tests-run]
|
needs: [serialize, build, tests-build, tests-run]
|
||||||
steps:
|
steps:
|
||||||
- name: Download IPA artifact
|
- name: Download IPA artifact
|
||||||
uses: actions/download-artifact@v4
|
uses: actions/download-artifact@v4
|
||||||
@@ -740,39 +834,43 @@ jobs:
|
|||||||
name: encrypted-build-logs-${{ needs.build.outputs.version }}.zip
|
name: encrypted-build-logs-${{ needs.build.outputs.version }}.zip
|
||||||
|
|
||||||
- name: Download encrypted-tests-build-logs artifact
|
- name: Download encrypted-tests-build-logs artifact
|
||||||
|
if: ${{ vars.ENABLE_TESTS == '1' && vars.ENABLE_TESTS_BUILD == '1' }}
|
||||||
uses: actions/download-artifact@v4
|
uses: actions/download-artifact@v4
|
||||||
with:
|
with:
|
||||||
name: encrypted-tests-build-logs-${{ needs.common.outputs.short-commit }}.zip
|
name: encrypted-tests-build-logs-${{ needs.serialize.outputs.short-commit }}.zip
|
||||||
|
|
||||||
- name: Download encrypted-tests-run-logs artifact
|
- name: Download encrypted-tests-run-logs artifact
|
||||||
|
if: ${{ vars.ENABLE_TESTS == '1' && vars.ENABLE_TESTS_RUN == '1' }}
|
||||||
uses: actions/download-artifact@v4
|
uses: actions/download-artifact@v4
|
||||||
with:
|
with:
|
||||||
name: encrypted-tests-run-logs-${{ needs.common.outputs.short-commit }}.zip
|
name: encrypted-tests-run-logs-${{ needs.serialize.outputs.short-commit }}.zip
|
||||||
|
|
||||||
- name: Download tests-recording artifact
|
- name: Download tests-recording artifact
|
||||||
|
if: ${{ vars.ENABLE_TESTS == '1' && vars.ENABLE_TESTS_RUN == '1' }}
|
||||||
uses: actions/download-artifact@v4
|
uses: actions/download-artifact@v4
|
||||||
with:
|
with:
|
||||||
name: tests-recording-${{ needs.common.outputs.short-commit }}.mp4
|
name: tests-recording-${{ needs.serialize.outputs.short-commit }}.mp4
|
||||||
|
|
||||||
- name: Download test-results artifact
|
- name: Download test-results artifact
|
||||||
|
if: ${{ vars.ENABLE_TESTS == '1' && vars.ENABLE_TESTS_RUN == '1' }}
|
||||||
uses: actions/download-artifact@v4
|
uses: actions/download-artifact@v4
|
||||||
with:
|
with:
|
||||||
name: test-results-${{ needs.common.outputs.short-commit }}.zip
|
name: test-results-${{ needs.serialize.outputs.short-commit }}.zip
|
||||||
|
|
||||||
- name: Download release-notes.md
|
- name: Download release-notes.md
|
||||||
uses: actions/download-artifact@v4
|
uses: actions/download-artifact@v4
|
||||||
with:
|
with:
|
||||||
name: release-notes-${{ needs.common.outputs.short-commit }}.md
|
name: release-notes-${{ needs.serialize.outputs.short-commit }}.md
|
||||||
|
|
||||||
- name: Download update_release_notes.py
|
- name: Download update_release_notes.py
|
||||||
uses: actions/download-artifact@v4
|
uses: actions/download-artifact@v4
|
||||||
with:
|
with:
|
||||||
name: update_release_notes-${{ needs.common.outputs.short-commit }}.py
|
name: update_release_notes-${{ needs.serialize.outputs.short-commit }}.py
|
||||||
|
|
||||||
- name: Download update_apps.py
|
- name: Download update_apps.py
|
||||||
uses: actions/download-artifact@v4
|
uses: actions/download-artifact@v4
|
||||||
with:
|
with:
|
||||||
name: update_apps-${{ needs.common.outputs.short-commit }}.py
|
name: update_apps-${{ needs.serialize.outputs.short-commit }}.py
|
||||||
|
|
||||||
- name: Read release notes
|
- name: Read release notes
|
||||||
id: release_notes
|
id: release_notes
|
||||||
@@ -850,7 +948,7 @@ jobs:
|
|||||||
LOCALIZED_DESCRIPTION=$(cat <<EOF
|
LOCALIZED_DESCRIPTION=$(cat <<EOF
|
||||||
This is release for:
|
This is release for:
|
||||||
- version: "${{ needs.build.outputs.version }}"
|
- version: "${{ needs.build.outputs.version }}"
|
||||||
- revision: "${{ needs.common.outputs.short-commit }}"
|
- revision: "${{ needs.serialize.outputs.short-commit }}"
|
||||||
- timestamp: "${{ steps.date.outputs.date }}"
|
- timestamp: "${{ steps.date.outputs.date }}"
|
||||||
|
|
||||||
Release Notes:
|
Release Notes:
|
||||||
@@ -903,7 +1001,7 @@ jobs:
|
|||||||
|
|
||||||
# Commit changes and push using SSH
|
# Commit changes and push using SSH
|
||||||
git add --verbose ./_includes/source.json
|
git add --verbose ./_includes/source.json
|
||||||
git commit -m " - updated for ${{ needs.common.outputs.short-commit }} deployment" || echo "No changes to commit"
|
git commit -m " - updated for ${{ needs.serialize.outputs.short-commit }} deployment" || echo "No changes to commit"
|
||||||
|
|
||||||
git push --verbose
|
git push --verbose
|
||||||
popd
|
popd
|
||||||
63
.github/maintenance/cache.py
vendored
Normal file
63
.github/maintenance/cache.py
vendored
Normal file
@@ -0,0 +1,63 @@
|
|||||||
|
import requests
|
||||||
|
import sys
|
||||||
|
import os
|
||||||
|
|
||||||
|
# Your GitHub Personal Access Token
|
||||||
|
GITHUB_TOKEN = os.getenv("GITHUB_TOKEN")
|
||||||
|
|
||||||
|
# Repository details
|
||||||
|
REPO_OWNER = "SideStore"
|
||||||
|
REPO_NAME = "SideStore"
|
||||||
|
|
||||||
|
|
||||||
|
API_URL = f"https://api.github.com/repos/{REPO_OWNER}/{REPO_NAME}/actions/caches"
|
||||||
|
|
||||||
|
# Common headers for GitHub API calls
|
||||||
|
HEADERS = {
|
||||||
|
"Accept": "application/vnd.github+json",
|
||||||
|
"Authorization": f"Bearer {GITHUB_TOKEN}"
|
||||||
|
}
|
||||||
|
|
||||||
|
def list_caches():
|
||||||
|
response = requests.get(API_URL, headers=HEADERS)
|
||||||
|
if response.status_code != 200:
|
||||||
|
print(f"Failed to list caches. HTTP {response.status_code}")
|
||||||
|
print("Response:", response.text)
|
||||||
|
sys.exit(1)
|
||||||
|
data = response.json()
|
||||||
|
return data.get("actions_caches", [])
|
||||||
|
|
||||||
|
def delete_cache(cache_id):
|
||||||
|
delete_url = f"{API_URL}/{cache_id}"
|
||||||
|
response = requests.delete(delete_url, headers=HEADERS)
|
||||||
|
return response.status_code
|
||||||
|
|
||||||
|
def main():
|
||||||
|
caches = list_caches()
|
||||||
|
if not caches:
|
||||||
|
print("No caches found.")
|
||||||
|
return
|
||||||
|
|
||||||
|
print("Found caches:")
|
||||||
|
for cache in caches:
|
||||||
|
print(f"ID: {cache.get('id')}, Key: {cache.get('key')}")
|
||||||
|
|
||||||
|
print("\nDeleting caches...")
|
||||||
|
for cache in caches:
|
||||||
|
cache_id = cache.get("id")
|
||||||
|
status = delete_cache(cache_id)
|
||||||
|
if status == 204:
|
||||||
|
print(f"Successfully deleted cache with ID: {cache_id}")
|
||||||
|
else:
|
||||||
|
print(f"Failed to delete cache with ID: {cache_id}. HTTP status code: {status}")
|
||||||
|
|
||||||
|
print("All caches processed.")
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
|
|
||||||
|
|
||||||
|
### How to use
|
||||||
|
'''
|
||||||
|
just export the GITHUB_TOKEN and then run this script via `python3 cache.py' to delete the caches
|
||||||
|
'''
|
||||||
4
.github/workflows/alpha.yml
vendored
4
.github/workflows/alpha.yml
vendored
@@ -10,8 +10,8 @@ concurrency:
|
|||||||
cancel-in-progress: true
|
cancel-in-progress: true
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
Reuseable-build:
|
Reusable-build:
|
||||||
uses: ./.github/workflows/reusable-build-workflow.yml
|
uses: ./.github/workflows/reusable-sidestore-build.yml
|
||||||
with:
|
with:
|
||||||
# bundle_id: "com.SideStore.SideStore.Alpha"
|
# bundle_id: "com.SideStore.SideStore.Alpha"
|
||||||
bundle_id: "com.SideStore.SideStore"
|
bundle_id: "com.SideStore.SideStore"
|
||||||
|
|||||||
4
.github/workflows/nightly.yml
vendored
4
.github/workflows/nightly.yml
vendored
@@ -58,13 +58,13 @@ jobs:
|
|||||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
LAST_SUCCESS: ${{ env.last_success }}
|
LAST_SUCCESS: ${{ env.last_success }}
|
||||||
|
|
||||||
Reuseable-build:
|
Reusable-build:
|
||||||
if: |
|
if: |
|
||||||
always() &&
|
always() &&
|
||||||
(github.event_name == 'push' ||
|
(github.event_name == 'push' ||
|
||||||
(github.event_name == 'schedule' && needs.check-changes.result == 'success' && needs.check-changes.outputs.has_changes == 'true'))
|
(github.event_name == 'schedule' && needs.check-changes.result == 'success' && needs.check-changes.outputs.has_changes == 'true'))
|
||||||
needs: check-changes
|
needs: check-changes
|
||||||
uses: ./.github/workflows/reusable-build-workflow.yml
|
uses: ./.github/workflows/reusable-sidestore-build.yml
|
||||||
with:
|
with:
|
||||||
# bundle_id: "com.SideStore.SideStore.Nightly"
|
# bundle_id: "com.SideStore.SideStore.Nightly"
|
||||||
bundle_id: "com.SideStore.SideStore"
|
bundle_id: "com.SideStore.SideStore"
|
||||||
|
|||||||
104
.github/workflows/reusable-sidestore-build.yml
vendored
Normal file
104
.github/workflows/reusable-sidestore-build.yml
vendored
Normal file
@@ -0,0 +1,104 @@
|
|||||||
|
name: Reusable SideStore Build
|
||||||
|
|
||||||
|
on:
|
||||||
|
workflow_call:
|
||||||
|
inputs:
|
||||||
|
is_beta:
|
||||||
|
required: false
|
||||||
|
default: false
|
||||||
|
type: boolean
|
||||||
|
publish:
|
||||||
|
required: false
|
||||||
|
default: false
|
||||||
|
type: boolean
|
||||||
|
is_shared_build_num:
|
||||||
|
required: false
|
||||||
|
default: true
|
||||||
|
type: boolean
|
||||||
|
release_name:
|
||||||
|
required: true
|
||||||
|
type: string
|
||||||
|
release_tag:
|
||||||
|
required: true
|
||||||
|
type: string
|
||||||
|
upstream_tag:
|
||||||
|
required: true
|
||||||
|
type: string
|
||||||
|
upstream_name:
|
||||||
|
required: true
|
||||||
|
type: string
|
||||||
|
bundle_id:
|
||||||
|
default: com.SideStore.SideStore
|
||||||
|
required: true
|
||||||
|
type: string
|
||||||
|
bundle_id_suffix:
|
||||||
|
default: ''
|
||||||
|
required: false
|
||||||
|
type: string
|
||||||
|
|
||||||
|
secrets:
|
||||||
|
# GITHUB_TOKEN:
|
||||||
|
# required: true
|
||||||
|
CROSS_REPO_PUSH_KEY:
|
||||||
|
required: true
|
||||||
|
BUILD_LOG_ZIP_PASSWORD:
|
||||||
|
required: false
|
||||||
|
|
||||||
|
|
||||||
|
# since build cache, test-build cache, test-run cache are involved, out of order exec if serialization is on individual jobs will wreak all sorts of havoc
|
||||||
|
# so we serialize on the entire workflow
|
||||||
|
concurrency:
|
||||||
|
group: serialize-workflow
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
shared:
|
||||||
|
uses: ./.github/workflows/sidestore-shared.yml
|
||||||
|
secrets: inherit
|
||||||
|
|
||||||
|
build:
|
||||||
|
needs: shared
|
||||||
|
uses: ./.github/workflows/sidestore-build.yml
|
||||||
|
with:
|
||||||
|
is_beta: ${{ inputs.is_beta }}
|
||||||
|
is_shared_build_num: ${{ inputs.is_shared_build_num }}
|
||||||
|
release_tag: ${{ inputs.release_tag }}
|
||||||
|
short_commit: ${{ needs.shared.outputs.short-commit }}
|
||||||
|
bundle_id: ${{ inputs.bundle_id }}
|
||||||
|
bundle_id_suffix: ${{ inputs.bundle_id_suffix }}
|
||||||
|
secrets: inherit
|
||||||
|
|
||||||
|
tests-build:
|
||||||
|
if: ${{ vars.ENABLE_TESTS == '1' && vars.ENABLE_TESTS_BUILD == '1' }}
|
||||||
|
needs: shared
|
||||||
|
uses: ./.github/workflows/sidestore-tests-build.yml
|
||||||
|
with:
|
||||||
|
release_tag: ${{ inputs.release_tag }}
|
||||||
|
short_commit: ${{ needs.shared.outputs.short-commit }}
|
||||||
|
secrets: inherit
|
||||||
|
|
||||||
|
tests-run:
|
||||||
|
if: ${{ vars.ENABLE_TESTS == '1' && vars.ENABLE_TESTS_RUN == '1' }}
|
||||||
|
needs: [shared, tests-build]
|
||||||
|
uses: ./.github/workflows/sidestore-tests-run.yml
|
||||||
|
with:
|
||||||
|
release_tag: ${{ inputs.release_tag }}
|
||||||
|
short_commit: ${{ needs.shared.outputs.short-commit }}
|
||||||
|
secrets: inherit
|
||||||
|
|
||||||
|
deploy:
|
||||||
|
needs: [shared, build, tests-build, tests-run] # Keep tests-run in needs
|
||||||
|
if: ${{ always() && (needs.tests-run.result == 'skipped' || needs.tests-run.result == 'success') }}
|
||||||
|
uses: ./.github/workflows/sidestore-deploy.yml
|
||||||
|
with:
|
||||||
|
is_beta: ${{ inputs.is_beta }}
|
||||||
|
publish: ${{ inputs.publish }}
|
||||||
|
release_name: ${{ inputs.release_name }}
|
||||||
|
release_tag: ${{ inputs.release_tag }}
|
||||||
|
upstream_tag: ${{ inputs.upstream_tag }}
|
||||||
|
upstream_name: ${{ inputs.upstream_name }}
|
||||||
|
version: ${{ needs.build.outputs.version }}
|
||||||
|
short_commit: ${{ needs.shared.outputs.short-commit }}
|
||||||
|
release_channel: ${{ needs.build.outputs.release-channel }}
|
||||||
|
marketing_version: ${{ needs.build.outputs.marketing-version }}
|
||||||
|
bundle_id: ${{ inputs.bundle_id }}
|
||||||
|
secrets: inherit
|
||||||
401
.github/workflows/sidestore-build.yml
vendored
Normal file
401
.github/workflows/sidestore-build.yml
vendored
Normal file
@@ -0,0 +1,401 @@
|
|||||||
|
name: SideStore Build
|
||||||
|
|
||||||
|
on:
|
||||||
|
workflow_call:
|
||||||
|
inputs:
|
||||||
|
is_beta:
|
||||||
|
type: boolean
|
||||||
|
is_shared_build_num:
|
||||||
|
type: boolean
|
||||||
|
release_tag:
|
||||||
|
type: string
|
||||||
|
bundle_id:
|
||||||
|
type: string
|
||||||
|
bundle_id_suffix:
|
||||||
|
type: string
|
||||||
|
short_commit:
|
||||||
|
type: string
|
||||||
|
secrets:
|
||||||
|
CROSS_REPO_PUSH_KEY:
|
||||||
|
required: true
|
||||||
|
BUILD_LOG_ZIP_PASSWORD:
|
||||||
|
required: false
|
||||||
|
outputs:
|
||||||
|
version:
|
||||||
|
value: ${{ jobs.build.outputs.version }}
|
||||||
|
marketing-version:
|
||||||
|
value: ${{ jobs.build.outputs.marketing-version }}
|
||||||
|
release-channel:
|
||||||
|
value: ${{ jobs.build.outputs.release-channel }}
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build:
|
||||||
|
name: Build SideStore - ${{ inputs.release_tag }}
|
||||||
|
strategy:
|
||||||
|
fail-fast: false
|
||||||
|
matrix:
|
||||||
|
include:
|
||||||
|
- os: 'macos-15'
|
||||||
|
version: '16.2'
|
||||||
|
runs-on: ${{ matrix.os }}
|
||||||
|
outputs:
|
||||||
|
version: ${{ steps.version.outputs.version }}
|
||||||
|
marketing-version: ${{ steps.marketing-version.outputs.MARKETING_VERSION }}
|
||||||
|
release-channel: ${{ steps.release-channel.outputs.RELEASE_CHANNEL }}
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Set beta status
|
||||||
|
run: echo "IS_BETA=${{ inputs.is_beta }}" >> $GITHUB_ENV
|
||||||
|
shell: bash
|
||||||
|
|
||||||
|
- name: Checkout code
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
submodules: recursive
|
||||||
|
fetch-depth: 0
|
||||||
|
|
||||||
|
- name: Install dependencies - ldid & xcbeautify
|
||||||
|
run: |
|
||||||
|
brew install ldid xcbeautify
|
||||||
|
|
||||||
|
- name: Set ref based on is_shared_build_num
|
||||||
|
if: ${{ inputs.is_beta }}
|
||||||
|
id: set_ref
|
||||||
|
run: |
|
||||||
|
if [ "${{ inputs.is_shared_build_num }}" == "true" ]; then
|
||||||
|
echo "ref=main" >> $GITHUB_ENV
|
||||||
|
else
|
||||||
|
echo "ref=${{ inputs.release_tag }}" >> $GITHUB_ENV
|
||||||
|
fi
|
||||||
|
shell: bash
|
||||||
|
|
||||||
|
- name: Checkout SideStore/beta-build-num repo
|
||||||
|
if: ${{ inputs.is_beta }}
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
repository: 'SideStore/beta-build-num'
|
||||||
|
ref: ${{ env.ref }}
|
||||||
|
token: ${{ secrets.CROSS_REPO_PUSH_KEY }}
|
||||||
|
path: 'SideStore/beta-build-num'
|
||||||
|
|
||||||
|
- name: Copy build_number.txt to repo root
|
||||||
|
if: ${{ inputs.is_beta }}
|
||||||
|
run: |
|
||||||
|
cp SideStore/beta-build-num/build_number.txt .
|
||||||
|
echo "cat build_number.txt"
|
||||||
|
cat build_number.txt
|
||||||
|
shell: bash
|
||||||
|
|
||||||
|
- name: Echo Build.xcconfig
|
||||||
|
run: |
|
||||||
|
echo "cat Build.xcconfig"
|
||||||
|
cat Build.xcconfig
|
||||||
|
shell: bash
|
||||||
|
|
||||||
|
- name: Set Release Channel info for build number bumper
|
||||||
|
id: release-channel
|
||||||
|
run: |
|
||||||
|
RELEASE_CHANNEL="${{ inputs.release_tag }}"
|
||||||
|
echo "RELEASE_CHANNEL=${RELEASE_CHANNEL}" >> $GITHUB_ENV
|
||||||
|
echo "RELEASE_CHANNEL=${RELEASE_CHANNEL}" >> $GITHUB_OUTPUT
|
||||||
|
echo "RELEASE_CHANNEL=${RELEASE_CHANNEL}"
|
||||||
|
shell: bash
|
||||||
|
|
||||||
|
- name: Increase build number for beta builds
|
||||||
|
if: ${{ inputs.is_beta }}
|
||||||
|
run: |
|
||||||
|
bash .github/workflows/increase-beta-build-num.sh
|
||||||
|
shell: bash
|
||||||
|
|
||||||
|
- name: Extract MARKETING_VERSION from Build.xcconfig
|
||||||
|
id: version
|
||||||
|
run: |
|
||||||
|
version=$(grep MARKETING_VERSION Build.xcconfig | sed -e 's/MARKETING_VERSION = //g')
|
||||||
|
echo "version=$version" >> $GITHUB_OUTPUT
|
||||||
|
echo "version=$version"
|
||||||
|
shell: bash
|
||||||
|
|
||||||
|
- name: Set MARKETING_VERSION
|
||||||
|
if: ${{ inputs.is_beta }}
|
||||||
|
id: marketing-version
|
||||||
|
run: |
|
||||||
|
# Extract version number (e.g., "0.6.0")
|
||||||
|
version=$(echo "${{ steps.version.outputs.version }}" | sed -E 's/^[^0-9]*([0-9]+\.[0-9]+\.[0-9]+).*/\1/')
|
||||||
|
# Extract date (YYYYMMDD) (e.g., "20250205")
|
||||||
|
date=$(echo "${{ steps.version.outputs.version }}" | sed -E 's/.*\.([0-9]{4})\.([0-9]{2})\.([0-9]{2})\..*/\1\2\3/')
|
||||||
|
# Extract build number (e.g., "2")
|
||||||
|
build_num=$(echo "${{ steps.version.outputs.version }}" | sed -E 's/.*\.([0-9]+)\+.*/\1/')
|
||||||
|
|
||||||
|
# Combine them into the final output
|
||||||
|
MARKETING_VERSION="${version}-${date}.${build_num}+${{ inputs.short_commit }}"
|
||||||
|
|
||||||
|
echo "MARKETING_VERSION=$MARKETING_VERSION" >> $GITHUB_ENV
|
||||||
|
echo "MARKETING_VERSION=$MARKETING_VERSION" >> $GITHUB_OUTPUT
|
||||||
|
echo "MARKETING_VERSION=$MARKETING_VERSION"
|
||||||
|
shell: bash
|
||||||
|
|
||||||
|
- name: Echo Updated Build.xcconfig, build_number.txt
|
||||||
|
if: ${{ inputs.is_beta }}
|
||||||
|
run: |
|
||||||
|
cat Build.xcconfig
|
||||||
|
cat build_number.txt
|
||||||
|
shell: bash
|
||||||
|
|
||||||
|
- name: Setup Xcode
|
||||||
|
uses: maxim-lobanov/setup-xcode@v1.6.0
|
||||||
|
with:
|
||||||
|
xcode-version: ${{ matrix.version }}
|
||||||
|
|
||||||
|
- name: (Build) Restore Xcode & SwiftPM Cache (Exact match)
|
||||||
|
id: xcode-cache-restore
|
||||||
|
uses: actions/cache/restore@v3
|
||||||
|
with:
|
||||||
|
path: |
|
||||||
|
~/Library/Developer/Xcode/DerivedData
|
||||||
|
~/Library/Caches/org.swift.swiftpm
|
||||||
|
key: xcode-cache-build-${{ github.ref_name }}-${{ github.sha }}
|
||||||
|
|
||||||
|
- name: (Build) Restore Xcode & SwiftPM Cache (Last Available)
|
||||||
|
id: xcode-cache-restore-recent
|
||||||
|
uses: actions/cache/restore@v3
|
||||||
|
with:
|
||||||
|
path: |
|
||||||
|
~/Library/Developer/Xcode/DerivedData
|
||||||
|
~/Library/Caches/org.swift.swiftpm
|
||||||
|
key: xcode-cache-build-${{ github.ref_name }}-
|
||||||
|
|
||||||
|
# - name: (Build) Cache Build
|
||||||
|
# uses: irgaly/xcode-cache@v1.8.1
|
||||||
|
# with:
|
||||||
|
# key: xcode-cache-deriveddata-build-${{ github.ref_name }}-${{ github.sha }}
|
||||||
|
# restore-keys: xcode-cache-deriveddata-build-${{ github.ref_name }}-
|
||||||
|
# swiftpm-cache-key: xcode-cache-sourcedata-build-${{ github.ref_name }}-${{ github.sha }}
|
||||||
|
# swiftpm-cache-restore-keys: |
|
||||||
|
# xcode-cache-sourcedata-build-${{ github.ref_name }}-
|
||||||
|
|
||||||
|
- name: (Build) Restore Pods from Cache (Exact match)
|
||||||
|
id: pods-restore
|
||||||
|
uses: actions/cache/restore@v3
|
||||||
|
with:
|
||||||
|
path: |
|
||||||
|
./Podfile.lock
|
||||||
|
./Pods/
|
||||||
|
./AltStore.xcworkspace/
|
||||||
|
key: pods-cache-build-${{ github.ref_name }}-${{ hashFiles('Podfile') }}
|
||||||
|
# restore-keys: | # commented out to strictly check cache for this particular podfile
|
||||||
|
# pods-cache-
|
||||||
|
|
||||||
|
- name: (Build) Restore Pods from Cache (Last Available)
|
||||||
|
if: ${{ steps.pods-restore.outputs.cache-hit != 'true' }}
|
||||||
|
id: pods-restore-recent
|
||||||
|
uses: actions/cache/restore@v3
|
||||||
|
with:
|
||||||
|
path: |
|
||||||
|
./Podfile.lock
|
||||||
|
./Pods/
|
||||||
|
./AltStore.xcworkspace/
|
||||||
|
key: pods-cache-build-${{ github.ref_name }}-
|
||||||
|
|
||||||
|
|
||||||
|
- name: (Build) Install CocoaPods
|
||||||
|
run: pod install
|
||||||
|
shell: bash
|
||||||
|
|
||||||
|
- name: (Build) Save Pods to Cache
|
||||||
|
id: save-pods
|
||||||
|
if: ${{ steps.pods-restore.outputs.cache-hit != 'true' }}
|
||||||
|
uses: actions/cache/save@v3
|
||||||
|
with:
|
||||||
|
path: |
|
||||||
|
./Podfile.lock
|
||||||
|
./Pods/
|
||||||
|
./AltStore.xcworkspace/
|
||||||
|
key: pods-cache-build-${{ github.ref_name }}-${{ hashFiles('Podfile') }}
|
||||||
|
|
||||||
|
- name: (Build) Clean previous build artifacts
|
||||||
|
# using 'tee' to intercept stdout and log for detailed build-log
|
||||||
|
run: |
|
||||||
|
make clean
|
||||||
|
mkdir -p build/logs
|
||||||
|
shell: bash
|
||||||
|
|
||||||
|
- name: (Build) List Files and derived data
|
||||||
|
if: always()
|
||||||
|
shell: bash
|
||||||
|
run: |
|
||||||
|
echo ">>>>>>>>> Workdir <<<<<<<<<<"
|
||||||
|
ls -la .
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
echo ">>>>>>>>> Pods <<<<<<<<<<"
|
||||||
|
find Pods -maxdepth 2 -exec ls -ld {} + || true # List contents if directory exists
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
echo ">>>>>>>>> SideStore <<<<<<<<<<"
|
||||||
|
find SideStore -maxdepth 2 -exec ls -ld {} + || true # List contents if directory exists
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
echo ">>>>>>>>> Dependencies <<<<<<<<<<"
|
||||||
|
find Dependencies -maxdepth 2 -exec ls -ld {} + || true # List contents if directory exists
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
echo ">>>>>>>>> Xcode-Derived-Data <<<<<<<<<<"
|
||||||
|
ls -la ~/Library/Developer/Xcode/DerivedData || true # List contents if directory exists
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
- name: Set BundleID Suffix for Sidestore build
|
||||||
|
run: |
|
||||||
|
echo "BUNDLE_ID_SUFFIX=${{ inputs.bundle_id_suffix }}" >> $GITHUB_ENV
|
||||||
|
shell: bash
|
||||||
|
|
||||||
|
|
||||||
|
- name: Build SideStore.xcarchive
|
||||||
|
# using 'tee' to intercept stdout and log for detailed build-log
|
||||||
|
run: |
|
||||||
|
NSUnbufferedIO=YES make -B build 2>&1 | tee -a build/logs/build.log | xcbeautify --renderer github-actions && exit ${PIPESTATUS[0]}
|
||||||
|
shell: bash
|
||||||
|
|
||||||
|
- name: Fakesign app
|
||||||
|
run: make fakesign | tee -a build/logs/build.log
|
||||||
|
shell: bash
|
||||||
|
|
||||||
|
- name: Convert to IPA
|
||||||
|
run: make ipa | tee -a build/logs/build.log
|
||||||
|
shell: bash
|
||||||
|
|
||||||
|
- name: (Build) Save Xcode & SwiftPM Cache
|
||||||
|
id: cache-save
|
||||||
|
if: ${{ steps.xcode-cache-restore.outputs.cache-hit != 'true' }}
|
||||||
|
uses: actions/cache/save@v3
|
||||||
|
with:
|
||||||
|
path: |
|
||||||
|
~/Library/Developer/Xcode/DerivedData
|
||||||
|
~/Library/Caches/org.swift.swiftpm
|
||||||
|
key: xcode-cache-build-${{ github.ref_name }}-${{ github.sha }}
|
||||||
|
|
||||||
|
- name: (Build) List Files and Build artifacts
|
||||||
|
run: |
|
||||||
|
echo ">>>>>>>>> Workdir <<<<<<<<<<"
|
||||||
|
ls -la .
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
echo ">>>>>>>>> Build <<<<<<<<<<"
|
||||||
|
find build -maxdepth 3 -exec ls -ld {} + || true # List contents if directory exists
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
echo ">>>>>>>>> SideStore <<<<<<<<<<"
|
||||||
|
find SideStore -maxdepth 3 -exec ls -ld {} + || true # List contents if directory exists
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
echo ">>>>>>>>> SideStore.xcarchive <<<<<<<<<<"
|
||||||
|
find SideStore.xcarchive -maxdepth 3 -exec ls -ld {} + || true # List contents if directory exists
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
echo ">>>>>>>>> Xcode-Derived-Data <<<<<<<<<<"
|
||||||
|
ls -la ~/Library/Developer/Xcode/DerivedData || true # List contents if directory exists
|
||||||
|
echo ""
|
||||||
|
shell: bash
|
||||||
|
|
||||||
|
- name: Encrypt build-logs for upload
|
||||||
|
id: encrypt-build-log
|
||||||
|
run: |
|
||||||
|
DEFAULT_BUILD_LOG_PASSWORD=12345
|
||||||
|
|
||||||
|
BUILD_LOG_ZIP_PASSWORD=${{ secrets.BUILD_LOG_ZIP_PASSWORD }}
|
||||||
|
BUILD_LOG_ZIP_PASSWORD=${BUILD_LOG_ZIP_PASSWORD:-$DEFAULT_BUILD_LOG_PASSWORD}
|
||||||
|
|
||||||
|
if [ "$BUILD_LOG_ZIP_PASSWORD" == "$DEFAULT_BUILD_LOG_PASSWORD" ]; then
|
||||||
|
echo "Warning: BUILD_LOG_ZIP_PASSWORD is not set. Defaulting to '${DEFAULT_BUILD_LOG_PASSWORD}'."
|
||||||
|
fi
|
||||||
|
|
||||||
|
pushd build/logs && zip -e -P "$BUILD_LOG_ZIP_PASSWORD" ../../encrypted-build-logs.zip * || popd
|
||||||
|
echo "::set-output name=encrypted::true"
|
||||||
|
shell: bash
|
||||||
|
|
||||||
|
- name: Upload encrypted-build-logs.zip
|
||||||
|
id: attach-encrypted-build-log
|
||||||
|
if: ${{ always() && steps.encrypt-build-log.outputs.encrypted == 'true' }}
|
||||||
|
uses: actions/upload-artifact@v4
|
||||||
|
with:
|
||||||
|
name: encrypted-build-logs-${{ steps.version.outputs.version }}.zip
|
||||||
|
path: encrypted-build-logs.zip
|
||||||
|
|
||||||
|
- name: Upload SideStore.ipa Artifact
|
||||||
|
uses: actions/upload-artifact@v4
|
||||||
|
with:
|
||||||
|
name: SideStore-${{ steps.version.outputs.version }}.ipa
|
||||||
|
path: SideStore.ipa
|
||||||
|
|
||||||
|
- name: Zip dSYMs
|
||||||
|
run: zip -r -9 ./SideStore.dSYMs.zip ./SideStore.xcarchive/dSYMs
|
||||||
|
shell: bash
|
||||||
|
|
||||||
|
- name: Upload *.dSYM Artifact
|
||||||
|
uses: actions/upload-artifact@v4
|
||||||
|
with:
|
||||||
|
name: SideStore-${{ steps.version.outputs.version }}-dSYMs.zip
|
||||||
|
path: SideStore.dSYMs.zip
|
||||||
|
|
||||||
|
- name: Keep rolling the build numbers for each successful build
|
||||||
|
if: ${{ inputs.is_beta }}
|
||||||
|
run: |
|
||||||
|
pushd SideStore/beta-build-num/
|
||||||
|
|
||||||
|
echo "Configure Git user (committer details)"
|
||||||
|
git config user.name "GitHub Actions"
|
||||||
|
git config user.email "github-actions@github.com"
|
||||||
|
|
||||||
|
echo "Adding files to commit"
|
||||||
|
git add --verbose build_number.txt
|
||||||
|
git commit -m " - updated for ${{ inputs.release_tag }} - ${{ inputs.short_commit }} deployment" || echo "No changes to commit"
|
||||||
|
|
||||||
|
echo "Pushing to remote repo"
|
||||||
|
git push --verbose
|
||||||
|
popd
|
||||||
|
shell: bash
|
||||||
|
|
||||||
|
- name: Get last successful commit
|
||||||
|
id: get_last_commit
|
||||||
|
run: |
|
||||||
|
# Try to get the last successful workflow run commit
|
||||||
|
LAST_SUCCESS_SHA=$(gh run list --branch "${{ github.ref_name }}" --status success --json headSha --jq '.[0].headSha')
|
||||||
|
echo "LAST_SUCCESS_SHA=$LAST_SUCCESS_SHA" >> $GITHUB_OUTPUT
|
||||||
|
echo "LAST_SUCCESS_SHA=$LAST_SUCCESS_SHA" >> $GITHUB_ENV
|
||||||
|
env:
|
||||||
|
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
shell: bash
|
||||||
|
|
||||||
|
- name: Create release notes
|
||||||
|
run: |
|
||||||
|
LAST_SUCCESS_SHA=${{ steps.get_last_commit.outputs.LAST_SUCCESS_SHA}}
|
||||||
|
echo "Last successful commit SHA: $LAST_SUCCESS_SHA"
|
||||||
|
|
||||||
|
FROM_COMMIT=$LAST_SUCCESS_SHA
|
||||||
|
# Check if we got a valid SHA
|
||||||
|
if [ -z "$LAST_SUCCESS_SHA" ] || [ "$LAST_SUCCESS_SHA" = "null" ]; then
|
||||||
|
echo "No successful run found, using initial commit of branch"
|
||||||
|
# Get the first commit of the branch (initial commit)
|
||||||
|
FROM_COMMIT=$(git rev-list --max-parents=0 HEAD)
|
||||||
|
fi
|
||||||
|
|
||||||
|
python3 update_release_notes.py $FROM_COMMIT ${{ inputs.release_tag }} ${{ github.ref_name }}
|
||||||
|
# cat release-notes.md
|
||||||
|
shell: bash
|
||||||
|
|
||||||
|
- name: Upload release-notes.md
|
||||||
|
uses: actions/upload-artifact@v4
|
||||||
|
with:
|
||||||
|
name: release-notes-${{ inputs.short_commit }}.md
|
||||||
|
path: release-notes.md
|
||||||
|
|
||||||
|
- name: Upload update_release_notes.py
|
||||||
|
uses: actions/upload-artifact@v4
|
||||||
|
with:
|
||||||
|
name: update_release_notes-${{ inputs.short_commit }}.py
|
||||||
|
path: update_release_notes.py
|
||||||
|
|
||||||
|
- name: Upload update_apps.py
|
||||||
|
uses: actions/upload-artifact@v4
|
||||||
|
with:
|
||||||
|
name: update_apps-${{ inputs.short_commit }}.py
|
||||||
|
path: update_apps.py
|
||||||
235
.github/workflows/sidestore-deploy.yml
vendored
Normal file
235
.github/workflows/sidestore-deploy.yml
vendored
Normal file
@@ -0,0 +1,235 @@
|
|||||||
|
name: SideStore Deploy
|
||||||
|
|
||||||
|
on:
|
||||||
|
workflow_call:
|
||||||
|
inputs:
|
||||||
|
is_beta:
|
||||||
|
type: boolean
|
||||||
|
publish:
|
||||||
|
type: boolean
|
||||||
|
release_name:
|
||||||
|
type: string
|
||||||
|
release_tag:
|
||||||
|
type: string
|
||||||
|
upstream_tag:
|
||||||
|
type: string
|
||||||
|
upstream_name:
|
||||||
|
type: string
|
||||||
|
version:
|
||||||
|
type: string
|
||||||
|
short_commit:
|
||||||
|
type: string
|
||||||
|
marketing_version:
|
||||||
|
type: string
|
||||||
|
release_channel:
|
||||||
|
type: string
|
||||||
|
bundle_id:
|
||||||
|
type: string
|
||||||
|
secrets:
|
||||||
|
CROSS_REPO_PUSH_KEY:
|
||||||
|
required: true
|
||||||
|
# GITHUB_TOKEN:
|
||||||
|
# required: true
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
deploy:
|
||||||
|
name: Deploy SideStore - ${{ inputs.release_tag }}
|
||||||
|
runs-on: macos-15
|
||||||
|
steps:
|
||||||
|
- name: Download IPA artifact
|
||||||
|
uses: actions/download-artifact@v4
|
||||||
|
with:
|
||||||
|
name: SideStore-${{ inputs.version }}.ipa
|
||||||
|
|
||||||
|
- name: Download dSYM artifact
|
||||||
|
uses: actions/download-artifact@v4
|
||||||
|
with:
|
||||||
|
name: SideStore-${{ inputs.version }}-dSYMs.zip
|
||||||
|
|
||||||
|
- name: Download encrypted-build-logs artifact
|
||||||
|
uses: actions/download-artifact@v4
|
||||||
|
with:
|
||||||
|
name: encrypted-build-logs-${{ inputs.version }}.zip
|
||||||
|
|
||||||
|
- name: Download encrypted-tests-build-logs artifact
|
||||||
|
if: ${{ vars.ENABLE_TESTS == '1' && vars.ENABLE_TESTS_BUILD == '1' }}
|
||||||
|
uses: actions/download-artifact@v4
|
||||||
|
with:
|
||||||
|
name: encrypted-tests-build-logs-${{ inputs.short_commit }}.zip
|
||||||
|
|
||||||
|
- name: Download encrypted-tests-run-logs artifact
|
||||||
|
if: ${{ vars.ENABLE_TESTS == '1' && vars.ENABLE_TESTS_RUN == '1' }}
|
||||||
|
uses: actions/download-artifact@v4
|
||||||
|
with:
|
||||||
|
name: encrypted-tests-run-logs-${{ inputs.short_commit }}.zip
|
||||||
|
|
||||||
|
- name: Download tests-recording artifact
|
||||||
|
if: ${{ vars.ENABLE_TESTS == '1' && vars.ENABLE_TESTS_RUN == '1' }}
|
||||||
|
uses: actions/download-artifact@v4
|
||||||
|
with:
|
||||||
|
name: tests-recording-${{ inputs.short_commit }}.mp4
|
||||||
|
|
||||||
|
- name: Download test-results artifact
|
||||||
|
if: ${{ vars.ENABLE_TESTS == '1' && vars.ENABLE_TESTS_RUN == '1' }}
|
||||||
|
uses: actions/download-artifact@v4
|
||||||
|
with:
|
||||||
|
name: test-results-${{ inputs.short_commit }}.zip
|
||||||
|
|
||||||
|
- name: Download release-notes.md
|
||||||
|
uses: actions/download-artifact@v4
|
||||||
|
with:
|
||||||
|
name: release-notes-${{ inputs.short_commit }}.md
|
||||||
|
|
||||||
|
- name: Download update_release_notes.py
|
||||||
|
uses: actions/download-artifact@v4
|
||||||
|
with:
|
||||||
|
name: update_release_notes-${{ inputs.short_commit }}.py
|
||||||
|
|
||||||
|
- name: Download update_apps.py
|
||||||
|
uses: actions/download-artifact@v4
|
||||||
|
with:
|
||||||
|
name: update_apps-${{ inputs.short_commit }}.py
|
||||||
|
|
||||||
|
- name: Read release notes
|
||||||
|
id: release_notes
|
||||||
|
run: |
|
||||||
|
CONTENT=$(python3 update_release_notes.py --retrieve ${{ inputs.release_tag }})
|
||||||
|
echo "content<<EOF" >> $GITHUB_OUTPUT
|
||||||
|
echo "$CONTENT" >> $GITHUB_OUTPUT
|
||||||
|
echo "EOF" >> $GITHUB_OUTPUT
|
||||||
|
shell: bash
|
||||||
|
|
||||||
|
- name: List files before upload
|
||||||
|
run: |
|
||||||
|
echo ">>>>>>>>> Workdir <<<<<<<<<<"
|
||||||
|
find . -maxdepth 4 -exec ls -ld {} + || true # List contents if directory exists
|
||||||
|
echo ""
|
||||||
|
shell: bash
|
||||||
|
|
||||||
|
- name: Get current date
|
||||||
|
id: date
|
||||||
|
run: echo "date=$(date -u +'%c')" >> $GITHUB_OUTPUT
|
||||||
|
shell: bash
|
||||||
|
|
||||||
|
- name: Get current date in AltStore date form
|
||||||
|
id: date_altstore
|
||||||
|
run: echo "date=$(date -u +'%Y-%m-%d')" >> $GITHUB_OUTPUT
|
||||||
|
shell: bash
|
||||||
|
|
||||||
|
- name: Upload to releases
|
||||||
|
uses: IsaacShelton/update-existing-release@v1.3.1
|
||||||
|
with:
|
||||||
|
token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
release: ${{ inputs.release_name }}
|
||||||
|
tag: ${{ inputs.release_tag }}
|
||||||
|
prerelease: ${{ inputs.is_beta }}
|
||||||
|
files: SideStore.ipa SideStore.dSYMs.zip encrypted-build-logs.zip encrypted-tests-build-logs.zip encrypted-tests-run-logs.zip test-results.zip tests-recording.mp4
|
||||||
|
body: |
|
||||||
|
This is an ⚠️ **EXPERIMENTAL** ⚠️ ${{ inputs.release_name }} build for commit [${{ github.sha }}](https://github.com/${{ github.repository }}/commit/${{ github.sha }}).
|
||||||
|
|
||||||
|
${{ inputs.release_name }} builds are **extremely experimental builds only meant to be used by developers and beta testers. They often contain bugs and experimental features. Use at your own risk!**
|
||||||
|
|
||||||
|
If you want to try out new features early but want a lower chance of bugs, you can look at [SideStore ${{ inputs.upstream_name }}](https://github.com/${{ github.repository }}/releases?q=${{ inputs.upstream_tag }}).
|
||||||
|
|
||||||
|
## Build Info
|
||||||
|
|
||||||
|
Built at (UTC): `${{ steps.date.outputs.date }}`
|
||||||
|
Built at (UTC date): `${{ steps.date_altstore.outputs.date }}`
|
||||||
|
Commit SHA: `${{ github.sha }}`
|
||||||
|
Version: `${{ inputs.version }}`
|
||||||
|
|
||||||
|
${{ steps.release_notes.outputs.content }}
|
||||||
|
|
||||||
|
- name: Get formatted date
|
||||||
|
run: |
|
||||||
|
FORMATTED_DATE=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
|
||||||
|
echo "Formatted date: $FORMATTED_DATE"
|
||||||
|
echo "FORMATTED_DATE=$FORMATTED_DATE" >> $GITHUB_ENV
|
||||||
|
shell: bash
|
||||||
|
|
||||||
|
- name: Get size of IPA in bytes (macOS/Linux)
|
||||||
|
run: |
|
||||||
|
if [[ "$(uname)" == "Darwin" ]]; then
|
||||||
|
# macOS
|
||||||
|
IPA_SIZE=$(stat -f %z SideStore.ipa)
|
||||||
|
else
|
||||||
|
# Linux
|
||||||
|
IPA_SIZE=$(stat -c %s SideStore.ipa)
|
||||||
|
fi
|
||||||
|
echo "IPA size in bytes: $IPA_SIZE"
|
||||||
|
echo "IPA_SIZE=$IPA_SIZE" >> $GITHUB_ENV
|
||||||
|
shell: bash
|
||||||
|
|
||||||
|
- name: Compute SHA-256 of IPA
|
||||||
|
run: |
|
||||||
|
SHA256_HASH=$(shasum -a 256 SideStore.ipa | awk '{ print $1 }')
|
||||||
|
echo "SHA-256 Hash: $SHA256_HASH"
|
||||||
|
echo "SHA256_HASH=$SHA256_HASH" >> $GITHUB_ENV
|
||||||
|
shell: bash
|
||||||
|
|
||||||
|
- name: Set Release Info variables
|
||||||
|
run: |
|
||||||
|
# Format localized description
|
||||||
|
LOCALIZED_DESCRIPTION=$(cat <<EOF
|
||||||
|
This is release for:
|
||||||
|
- version: "${{ inputs.version }}"
|
||||||
|
- revision: "${{ inputs.short_commit }}"
|
||||||
|
- timestamp: "${{ steps.date.outputs.date }}"
|
||||||
|
|
||||||
|
Release Notes:
|
||||||
|
${{ steps.release_notes.outputs.content }}
|
||||||
|
EOF
|
||||||
|
)
|
||||||
|
|
||||||
|
echo "IS_BETA=${{ inputs.is_beta }}" >> $GITHUB_ENV
|
||||||
|
echo "BUNDLE_IDENTIFIER=${{ inputs.bundle_id }}" >> $GITHUB_ENV
|
||||||
|
echo "VERSION_IPA=${{ inputs.marketing_version }}" >> $GITHUB_ENV
|
||||||
|
echo "VERSION_DATE=$FORMATTED_DATE" >> $GITHUB_ENV
|
||||||
|
echo "RELEASE_CHANNEL=${{ inputs.release_channel }}" >> $GITHUB_ENV
|
||||||
|
echo "SIZE=$IPA_SIZE" >> $GITHUB_ENV
|
||||||
|
echo "SHA256=$SHA256_HASH" >> $GITHUB_ENV
|
||||||
|
echo "DOWNLOAD_URL=https://github.com/SideStore/SideStore/releases/download/${{ inputs.release_tag }}/SideStore.ipa" >> $GITHUB_ENV
|
||||||
|
|
||||||
|
# multiline strings
|
||||||
|
echo "LOCALIZED_DESCRIPTION<<EOF" >> $GITHUB_ENV
|
||||||
|
echo "$LOCALIZED_DESCRIPTION" >> $GITHUB_ENV
|
||||||
|
echo "EOF" >> $GITHUB_ENV
|
||||||
|
shell: bash
|
||||||
|
|
||||||
|
- name: Check if Publish updates is set
|
||||||
|
id: check_publish
|
||||||
|
run: |
|
||||||
|
echo "Publish updates to source.json = ${{ inputs.publish }}"
|
||||||
|
shell: bash
|
||||||
|
|
||||||
|
- name: Checkout SideStore/apps-v2.json
|
||||||
|
if: ${{ inputs.is_beta && inputs.publish }}
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
repository: 'SideStore/apps-v2.json'
|
||||||
|
ref: 'main' # this branch is shared by all beta builds, so beta build workflows are serialized
|
||||||
|
token: ${{ secrets.CROSS_REPO_PUSH_KEY }}
|
||||||
|
path: 'SideStore/apps-v2.json'
|
||||||
|
|
||||||
|
# for stable builds, let the user manually edit the source.json
|
||||||
|
- name: Publish to SideStore/apps-v2.json
|
||||||
|
if: ${{ inputs.is_beta && inputs.publish }}
|
||||||
|
id: publish-release
|
||||||
|
shell: bash
|
||||||
|
run: |
|
||||||
|
# Copy and execute the update script
|
||||||
|
pushd SideStore/apps-v2.json/
|
||||||
|
|
||||||
|
# Configure Git user (committer details)
|
||||||
|
git config user.name "GitHub Actions"
|
||||||
|
git config user.email "github-actions@github.com"
|
||||||
|
|
||||||
|
# update the source.json
|
||||||
|
python3 ../../update_apps.py "./_includes/source.json"
|
||||||
|
|
||||||
|
# Commit changes and push using SSH
|
||||||
|
git add --verbose ./_includes/source.json
|
||||||
|
git commit -m " - updated for ${{ inputs.short_commit }} deployment" || echo "No changes to commit"
|
||||||
|
|
||||||
|
git push --verbose
|
||||||
|
popd
|
||||||
24
.github/workflows/sidestore-shared.yml
vendored
Normal file
24
.github/workflows/sidestore-shared.yml
vendored
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
name: SideStore Shared
|
||||||
|
|
||||||
|
on:
|
||||||
|
workflow_call:
|
||||||
|
outputs:
|
||||||
|
short-commit:
|
||||||
|
value: ${{ jobs.shared.outputs.short-commit }}
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
shared:
|
||||||
|
name: Shared Steps
|
||||||
|
strategy:
|
||||||
|
fail-fast: false
|
||||||
|
runs-on: 'macos-15'
|
||||||
|
steps:
|
||||||
|
- name: Set short commit hash
|
||||||
|
id: commit-id
|
||||||
|
run: |
|
||||||
|
# SHORT_COMMIT="${{ github.sha }}"
|
||||||
|
SHORT_COMMIT=${GITHUB_SHA:0:7}
|
||||||
|
echo "Short commit hash: $SHORT_COMMIT"
|
||||||
|
echo "SHORT_COMMIT=$SHORT_COMMIT" >> $GITHUB_OUTPUT
|
||||||
|
outputs:
|
||||||
|
short-commit: ${{ steps.commit-id.outputs.SHORT_COMMIT }}
|
||||||
204
.github/workflows/sidestore-tests-build.yml
vendored
Normal file
204
.github/workflows/sidestore-tests-build.yml
vendored
Normal file
@@ -0,0 +1,204 @@
|
|||||||
|
name: SideStore Tests Build
|
||||||
|
|
||||||
|
on:
|
||||||
|
workflow_call:
|
||||||
|
inputs:
|
||||||
|
release_tag:
|
||||||
|
type: string
|
||||||
|
short_commit:
|
||||||
|
type: string
|
||||||
|
secrets:
|
||||||
|
BUILD_LOG_ZIP_PASSWORD:
|
||||||
|
required: false
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
tests-build:
|
||||||
|
name: Tests-Build SideStore - ${{ inputs.release_tag }}
|
||||||
|
if: ${{ vars.ENABLE_TESTS == '1' && vars.ENABLE_TESTS_BUILD == '1' }}
|
||||||
|
strategy:
|
||||||
|
fail-fast: false
|
||||||
|
matrix:
|
||||||
|
include:
|
||||||
|
- os: 'macos-15'
|
||||||
|
version: '16.2'
|
||||||
|
runs-on: ${{ matrix.os }}
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Checkout code
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
submodules: recursive
|
||||||
|
|
||||||
|
- name: Install dependencies - xcbeautify
|
||||||
|
run: |
|
||||||
|
brew install xcbeautify
|
||||||
|
shell: bash
|
||||||
|
|
||||||
|
- name: Setup Xcode
|
||||||
|
uses: maxim-lobanov/setup-xcode@v1.6.0
|
||||||
|
with:
|
||||||
|
xcode-version: '16.2'
|
||||||
|
|
||||||
|
# - name: (Tests-Build) Cache Build
|
||||||
|
# uses: irgaly/xcode-cache@v1.8.1
|
||||||
|
# with:
|
||||||
|
# key: xcode-cache-deriveddata-test-${{ github.ref_name }}-${{ github.sha }}
|
||||||
|
# # tests shouldn't restore cache unless it is same build
|
||||||
|
# # restore-keys: xcode-cache-deriveddata-test-${{ github.ref_name }}-
|
||||||
|
# swiftpm-cache-key: xcode-cache-sourcedata-test-${{ github.ref_name }}-${{ github.sha }}
|
||||||
|
# swiftpm-cache-restore-keys: |
|
||||||
|
# xcode-cache-sourcedata-test-${{ github.ref_name }}-
|
||||||
|
# delete-used-deriveddata-cache: true
|
||||||
|
|
||||||
|
- name: (Tests-Build) Restore Xcode & SwiftPM Cache (Exact match)
|
||||||
|
id: xcode-cache-restore
|
||||||
|
uses: actions/cache/restore@v3
|
||||||
|
with:
|
||||||
|
path: |
|
||||||
|
~/Library/Developer/Xcode/DerivedData
|
||||||
|
~/Library/Caches/org.swift.swiftpm
|
||||||
|
key: xcode-cache-tests-${{ github.ref_name }}-${{ github.sha }}
|
||||||
|
|
||||||
|
- name: (Tests-Build) Restore Xcode & SwiftPM Cache (Last Available)
|
||||||
|
id: xcode-cache-restore-recent
|
||||||
|
uses: actions/cache/restore@v3
|
||||||
|
with:
|
||||||
|
path: |
|
||||||
|
~/Library/Developer/Xcode/DerivedData
|
||||||
|
~/Library/Caches/org.swift.swiftpm
|
||||||
|
key: xcode-cache-tests-${{ github.ref_name }}-
|
||||||
|
|
||||||
|
- name: (Tests-Build) Restore Pods from Cache (Exact match)
|
||||||
|
id: pods-restore
|
||||||
|
uses: actions/cache/restore@v3
|
||||||
|
with:
|
||||||
|
path: |
|
||||||
|
./Podfile.lock
|
||||||
|
./Pods/
|
||||||
|
./AltStore.xcworkspace/
|
||||||
|
key: pods-cache-test-${{ github.ref_name }}-${{ hashFiles('Podfile') }}
|
||||||
|
|
||||||
|
- name: (Tests-Build) Restore Pods from Cache (Last Available)
|
||||||
|
if: ${{ steps.pods-restore.outputs.cache-hit != 'true' }}
|
||||||
|
id: pods-restore-recent
|
||||||
|
uses: actions/cache/restore@v3
|
||||||
|
with:
|
||||||
|
path: |
|
||||||
|
./Podfile.lock
|
||||||
|
./Pods/
|
||||||
|
./AltStore.xcworkspace/
|
||||||
|
key: pods-cache-test-${{ github.ref_name }}-
|
||||||
|
|
||||||
|
- name: (Tests-Build) Install CocoaPods
|
||||||
|
run: pod install
|
||||||
|
shell: bash
|
||||||
|
|
||||||
|
- name: (Tests-Build) Save Pods to Cache
|
||||||
|
if: ${{ steps.pods-restore.outputs.cache-hit != 'true' }}
|
||||||
|
uses: actions/cache/save@v3
|
||||||
|
with:
|
||||||
|
path: |
|
||||||
|
./Podfile.lock
|
||||||
|
./Pods/
|
||||||
|
./AltStore.xcworkspace/
|
||||||
|
key: pods-cache-test-${{ github.ref_name }}-${{ hashFiles('Podfile') }}
|
||||||
|
|
||||||
|
- name: Clean Derived Data (if required)
|
||||||
|
if: ${{ vars.PERFORM_CLEAN_TESTS_BUILD == '1' }}
|
||||||
|
run: |
|
||||||
|
rm -rf ~/Library/Developer/Xcode/DerivedData/
|
||||||
|
make clean
|
||||||
|
xcodebuild clean
|
||||||
|
shell: bash
|
||||||
|
|
||||||
|
- name: (Tests-Build) Clean previous build artifacts
|
||||||
|
run: |
|
||||||
|
make clean
|
||||||
|
mkdir -p build/logs
|
||||||
|
shell: bash
|
||||||
|
|
||||||
|
- name: (Tests-Build) List Files and derived data
|
||||||
|
shell: bash
|
||||||
|
run: |
|
||||||
|
echo ">>>>>>>>> Workdir <<<<<<<<<<"
|
||||||
|
ls -la .
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
echo ">>>>>>>>> Pods <<<<<<<<<<"
|
||||||
|
find Pods -maxdepth 2 -exec ls -ld {} + || true # List contents if directory exists
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
echo ">>>>>>>>> SideStore <<<<<<<<<<"
|
||||||
|
find SideStore -maxdepth 2 -exec ls -ld {} + || true # List contents if directory exists
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
echo ">>>>>>>>> Dependencies <<<<<<<<<<"
|
||||||
|
find Dependencies -maxdepth 2 -exec ls -ld {} + || true # List contents if directory exists
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
echo ">>>>>>>>> Xcode-Derived-Data <<<<<<<<<<"
|
||||||
|
ls -la ~/Library/Developer/Xcode/DerivedData || true # List contents if directory exists
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
- name: Build SideStore Tests
|
||||||
|
# using 'tee' to intercept stdout and log for detailed build-log
|
||||||
|
shell: bash
|
||||||
|
run: |
|
||||||
|
NSUnbufferedIO=YES make -B build-tests 2>&1 | tee -a build/logs/tests-build.log | xcbeautify --renderer github-actions && exit ${PIPESTATUS[0]}
|
||||||
|
|
||||||
|
- name: (Tests-Build) Save Xcode & SwiftPM Cache
|
||||||
|
id: cache-save
|
||||||
|
if: ${{ steps.xcode-cache-restore.outputs.cache-hit != 'true' }}
|
||||||
|
uses: actions/cache/save@v3
|
||||||
|
with:
|
||||||
|
path: |
|
||||||
|
~/Library/Developer/Xcode/DerivedData
|
||||||
|
~/Library/Caches/org.swift.swiftpm
|
||||||
|
key: xcode-cache-tests-${{ github.ref_name }}-${{ github.sha }}
|
||||||
|
|
||||||
|
- name: (Tests-Build) List Files and Build artifacts
|
||||||
|
if: always()
|
||||||
|
shell: bash
|
||||||
|
run: |
|
||||||
|
echo ">>>>>>>>> Workdir <<<<<<<<<<"
|
||||||
|
ls -la .
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
echo ">>>>>>>>> Build <<<<<<<<<<"
|
||||||
|
find build -maxdepth 3 -exec ls -ld {} + || true # List contents if directory exists
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
echo ">>>>>>>>> Xcode-Derived-Data <<<<<<<<<<"
|
||||||
|
find ~/Library/Developer/Xcode/DerivedData -maxdepth 8 -exec ls -ld {} + | grep "Build/Products" >> tests-build-deriveddata.txt || true
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
- uses: actions/upload-artifact@v4
|
||||||
|
if: always()
|
||||||
|
with:
|
||||||
|
name: tests-build-deriveddata-${{ inputs.short_commit }}.txt
|
||||||
|
path: tests-build-deriveddata.txt
|
||||||
|
|
||||||
|
- name: Encrypt tests-build-logs for upload
|
||||||
|
id: encrypt-test-log
|
||||||
|
if: always()
|
||||||
|
shell: bash
|
||||||
|
run: |
|
||||||
|
DEFAULT_BUILD_LOG_PASSWORD=12345
|
||||||
|
|
||||||
|
BUILD_LOG_ZIP_PASSWORD=${{ secrets.BUILD_LOG_ZIP_PASSWORD }}
|
||||||
|
BUILD_LOG_ZIP_PASSWORD=${BUILD_LOG_ZIP_PASSWORD:-$DEFAULT_BUILD_LOG_PASSWORD}
|
||||||
|
|
||||||
|
if [ "$BUILD_LOG_ZIP_PASSWORD" == "$DEFAULT_BUILD_LOG_PASSWORD" ]; then
|
||||||
|
echo "Warning: BUILD_LOG_ZIP_PASSWORD is not set. Defaulting to '${DEFAULT_BUILD_LOG_PASSWORD}'."
|
||||||
|
fi
|
||||||
|
|
||||||
|
pushd build/logs && zip -e -P "$BUILD_LOG_ZIP_PASSWORD" ../../encrypted-tests-build-logs.zip * || popd
|
||||||
|
echo "::set-output name=encrypted::true"
|
||||||
|
|
||||||
|
- name: Upload encrypted-tests-build-logs.zip
|
||||||
|
id: attach-encrypted-test-log
|
||||||
|
if: always() && steps.encrypt-test-log.outputs.encrypted == 'true'
|
||||||
|
uses: actions/upload-artifact@v4
|
||||||
|
with:
|
||||||
|
name: encrypted-tests-build-logs-${{ inputs.short_commit }}.zip
|
||||||
|
path: encrypted-tests-build-logs.zip
|
||||||
235
.github/workflows/sidestore-tests-run.yml
vendored
Normal file
235
.github/workflows/sidestore-tests-run.yml
vendored
Normal file
@@ -0,0 +1,235 @@
|
|||||||
|
name: SideStore Tests Run
|
||||||
|
|
||||||
|
on:
|
||||||
|
workflow_call:
|
||||||
|
inputs:
|
||||||
|
release_tag:
|
||||||
|
type: string
|
||||||
|
short_commit:
|
||||||
|
type: string
|
||||||
|
secrets:
|
||||||
|
BUILD_LOG_ZIP_PASSWORD:
|
||||||
|
required: false
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
tests-run:
|
||||||
|
name: Tests-Run SideStore - ${{ inputs.release_tag }}
|
||||||
|
if: ${{ vars.ENABLE_TESTS == '1' && vars.ENABLE_TESTS_RUN == '1' }}
|
||||||
|
strategy:
|
||||||
|
fail-fast: false
|
||||||
|
matrix:
|
||||||
|
include:
|
||||||
|
- os: 'macos-15'
|
||||||
|
version: '16.2'
|
||||||
|
runs-on: ${{ matrix.os }}
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Checkout code
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
submodules: recursive
|
||||||
|
|
||||||
|
- name: Boot Simulator async(nohup) for testing
|
||||||
|
run: |
|
||||||
|
mkdir -p build/logs
|
||||||
|
nohup make -B boot-sim-async </dev/null >> build/logs/tests-run.log 2>&1 &
|
||||||
|
shell: bash
|
||||||
|
|
||||||
|
- name: Setup Xcode
|
||||||
|
uses: maxim-lobanov/setup-xcode@v1.6.0
|
||||||
|
with:
|
||||||
|
xcode-version: '16.2'
|
||||||
|
|
||||||
|
# - name: (Tests-Run) Cache Build
|
||||||
|
# uses: irgaly/xcode-cache@v1.8.1
|
||||||
|
# with:
|
||||||
|
# # This comes from
|
||||||
|
# key: xcode-cache-deriveddata-test-${{ github.ref_name }}-${{ github.sha }}
|
||||||
|
# swiftpm-cache-key: xcode-cache-sourcedata-test-${{ github.ref_name }}-${{ github.sha }}
|
||||||
|
|
||||||
|
- name: (Tests-Build) Restore Xcode & SwiftPM Cache (Exact match) [from tests-build job]
|
||||||
|
id: xcode-cache-restore
|
||||||
|
uses: actions/cache/restore@v3
|
||||||
|
with:
|
||||||
|
path: |
|
||||||
|
~/Library/Developer/Xcode/DerivedData
|
||||||
|
~/Library/Caches/org.swift.swiftpm
|
||||||
|
key: xcode-cache-tests-${{ github.ref_name }}-${{ github.sha }}
|
||||||
|
|
||||||
|
- name: (Tests-Run) Restore Pods from Cache (Exact match)
|
||||||
|
id: pods-restore
|
||||||
|
uses: actions/cache/restore@v3
|
||||||
|
with:
|
||||||
|
path: |
|
||||||
|
./Podfile.lock
|
||||||
|
./Pods/
|
||||||
|
./AltStore.xcworkspace/
|
||||||
|
key: pods-cache-test-${{ github.ref_name }}-${{ hashFiles('Podfile') }}
|
||||||
|
|
||||||
|
- name: (Tests-Run) Restore Pods from Cache (Last Available)
|
||||||
|
if: ${{ steps.pods-restore.outputs.cache-hit != 'true' }}
|
||||||
|
id: pods-restore-recent
|
||||||
|
uses: actions/cache/restore@v3
|
||||||
|
with:
|
||||||
|
path: |
|
||||||
|
./Podfile.lock
|
||||||
|
./Pods/
|
||||||
|
./AltStore.xcworkspace/
|
||||||
|
key: pods-cache-test-${{ github.ref_name }}-
|
||||||
|
|
||||||
|
- name: (Tests-Run) Install CocoaPods
|
||||||
|
run: pod install
|
||||||
|
shell: bash
|
||||||
|
|
||||||
|
- name: (Tests-Run) Save Pods to Cache
|
||||||
|
if: ${{ steps.pods-restore.outputs.cache-hit != 'true' }}
|
||||||
|
uses: actions/cache/save@v3
|
||||||
|
with:
|
||||||
|
path: |
|
||||||
|
./Podfile.lock
|
||||||
|
./Pods/
|
||||||
|
./AltStore.xcworkspace/
|
||||||
|
key: pods-cache-test-${{ github.ref_name }}-${{ hashFiles('Podfile') }}
|
||||||
|
|
||||||
|
- name: (Tests-Run) Clean previous build artifacts
|
||||||
|
run: |
|
||||||
|
make clean
|
||||||
|
mkdir -p build/logs
|
||||||
|
shell: bash
|
||||||
|
|
||||||
|
- name: (Tests-Run) List Files and derived data
|
||||||
|
shell: bash
|
||||||
|
run: |
|
||||||
|
echo ">>>>>>>>> Workdir <<<<<<<<<<"
|
||||||
|
ls -la .
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
echo ">>>>>>>>> Pods <<<<<<<<<<"
|
||||||
|
find Pods -maxdepth 2 -exec ls -ld {} + || true # List contents if directory exists
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
echo ">>>>>>>>> SideStore <<<<<<<<<<"
|
||||||
|
find SideStore -maxdepth 2 -exec ls -ld {} + || true # List contents if directory exists
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
echo ">>>>>>>>> Dependencies <<<<<<<<<<"
|
||||||
|
find Dependencies -maxdepth 2 -exec ls -ld {} + || true # List contents if directory exists
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
echo ">>>>>>>>> Xcode-Derived-Data <<<<<<<<<<"
|
||||||
|
find ~/Library/Developer/Xcode/DerivedData -maxdepth 8 -exec ls -ld {} + | grep "Build/Products" >> tests-run-deriveddata.txt || true
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
- uses: actions/upload-artifact@v4
|
||||||
|
if: always()
|
||||||
|
with:
|
||||||
|
name: tests-run-deriveddata-${{ inputs.short_commit }}.txt
|
||||||
|
path: tests-run-deriveddata.txt
|
||||||
|
|
||||||
|
# we expect simulator to have been booted by now, so exit otherwise
|
||||||
|
- name: Simulator Boot Check
|
||||||
|
run: |
|
||||||
|
mkdir -p build/logs
|
||||||
|
make -B sim-boot-check | tee -a build/logs/tests-run.log
|
||||||
|
exit ${PIPESTATUS[0]}
|
||||||
|
shell: bash
|
||||||
|
|
||||||
|
- name: Start Recording UI tests (if DEBUG_RECORD_TESTS is set to 1)
|
||||||
|
if: ${{ vars.DEBUG_RECORD_TESTS == '1' }}
|
||||||
|
run: |
|
||||||
|
nohup xcrun simctl io booted recordVideo -f tests-recording.mp4 --codec h264 </dev/null > tests-recording.log 2>&1 &
|
||||||
|
RECORD_PID=$!
|
||||||
|
echo "RECORD_PID=$RECORD_PID" >> $GITHUB_ENV
|
||||||
|
shell: bash
|
||||||
|
|
||||||
|
- name: Run SideStore Tests
|
||||||
|
# using 'tee' to intercept stdout and log for detailed build-log
|
||||||
|
run: |
|
||||||
|
make run-tests 2>&1 | tee -a build/logs/tests-run.log && exit ${PIPESTATUS[0]}
|
||||||
|
# NSUnbufferedIO=YES make -B run-tests 2>&1 | tee build/logs/tests-run.log | xcpretty -r junit --output ./build/tests/test-results.xml && exit ${PIPESTATUS[0]}
|
||||||
|
shell: bash
|
||||||
|
|
||||||
|
- name: Stop Recording tests
|
||||||
|
if: ${{ always() && env.RECORD_PID != '' }}
|
||||||
|
run: |
|
||||||
|
kill -INT ${{ env.RECORD_PID }}
|
||||||
|
shell: bash
|
||||||
|
|
||||||
|
- name: (Tests-Run) List Files and Build artifacts
|
||||||
|
if: always()
|
||||||
|
run: |
|
||||||
|
echo ">>>>>>>>> Workdir <<<<<<<<<<"
|
||||||
|
ls -la .
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
echo ">>>>>>>>> Build <<<<<<<<<<"
|
||||||
|
find build -maxdepth 3 -exec ls -ld {} + || true # List contents if directory exists
|
||||||
|
echo ""
|
||||||
|
shell: bash
|
||||||
|
|
||||||
|
- name: Encrypt tests-run-logs for upload
|
||||||
|
id: encrypt-test-log
|
||||||
|
if: always()
|
||||||
|
run: |
|
||||||
|
DEFAULT_BUILD_LOG_PASSWORD=12345
|
||||||
|
|
||||||
|
BUILD_LOG_ZIP_PASSWORD=${{ secrets.BUILD_LOG_ZIP_PASSWORD }}
|
||||||
|
BUILD_LOG_ZIP_PASSWORD=${BUILD_LOG_ZIP_PASSWORD:-$DEFAULT_BUILD_LOG_PASSWORD}
|
||||||
|
|
||||||
|
if [ "$BUILD_LOG_ZIP_PASSWORD" == "$DEFAULT_BUILD_LOG_PASSWORD" ]; then
|
||||||
|
echo "Warning: BUILD_LOG_ZIP_PASSWORD is not set. Defaulting to '${DEFAULT_BUILD_LOG_PASSWORD}'."
|
||||||
|
fi
|
||||||
|
|
||||||
|
pushd build/logs && zip -e -P "$BUILD_LOG_ZIP_PASSWORD" ../../encrypted-tests-run-logs.zip * || popd
|
||||||
|
echo "::set-output name=encrypted::true"
|
||||||
|
shell: bash
|
||||||
|
|
||||||
|
- name: Upload encrypted-tests-run-logs.zip
|
||||||
|
id: attach-encrypted-test-log
|
||||||
|
if: always() && steps.encrypt-test-log.outputs.encrypted == 'true'
|
||||||
|
uses: actions/upload-artifact@v4
|
||||||
|
with:
|
||||||
|
name: encrypted-tests-run-logs-${{ inputs.short_commit }}.zip
|
||||||
|
path: encrypted-tests-run-logs.zip
|
||||||
|
|
||||||
|
- name: Print tests-recording.log contents (if exists)
|
||||||
|
if: ${{ always() && env.RECORD_PID != '' }}
|
||||||
|
run: |
|
||||||
|
if [ -f tests-recording.log ]; then
|
||||||
|
echo "tests-recording.log found. Its contents:"
|
||||||
|
cat tests-recording.log
|
||||||
|
else
|
||||||
|
echo "tests-recording.log not found."
|
||||||
|
fi
|
||||||
|
shell: bash
|
||||||
|
|
||||||
|
- name: Check for tests-recording.mp4 presence
|
||||||
|
id: check-recording
|
||||||
|
if: ${{ always() && env.RECORD_PID != '' }}
|
||||||
|
run: |
|
||||||
|
if [ -f tests-recording.mp4 ]; then
|
||||||
|
echo "::set-output name=found::true"
|
||||||
|
echo "tests-recording.mp4 found."
|
||||||
|
else
|
||||||
|
echo "tests-recording.mp4 not found, skipping upload."
|
||||||
|
echo "::set-output name=found::false"
|
||||||
|
fi
|
||||||
|
shell: bash
|
||||||
|
|
||||||
|
- name: Upload tests-recording.mp4
|
||||||
|
id: upload-recording
|
||||||
|
if: ${{ always() && steps.check-recording.outputs.found == 'true' }}
|
||||||
|
uses: actions/upload-artifact@v4
|
||||||
|
with:
|
||||||
|
name: tests-recording-${{ inputs.short_commit }}.mp4
|
||||||
|
path: tests-recording.mp4
|
||||||
|
|
||||||
|
- name: Zip test-results
|
||||||
|
run: zip -r -9 ./test-results.zip ./build/tests
|
||||||
|
shell: bash
|
||||||
|
|
||||||
|
- name: Upload Test Artifacts
|
||||||
|
uses: actions/upload-artifact@v4
|
||||||
|
with:
|
||||||
|
name: test-results-${{ inputs.short_commit }}.zip
|
||||||
|
path: test-results.zip
|
||||||
290
.github/workflows/stable.yml
vendored
290
.github/workflows/stable.yml
vendored
@@ -7,97 +7,277 @@ on:
|
|||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
build:
|
build:
|
||||||
name: Build and upload SideStore
|
name: Build SideStore - stable (on tag push)
|
||||||
strategy:
|
strategy:
|
||||||
fail-fast: false
|
fail-fast: false
|
||||||
matrix:
|
matrix:
|
||||||
include:
|
include:
|
||||||
- os: 'macos-14'
|
- os: 'macos-15'
|
||||||
version: '15.4'
|
version: '16.2'
|
||||||
|
|
||||||
runs-on: ${{ matrix.os }}
|
runs-on: ${{ matrix.os }}
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout code
|
- name: Checkout code
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v4
|
||||||
with:
|
with:
|
||||||
submodules: recursive
|
submodules: recursive
|
||||||
|
|
||||||
- name: Install dependencies
|
- name: Echo Build.xcconfig
|
||||||
run: brew install ldid
|
run: |
|
||||||
|
echo "cat Build.xcconfig"
|
||||||
|
cat Build.xcconfig
|
||||||
|
shell: bash
|
||||||
|
|
||||||
- name: Change version to tag
|
# - name: Change MARKETING_VERSION to the pushed tag that triggered this build
|
||||||
run: sed -e '/MARKETING_VERSION = .*/s/= .*/= ${{ github.ref_name }}/' -i '' Build.xcconfig
|
# run: sed -e '/MARKETING_VERSION = .*/s/= .*/= ${{ github.ref_name }}/' -i '' Build.xcconfig
|
||||||
|
|
||||||
- name: Get version
|
- name: Echo Updated Build.xcconfig
|
||||||
|
run: |
|
||||||
|
cat Build.xcconfig
|
||||||
|
shell: bash
|
||||||
|
|
||||||
|
- name: Extract MARKETING_VERSION from Build.xcconfig
|
||||||
id: version
|
id: version
|
||||||
run: echo "version=$(grep MARKETING_VERSION Build.xcconfig | sed -e "s/MARKETING_VERSION = //g")" >> $GITHUB_OUTPUT
|
run: |
|
||||||
|
version=$(grep MARKETING_VERSION Build.xcconfig | sed -e 's/MARKETING_VERSION = //g')
|
||||||
|
echo "version=$version" >> $GITHUB_OUTPUT
|
||||||
|
echo "version=$version"
|
||||||
|
|
||||||
- name: Echo version
|
echo "MARKETING_VERSION=$version" >> $GITHUB_ENV
|
||||||
run: echo "${{ steps.version.outputs.version }}"
|
echo "MARKETING_VERSION=$version" >> $GITHUB_OUTPUT
|
||||||
|
echo "MARKETING_VERSION=$version"
|
||||||
|
|
||||||
|
shell: bash
|
||||||
|
|
||||||
|
- name: Fail the build if pushed tag and embedded MARKETING_VERSION in Build.xcconfig are mismatching
|
||||||
|
run: |
|
||||||
|
if [ "$MARKETING_VERSION" != "${{ github.ref_name }}" ]; then
|
||||||
|
echo 'Version mismatch: $tag != $marketing_version ... '
|
||||||
|
echo " expected-tag : $MARKETING_VERSION"
|
||||||
|
echo " pushed-tag : ${{ github.ref_name }}"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
echo 'Version matches: $tag == $marketing_version ... '
|
||||||
|
echo " expected-tag : $MARKETING_VERSION"
|
||||||
|
echo " pushed-tag : ${{ github.ref_name }}"
|
||||||
|
shell: bash
|
||||||
|
|
||||||
|
- name: Install dependencies - ldid & xcbeautify
|
||||||
|
run: |
|
||||||
|
brew install ldid xcbeautify
|
||||||
|
|
||||||
- name: Setup Xcode
|
- name: Setup Xcode
|
||||||
uses: maxim-lobanov/setup-xcode@v1.6.0
|
uses: maxim-lobanov/setup-xcode@v1.6.0
|
||||||
with:
|
with:
|
||||||
xcode-version: ${{ matrix.version }}
|
xcode-version: ${{ matrix.version }}
|
||||||
|
|
||||||
- name: Cache Build
|
- name: (Build) Restore Xcode & SwiftPM Cache (Exact match)
|
||||||
uses: irgaly/xcode-cache@v1
|
id: xcode-cache-restore
|
||||||
|
uses: actions/cache/restore@v3
|
||||||
with:
|
with:
|
||||||
key: xcode-cache-deriveddata-${{ github.sha }}
|
path: |
|
||||||
restore-keys: xcode-cache-deriveddata-
|
~/Library/Developer/Xcode/DerivedData
|
||||||
swiftpm-cache-key: xcode-cache-sourcedata-${{ github.sha }}
|
~/Library/Caches/org.swift.swiftpm
|
||||||
swiftpm-cache-restore-keys: |
|
key: xcode-cache-build-stable-${{ github.sha }}
|
||||||
xcode-cache-sourcedata-
|
|
||||||
|
|
||||||
- name: Build SideStore
|
- name: (Build) Restore Xcode & SwiftPM Cache (Last Available)
|
||||||
run: NSUnbufferedIO=YES make build | xcbeautify --renderer github-actions && exit ${PIPESTATUS[0]}
|
id: xcode-cache-restore-recent
|
||||||
|
uses: actions/cache/restore@v3
|
||||||
|
with:
|
||||||
|
path: |
|
||||||
|
~/Library/Developer/Xcode/DerivedData
|
||||||
|
~/Library/Caches/org.swift.swiftpm
|
||||||
|
key: xcode-cache-build-stable-
|
||||||
|
|
||||||
|
- name: (Build) Restore Pods from Cache (Exact match)
|
||||||
|
id: pods-restore
|
||||||
|
uses: actions/cache/restore@v3
|
||||||
|
with:
|
||||||
|
path: |
|
||||||
|
./Podfile.lock
|
||||||
|
./Pods/
|
||||||
|
./AltStore.xcworkspace/
|
||||||
|
key: pods-cache-build-stable-${{ hashFiles('Podfile') }}
|
||||||
|
|
||||||
|
- name: (Build) Restore Pods from Cache (Last Available)
|
||||||
|
if: ${{ steps.pods-restore.outputs.cache-hit != 'true' }}
|
||||||
|
id: pods-restore-recent
|
||||||
|
uses: actions/cache/restore@v3
|
||||||
|
with:
|
||||||
|
path: |
|
||||||
|
./Podfile.lock
|
||||||
|
./Pods/
|
||||||
|
./AltStore.xcworkspace/
|
||||||
|
key: pods-cache-build-stable-
|
||||||
|
|
||||||
|
|
||||||
|
- name: (Build) Install CocoaPods
|
||||||
|
run: pod install
|
||||||
|
shell: bash
|
||||||
|
|
||||||
|
- name: (Build) Save Pods to Cache
|
||||||
|
id: save-pods
|
||||||
|
if: ${{ steps.pods-restore.outputs.cache-hit != 'true' }}
|
||||||
|
uses: actions/cache/save@v3
|
||||||
|
with:
|
||||||
|
path: |
|
||||||
|
./Podfile.lock
|
||||||
|
./Pods/
|
||||||
|
./AltStore.xcworkspace/
|
||||||
|
key: pods-cache-build-stable-${{ hashFiles('Podfile') }}
|
||||||
|
|
||||||
|
- name: (Build) Clean previous build artifacts
|
||||||
|
run: |
|
||||||
|
make clean
|
||||||
|
mkdir -p build/logs
|
||||||
|
shell: bash
|
||||||
|
|
||||||
|
- name: (Build) List Files and derived data
|
||||||
|
if: always()
|
||||||
|
shell: bash
|
||||||
|
run: |
|
||||||
|
echo ">>>>>>>>> Workdir <<<<<<<<<<"
|
||||||
|
ls -la .
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
echo ">>>>>>>>> Pods <<<<<<<<<<"
|
||||||
|
find Pods -maxdepth 2 -exec ls -ld {} + || true # List contents if directory exists
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
echo ">>>>>>>>> SideStore <<<<<<<<<<"
|
||||||
|
find SideStore -maxdepth 2 -exec ls -ld {} + || true # List contents if directory exists
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
echo ">>>>>>>>> Dependencies <<<<<<<<<<"
|
||||||
|
find Dependencies -maxdepth 2 -exec ls -ld {} + || true # List contents if directory exists
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
echo ">>>>>>>>> Xcode-Derived-Data <<<<<<<<<<"
|
||||||
|
ls -la ~/Library/Developer/Xcode/DerivedData || true # List contents if directory exists
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
- name: Build SideStore.xcarchive
|
||||||
|
# using 'tee' to intercept stdout and log for detailed build-log
|
||||||
|
run: |
|
||||||
|
NSUnbufferedIO=YES make -B build 2>&1 | tee -a build/logs/build.log | xcbeautify --renderer github-actions && exit ${PIPESTATUS[0]}
|
||||||
|
shell: bash
|
||||||
|
|
||||||
- name: Fakesign app
|
- name: Fakesign app
|
||||||
run: make fakesign
|
run: make fakesign | tee -a build/logs/build.log
|
||||||
|
shell: bash
|
||||||
|
|
||||||
- name: Convert to IPA
|
- name: Convert to IPA
|
||||||
run: make ipa
|
run: make ipa | tee -a build/logs/build.log
|
||||||
|
shell: bash
|
||||||
|
|
||||||
- name: Get current date
|
- name: (Build) Save Xcode & SwiftPM Cache
|
||||||
id: date
|
id: cache-save
|
||||||
run: echo "date=$(date -u +'%c')" >> $GITHUB_OUTPUT
|
if: ${{ steps.xcode-cache-restore.outputs.cache-hit != 'true' }}
|
||||||
|
uses: actions/cache/save@v3
|
||||||
- name: Get current date in AltStore date form
|
|
||||||
id: date_altstore
|
|
||||||
run: echo "date=$(date -u +'%Y-%m-%d')" >> $GITHUB_OUTPUT
|
|
||||||
|
|
||||||
- name: Upload to new stable release
|
|
||||||
uses: softprops/action-gh-release@v1
|
|
||||||
with:
|
with:
|
||||||
token: ${{ secrets.GITHUB_TOKEN }}
|
path: |
|
||||||
name: ${{ steps.version.outputs.version }}
|
~/Library/Developer/Xcode/DerivedData
|
||||||
tag_name: ${{ github.ref_name }}
|
~/Library/Caches/org.swift.swiftpm
|
||||||
draft: true
|
key: xcode-cache-build-stable-${{ github.sha }}
|
||||||
files: SideStore.ipa
|
|
||||||
body: |
|
- name: (Build) List Files and Build artifacts
|
||||||
<!-- NOTE: to reset SideSource cache, go to `https://apps.sidestore.io/reset-cache/nightly/<sidesource key>`. This is not included in the GitHub Action since it makes draft releases so they can be edited and have a changelog. -->
|
run: |
|
||||||
## Changelog
|
echo ">>>>>>>>> Workdir <<<<<<<<<<"
|
||||||
|
ls -la .
|
||||||
- TODO
|
echo ""
|
||||||
|
|
||||||
## Build Info
|
|
||||||
|
|
||||||
Built at (UTC): `${{ steps.date.outputs.date }}`
|
|
||||||
Built at (UTC date): `${{ steps.date_altstore.outputs.date }}`
|
|
||||||
Commit SHA: `${{ github.sha }}`
|
|
||||||
Version: `${{ steps.version.outputs.version }}`
|
|
||||||
|
|
||||||
- name: Add version to IPA file name
|
echo ">>>>>>>>> Build <<<<<<<<<<"
|
||||||
run: mv SideStore.ipa SideStore-${{ steps.version.outputs.version }}.ipa
|
find build -maxdepth 3 -exec ls -ld {} + || true # List contents if directory exists
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
echo ">>>>>>>>> SideStore <<<<<<<<<<"
|
||||||
|
find SideStore -maxdepth 3 -exec ls -ld {} + || true # List contents if directory exists
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
echo ">>>>>>>>> SideStore.xcarchive <<<<<<<<<<"
|
||||||
|
find SideStore.xcarchive -maxdepth 3 -exec ls -ld {} + || true # List contents if directory exists
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
echo ">>>>>>>>> Xcode-Derived-Data <<<<<<<<<<"
|
||||||
|
ls -la ~/Library/Developer/Xcode/DerivedData || true # List contents if directory exists
|
||||||
|
echo ""
|
||||||
|
shell: bash
|
||||||
|
|
||||||
|
- name: Encrypt build-logs for upload
|
||||||
|
id: encrypt-build-log
|
||||||
|
run: |
|
||||||
|
DEFAULT_BUILD_LOG_PASSWORD=12345
|
||||||
|
|
||||||
|
BUILD_LOG_ZIP_PASSWORD=${{ secrets.BUILD_LOG_ZIP_PASSWORD }}
|
||||||
|
BUILD_LOG_ZIP_PASSWORD=${BUILD_LOG_ZIP_PASSWORD:-$DEFAULT_BUILD_LOG_PASSWORD}
|
||||||
|
|
||||||
|
if [ "$BUILD_LOG_ZIP_PASSWORD" == "$DEFAULT_BUILD_LOG_PASSWORD" ]; then
|
||||||
|
echo "Warning: BUILD_LOG_ZIP_PASSWORD is not set. Defaulting to '${DEFAULT_BUILD_LOG_PASSWORD}'."
|
||||||
|
fi
|
||||||
|
|
||||||
|
pushd build/logs && zip -e -P "$BUILD_LOG_ZIP_PASSWORD" ../../encrypted-build-logs.zip * || popd
|
||||||
|
echo "::set-output name=encrypted::true"
|
||||||
|
shell: bash
|
||||||
|
|
||||||
|
- name: Upload encrypted-build-logs.zip
|
||||||
|
id: attach-encrypted-build-log
|
||||||
|
if: ${{ always() && steps.encrypt-build-log.outputs.encrypted == 'true' }}
|
||||||
|
uses: actions/upload-artifact@v4
|
||||||
|
with:
|
||||||
|
name: encrypted-build-logs-${{ steps.version.outputs.version }}.zip
|
||||||
|
path: encrypted-build-logs.zip
|
||||||
|
|
||||||
- name: Upload SideStore.ipa Artifact
|
- name: Upload SideStore.ipa Artifact
|
||||||
uses: actions/upload-artifact@v4
|
uses: actions/upload-artifact@v4
|
||||||
with:
|
with:
|
||||||
name: SideStore-${{ steps.version.outputs.version }}.ipa
|
name: SideStore-${{ steps.version.outputs.version }}.ipa
|
||||||
path: SideStore-${{ steps.version.outputs.version }}.ipa
|
path: SideStore.ipa
|
||||||
|
|
||||||
|
- name: Zip dSYMs
|
||||||
|
run: zip -r -9 ./SideStore.dSYMs.zip ./SideStore.xcarchive/dSYMs
|
||||||
|
shell: bash
|
||||||
|
|
||||||
- name: Upload *.dSYM Artifact
|
- name: Upload *.dSYM Artifact
|
||||||
uses: actions/upload-artifact@v4
|
uses: actions/upload-artifact@v4
|
||||||
with:
|
with:
|
||||||
name: SideStore-${{ steps.version.outputs.version }}-dSYM
|
name: SideStore-${{ steps.version.outputs.version }}-dSYMs.zip
|
||||||
path: ./*.dSYM/
|
path: SideStore.dSYMs.zip
|
||||||
|
|
||||||
|
- name: Get current date
|
||||||
|
id: date
|
||||||
|
run: echo "date=$(date -u +'%c')" >> $GITHUB_OUTPUT
|
||||||
|
shell: bash
|
||||||
|
|
||||||
|
- name: Get current date in AltStore date form
|
||||||
|
id: date_altstore
|
||||||
|
run: echo "date=$(date -u +'%Y-%m-%d')" >> $GITHUB_OUTPUT
|
||||||
|
shell: bash
|
||||||
|
|
||||||
|
- name: Upload to releases
|
||||||
|
uses: IsaacShelton/update-existing-release@v1.3.1
|
||||||
|
with:
|
||||||
|
token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
draft: true
|
||||||
|
release: ${{ github.ref_name }} # name
|
||||||
|
tag: ${{ github.ref_name }}
|
||||||
|
# stick with what the user pushed, do not use latest commit or anything,
|
||||||
|
# ex: if we want to go back to previous release due to hot issue, dev can create a new tag pointing to that older working tag/commit so as to keep it as an update (to revert major issue)
|
||||||
|
# in this case we do not want the tag to be auto-updated to latest
|
||||||
|
updateTag: false
|
||||||
|
prerelease: false
|
||||||
|
files: >
|
||||||
|
SideStore.ipa
|
||||||
|
SideStore.dSYMs.zip
|
||||||
|
encrypted-build-logs.zip
|
||||||
|
body: |
|
||||||
|
<!-- NOTE: to reset SideSource cache, go to `https://apps.sidestore.io/reset-cache/nightly/<sidesource key>`. This is not included in the GitHub Action since it makes draft releases so they can be edited and have a changelog. -->
|
||||||
|
## Changelog
|
||||||
|
|
||||||
|
- TODO
|
||||||
|
|
||||||
|
## Build Info
|
||||||
|
|
||||||
|
Built at (UTC): `${{ steps.date.outputs.date }}`
|
||||||
|
Built at (UTC date): `${{ steps.date_altstore.outputs.date }}`
|
||||||
|
Commit SHA: `${{ github.sha }}`
|
||||||
|
Version: `${{ steps.version.outputs.version }}`
|
||||||
|
|||||||
@@ -87,6 +87,9 @@
|
|||||||
A8AD35592D31BF2C003A28B4 /* PageInfoManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = A8AD35582D31BF29003A28B4 /* PageInfoManager.swift */; };
|
A8AD35592D31BF2C003A28B4 /* PageInfoManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = A8AD35582D31BF29003A28B4 /* PageInfoManager.swift */; };
|
||||||
A8B516E32D2666CA0047047C /* CoreDataHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = A8B516E22D2666CA0047047C /* CoreDataHelper.swift */; };
|
A8B516E32D2666CA0047047C /* CoreDataHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = A8B516E22D2666CA0047047C /* CoreDataHelper.swift */; };
|
||||||
A8B516E62D2668170047047C /* DateTimeUtil.swift in Sources */ = {isa = PBXBuildFile; fileRef = A8B516E52D2668020047047C /* DateTimeUtil.swift */; };
|
A8B516E62D2668170047047C /* DateTimeUtil.swift in Sources */ = {isa = PBXBuildFile; fileRef = A8B516E52D2668020047047C /* DateTimeUtil.swift */; };
|
||||||
|
A8B645FC2D70C10300125819 /* CollapsingMarkdownView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A8B645FB2D70C10300125819 /* CollapsingMarkdownView.swift */; };
|
||||||
|
A8B645FF2D70C1AD00125819 /* MarkdownKit in Frameworks */ = {isa = PBXBuildFile; productRef = A8B645FE2D70C1AD00125819 /* MarkdownKit */; };
|
||||||
|
A8B646012D70C23E00125819 /* MarkdownKit in Frameworks */ = {isa = PBXBuildFile; productRef = A8B646002D70C23E00125819 /* MarkdownKit */; };
|
||||||
A8BB34E52D04EC8E000A8B4D /* minimuxer-helpers.swift in Sources */ = {isa = PBXBuildFile; fileRef = A809F6A52D04DA1900F0F0F3 /* minimuxer-helpers.swift */; };
|
A8BB34E52D04EC8E000A8B4D /* minimuxer-helpers.swift in Sources */ = {isa = PBXBuildFile; fileRef = A809F6A52D04DA1900F0F0F3 /* minimuxer-helpers.swift */; };
|
||||||
A8C38C242D206A3A00E83DBD /* ConsoleLogger.swift in Sources */ = {isa = PBXBuildFile; fileRef = A8C38C1D2D206A3A00E83DBD /* ConsoleLogger.swift */; };
|
A8C38C242D206A3A00E83DBD /* ConsoleLogger.swift in Sources */ = {isa = PBXBuildFile; fileRef = A8C38C1D2D206A3A00E83DBD /* ConsoleLogger.swift */; };
|
||||||
A8C38C262D206A3A00E83DBD /* ConsoleLog.swift in Sources */ = {isa = PBXBuildFile; fileRef = A8C38C1E2D206A3A00E83DBD /* ConsoleLog.swift */; };
|
A8C38C262D206A3A00E83DBD /* ConsoleLog.swift in Sources */ = {isa = PBXBuildFile; fileRef = A8C38C1E2D206A3A00E83DBD /* ConsoleLog.swift */; };
|
||||||
@@ -678,6 +681,7 @@
|
|||||||
A8696EE32D34512C00E96389 /* RemoveAppExtensionsOperation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RemoveAppExtensionsOperation.swift; sourceTree = "<group>"; };
|
A8696EE32D34512C00E96389 /* RemoveAppExtensionsOperation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RemoveAppExtensionsOperation.swift; sourceTree = "<group>"; };
|
||||||
A881E7C62D6EF58C00954AD2 /* AltStore11ToAltStore17.xcmappingmodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcmappingmodel; path = AltStore11ToAltStore17.xcmappingmodel; sourceTree = "<group>"; };
|
A881E7C62D6EF58C00954AD2 /* AltStore11ToAltStore17.xcmappingmodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcmappingmodel; path = AltStore11ToAltStore17.xcmappingmodel; sourceTree = "<group>"; };
|
||||||
A881E7CA2D6EF5AB00954AD2 /* StoreApp11To17MigrationPolicy.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StoreApp11To17MigrationPolicy.swift; sourceTree = "<group>"; };
|
A881E7CA2D6EF5AB00954AD2 /* StoreApp11To17MigrationPolicy.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StoreApp11To17MigrationPolicy.swift; sourceTree = "<group>"; };
|
||||||
|
A881E8562D6FBBAF00954AD2 /* DataStructureTests.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = DataStructureTests.xcconfig; sourceTree = "<group>"; };
|
||||||
A88B8C482D35AD3200F53F9D /* OperationsLoggingContolView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OperationsLoggingContolView.swift; sourceTree = "<group>"; };
|
A88B8C482D35AD3200F53F9D /* OperationsLoggingContolView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OperationsLoggingContolView.swift; sourceTree = "<group>"; };
|
||||||
A88B8C542D35F1EC00F53F9D /* OperationsLoggingControl.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OperationsLoggingControl.swift; sourceTree = "<group>"; };
|
A88B8C542D35F1EC00F53F9D /* OperationsLoggingControl.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OperationsLoggingControl.swift; sourceTree = "<group>"; };
|
||||||
A8945AA52D059B6100D86CBE /* Roxas.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = Roxas.framework; sourceTree = BUILT_PRODUCTS_DIR; };
|
A8945AA52D059B6100D86CBE /* Roxas.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = Roxas.framework; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||||
@@ -685,6 +689,7 @@
|
|||||||
A8AD35582D31BF29003A28B4 /* PageInfoManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PageInfoManager.swift; sourceTree = "<group>"; };
|
A8AD35582D31BF29003A28B4 /* PageInfoManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PageInfoManager.swift; sourceTree = "<group>"; };
|
||||||
A8B516E22D2666CA0047047C /* CoreDataHelper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CoreDataHelper.swift; sourceTree = "<group>"; };
|
A8B516E22D2666CA0047047C /* CoreDataHelper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CoreDataHelper.swift; sourceTree = "<group>"; };
|
||||||
A8B516E52D2668020047047C /* DateTimeUtil.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DateTimeUtil.swift; sourceTree = "<group>"; };
|
A8B516E52D2668020047047C /* DateTimeUtil.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DateTimeUtil.swift; sourceTree = "<group>"; };
|
||||||
|
A8B645FB2D70C10300125819 /* CollapsingMarkdownView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CollapsingMarkdownView.swift; sourceTree = "<group>"; };
|
||||||
A8C38C1D2D206A3A00E83DBD /* ConsoleLogger.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConsoleLogger.swift; sourceTree = "<group>"; };
|
A8C38C1D2D206A3A00E83DBD /* ConsoleLogger.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConsoleLogger.swift; sourceTree = "<group>"; };
|
||||||
A8C38C1E2D206A3A00E83DBD /* ConsoleLog.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConsoleLog.swift; sourceTree = "<group>"; };
|
A8C38C1E2D206A3A00E83DBD /* ConsoleLog.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConsoleLog.swift; sourceTree = "<group>"; };
|
||||||
A8C38C282D206AC100E83DBD /* OutputStream.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OutputStream.swift; sourceTree = "<group>"; };
|
A8C38C282D206AC100E83DBD /* OutputStream.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OutputStream.swift; sourceTree = "<group>"; };
|
||||||
@@ -1136,6 +1141,7 @@
|
|||||||
files = (
|
files = (
|
||||||
A8C6D5172D1EE95B00DF01F1 /* OpenSSL.xcframework in Frameworks */,
|
A8C6D5172D1EE95B00DF01F1 /* OpenSSL.xcframework in Frameworks */,
|
||||||
A8A543302D04F14400D72399 /* libfragmentzip.a in Frameworks */,
|
A8A543302D04F14400D72399 /* libfragmentzip.a in Frameworks */,
|
||||||
|
A8B646012D70C23E00125819 /* MarkdownKit in Frameworks */,
|
||||||
A805C3CD2D0C316A00E76BDD /* Pods_SideStore.framework in Frameworks */,
|
A805C3CD2D0C316A00E76BDD /* Pods_SideStore.framework in Frameworks */,
|
||||||
A8F838942D048ECE00ED425D /* libimobiledevice.a in Frameworks */,
|
A8F838942D048ECE00ED425D /* libimobiledevice.a in Frameworks */,
|
||||||
A8F838922D048E8F00ED425D /* libEmotionalDamage.a in Frameworks */,
|
A8F838922D048E8F00ED425D /* libEmotionalDamage.a in Frameworks */,
|
||||||
@@ -1143,6 +1149,7 @@
|
|||||||
D533E8B72727841800A9B5DD /* libAppleArchive.tbd in Frameworks */,
|
D533E8B72727841800A9B5DD /* libAppleArchive.tbd in Frameworks */,
|
||||||
D533E8BE2727BBF800A9B5DD /* libcurl.a in Frameworks */,
|
D533E8BE2727BBF800A9B5DD /* libcurl.a in Frameworks */,
|
||||||
BF1614F1250822F100767AEA /* Roxas.framework in Frameworks */,
|
BF1614F1250822F100767AEA /* Roxas.framework in Frameworks */,
|
||||||
|
A8B645FF2D70C1AD00125819 /* MarkdownKit in Frameworks */,
|
||||||
BF66EE852501AE50007EE018 /* AltStoreCore.framework in Frameworks */,
|
BF66EE852501AE50007EE018 /* AltStoreCore.framework in Frameworks */,
|
||||||
);
|
);
|
||||||
runOnlyForDeploymentPostprocessing = 0;
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
@@ -1269,6 +1276,7 @@
|
|||||||
A85ACB8E2D1F31C400AA3DE7 /* AltBackup.xcconfig */,
|
A85ACB8E2D1F31C400AA3DE7 /* AltBackup.xcconfig */,
|
||||||
A85ACB932D1F31C400AA3DE7 /* AltWidgetExtension.xcconfig */,
|
A85ACB932D1F31C400AA3DE7 /* AltWidgetExtension.xcconfig */,
|
||||||
A8E2DB2C2D684D39009E5D31 /* UITests.xcconfig */,
|
A8E2DB2C2D684D39009E5D31 /* UITests.xcconfig */,
|
||||||
|
A881E8562D6FBBAF00954AD2 /* DataStructureTests.xcconfig */,
|
||||||
);
|
);
|
||||||
path = xcconfigs;
|
path = xcconfigs;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
@@ -1344,6 +1352,22 @@
|
|||||||
path = database;
|
path = database;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
};
|
};
|
||||||
|
A8B645F82D70C0DD00125819 /* Views */ = {
|
||||||
|
isa = PBXGroup;
|
||||||
|
children = (
|
||||||
|
A8B645FA2D70C0F600125819 /* UIKit */,
|
||||||
|
);
|
||||||
|
path = Views;
|
||||||
|
sourceTree = "<group>";
|
||||||
|
};
|
||||||
|
A8B645FA2D70C0F600125819 /* UIKit */ = {
|
||||||
|
isa = PBXGroup;
|
||||||
|
children = (
|
||||||
|
A8B645FB2D70C10300125819 /* CollapsingMarkdownView.swift */,
|
||||||
|
);
|
||||||
|
path = UIKit;
|
||||||
|
sourceTree = "<group>";
|
||||||
|
};
|
||||||
A8C38C1C2D2068D100E83DBD /* Utils */ = {
|
A8C38C1C2D2068D100E83DBD /* Utils */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
@@ -1425,6 +1449,7 @@
|
|||||||
A8F66C072D04C025009689E6 /* SideStore */ = {
|
A8F66C072D04C025009689E6 /* SideStore */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
|
A8B645F82D70C0DD00125819 /* Views */,
|
||||||
A8E2DB352D6850A9009E5D31 /* Tests */,
|
A8E2DB352D6850A9009E5D31 /* Tests */,
|
||||||
A8F66C5C2D04D433009689E6 /* minimuxer */,
|
A8F66C5C2D04D433009689E6 /* minimuxer */,
|
||||||
A8F66C602D04D464009689E6 /* minimuxer.xcodeproj */,
|
A8F66C602D04D464009689E6 /* minimuxer.xcodeproj */,
|
||||||
@@ -2680,6 +2705,7 @@
|
|||||||
D58D5F2C26DFE68E00E55E38 /* XCRemoteSwiftPackageReference "LaunchAtLogin" */,
|
D58D5F2C26DFE68E00E55E38 /* XCRemoteSwiftPackageReference "LaunchAtLogin" */,
|
||||||
D5FB7A2C2AA2859400EF863D /* XCRemoteSwiftPackageReference "swift-argument-parser" */,
|
D5FB7A2C2AA2859400EF863D /* XCRemoteSwiftPackageReference "swift-argument-parser" */,
|
||||||
A82067C22D03E0DE00645C0D /* XCRemoteSwiftPackageReference "SemanticVersion" */,
|
A82067C22D03E0DE00645C0D /* XCRemoteSwiftPackageReference "SemanticVersion" */,
|
||||||
|
A8B645FD2D70C1AD00125819 /* XCRemoteSwiftPackageReference "MarkdownKit" */,
|
||||||
);
|
);
|
||||||
productRefGroup = BFD2476B2284B9A500981D42 /* Products */;
|
productRefGroup = BFD2476B2284B9A500981D42 /* Products */;
|
||||||
projectDirPath = "";
|
projectDirPath = "";
|
||||||
@@ -3290,6 +3316,7 @@
|
|||||||
D57FE84428C7DB7100216002 /* ErrorLogViewController.swift in Sources */,
|
D57FE84428C7DB7100216002 /* ErrorLogViewController.swift in Sources */,
|
||||||
A88B8C552D35F1EC00F53F9D /* OperationsLoggingControl.swift in Sources */,
|
A88B8C552D35F1EC00F53F9D /* OperationsLoggingControl.swift in Sources */,
|
||||||
D50107EC2ADF2E1A0069F2A1 /* AddSourceTextFieldCell.swift in Sources */,
|
D50107EC2ADF2E1A0069F2A1 /* AddSourceTextFieldCell.swift in Sources */,
|
||||||
|
A8B645FC2D70C10300125819 /* CollapsingMarkdownView.swift in Sources */,
|
||||||
D5151BD92A8FF64300C96F28 /* RefreshAllAppsIntent.swift in Sources */,
|
D5151BD92A8FF64300C96F28 /* RefreshAllAppsIntent.swift in Sources */,
|
||||||
BFBE0007250AD0E70080826E /* ViewAppIntentHandler.swift in Sources */,
|
BFBE0007250AD0E70080826E /* ViewAppIntentHandler.swift in Sources */,
|
||||||
BFDB6A0822AAED73007EA6D6 /* ResignAppOperation.swift in Sources */,
|
BFDB6A0822AAED73007EA6D6 /* ResignAppOperation.swift in Sources */,
|
||||||
@@ -3532,6 +3559,7 @@
|
|||||||
};
|
};
|
||||||
A81A8CCA2D68BA610086C96F /* Debug */ = {
|
A81A8CCA2D68BA610086C96F /* Debug */ = {
|
||||||
isa = XCBuildConfiguration;
|
isa = XCBuildConfiguration;
|
||||||
|
baseConfigurationReference = A881E8562D6FBBAF00954AD2 /* DataStructureTests.xcconfig */;
|
||||||
buildSettings = {
|
buildSettings = {
|
||||||
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
|
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
|
||||||
CLANG_CXX_LANGUAGE_STANDARD = "gnu++20";
|
CLANG_CXX_LANGUAGE_STANDARD = "gnu++20";
|
||||||
@@ -3546,7 +3574,7 @@
|
|||||||
IPHONEOS_DEPLOYMENT_TARGET = 18.2;
|
IPHONEOS_DEPLOYMENT_TARGET = 18.2;
|
||||||
LOCALIZATION_PREFERS_STRING_CATALOGS = YES;
|
LOCALIZATION_PREFERS_STRING_CATALOGS = YES;
|
||||||
MARKETING_VERSION = 1.0;
|
MARKETING_VERSION = 1.0;
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = com.SideStore.SideStore.DataStructuresTests;
|
PRODUCT_BUNDLE_IDENTIFIER = "$(PRODUCT_BUNDLE_IDENTIFIER)";
|
||||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
SWIFT_ACTIVE_COMPILATION_CONDITIONS = "DEBUG $(inherited)";
|
SWIFT_ACTIVE_COMPILATION_CONDITIONS = "DEBUG $(inherited)";
|
||||||
SWIFT_EMIT_LOC_STRINGS = NO;
|
SWIFT_EMIT_LOC_STRINGS = NO;
|
||||||
@@ -3557,6 +3585,7 @@
|
|||||||
};
|
};
|
||||||
A81A8CCB2D68BA610086C96F /* Release */ = {
|
A81A8CCB2D68BA610086C96F /* Release */ = {
|
||||||
isa = XCBuildConfiguration;
|
isa = XCBuildConfiguration;
|
||||||
|
baseConfigurationReference = A881E8562D6FBBAF00954AD2 /* DataStructureTests.xcconfig */;
|
||||||
buildSettings = {
|
buildSettings = {
|
||||||
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
|
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
|
||||||
CLANG_CXX_LANGUAGE_STANDARD = "gnu++20";
|
CLANG_CXX_LANGUAGE_STANDARD = "gnu++20";
|
||||||
@@ -3573,7 +3602,7 @@
|
|||||||
IPHONEOS_DEPLOYMENT_TARGET = 18.2;
|
IPHONEOS_DEPLOYMENT_TARGET = 18.2;
|
||||||
LOCALIZATION_PREFERS_STRING_CATALOGS = YES;
|
LOCALIZATION_PREFERS_STRING_CATALOGS = YES;
|
||||||
MARKETING_VERSION = 1.0;
|
MARKETING_VERSION = 1.0;
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = com.SideStore.SideStore.DataStructuresTests;
|
PRODUCT_BUNDLE_IDENTIFIER = "$(PRODUCT_BUNDLE_IDENTIFIER)";
|
||||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
PROVISIONING_PROFILE_SPECIFIER = "";
|
PROVISIONING_PROFILE_SPECIFIER = "";
|
||||||
SWIFT_COMPILATION_MODE = wholemodule;
|
SWIFT_COMPILATION_MODE = wholemodule;
|
||||||
@@ -3600,7 +3629,7 @@
|
|||||||
IPHONEOS_DEPLOYMENT_TARGET = 18.2;
|
IPHONEOS_DEPLOYMENT_TARGET = 18.2;
|
||||||
LOCALIZATION_PREFERS_STRING_CATALOGS = YES;
|
LOCALIZATION_PREFERS_STRING_CATALOGS = YES;
|
||||||
MARKETING_VERSION = 1.0;
|
MARKETING_VERSION = 1.0;
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = "--PRODUCT-BUNDLE-IDENTIFIER-.UITests";
|
PRODUCT_BUNDLE_IDENTIFIER = "$(PRODUCT_BUNDLE_IDENTIFIER)";
|
||||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
SWIFT_ACTIVE_COMPILATION_CONDITIONS = "DEBUG $(inherited)";
|
SWIFT_ACTIVE_COMPILATION_CONDITIONS = "DEBUG $(inherited)";
|
||||||
SWIFT_EMIT_LOC_STRINGS = NO;
|
SWIFT_EMIT_LOC_STRINGS = NO;
|
||||||
@@ -3618,6 +3647,7 @@
|
|||||||
CLANG_CXX_LANGUAGE_STANDARD = "gnu++20";
|
CLANG_CXX_LANGUAGE_STANDARD = "gnu++20";
|
||||||
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
|
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
|
||||||
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
|
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
|
||||||
|
"CODE_SIGN_IDENTITY[sdk=macosx*]" = "-";
|
||||||
CODE_SIGN_STYLE = Automatic;
|
CODE_SIGN_STYLE = Automatic;
|
||||||
CURRENT_PROJECT_VERSION = 1;
|
CURRENT_PROJECT_VERSION = 1;
|
||||||
ENABLE_USER_SCRIPT_SANDBOXING = YES;
|
ENABLE_USER_SCRIPT_SANDBOXING = YES;
|
||||||
@@ -3626,7 +3656,7 @@
|
|||||||
IPHONEOS_DEPLOYMENT_TARGET = 18.2;
|
IPHONEOS_DEPLOYMENT_TARGET = 18.2;
|
||||||
LOCALIZATION_PREFERS_STRING_CATALOGS = YES;
|
LOCALIZATION_PREFERS_STRING_CATALOGS = YES;
|
||||||
MARKETING_VERSION = 1.0;
|
MARKETING_VERSION = 1.0;
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = "--PRODUCT-BUNDLE-IDENTIFIER-.UITests";
|
PRODUCT_BUNDLE_IDENTIFIER = "$(PRODUCT_BUNDLE_IDENTIFIER)";
|
||||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
SWIFT_COMPILATION_MODE = wholemodule;
|
SWIFT_COMPILATION_MODE = wholemodule;
|
||||||
SWIFT_EMIT_LOC_STRINGS = NO;
|
SWIFT_EMIT_LOC_STRINGS = NO;
|
||||||
@@ -4254,6 +4284,14 @@
|
|||||||
minimumVersion = 0.4.0;
|
minimumVersion = 0.4.0;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
A8B645FD2D70C1AD00125819 /* XCRemoteSwiftPackageReference "MarkdownKit" */ = {
|
||||||
|
isa = XCRemoteSwiftPackageReference;
|
||||||
|
repositoryURL = "https://github.com/bmoliveira/MarkdownKit.git";
|
||||||
|
requirement = {
|
||||||
|
kind = upToNextMajorVersion;
|
||||||
|
minimumVersion = 1.7.1;
|
||||||
|
};
|
||||||
|
};
|
||||||
D58D5F2C26DFE68E00E55E38 /* XCRemoteSwiftPackageReference "LaunchAtLogin" */ = {
|
D58D5F2C26DFE68E00E55E38 /* XCRemoteSwiftPackageReference "LaunchAtLogin" */ = {
|
||||||
isa = XCRemoteSwiftPackageReference;
|
isa = XCRemoteSwiftPackageReference;
|
||||||
repositoryURL = "https://github.com/sindresorhus/LaunchAtLogin.git";
|
repositoryURL = "https://github.com/sindresorhus/LaunchAtLogin.git";
|
||||||
@@ -4278,6 +4316,16 @@
|
|||||||
package = A82067C22D03E0DE00645C0D /* XCRemoteSwiftPackageReference "SemanticVersion" */;
|
package = A82067C22D03E0DE00645C0D /* XCRemoteSwiftPackageReference "SemanticVersion" */;
|
||||||
productName = SemanticVersion;
|
productName = SemanticVersion;
|
||||||
};
|
};
|
||||||
|
A8B645FE2D70C1AD00125819 /* MarkdownKit */ = {
|
||||||
|
isa = XCSwiftPackageProductDependency;
|
||||||
|
package = A8B645FD2D70C1AD00125819 /* XCRemoteSwiftPackageReference "MarkdownKit" */;
|
||||||
|
productName = MarkdownKit;
|
||||||
|
};
|
||||||
|
A8B646002D70C23E00125819 /* MarkdownKit */ = {
|
||||||
|
isa = XCSwiftPackageProductDependency;
|
||||||
|
package = A8B645FD2D70C1AD00125819 /* XCRemoteSwiftPackageReference "MarkdownKit" */;
|
||||||
|
productName = MarkdownKit;
|
||||||
|
};
|
||||||
A8C6D50B2D1EE87600DF01F1 /* AltSign-Static */ = {
|
A8C6D50B2D1EE87600DF01F1 /* AltSign-Static */ = {
|
||||||
isa = XCSwiftPackageProductDependency;
|
isa = XCSwiftPackageProductDependency;
|
||||||
productName = "AltSign-Static";
|
productName = "AltSign-Static";
|
||||||
|
|||||||
@@ -43,8 +43,10 @@ final class AppContentViewController: UITableViewController
|
|||||||
}()
|
}()
|
||||||
|
|
||||||
@IBOutlet private var subtitleLabel: UILabel!
|
@IBOutlet private var subtitleLabel: UILabel!
|
||||||
@IBOutlet private var descriptionTextView: CollapsingTextView!
|
// @IBOutlet private var descriptionTextView: CollapsingTextView!
|
||||||
@IBOutlet private var versionDescriptionTextView: CollapsingTextView!
|
@IBOutlet private var descriptionTextView: CollapsingMarkdownView!
|
||||||
|
// @IBOutlet private var versionDescriptionTextView: CollapsingTextView!
|
||||||
|
@IBOutlet private var versionDescriptionTextView: CollapsingMarkdownView!
|
||||||
@IBOutlet private var versionLabel: UILabel!
|
@IBOutlet private var versionLabel: UILabel!
|
||||||
@IBOutlet private var versionDateLabel: UILabel!
|
@IBOutlet private var versionDateLabel: UILabel!
|
||||||
@IBOutlet private var sizeLabel: UILabel!
|
@IBOutlet private var sizeLabel: UILabel!
|
||||||
@@ -55,35 +57,32 @@ final class AppContentViewController: UITableViewController
|
|||||||
@IBOutlet private(set) var appDetailCollectionViewController: AppDetailCollectionViewController!
|
@IBOutlet private(set) var appDetailCollectionViewController: AppDetailCollectionViewController!
|
||||||
@IBOutlet private var appDetailCollectionViewHeightConstraint: NSLayoutConstraint!
|
@IBOutlet private var appDetailCollectionViewHeightConstraint: NSLayoutConstraint!
|
||||||
|
|
||||||
override func viewDidLoad()
|
override func viewDidLoad() {
|
||||||
{
|
|
||||||
super.viewDidLoad()
|
super.viewDidLoad()
|
||||||
|
|
||||||
self.tableView.contentInset.bottom = 20
|
self.tableView.contentInset.bottom = 20
|
||||||
|
|
||||||
self.subtitleLabel.text = self.app.subtitle
|
self.subtitleLabel.text = self.app.subtitle
|
||||||
self.descriptionTextView.text = self.app.localizedDescription
|
let desc = self.app.localizedDescription
|
||||||
|
self.descriptionTextView.text = desc
|
||||||
|
|
||||||
if let version = self.app.latestAvailableVersion
|
if let version = self.app.latestAvailableVersion {
|
||||||
{
|
self.versionDescriptionTextView.text = version.localizedDescription ?? "nil"
|
||||||
self.versionDescriptionTextView.text = version.localizedDescription
|
|
||||||
self.versionLabel.text = String(format: NSLocalizedString("Version %@", comment: ""), version.localizedVersion)
|
self.versionLabel.text = String(format: NSLocalizedString("Version %@", comment: ""), version.localizedVersion)
|
||||||
self.versionDateLabel.text = Date().relativeDateString(since: version.date)
|
self.versionDateLabel.text = Date().relativeDateString(since: version.date)
|
||||||
self.sizeLabel.text = self.byteCountFormatter.string(fromByteCount: version.size)
|
self.sizeLabel.text = ByteCountFormatter.string(fromByteCount: version.size, countStyle: .file)
|
||||||
}
|
} else {
|
||||||
else
|
self.versionDescriptionTextView.text = "nil"
|
||||||
{
|
|
||||||
self.versionDescriptionTextView.text = nil
|
|
||||||
self.versionLabel.text = nil
|
self.versionLabel.text = nil
|
||||||
self.versionDateLabel.text = nil
|
self.versionDateLabel.text = nil
|
||||||
self.sizeLabel.text = self.byteCountFormatter.string(fromByteCount: 0)
|
self.sizeLabel.text = ByteCountFormatter.string(fromByteCount: 0, countStyle: .file)
|
||||||
}
|
}
|
||||||
|
|
||||||
self.descriptionTextView.maximumNumberOfLines = 5
|
self.descriptionTextView.maximumNumberOfLines = 5
|
||||||
self.descriptionTextView.moreButton.addTarget(self, action: #selector(AppContentViewController.toggleCollapsingSection(_:)), for: .primaryActionTriggered)
|
self.versionDescriptionTextView.maximumNumberOfLines = 5
|
||||||
|
|
||||||
self.versionDescriptionTextView.maximumNumberOfLines = 3
|
self.descriptionTextView.toggleButton.addTarget(self, action: #selector(AppContentViewController.toggleCollapsingSection(_:)), for: .primaryActionTriggered)
|
||||||
self.versionDescriptionTextView.moreButton.addTarget(self, action: #selector(AppContentViewController.toggleCollapsingSection(_:)), for: .primaryActionTriggered)
|
self.versionDescriptionTextView.toggleButton.addTarget(self, action: #selector(AppContentViewController.toggleCollapsingSection(_:)), for: .primaryActionTriggered)
|
||||||
}
|
}
|
||||||
|
|
||||||
override func viewDidLayoutSubviews()
|
override func viewDidLayoutSubviews()
|
||||||
@@ -162,8 +161,12 @@ private extension AppContentViewController
|
|||||||
|
|
||||||
switch sender
|
switch sender
|
||||||
{
|
{
|
||||||
case self.descriptionTextView.moreButton: indexPath = IndexPath(row: Row.description.rawValue, section: 0)
|
case self.descriptionTextView.toggleButton:
|
||||||
case self.versionDescriptionTextView.moreButton: indexPath = IndexPath(row: Row.versionDescription.rawValue, section: 0)
|
indexPath = IndexPath(row: Row.description.rawValue, section: 0)
|
||||||
|
|
||||||
|
case self.versionDescriptionTextView.toggleButton:
|
||||||
|
indexPath = IndexPath(row: Row.versionDescription.rawValue, section: 0)
|
||||||
|
|
||||||
default: return
|
default: return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -287,7 +287,7 @@
|
|||||||
<rect key="frame" x="0.0" y="0.0" width="375" height="98"/>
|
<rect key="frame" x="0.0" y="0.0" width="375" height="98"/>
|
||||||
<autoresizingMask key="autoresizingMask"/>
|
<autoresizingMask key="autoresizingMask"/>
|
||||||
<subviews>
|
<subviews>
|
||||||
<textView clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="scaleToFill" scrollEnabled="NO" editable="NO" text="Hello Me" textAlignment="natural" translatesAutoresizingMaskIntoConstraints="NO" id="Pyt-8D-BZA" customClass="CollapsingTextView" customModule="SideStore" customModuleProvider="target">
|
<textView clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="scaleToFill" scrollEnabled="NO" editable="NO" text="Hello Me" textAlignment="natural" translatesAutoresizingMaskIntoConstraints="NO" id="Pyt-8D-BZA" customClass="CollapsingMarkdownView" customModule="SideStore" customModuleProvider="target">
|
||||||
<rect key="frame" x="20" y="20" width="335" height="34"/>
|
<rect key="frame" x="20" y="20" width="335" height="34"/>
|
||||||
<color key="backgroundColor" name="Background"/>
|
<color key="backgroundColor" name="Background"/>
|
||||||
<fontDescription key="fontDescription" type="system" pointSize="15"/>
|
<fontDescription key="fontDescription" type="system" pointSize="15"/>
|
||||||
@@ -353,7 +353,7 @@
|
|||||||
</stackView>
|
</stackView>
|
||||||
</subviews>
|
</subviews>
|
||||||
</stackView>
|
</stackView>
|
||||||
<textView clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="scaleToFill" scrollEnabled="NO" editable="NO" textAlignment="natural" translatesAutoresizingMaskIntoConstraints="NO" id="wQF-WY-Gdz" customClass="CollapsingTextView" customModule="SideStore" customModuleProvider="target">
|
<textView clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="scaleToFill" scrollEnabled="NO" editable="NO" textAlignment="natural" translatesAutoresizingMaskIntoConstraints="NO" id="wQF-WY-Gdz" customClass="CollapsingMarkdownView" customModule="SideStore" customModuleProvider="target">
|
||||||
<rect key="frame" x="20" y="59.5" width="335" height="34"/>
|
<rect key="frame" x="20" y="59.5" width="335" height="34"/>
|
||||||
<color key="backgroundColor" name="Background"/>
|
<color key="backgroundColor" name="Background"/>
|
||||||
<fontDescription key="fontDescription" type="system" pointSize="15"/>
|
<fontDescription key="fontDescription" type="system" pointSize="15"/>
|
||||||
|
|||||||
@@ -13,21 +13,18 @@ final class CollapsingTextView: UITextView
|
|||||||
var isCollapsed = true {
|
var isCollapsed = true {
|
||||||
didSet {
|
didSet {
|
||||||
guard self.isCollapsed != oldValue else { return }
|
guard self.isCollapsed != oldValue else { return }
|
||||||
self.shouldResetLayout = true
|
|
||||||
self.setNeedsLayout()
|
self.setNeedsLayout()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var maximumNumberOfLines = 2 {
|
var maximumNumberOfLines = 2 {
|
||||||
didSet {
|
didSet {
|
||||||
self.shouldResetLayout = true
|
|
||||||
self.setNeedsLayout()
|
self.setNeedsLayout()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var lineSpacing: Double = 2 {
|
var lineSpacing: Double = 2 {
|
||||||
didSet {
|
didSet {
|
||||||
self.shouldResetLayout = true
|
|
||||||
|
|
||||||
if #available(iOS 16, *)
|
if #available(iOS 16, *)
|
||||||
{
|
{
|
||||||
@@ -42,7 +39,6 @@ final class CollapsingTextView: UITextView
|
|||||||
|
|
||||||
override var text: String! {
|
override var text: String! {
|
||||||
didSet {
|
didSet {
|
||||||
self.shouldResetLayout = true
|
|
||||||
|
|
||||||
guard #available(iOS 16, *) else { return }
|
guard #available(iOS 16, *) else { return }
|
||||||
self.updateText()
|
self.updateText()
|
||||||
@@ -51,9 +47,6 @@ final class CollapsingTextView: UITextView
|
|||||||
|
|
||||||
let moreButton = UIButton(type: .system)
|
let moreButton = UIButton(type: .system)
|
||||||
|
|
||||||
private var shouldResetLayout: Bool = false
|
|
||||||
private var previousSize: CGSize?
|
|
||||||
|
|
||||||
override init(frame: CGRect, textContainer: NSTextContainer?)
|
override init(frame: CGRect, textContainer: NSTextContainer?)
|
||||||
{
|
{
|
||||||
super.init(frame: frame, textContainer: textContainer)
|
super.init(frame: frame, textContainer: textContainer)
|
||||||
@@ -115,45 +108,39 @@ final class CollapsingTextView: UITextView
|
|||||||
height: font.lineHeight)
|
height: font.lineHeight)
|
||||||
self.moreButton.frame = moreButtonFrame
|
self.moreButton.frame = moreButtonFrame
|
||||||
|
|
||||||
if self.shouldResetLayout || self.previousSize != self.bounds.size
|
if self.isCollapsed
|
||||||
{
|
{
|
||||||
if self.isCollapsed
|
let boundingSize = self.attributedText.boundingRect(with: CGSize(width: self.textContainer.size.width, height: .infinity), options: [.usesLineFragmentOrigin, .usesFontLeading], context: nil)
|
||||||
|
let maximumCollapsedHeight = font.lineHeight * Double(self.maximumNumberOfLines) + self.lineSpacing * Double(self.maximumNumberOfLines - 1)
|
||||||
|
|
||||||
|
if boundingSize.height.rounded() > maximumCollapsedHeight.rounded()
|
||||||
{
|
{
|
||||||
let boundingSize = self.attributedText.boundingRect(with: CGSize(width: self.textContainer.size.width, height: .infinity), options: [.usesLineFragmentOrigin, .usesFontLeading], context: nil)
|
self.textContainer.maximumNumberOfLines = self.maximumNumberOfLines
|
||||||
let maximumCollapsedHeight = font.lineHeight * Double(self.maximumNumberOfLines) + self.lineSpacing * Double(self.maximumNumberOfLines - 1)
|
|
||||||
|
|
||||||
if boundingSize.height.rounded() > maximumCollapsedHeight.rounded()
|
var exclusionFrame = moreButtonFrame
|
||||||
{
|
exclusionFrame.origin.y += self.moreButton.bounds.midY
|
||||||
self.textContainer.maximumNumberOfLines = self.maximumNumberOfLines
|
exclusionFrame.size.width = self.bounds.width // Extra wide to make sure it wraps to next line.
|
||||||
|
self.textContainer.exclusionPaths = [UIBezierPath(rect: exclusionFrame)]
|
||||||
var exclusionFrame = moreButtonFrame
|
|
||||||
exclusionFrame.origin.y += self.moreButton.bounds.midY
|
self.moreButton.isHidden = false
|
||||||
exclusionFrame.size.width = self.bounds.width // Extra wide to make sure it wraps to next line.
|
|
||||||
self.textContainer.exclusionPaths = [UIBezierPath(rect: exclusionFrame)]
|
|
||||||
|
|
||||||
self.moreButton.isHidden = false
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
self.textContainer.maximumNumberOfLines = 0 // Fixes last line having slightly smaller line spacing.
|
|
||||||
self.textContainer.exclusionPaths = []
|
|
||||||
|
|
||||||
self.moreButton.isHidden = true
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
self.textContainer.maximumNumberOfLines = 0
|
self.textContainer.maximumNumberOfLines = 0 // Fixes last line having slightly smaller line spacing.
|
||||||
self.textContainer.exclusionPaths = []
|
self.textContainer.exclusionPaths = []
|
||||||
|
|
||||||
self.moreButton.isHidden = true
|
self.moreButton.isHidden = true
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
self.textContainer.maximumNumberOfLines = 0
|
||||||
|
self.textContainer.exclusionPaths = []
|
||||||
|
|
||||||
self.invalidateIntrinsicContentSize()
|
self.moreButton.isHidden = true
|
||||||
}
|
}
|
||||||
|
|
||||||
self.shouldResetLayout = false
|
self.invalidateIntrinsicContentSize()
|
||||||
self.previousSize = self.bounds.size
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -242,7 +242,6 @@
|
|||||||
<key>public.filename-extension</key>
|
<key>public.filename-extension</key>
|
||||||
<array>
|
<array>
|
||||||
<string>mobiledevicepairing</string>
|
<string>mobiledevicepairing</string>
|
||||||
<string>mobiledevicepair</string>
|
|
||||||
</array>
|
</array>
|
||||||
</dict>
|
</dict>
|
||||||
</dict>
|
</dict>
|
||||||
|
|||||||
@@ -235,7 +235,8 @@ private extension MyAppsViewController
|
|||||||
cell.layoutMargins.right = self.view.layoutMargins.right
|
cell.layoutMargins.right = self.view.layoutMargins.right
|
||||||
|
|
||||||
cell.tintColor = app.tintColor ?? .altPrimary
|
cell.tintColor = app.tintColor ?? .altPrimary
|
||||||
cell.versionDescriptionTextView.text = latestSupportedVersion.localizedDescription
|
cell.versionDescriptionTextView.maximumNumberOfLines = 2
|
||||||
|
cell.versionDescriptionTextView.text = latestSupportedVersion.localizedDescription ?? "nil"
|
||||||
|
|
||||||
cell.bannerView.iconImageView.image = nil
|
cell.bannerView.iconImageView.image = nil
|
||||||
cell.bannerView.iconImageView.isIndicatingActivity = true
|
cell.bannerView.iconImageView.isIndicatingActivity = true
|
||||||
@@ -281,12 +282,9 @@ private extension MyAppsViewController
|
|||||||
cell.mode = .collapsed
|
cell.mode = .collapsed
|
||||||
}
|
}
|
||||||
|
|
||||||
cell.versionDescriptionTextView.moreButton.addTarget(self, action: #selector(MyAppsViewController.toggleUpdateCellMode(_:)), for: .primaryActionTriggered)
|
cell.versionDescriptionTextView.toggleButton.addTarget(self, action: #selector(MyAppsViewController.toggleUpdateCellMode(_:)), for: .primaryActionTriggered)
|
||||||
|
|
||||||
cell.setNeedsLayout()
|
cell.setNeedsLayout()
|
||||||
|
|
||||||
// Below lines are necessary to avoid "more" button layout issues.
|
|
||||||
cell.versionDescriptionTextView.setNeedsLayout()
|
|
||||||
cell.layoutIfNeeded()
|
cell.layoutIfNeeded()
|
||||||
}
|
}
|
||||||
dataSource.prefetchHandler = { (installedApp, indexPath, completionHandler) in
|
dataSource.prefetchHandler = { (installedApp, indexPath, completionHandler) in
|
||||||
@@ -724,22 +722,29 @@ private extension MyAppsViewController
|
|||||||
|
|
||||||
let cell = self.collectionView.cellForItem(at: indexPath) as? UpdateCollectionViewCell
|
let cell = self.collectionView.cellForItem(at: indexPath) as? UpdateCollectionViewCell
|
||||||
|
|
||||||
|
// Toggle the state
|
||||||
if self.expandedAppUpdates.contains(installedApp.bundleIdentifier)
|
if self.expandedAppUpdates.contains(installedApp.bundleIdentifier)
|
||||||
{
|
{
|
||||||
self.expandedAppUpdates.remove(installedApp.bundleIdentifier)
|
self.expandedAppUpdates.remove(installedApp.bundleIdentifier)
|
||||||
|
// Set collapsed mode on the cell
|
||||||
cell?.mode = .collapsed
|
cell?.mode = .collapsed
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
self.expandedAppUpdates.insert(installedApp.bundleIdentifier)
|
self.expandedAppUpdates.insert(installedApp.bundleIdentifier)
|
||||||
|
// Set expanded mode on the cell
|
||||||
cell?.mode = .expanded
|
cell?.mode = .expanded
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Clear cached size so it's recalculated
|
||||||
self.cachedUpdateSizes[installedApp.bundleIdentifier] = nil
|
self.cachedUpdateSizes[installedApp.bundleIdentifier] = nil
|
||||||
|
|
||||||
self.collectionView.performBatchUpdates({
|
// Animate the change smoothly with a duration
|
||||||
self.collectionView.collectionViewLayout.invalidateLayout()
|
UIView.animate(withDuration: 0.25) {
|
||||||
}, completion: nil)
|
self.collectionView.performBatchUpdates({
|
||||||
|
self.collectionView.collectionViewLayout.invalidateLayout()
|
||||||
|
}, completion: nil)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@IBAction func refreshApp(_ sender: UIButton)
|
@IBAction func refreshApp(_ sender: UIButton)
|
||||||
|
|||||||
@@ -21,12 +21,19 @@ extension UpdateCollectionViewCell
|
|||||||
{
|
{
|
||||||
var mode: Mode = .expanded {
|
var mode: Mode = .expanded {
|
||||||
didSet {
|
didSet {
|
||||||
self.update()
|
switch self.mode {
|
||||||
|
case .collapsed:
|
||||||
|
self.versionDescriptionTextView.isCollapsed = true
|
||||||
|
case .expanded:
|
||||||
|
self.versionDescriptionTextView.isCollapsed = false
|
||||||
|
}
|
||||||
|
self.setNeedsLayout()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@IBOutlet var bannerView: AppBannerView!
|
@IBOutlet var bannerView: AppBannerView!
|
||||||
@IBOutlet var versionDescriptionTextView: CollapsingTextView!
|
// @IBOutlet var versionDescriptionTextView: CollapsingTextView!
|
||||||
|
@IBOutlet var versionDescriptionTextView: CollapsingMarkdownView!
|
||||||
|
|
||||||
@IBOutlet private var blurView: UIVisualEffectView!
|
@IBOutlet private var blurView: UIVisualEffectView!
|
||||||
|
|
||||||
@@ -85,16 +92,16 @@ extension UpdateCollectionViewCell
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override func systemLayoutSizeFitting(_ targetSize: CGSize, withHorizontalFittingPriority horizontalFittingPriority: UILayoutPriority, verticalFittingPriority: UILayoutPriority) -> CGSize
|
// override func systemLayoutSizeFitting(_ targetSize: CGSize, withHorizontalFittingPriority horizontalFittingPriority: UILayoutPriority, verticalFittingPriority: UILayoutPriority) -> CGSize
|
||||||
{
|
// {
|
||||||
// Ensure cell is laid out so it will report correct size.
|
// // Ensure cell is laid out so it will report correct size.
|
||||||
self.versionDescriptionTextView.setNeedsLayout()
|
// self.versionDescriptionTextView.setNeedsLayout()
|
||||||
self.versionDescriptionTextView.layoutIfNeeded()
|
// self.versionDescriptionTextView.layoutIfNeeded()
|
||||||
|
//
|
||||||
let size = super.systemLayoutSizeFitting(targetSize, withHorizontalFittingPriority: horizontalFittingPriority, verticalFittingPriority: verticalFittingPriority)
|
// let size = super.systemLayoutSizeFitting(targetSize, withHorizontalFittingPriority: horizontalFittingPriority, verticalFittingPriority: verticalFittingPriority)
|
||||||
|
//
|
||||||
return size
|
// return size
|
||||||
}
|
// }
|
||||||
}
|
}
|
||||||
|
|
||||||
private extension UpdateCollectionViewCell
|
private extension UpdateCollectionViewCell
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="22505" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES">
|
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="23504" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES">
|
||||||
<device id="retina6_1" orientation="portrait" appearance="light"/>
|
<device id="retina6_1" orientation="portrait" appearance="light"/>
|
||||||
<dependencies>
|
<dependencies>
|
||||||
<deployment identifier="iOS"/>
|
<deployment identifier="iOS"/>
|
||||||
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="22504"/>
|
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="23506"/>
|
||||||
<capability name="Named colors" minToolsVersion="9.0"/>
|
<capability name="Named colors" minToolsVersion="9.0"/>
|
||||||
<capability name="System colors in document resources" minToolsVersion="11.0"/>
|
<capability name="System colors in document resources" minToolsVersion="11.0"/>
|
||||||
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
|
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
|
||||||
@@ -11,7 +11,7 @@
|
|||||||
<objects>
|
<objects>
|
||||||
<placeholder placeholderIdentifier="IBFilesOwner" id="-1" userLabel="File's Owner"/>
|
<placeholder placeholderIdentifier="IBFilesOwner" id="-1" userLabel="File's Owner"/>
|
||||||
<placeholder placeholderIdentifier="IBFirstResponder" id="-2" customClass="UIResponder"/>
|
<placeholder placeholderIdentifier="IBFirstResponder" id="-2" customClass="UIResponder"/>
|
||||||
<collectionViewCell opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" insetsLayoutMarginsFromSafeArea="NO" reuseIdentifier="UpdateCell" id="Kqf-Pv-ca3" customClass="UpdateCollectionViewCell" customModule="AltStore" customModuleProvider="target">
|
<collectionViewCell opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" insetsLayoutMarginsFromSafeArea="NO" reuseIdentifier="UpdateCell" id="Kqf-Pv-ca3" customClass="UpdateCollectionViewCell" customModule="SideStore" customModuleProvider="target">
|
||||||
<rect key="frame" x="0.0" y="0.0" width="375" height="125"/>
|
<rect key="frame" x="0.0" y="0.0" width="375" height="125"/>
|
||||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
|
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
|
||||||
<view key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" insetsLayoutMarginsFromSafeArea="NO">
|
<view key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" insetsLayoutMarginsFromSafeArea="NO">
|
||||||
@@ -30,7 +30,7 @@
|
|||||||
<stackView opaque="NO" contentMode="scaleToFill" axis="vertical" translatesAutoresizingMaskIntoConstraints="NO" id="uYl-PH-DuP">
|
<stackView opaque="NO" contentMode="scaleToFill" axis="vertical" translatesAutoresizingMaskIntoConstraints="NO" id="uYl-PH-DuP">
|
||||||
<rect key="frame" x="0.0" y="0.0" width="343" height="125"/>
|
<rect key="frame" x="0.0" y="0.0" width="343" height="125"/>
|
||||||
<subviews>
|
<subviews>
|
||||||
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="Nop-pL-Icx" customClass="AppBannerView" customModule="AltStore" customModuleProvider="target">
|
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="Nop-pL-Icx" customClass="AppBannerView" customModule="SideStore" customModuleProvider="target">
|
||||||
<rect key="frame" x="0.0" y="0.0" width="343" height="50"/>
|
<rect key="frame" x="0.0" y="0.0" width="343" height="50"/>
|
||||||
<constraints>
|
<constraints>
|
||||||
<constraint firstAttribute="height" constant="88" id="EPP-7O-1Ad"/>
|
<constraint firstAttribute="height" constant="88" id="EPP-7O-1Ad"/>
|
||||||
@@ -39,7 +39,7 @@
|
|||||||
<stackView opaque="NO" contentMode="scaleToFill" alignment="firstBaseline" translatesAutoresizingMaskIntoConstraints="NO" id="RSR-5W-7tt" userLabel="Release Notes">
|
<stackView opaque="NO" contentMode="scaleToFill" alignment="firstBaseline" translatesAutoresizingMaskIntoConstraints="NO" id="RSR-5W-7tt" userLabel="Release Notes">
|
||||||
<rect key="frame" x="0.0" y="50" width="343" height="75"/>
|
<rect key="frame" x="0.0" y="50" width="343" height="75"/>
|
||||||
<subviews>
|
<subviews>
|
||||||
<textView clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="scaleToFill" scrollEnabled="NO" editable="NO" text="Version Notes" textAlignment="natural" selectable="NO" translatesAutoresizingMaskIntoConstraints="NO" id="rNs-2O-k3V" customClass="CollapsingTextView" customModule="AltStore" customModuleProvider="target">
|
<textView clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="scaleToFill" scrollEnabled="NO" editable="NO" text="Version Notes" textAlignment="natural" selectable="NO" translatesAutoresizingMaskIntoConstraints="NO" id="rNs-2O-k3V" customClass="CollapsingMarkdownView" customModule="SideStore" customModuleProvider="target">
|
||||||
<rect key="frame" x="15" y="0.0" width="313" height="26"/>
|
<rect key="frame" x="15" y="0.0" width="313" height="26"/>
|
||||||
<color key="textColor" systemColor="secondaryLabelColor"/>
|
<color key="textColor" systemColor="secondaryLabelColor"/>
|
||||||
<fontDescription key="fontDescription" type="system" pointSize="13"/>
|
<fontDescription key="fontDescription" type="system" pointSize="13"/>
|
||||||
@@ -91,7 +91,7 @@
|
|||||||
<color red="1" green="1" blue="1" alpha="0.30000001192092896" colorSpace="custom" customColorSpace="sRGB"/>
|
<color red="1" green="1" blue="1" alpha="0.30000001192092896" colorSpace="custom" customColorSpace="sRGB"/>
|
||||||
</namedColor>
|
</namedColor>
|
||||||
<systemColor name="secondaryLabelColor">
|
<systemColor name="secondaryLabelColor">
|
||||||
<color red="0.23529411764705882" green="0.23529411764705882" blue="0.2627450980392157" alpha="0.59999999999999998" colorSpace="custom" customColorSpace="sRGB"/>
|
<color red="0.23529411759999999" green="0.23529411759999999" blue="0.26274509800000001" alpha="0.59999999999999998" colorSpace="custom" customColorSpace="sRGB"/>
|
||||||
</systemColor>
|
</systemColor>
|
||||||
</resources>
|
</resources>
|
||||||
</document>
|
</document>
|
||||||
|
|||||||
@@ -360,13 +360,6 @@ extension FetchProvisioningProfilesOperation
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class FetchProvisioningProfilesRefreshOperation: FetchProvisioningProfilesOperation, @unchecked Sendable {
|
|
||||||
override init(context: AppOperationContext)
|
|
||||||
{
|
|
||||||
super.init(context: context)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class FetchProvisioningProfilesInstallOperation: FetchProvisioningProfilesOperation, @unchecked Sendable{
|
class FetchProvisioningProfilesInstallOperation: FetchProvisioningProfilesOperation, @unchecked Sendable{
|
||||||
override init(context: AppOperationContext)
|
override init(context: AppOperationContext)
|
||||||
{
|
{
|
||||||
@@ -613,3 +606,14 @@ class FetchProvisioningProfilesInstallOperation: FetchProvisioningProfilesOperat
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// <TEST> : users were reporting that refresh (though seemed like it refreshed the app becomes no longer available)
|
||||||
|
// possibly, this is caused since refesh was not updating appFeatures and AppGroups in the new profile? not sure.
|
||||||
|
// for now we are reverting by keeping same operation that happens during fetch in install path to see if it fixes issue #893
|
||||||
|
// class FetchProvisioningProfilesRefreshOperation: FetchProvisioningProfilesOperation, @unchecked Sendable {
|
||||||
|
class FetchProvisioningProfilesRefreshOperation: FetchProvisioningProfilesInstallOperation, @unchecked Sendable {
|
||||||
|
override init(context: AppOperationContext)
|
||||||
|
{
|
||||||
|
super.init(context: context)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -141,7 +141,7 @@ public class InstalledApp: BaseEntity, InstalledAppProtocol
|
|||||||
let latestVer = SemanticVersion("\(latestSemVer!.major).\(latestSemVer!.minor).\(latestSemVer!.patch)")
|
let latestVer = SemanticVersion("\(latestSemVer!.major).\(latestSemVer!.minor).\(latestSemVer!.patch)")
|
||||||
|
|
||||||
// Compare by major.minor.patch
|
// Compare by major.minor.patch
|
||||||
if latestVer! > latestVer! {
|
if latestVer! > currentVer! {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
DEVELOPMENT_TEAM = XYZ0123456
|
DEVELOPMENT_TEAM = XYZ0123456
|
||||||
|
|
||||||
// Set this for dev-local xcode builds
|
// Set this for dev-local xcode builds
|
||||||
MARKETING_VERSION_SUFFIX = -local
|
//MARKETING_VERSION_SUFFIX = -local
|
||||||
|
|
||||||
// Prefix of unique bundle IDs registered to you in Apple Developer Portal.
|
// Prefix of unique bundle IDs registered to you in Apple Developer Portal.
|
||||||
// You need to register:
|
// You need to register:
|
||||||
|
|||||||
1
Makefile
1
Makefile
@@ -213,6 +213,7 @@ build-tests:
|
|||||||
@echo "Performing a build-for-testing..."
|
@echo "Performing a build-for-testing..."
|
||||||
@xcodebuild build-for-testing \
|
@xcodebuild build-for-testing \
|
||||||
-enableCodeCoverage YES \
|
-enableCodeCoverage YES \
|
||||||
|
-destination 'platform=iOS Simulator,name=iPhone 16 Pro,OS=18.2' \
|
||||||
$(COMMON_BUILD_SETTINGS)
|
$(COMMON_BUILD_SETTINGS)
|
||||||
|
|
||||||
run-tests:
|
run-tests:
|
||||||
|
|||||||
301
SideStore/Views/UIKit/CollapsingMarkdownView.swift
Normal file
301
SideStore/Views/UIKit/CollapsingMarkdownView.swift
Normal file
@@ -0,0 +1,301 @@
|
|||||||
|
//
|
||||||
|
// CollapsingMarkdownView.swift
|
||||||
|
// AltStore
|
||||||
|
//
|
||||||
|
// Created by Magesh K on 27/02/25.
|
||||||
|
// Copyright © 2025 SideStore. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
|
||||||
|
import UIKit
|
||||||
|
import MarkdownKit
|
||||||
|
|
||||||
|
struct MarkdownManager
|
||||||
|
{
|
||||||
|
struct Fonts{
|
||||||
|
static let body: UIFont = .systemFont(ofSize: UIFont.systemFontSize)
|
||||||
|
// static let body: UIFont = .systemFont(ofSize: UIFont.labelFontSize)
|
||||||
|
|
||||||
|
static let header: UIFont = .boldSystemFont(ofSize: 14)
|
||||||
|
static let list: UIFont = .systemFont(ofSize: 14)
|
||||||
|
static let bold: UIFont = .boldSystemFont(ofSize: 14)
|
||||||
|
static let italic: UIFont = .italicSystemFont(ofSize: 14)
|
||||||
|
static let quote: UIFont = .italicSystemFont(ofSize: 14)
|
||||||
|
}
|
||||||
|
|
||||||
|
struct Color{
|
||||||
|
static let header = UIColor { traitCollection in
|
||||||
|
traitCollection.userInterfaceStyle == .dark ? UIColor.white : UIColor.black
|
||||||
|
}
|
||||||
|
static let bold = UIColor { traitCollection in
|
||||||
|
traitCollection.userInterfaceStyle == .dark ? UIColor.lightText : UIColor.darkText
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static var enabledElements: MarkdownParser.EnabledElements {
|
||||||
|
[
|
||||||
|
.header,
|
||||||
|
.list,
|
||||||
|
.quote,
|
||||||
|
.code,
|
||||||
|
.link,
|
||||||
|
.bold,
|
||||||
|
.italic,
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
var markdownParser: MarkdownParser {
|
||||||
|
MarkdownParser(
|
||||||
|
font: Self.Fonts.body,
|
||||||
|
color: Self.Color.bold
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
final class CollapsingMarkdownView: UIView {
|
||||||
|
/// Called when the collapse state toggles.
|
||||||
|
var didToggleCollapse: (() -> Void)?
|
||||||
|
|
||||||
|
// MARK: - Properties
|
||||||
|
var isCollapsed = true {
|
||||||
|
didSet {
|
||||||
|
guard self.isCollapsed != oldValue else { return }
|
||||||
|
self.updateCollapsedState()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var maximumNumberOfLines = 3 {
|
||||||
|
didSet {
|
||||||
|
self.checkIfNeedsCollapsing()
|
||||||
|
self.updateCollapsedState()
|
||||||
|
self.setNeedsLayout()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var text: String = "" {
|
||||||
|
didSet {
|
||||||
|
self.updateMarkdownContent()
|
||||||
|
self.setNeedsLayout()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var lineSpacing: Double = 2 {
|
||||||
|
didSet {
|
||||||
|
self.setNeedsLayout()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let toggleButton = UIButton(type: .system)
|
||||||
|
|
||||||
|
private let textView = UITextView()
|
||||||
|
private let markdownParser = MarkdownManager().markdownParser
|
||||||
|
|
||||||
|
private var previousSize: CGSize?
|
||||||
|
private var actualLineCount: Int = 0
|
||||||
|
private var needsCollapsing = false
|
||||||
|
|
||||||
|
// MARK: - Initialization
|
||||||
|
|
||||||
|
override init(frame: CGRect) {
|
||||||
|
super.init(frame: frame)
|
||||||
|
initialize()
|
||||||
|
}
|
||||||
|
|
||||||
|
required init?(coder: NSCoder) {
|
||||||
|
super.init(coder: coder)
|
||||||
|
initialize()
|
||||||
|
}
|
||||||
|
|
||||||
|
override func awakeFromNib() {
|
||||||
|
super.awakeFromNib()
|
||||||
|
initialize()
|
||||||
|
}
|
||||||
|
|
||||||
|
private func checkIfNeedsCollapsing() {
|
||||||
|
guard bounds.width > 0, let font = textView.font, font.lineHeight > 0 else {
|
||||||
|
needsCollapsing = false
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Calculate the number of lines in the text
|
||||||
|
let textSize = textView.sizeThatFits(CGSize(width: bounds.width, height: .greatestFiniteMagnitude))
|
||||||
|
let lineHeight = font.lineHeight
|
||||||
|
|
||||||
|
// Safely calculate actual line count
|
||||||
|
actualLineCount = max(1, Int(ceil(textSize.height / lineHeight)))
|
||||||
|
|
||||||
|
// Only needs collapsing if actual lines exceed the maximum
|
||||||
|
needsCollapsing = actualLineCount > maximumNumberOfLines
|
||||||
|
|
||||||
|
// Update button visibility
|
||||||
|
toggleButton.isHidden = !needsCollapsing
|
||||||
|
}
|
||||||
|
|
||||||
|
private func updateCollapsedState() {
|
||||||
|
// Disable animations for this update
|
||||||
|
UIView.performWithoutAnimation {
|
||||||
|
// Update the button title
|
||||||
|
let title = isCollapsed ? NSLocalizedString("More", comment: "") : NSLocalizedString("Less", comment: "")
|
||||||
|
toggleButton.setTitle(title, for: .normal)
|
||||||
|
|
||||||
|
// Set max lines based on collapsed state
|
||||||
|
if isCollapsed && needsCollapsing {
|
||||||
|
textView.textContainer.maximumNumberOfLines = maximumNumberOfLines
|
||||||
|
} else {
|
||||||
|
textView.textContainer.maximumNumberOfLines = 0
|
||||||
|
}
|
||||||
|
|
||||||
|
// Button is only visible if content needs collapsing
|
||||||
|
toggleButton.isHidden = !needsCollapsing
|
||||||
|
|
||||||
|
// Force layout updates
|
||||||
|
textView.layoutIfNeeded()
|
||||||
|
self.layoutIfNeeded()
|
||||||
|
self.invalidateIntrinsicContentSize()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private func initialize() {
|
||||||
|
// Configure text view
|
||||||
|
textView.isEditable = false
|
||||||
|
textView.isScrollEnabled = false
|
||||||
|
textView.textContainerInset = .zero
|
||||||
|
textView.textContainer.lineFragmentPadding = 0
|
||||||
|
textView.textContainer.lineBreakMode = .byTruncatingTail
|
||||||
|
textView.backgroundColor = .clear
|
||||||
|
|
||||||
|
// Make textView selectable to enable link interactions
|
||||||
|
textView.isSelectable = true
|
||||||
|
textView.delegate = self
|
||||||
|
|
||||||
|
// Important: This prevents selection handles from appearing
|
||||||
|
textView.dataDetectorTypes = .link
|
||||||
|
|
||||||
|
// Configure markdown parser
|
||||||
|
configureMarkdownParser()
|
||||||
|
|
||||||
|
// Add subviews
|
||||||
|
addSubview(textView)
|
||||||
|
|
||||||
|
// Configure toggle button
|
||||||
|
toggleButton.addTarget(self, action: #selector(toggleCollapsed(_:)), for: .primaryActionTriggered)
|
||||||
|
addSubview(toggleButton)
|
||||||
|
|
||||||
|
setNeedsLayout()
|
||||||
|
}
|
||||||
|
|
||||||
|
private func configureMarkdownParser() {
|
||||||
|
// Configure markdown parser with desired settings
|
||||||
|
markdownParser.enabledElements = MarkdownManager.enabledElements
|
||||||
|
|
||||||
|
// You can also customize the styling if needed
|
||||||
|
markdownParser.header.font = MarkdownManager.Fonts.header
|
||||||
|
markdownParser.list.font = MarkdownManager.Fonts.list
|
||||||
|
markdownParser.bold.font = MarkdownManager.Fonts.bold
|
||||||
|
markdownParser.italic.font = MarkdownManager.Fonts.italic
|
||||||
|
markdownParser.quote.font = MarkdownManager.Fonts.quote
|
||||||
|
|
||||||
|
markdownParser.header.color = MarkdownManager.Color.header
|
||||||
|
markdownParser.bold.color = MarkdownManager.Color.bold
|
||||||
|
markdownParser.list.color = MarkdownManager.Color.bold
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - Layout
|
||||||
|
override func layoutSubviews() {
|
||||||
|
super.layoutSubviews()
|
||||||
|
|
||||||
|
UIView.performWithoutAnimation {
|
||||||
|
// Calculate button height (for spacing)
|
||||||
|
let buttonHeight = toggleButton.sizeThatFits(CGSize(width: 1000, height: 1000)).height
|
||||||
|
|
||||||
|
// Set textView frame to leave space for button
|
||||||
|
textView.frame = CGRect(
|
||||||
|
x: 0,
|
||||||
|
y: 0,
|
||||||
|
width: bounds.width,
|
||||||
|
height: bounds.height - buttonHeight
|
||||||
|
)
|
||||||
|
|
||||||
|
// Check if layout changed
|
||||||
|
if previousSize?.width != bounds.width {
|
||||||
|
checkIfNeedsCollapsing()
|
||||||
|
updateCollapsedState()
|
||||||
|
previousSize = bounds.size
|
||||||
|
}
|
||||||
|
|
||||||
|
// Position toggle button at bottom right
|
||||||
|
let buttonSize = toggleButton.sizeThatFits(CGSize(width: 1000, height: 1000))
|
||||||
|
toggleButton.frame = CGRect(
|
||||||
|
x: bounds.width - buttonSize.width,
|
||||||
|
y: textView.frame.maxY,
|
||||||
|
width: buttonSize.width,
|
||||||
|
height: buttonHeight
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@objc private func toggleCollapsed(_ sender: UIButton) {
|
||||||
|
isCollapsed.toggle()
|
||||||
|
didToggleCollapse?()
|
||||||
|
}
|
||||||
|
|
||||||
|
override var intrinsicContentSize: CGSize {
|
||||||
|
guard bounds.width > 0, let font = textView.font, font.lineHeight > 0 else {
|
||||||
|
return CGSize(width: UIView.noIntrinsicMetric, height: 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
let lineHeight = font.lineHeight
|
||||||
|
let buttonHeight = toggleButton.sizeThatFits(CGSize(width: 1000, height: 1000)).height
|
||||||
|
|
||||||
|
// Always add button height to reserve space for it
|
||||||
|
if isCollapsed && needsCollapsing {
|
||||||
|
// When collapsed and needs collapsing, use maximumNumberOfLines
|
||||||
|
let collapsedHeight = lineHeight * CGFloat(maximumNumberOfLines) +
|
||||||
|
lineSpacing * CGFloat(max(0, maximumNumberOfLines - 1))
|
||||||
|
return CGSize(width: UIView.noIntrinsicMetric, height: collapsedHeight + buttonHeight)
|
||||||
|
} else if !needsCollapsing {
|
||||||
|
// Text is shorter than max lines - use actual text height
|
||||||
|
let textSize = textView.sizeThatFits(CGSize(width: bounds.width, height: .greatestFiniteMagnitude))
|
||||||
|
return CGSize(width: UIView.noIntrinsicMetric, height: textSize.height + buttonHeight)
|
||||||
|
} else {
|
||||||
|
// When expanded and needs collapsing, use full text height plus button
|
||||||
|
let textSize = textView.sizeThatFits(CGSize(width: bounds.width, height: .greatestFiniteMagnitude))
|
||||||
|
return CGSize(width: UIView.noIntrinsicMetric, height: textSize.height + buttonHeight)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - Markdown Processing
|
||||||
|
private func updateMarkdownContent() {
|
||||||
|
let attributedString = markdownParser.parse(text)
|
||||||
|
|
||||||
|
// Apply line spacing
|
||||||
|
let mutableAttributedString = NSMutableAttributedString(attributedString: attributedString)
|
||||||
|
let paragraphStyle = NSMutableParagraphStyle()
|
||||||
|
paragraphStyle.lineSpacing = lineSpacing
|
||||||
|
|
||||||
|
mutableAttributedString.addAttribute(
|
||||||
|
.paragraphStyle,
|
||||||
|
value: paragraphStyle,
|
||||||
|
range: NSRange(location: 0, length: mutableAttributedString.length)
|
||||||
|
)
|
||||||
|
|
||||||
|
textView.attributedText = mutableAttributedString
|
||||||
|
|
||||||
|
// Check if content needs collapsing after setting text
|
||||||
|
checkIfNeedsCollapsing()
|
||||||
|
updateCollapsedState()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension CollapsingMarkdownView: UITextViewDelegate {
|
||||||
|
// This enables tapping on links while preventing text selection
|
||||||
|
func textView(_ textView: UITextView, shouldInteractWith URL: URL, in characterRange: NSRange, interaction: UITextItemInteraction) -> Bool {
|
||||||
|
// Open the URL using UIApplication
|
||||||
|
UIApplication.shared.open(URL)
|
||||||
|
return false // Return false to prevent the default behavior
|
||||||
|
}
|
||||||
|
|
||||||
|
// This prevents text selection
|
||||||
|
func textViewDidChangeSelection(_ textView: UITextView) {
|
||||||
|
textView.selectedTextRange = nil
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -5,7 +5,7 @@ import os
|
|||||||
import re
|
import re
|
||||||
|
|
||||||
IGNORED_AUTHORS = [
|
IGNORED_AUTHORS = [
|
||||||
"=", # probably someone used an equalTo ?! # anyway we are ignoring it!
|
|
||||||
]
|
]
|
||||||
|
|
||||||
TAG_MARKER = "###"
|
TAG_MARKER = "###"
|
||||||
@@ -82,7 +82,7 @@ def format_commit_message(msg):
|
|||||||
msg_clean = msg_clean[1:].strip() # remove leading '-' and spaces
|
msg_clean = msg_clean[1:].strip() # remove leading '-' and spaces
|
||||||
return f"- {msg_clean}"
|
return f"- {msg_clean}"
|
||||||
|
|
||||||
def generate_release_notes(last_successful, tag, branch):
|
# def generate_release_notes(last_successful, tag, branch):
|
||||||
"""Generate release notes for the given tag."""
|
"""Generate release notes for the given tag."""
|
||||||
current_commit = get_head_commit()
|
current_commit = get_head_commit()
|
||||||
messages = get_commit_messages(last_successful, current_commit)
|
messages = get_commit_messages(last_successful, current_commit)
|
||||||
@@ -117,6 +117,50 @@ def generate_release_notes(last_successful, tag, branch):
|
|||||||
|
|
||||||
return new_section
|
return new_section
|
||||||
|
|
||||||
|
def generate_release_notes(last_successful, tag, branch):
|
||||||
|
"""Generate release notes for the given tag."""
|
||||||
|
current_commit = get_head_commit()
|
||||||
|
try:
|
||||||
|
# Try to get commit messages using the provided last_successful commit
|
||||||
|
messages = get_commit_messages(last_successful, current_commit)
|
||||||
|
except subprocess.CalledProcessError:
|
||||||
|
# If the range is invalid (e.g. force push made last_successful obsolete),
|
||||||
|
# fall back to using the last 10 commits in the current branch.
|
||||||
|
print("\nInvalid revision range error, using last 10 commits as fallback.\n")
|
||||||
|
fallback_commit = run_command("git rev-parse HEAD~5")
|
||||||
|
messages = get_commit_messages(fallback_commit, current_commit)
|
||||||
|
last_successful = fallback_commit
|
||||||
|
|
||||||
|
# Start with the tag header
|
||||||
|
new_section = f"{TAG_MARKER} {tag}\n"
|
||||||
|
|
||||||
|
# What's Changed section (always present)
|
||||||
|
new_section += f"{HEADER_MARKER} What's Changed\n"
|
||||||
|
|
||||||
|
if not messages or last_successful == current_commit:
|
||||||
|
new_section += "- Nothing...\n"
|
||||||
|
else:
|
||||||
|
for msg in messages:
|
||||||
|
new_section += f"{format_commit_message(msg)}\n"
|
||||||
|
|
||||||
|
# New Contributors section (only if there are new contributors)
|
||||||
|
all_previous_authors = get_authors_in_range(f"{branch}")
|
||||||
|
recent_authors = get_authors_in_range(f"{last_successful}..{current_commit}")
|
||||||
|
new_contributors = recent_authors - all_previous_authors
|
||||||
|
|
||||||
|
if new_contributors:
|
||||||
|
new_section += f"\n{HEADER_MARKER} New Contributors\n"
|
||||||
|
for author in sorted(new_contributors):
|
||||||
|
new_section += f"- {format_contributor(author)} made their first contribution\n"
|
||||||
|
|
||||||
|
# Full Changelog section (only if there are changes)
|
||||||
|
if messages and last_successful != current_commit:
|
||||||
|
repo_url = get_repo_url()
|
||||||
|
changelog_link = f"{repo_url}/compare/{last_successful}...{current_commit}"
|
||||||
|
new_section += f"\n{HEADER_MARKER} Full Changelog: [{last_successful[:8]}...{current_commit[:8]}]({changelog_link})\n"
|
||||||
|
|
||||||
|
return new_section
|
||||||
|
|
||||||
def update_release_md(existing_content, new_section, tag):
|
def update_release_md(existing_content, new_section, tag):
|
||||||
"""
|
"""
|
||||||
Update input based on rules:
|
Update input based on rules:
|
||||||
@@ -334,4 +378,4 @@ def main():
|
|||||||
print(new_section)
|
print(new_section)
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
main()
|
main()
|
||||||
|
|||||||
3
xcconfigs/DataStructureTests.xcconfig
Normal file
3
xcconfigs/DataStructureTests.xcconfig
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
#include "../Build.xcconfig"
|
||||||
|
|
||||||
|
PRODUCT_BUNDLE_IDENTIFIER = $(PRODUCT_BUNDLE_IDENTIFIER).DataStructureTests
|
||||||
Reference in New Issue
Block a user