mirror of
https://github.com/SideStore/SideStore.git
synced 2026-02-14 09:13:25 +01:00
Compare commits
66 Commits
ny/fix/703
...
xcode-26-b
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
430ed56ca9 | ||
|
|
c74dc28773 | ||
|
|
5058eb7709 | ||
|
|
242429b161 | ||
|
|
724e8db980 | ||
|
|
669d33183e | ||
|
|
a12b6cd62b | ||
|
|
685d956775 | ||
|
|
00ed6e61be | ||
|
|
7057d59992 | ||
|
|
6d308487c1 | ||
|
|
6c45eb096f | ||
|
|
4b6ffa5d4a | ||
|
|
728b12d004 | ||
|
|
97160569ba | ||
|
|
1b754e137a | ||
|
|
4176b8c83c | ||
|
|
479f877dbf | ||
|
|
4ff643805b | ||
|
|
0e3c3dddfe | ||
|
|
7415fe6204 | ||
|
|
c128c9268b | ||
|
|
131a0289a2 | ||
|
|
116f045e51 | ||
|
|
c68efd2b44 | ||
|
|
e519389780 | ||
|
|
e6135c6518 | ||
|
|
3d444f301d | ||
|
|
7adfd3d3e8 | ||
|
|
591913743e | ||
|
|
77d95fe278 | ||
|
|
7ec6324b62 | ||
|
|
0cd62d371a | ||
|
|
9771f6bb9a | ||
|
|
e553efbad5 | ||
|
|
a4dfd28a3c | ||
|
|
291d7fd8d9 | ||
|
|
a7496e08e3 | ||
|
|
2f3be07b5d | ||
|
|
b57d279670 | ||
|
|
cbde3e6495 | ||
|
|
117f31e158 | ||
|
|
420efcbb11 | ||
|
|
dc29b65bd5 | ||
|
|
1e64f50ab9 | ||
|
|
ae8e9a3506 | ||
|
|
3785891923 | ||
|
|
e85db67ac7 | ||
|
|
39d0835f5b | ||
|
|
c8127fb3b9 | ||
|
|
729fca9100 | ||
|
|
c6703d66c1 | ||
|
|
2197161d55 | ||
|
|
cfaf79f878 | ||
|
|
2bea980d1f | ||
|
|
f11e27c712 | ||
|
|
b316e84f0d | ||
|
|
4668f8499b | ||
|
|
f9aedaba04 | ||
|
|
8cb3de9ab5 | ||
|
|
ca57d58219 | ||
|
|
6a56fbd206 | ||
|
|
cec3825de0 | ||
|
|
b3e99d1ae3 | ||
|
|
7243d79646 | ||
|
|
e50da6603c |
@@ -47,13 +47,15 @@ on:
|
||||
jobs:
|
||||
serialize:
|
||||
name: Wait for other jobs
|
||||
concurrency:
|
||||
group: build-number-increment # serialize for build num cache access
|
||||
# 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
|
||||
strategy:
|
||||
fail-fast: false
|
||||
runs-on: 'macos-15'
|
||||
steps:
|
||||
- run: echo "No other contending jobs are running now...Build is ready to start"
|
||||
- run: echo "No other contending jobs are running now..."
|
||||
- name: Set short commit hash
|
||||
id: commit-id
|
||||
run: |
|
||||
@@ -72,7 +74,7 @@ jobs:
|
||||
matrix:
|
||||
include:
|
||||
- os: 'macos-15'
|
||||
version: '16.1'
|
||||
version: '16.2'
|
||||
|
||||
runs-on: ${{ matrix.os }}
|
||||
outputs:
|
||||
@@ -88,6 +90,7 @@ jobs:
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
submodules: recursive
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Install dependencies - ldid & xcbeautify
|
||||
run: |
|
||||
@@ -174,14 +177,32 @@ jobs:
|
||||
with:
|
||||
xcode-version: ${{ matrix.version }}
|
||||
|
||||
- name: (Build) Cache Build
|
||||
uses: irgaly/xcode-cache@v1
|
||||
- name: (Build) Restore Xcode & SwiftPM Cache (Exact match)
|
||||
id: xcode-cache-restore
|
||||
uses: actions/cache/restore@v3
|
||||
with:
|
||||
key: xcode-cache-deriveddata-build-${{ github.sha }}
|
||||
restore-keys: xcode-cache-deriveddata-build-
|
||||
swiftpm-cache-key: xcode-cache-sourcedata-build-${{ github.sha }}
|
||||
swiftpm-cache-restore-keys: |
|
||||
xcode-cache-sourcedata-build-
|
||||
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
|
||||
@@ -191,7 +212,7 @@ jobs:
|
||||
./Podfile.lock
|
||||
./Pods/
|
||||
./AltStore.xcworkspace/
|
||||
key: pods-cache-build-${{ hashFiles('Podfile') }}
|
||||
key: pods-cache-build-${{ github.ref_name }}-${{ hashFiles('Podfile') }}
|
||||
# restore-keys: | # commented out to strictly check cache for this particular podfile
|
||||
# pods-cache-
|
||||
|
||||
@@ -204,7 +225,7 @@ jobs:
|
||||
./Podfile.lock
|
||||
./Pods/
|
||||
./AltStore.xcworkspace/
|
||||
key: pods-cache-build-
|
||||
key: pods-cache-build-${{ github.ref_name }}-
|
||||
|
||||
|
||||
- name: (Build) Install CocoaPods
|
||||
@@ -219,7 +240,7 @@ jobs:
|
||||
./Podfile.lock
|
||||
./Pods/
|
||||
./AltStore.xcworkspace/
|
||||
key: pods-cache-build-${{ hashFiles('Podfile') }}
|
||||
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
|
||||
@@ -266,6 +287,16 @@ jobs:
|
||||
- name: Convert to IPA
|
||||
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
|
||||
run: |
|
||||
echo ">>>>>>>>> Workdir <<<<<<<<<<"
|
||||
@@ -284,6 +315,10 @@ jobs:
|
||||
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 ""
|
||||
|
||||
- name: Encrypt build-logs for upload
|
||||
id: encrypt-build-log
|
||||
run: |
|
||||
@@ -322,19 +357,69 @@ jobs:
|
||||
name: SideStore-${{ steps.version.outputs.version }}-dSYMs.zip
|
||||
path: SideStore.dSYMs.zip
|
||||
|
||||
- name: Zip beta-beta-build-num & update_apps.py
|
||||
run: |
|
||||
zip -r -9 ./beta-build-num.zip ./SideStore/beta-build-num update_apps.py
|
||||
|
||||
- name: Upload beta-build-num artifact
|
||||
- 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 }} - ${{ needs.serialize.outputs.short-commit }} deployment" || echo "No changes to commit"
|
||||
|
||||
echo "Pushing to remote repo"
|
||||
git push --verbose
|
||||
popd
|
||||
|
||||
- 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 }}
|
||||
|
||||
- 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
|
||||
|
||||
- name: Upload release-notes.md
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: beta-build-num-${{ steps.version.outputs.version }}.zip
|
||||
path: beta-build-num.zip
|
||||
name: release-notes-${{ needs.serialize.outputs.short-commit }}.md
|
||||
path: release-notes.md
|
||||
|
||||
- name: Upload update_release_notes.py
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: update_release_notes-${{ needs.serialize.outputs.short-commit }}.py
|
||||
path: update_release_notes.py
|
||||
|
||||
- name: Upload update_apps.py
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: update_apps-${{ needs.serialize.outputs.short-commit }}.py
|
||||
path: update_apps.py
|
||||
|
||||
tests-build:
|
||||
if: ${{ vars.ENABLE_TESTS == '1' && vars.ENABLE_TESTS_BUILD == '1' }}
|
||||
name: Tests-Build SideStore - ${{ inputs.release_tag }}
|
||||
needs: serialize
|
||||
strategy:
|
||||
@@ -342,7 +427,7 @@ jobs:
|
||||
matrix:
|
||||
include:
|
||||
- os: 'macos-15'
|
||||
version: '16.1'
|
||||
version: '16.2'
|
||||
runs-on: ${{ matrix.os }}
|
||||
|
||||
steps:
|
||||
@@ -358,16 +443,36 @@ jobs:
|
||||
- name: Setup Xcode
|
||||
uses: maxim-lobanov/setup-xcode@v1.6.0
|
||||
with:
|
||||
xcode-version: '16.1'
|
||||
xcode-version: '16.2'
|
||||
|
||||
- name: (Tests-Build) Cache Build
|
||||
uses: irgaly/xcode-cache@v1
|
||||
# - 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:
|
||||
key: xcode-cache-deriveddata-test-${{ github.sha }}
|
||||
restore-keys: xcode-cache-deriveddata-test-
|
||||
swiftpm-cache-key: xcode-cache-sourcedata-test-${{ github.sha }}
|
||||
swiftpm-cache-restore-keys: |
|
||||
xcode-cache-sourcedata-test-
|
||||
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
|
||||
@@ -377,7 +482,7 @@ jobs:
|
||||
./Podfile.lock
|
||||
./Pods/
|
||||
./AltStore.xcworkspace/
|
||||
key: pods-cache-test-${{ hashFiles('Podfile') }}
|
||||
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' }}
|
||||
@@ -388,7 +493,7 @@ jobs:
|
||||
./Podfile.lock
|
||||
./Pods/
|
||||
./AltStore.xcworkspace/
|
||||
key: pods-cache-test-
|
||||
key: pods-cache-test-${{ github.ref_name }}-
|
||||
|
||||
- name: (Tests-Build) Install CocoaPods
|
||||
run: pod install
|
||||
@@ -401,8 +506,15 @@ jobs:
|
||||
./Podfile.lock
|
||||
./Pods/
|
||||
./AltStore.xcworkspace/
|
||||
key: pods-cache-test-${{ 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
|
||||
run: |
|
||||
make clean
|
||||
@@ -435,6 +547,16 @@ jobs:
|
||||
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()
|
||||
run: |
|
||||
@@ -446,6 +568,16 @@ jobs:
|
||||
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-${{ needs.serialize.outputs.short-commit }}.txt
|
||||
path: tests-build-deriveddata.txt
|
||||
|
||||
- name: Encrypt tests-build-logs for upload
|
||||
id: encrypt-test-log
|
||||
if: always()
|
||||
@@ -471,6 +603,7 @@ jobs:
|
||||
path: encrypted-tests-build-logs.zip
|
||||
|
||||
tests-run:
|
||||
if: ${{ vars.ENABLE_TESTS == '1' && vars.ENABLE_TESTS_RUN == '1' }}
|
||||
name: Tests-Run SideStore - ${{ inputs.release_tag }}
|
||||
needs: [serialize, tests-build]
|
||||
strategy:
|
||||
@@ -478,7 +611,7 @@ jobs:
|
||||
matrix:
|
||||
include:
|
||||
- os: 'macos-15'
|
||||
version: '16.1'
|
||||
version: '16.2'
|
||||
runs-on: ${{ matrix.os }}
|
||||
|
||||
steps:
|
||||
@@ -487,25 +620,31 @@ jobs:
|
||||
with:
|
||||
submodules: recursive
|
||||
|
||||
- name: Boot Simulator for testing
|
||||
- name: Boot Simulator async(nohup) for testing
|
||||
run: |
|
||||
mkdir -p build/logs
|
||||
make -B boot-sim-async | tee -a build/logs/tests-run.log
|
||||
exit ${PIPESTATUS[0]}
|
||||
nohup make -B boot-sim-async </dev/null >> build/logs/tests-run.log 2>&1 &
|
||||
|
||||
- name: Setup Xcode
|
||||
uses: maxim-lobanov/setup-xcode@v1.6.0
|
||||
with:
|
||||
xcode-version: '16.1'
|
||||
xcode-version: '16.2'
|
||||
|
||||
- name: (Tests-Run) Cache Build
|
||||
uses: irgaly/xcode-cache@v1
|
||||
# - 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:
|
||||
key: xcode-cache-deriveddata-test-${{ github.sha }}
|
||||
restore-keys: xcode-cache-deriveddata-test-
|
||||
swiftpm-cache-key: xcode-cache-sourcedata-test-${{ github.sha }}
|
||||
swiftpm-cache-restore-keys: |
|
||||
xcode-cache-sourcedata-test-
|
||||
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
|
||||
@@ -515,7 +654,7 @@ jobs:
|
||||
./Podfile.lock
|
||||
./Pods/
|
||||
./AltStore.xcworkspace/
|
||||
key: pods-cache-test-${{ hashFiles('Podfile') }}
|
||||
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' }}
|
||||
@@ -526,7 +665,7 @@ jobs:
|
||||
./Podfile.lock
|
||||
./Pods/
|
||||
./AltStore.xcworkspace/
|
||||
key: pods-cache-test-
|
||||
key: pods-cache-test-${{ github.ref_name }}-
|
||||
|
||||
- name: (Tests-Run) Install CocoaPods
|
||||
run: pod install
|
||||
@@ -539,7 +678,7 @@ jobs:
|
||||
./Podfile.lock
|
||||
./Pods/
|
||||
./AltStore.xcworkspace/
|
||||
key: pods-cache-test-${{ hashFiles('Podfile') }}
|
||||
key: pods-cache-test-${{ github.ref_name }}-${{ hashFiles('Podfile') }}
|
||||
|
||||
- name: (Tests-Run) Clean previous build artifacts
|
||||
run: |
|
||||
@@ -565,9 +704,15 @@ jobs:
|
||||
echo ""
|
||||
|
||||
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 ""
|
||||
|
||||
- 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
|
||||
- name: Simulator Boot Check
|
||||
run: |
|
||||
@@ -689,34 +834,51 @@ jobs:
|
||||
name: encrypted-build-logs-${{ needs.build.outputs.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-${{ needs.serialize.outputs.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-${{ needs.serialize.outputs.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-${{ needs.serialize.outputs.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-${{ needs.serialize.outputs.short-commit }}.zip
|
||||
|
||||
- name: Download beta-build-num artifact
|
||||
if: ${{ inputs.is_beta }}
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: beta-build-num-${{ needs.build.outputs.version }}.zip
|
||||
- name: Un-Zip beta-beta-build-num & update_apps.py
|
||||
run: |
|
||||
unzip beta-build-num.zip -d .
|
||||
name: test-results-${{ needs.serialize.outputs.short-commit }}.zip
|
||||
|
||||
- name: Download release-notes.md
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: release-notes-${{ needs.serialize.outputs.short-commit }}.md
|
||||
|
||||
- name: Download update_release_notes.py
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: update_release_notes-${{ needs.serialize.outputs.short-commit }}.py
|
||||
|
||||
- name: Download update_apps.py
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: update_apps-${{ needs.serialize.outputs.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
|
||||
|
||||
- name: List files before upload
|
||||
run: |
|
||||
@@ -754,22 +916,7 @@ jobs:
|
||||
Commit SHA: `${{ github.sha }}`
|
||||
Version: `${{ needs.build.outputs.version }}`
|
||||
|
||||
- name: Publish to SideStore/beta-build-num
|
||||
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 }} - ${{ needs.serialize.outputs.short-commit }} deployment" || echo "No changes to commit"
|
||||
|
||||
echo "Pushing to remote repo"
|
||||
git push --verbose
|
||||
popd
|
||||
${{ steps.release_notes.outputs.content }}
|
||||
|
||||
- name: Get formatted date
|
||||
run: |
|
||||
@@ -803,6 +950,9 @@ jobs:
|
||||
- version: "${{ needs.build.outputs.version }}"
|
||||
- revision: "${{ needs.serialize.outputs.short-commit }}"
|
||||
- timestamp: "${{ steps.date.outputs.date }}"
|
||||
|
||||
Release Notes:
|
||||
${{ steps.release_notes.outputs.content }}
|
||||
EOF
|
||||
)
|
||||
|
||||
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
|
||||
|
||||
jobs:
|
||||
Reuseable-build:
|
||||
uses: ./.github/workflows/reusable-build-workflow.yml
|
||||
Reusable-build:
|
||||
uses: ./.github/workflows/reusable-sidestore-build.yml
|
||||
with:
|
||||
# bundle_id: "com.SideStore.SideStore.Alpha"
|
||||
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 }}
|
||||
LAST_SUCCESS: ${{ env.last_success }}
|
||||
|
||||
Reuseable-build:
|
||||
Reusable-build:
|
||||
if: |
|
||||
always() &&
|
||||
(github.event_name == 'push' ||
|
||||
(github.event_name == 'schedule' && needs.check-changes.result == 'success' && needs.check-changes.outputs.has_changes == 'true'))
|
||||
needs: check-changes
|
||||
uses: ./.github/workflows/reusable-build-workflow.yml
|
||||
uses: ./.github/workflows/reusable-sidestore-build.yml
|
||||
with:
|
||||
# bundle_id: "com.SideStore.SideStore.Nightly"
|
||||
bundle_id: "com.SideStore.SideStore"
|
||||
|
||||
4
.github/workflows/pr.yml
vendored
4
.github/workflows/pr.yml
vendored
@@ -12,8 +12,8 @@ jobs:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
include:
|
||||
- os: 'macos-14'
|
||||
version: '16.1'
|
||||
- os: 'macos-15'
|
||||
version: '26.0-beta'
|
||||
|
||||
runs-on: ${{ matrix.os }}
|
||||
steps:
|
||||
|
||||
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:
|
||||
build:
|
||||
name: Build and upload SideStore
|
||||
name: Build SideStore - stable (on tag push)
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
include:
|
||||
- os: 'macos-14'
|
||||
version: '15.4'
|
||||
|
||||
- os: 'macos-15'
|
||||
version: '26.0'
|
||||
runs-on: ${{ matrix.os }}
|
||||
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
submodules: recursive
|
||||
|
||||
- name: Install dependencies
|
||||
run: brew install ldid
|
||||
- name: Echo Build.xcconfig
|
||||
run: |
|
||||
echo "cat Build.xcconfig"
|
||||
cat Build.xcconfig
|
||||
shell: bash
|
||||
|
||||
- name: Change version to tag
|
||||
run: sed -e '/MARKETING_VERSION = .*/s/= .*/= ${{ github.ref_name }}/' -i '' Build.xcconfig
|
||||
# - name: Change MARKETING_VERSION to the pushed tag that triggered this build
|
||||
# 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
|
||||
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
|
||||
run: echo "${{ steps.version.outputs.version }}"
|
||||
echo "MARKETING_VERSION=$version" >> $GITHUB_ENV
|
||||
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
|
||||
uses: maxim-lobanov/setup-xcode@v1.6.0
|
||||
with:
|
||||
xcode-version: ${{ matrix.version }}
|
||||
|
||||
- name: Cache Build
|
||||
uses: irgaly/xcode-cache@v1
|
||||
- name: (Build) Restore Xcode & SwiftPM Cache (Exact match)
|
||||
id: xcode-cache-restore
|
||||
uses: actions/cache/restore@v3
|
||||
with:
|
||||
key: xcode-cache-deriveddata-${{ github.sha }}
|
||||
restore-keys: xcode-cache-deriveddata-
|
||||
swiftpm-cache-key: xcode-cache-sourcedata-${{ github.sha }}
|
||||
swiftpm-cache-restore-keys: |
|
||||
xcode-cache-sourcedata-
|
||||
path: |
|
||||
~/Library/Developer/Xcode/DerivedData
|
||||
~/Library/Caches/org.swift.swiftpm
|
||||
key: xcode-cache-build-stable-${{ github.sha }}
|
||||
|
||||
- name: Build SideStore
|
||||
run: NSUnbufferedIO=YES make build | xcbeautify --renderer github-actions && exit ${PIPESTATUS[0]}
|
||||
- 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-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
|
||||
run: make fakesign
|
||||
run: make fakesign | tee -a build/logs/build.log
|
||||
shell: bash
|
||||
|
||||
- name: Convert to IPA
|
||||
run: make ipa
|
||||
run: make ipa | tee -a build/logs/build.log
|
||||
shell: bash
|
||||
|
||||
- name: Get current date
|
||||
id: date
|
||||
run: echo "date=$(date -u +'%c')" >> $GITHUB_OUTPUT
|
||||
|
||||
- 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
|
||||
- name: (Build) Save Xcode & SwiftPM Cache
|
||||
id: cache-save
|
||||
if: ${{ steps.xcode-cache-restore.outputs.cache-hit != 'true' }}
|
||||
uses: actions/cache/save@v3
|
||||
with:
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
name: ${{ steps.version.outputs.version }}
|
||||
tag_name: ${{ github.ref_name }}
|
||||
draft: true
|
||||
files: SideStore.ipa
|
||||
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 }}`
|
||||
path: |
|
||||
~/Library/Developer/Xcode/DerivedData
|
||||
~/Library/Caches/org.swift.swiftpm
|
||||
key: xcode-cache-build-stable-${{ github.sha }}
|
||||
|
||||
- name: (Build) List Files and Build artifacts
|
||||
run: |
|
||||
echo ">>>>>>>>> Workdir <<<<<<<<<<"
|
||||
ls -la .
|
||||
echo ""
|
||||
|
||||
- name: Add version to IPA file name
|
||||
run: mv SideStore.ipa SideStore-${{ steps.version.outputs.version }}.ipa
|
||||
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-${{ 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 }}-dSYM
|
||||
path: ./*.dSYM/
|
||||
name: SideStore-${{ steps.version.outputs.version }}-dSYMs.zip
|
||||
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 }}`
|
||||
|
||||
6
.gitmodules
vendored
6
.gitmodules
vendored
@@ -30,7 +30,7 @@
|
||||
url = https://github.com/rileytestut/Roxas.git
|
||||
[submodule "Dependencies/libimobiledevice"]
|
||||
path = Dependencies/libimobiledevice
|
||||
url = https://github.com/libimobiledevice/libimobiledevice
|
||||
url = https://github.com/SideStore/libimobiledevice
|
||||
[submodule "Dependencies/libusbmuxd"]
|
||||
path = Dependencies/libusbmuxd
|
||||
url = https://github.com/libimobiledevice/libusbmuxd.git
|
||||
@@ -50,10 +50,6 @@
|
||||
path = SideStore/minimuxer
|
||||
url = https://github.com/SideStore/minimuxer
|
||||
branch = master
|
||||
[submodule "SideStore/em_proxy"]
|
||||
path = SideStore/em_proxy
|
||||
url = https://github.com/SideStore/em_proxy
|
||||
branch = master
|
||||
[submodule "SideStore/libfragmentzip"]
|
||||
path = SideStore/libfragmentzip
|
||||
url = https://github.com/SideStore/libfragmentzip
|
||||
|
||||
@@ -44,7 +44,6 @@
|
||||
0EE7FDC92BE8D07400D1E390 /* NSError+AltStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF6C336124197D700034FD24 /* NSError+AltStore.swift */; };
|
||||
0EE7FDCB2BE8D12B00D1E390 /* ALTLocalizedError.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EE7FDC32BE8BC7900D1E390 /* ALTLocalizedError.swift */; };
|
||||
0EE7FDCD2BE9124400D1E390 /* ErrorDetailsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EE7FDCC2BE9124400D1E390 /* ErrorDetailsViewController.swift */; };
|
||||
19104DB52909C06D00C49C7B /* EmotionalDamage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19104DB42909C06D00C49C7B /* EmotionalDamage.swift */; };
|
||||
19B9B7452845E6DF0076EF69 /* SelectTeamViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 19B9B7442845E6DF0076EF69 /* SelectTeamViewController.swift */; };
|
||||
551A15E55999499418AC1022 /* Pods_AltStoreCore.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 707E746318F0B6F1A44935D3 /* Pods_AltStoreCore.framework */; };
|
||||
A800F7042CE28E3800208744 /* View+AltWidget.swift in Sources */ = {isa = PBXBuildFile; fileRef = A800F7032CE28E2F00208744 /* View+AltWidget.swift */; };
|
||||
@@ -53,12 +52,20 @@
|
||||
A8096D182D30AD4F000C39C6 /* WidgetUpdateIntent.swift in Sources */ = {isa = PBXBuildFile; fileRef = A8096D172D30AD4F000C39C6 /* WidgetUpdateIntent.swift */; };
|
||||
A8096D1C2D30ADA9000C39C6 /* ActiveAppsTimelineProvider+Simulator.swift in Sources */ = {isa = PBXBuildFile; fileRef = A8096D1B2D30ADA9000C39C6 /* ActiveAppsTimelineProvider+Simulator.swift */; };
|
||||
A809F69E2D04D7AC00F0F0F3 /* libminimuxer_static.a in Frameworks */ = {isa = PBXBuildFile; fileRef = A809F68E2D04D71200F0F0F3 /* libminimuxer_static.a */; };
|
||||
A809F69F2D04D7B300F0F0F3 /* libem_proxy_static.a in Frameworks */ = {isa = PBXBuildFile; fileRef = A809F6942D04D71200F0F0F3 /* libem_proxy_static.a */; };
|
||||
A809F6A82D04DA1900F0F0F3 /* minimuxer.swift in Sources */ = {isa = PBXBuildFile; fileRef = A809F6A32D04DA1900F0F0F3 /* minimuxer.swift */; };
|
||||
A809F6A92D04DA1900F0F0F3 /* SwiftBridgeCore.swift in Sources */ = {isa = PBXBuildFile; fileRef = A809F6A72D04DA1900F0F0F3 /* SwiftBridgeCore.swift */; };
|
||||
A80D60D32D3DD85100CEF65D /* ReleaseTrack.swift in Sources */ = {isa = PBXBuildFile; fileRef = A80D60D12D3D705F00CEF65D /* ReleaseTrack.swift */; };
|
||||
A80D790D2D2F20AF00A40F40 /* PaginationIntent.swift in Sources */ = {isa = PBXBuildFile; fileRef = A80D790C2D2F20AF00A40F40 /* PaginationIntent.swift */; };
|
||||
A80D790F2D2F217000A40F40 /* PaginationDataHolder.swift in Sources */ = {isa = PBXBuildFile; fileRef = A80D790E2D2F217000A40F40 /* PaginationDataHolder.swift */; };
|
||||
A815AA952D90D2A100929A9E /* StoreApp17To17_1MigrationPolicy.swift in Sources */ = {isa = PBXBuildFile; fileRef = A815AA942D90D2A100929A9E /* StoreApp17To17_1MigrationPolicy.swift */; };
|
||||
A815AA972D90E16400929A9E /* ReleaseTrack17To17_1MigrationPolicy.swift in Sources */ = {isa = PBXBuildFile; fileRef = A815AA962D90E16400929A9E /* ReleaseTrack17To17_1MigrationPolicy.swift */; };
|
||||
A815AA992D90E5E500929A9E /* AltStore17ToAltStore17_1.xcmappingmodel in Sources */ = {isa = PBXBuildFile; fileRef = A815AA982D90E5E500929A9E /* AltStore17ToAltStore17_1.xcmappingmodel */; };
|
||||
A815AA9F2D9104DD00929A9E /* BuildInfo.swift in Sources */ = {isa = PBXBuildFile; fileRef = A815AA9E2D9104DD00929A9E /* BuildInfo.swift */; };
|
||||
A815AAA12D9108BC00929A9E /* AltStore11ToAltStore17_1.xcmappingmodel in Sources */ = {isa = PBXBuildFile; fileRef = A815AAA02D9108BC00929A9E /* AltStore11ToAltStore17_1.xcmappingmodel */; };
|
||||
A815AAA72D9108CB00929A9E /* ReleaseTrack11To17_1MigrationPolicy.swift in Sources */ = {isa = PBXBuildFile; fileRef = A815AAA42D9108CB00929A9E /* ReleaseTrack11To17_1MigrationPolicy.swift */; };
|
||||
A815AAA82D9108CB00929A9E /* AppPermission11To17_1MigrationPolicy.swift in Sources */ = {isa = PBXBuildFile; fileRef = A815AAA52D9108CB00929A9E /* AppPermission11To17_1MigrationPolicy.swift */; };
|
||||
A815AAA92D9108CB00929A9E /* Source11To17_1MigrationPolicy.swift in Sources */ = {isa = PBXBuildFile; fileRef = A815AAA22D9108CB00929A9E /* Source11To17_1MigrationPolicy.swift */; };
|
||||
A815AAAA2D9108CB00929A9E /* StoreApp11To17_1MigrationPolicy.swift in Sources */ = {isa = PBXBuildFile; fileRef = A815AAA32D9108CB00929A9E /* StoreApp11To17_1MigrationPolicy.swift */; };
|
||||
A81A8CB92D68B30B0086C96F /* SingletonGenericMap.swift in Sources */ = {isa = PBXBuildFile; fileRef = A868CFE32D319988002F1201 /* SingletonGenericMap.swift */; };
|
||||
A81A8CBA2D68B3110086C96F /* TreeMap.swift in Sources */ = {isa = PBXBuildFile; fileRef = A81A8CB02D68B0320086C96F /* TreeMap.swift */; };
|
||||
A81A8CBD2D68B43F0086C96F /* LinkedHashMap.swift in Sources */ = {isa = PBXBuildFile; fileRef = A81A8CBC2D68B43F0086C96F /* LinkedHashMap.swift */; };
|
||||
@@ -69,6 +76,8 @@
|
||||
A81A8CD12D68BA9B0086C96F /* TreeMap.swift in Sources */ = {isa = PBXBuildFile; fileRef = A81A8CB02D68B0320086C96F /* TreeMap.swift */; };
|
||||
A81A8CD22D68BAA30086C96F /* SingletonGenericMap.swift in Sources */ = {isa = PBXBuildFile; fileRef = A868CFE32D319988002F1201 /* SingletonGenericMap.swift */; };
|
||||
A81A8CD42D68BAFF0086C96F /* DataStructureTests.xctestplan in Resources */ = {isa = PBXBuildFile; fileRef = A81A8CD32D68BAFF0086C96F /* DataStructureTests.xctestplan */; };
|
||||
A81BF9E52D84CB0C00768940 /* AppPermission17To17_1MigrationPolicy.swift in Sources */ = {isa = PBXBuildFile; fileRef = A81BF9E22D84CB0C00768940 /* AppPermission17To17_1MigrationPolicy.swift */; };
|
||||
A81BF9E72D84CB0C00768940 /* Source17To17_1MigrationPolicy.swift in Sources */ = {isa = PBXBuildFile; fileRef = A81BF9E32D84CB0C00768940 /* Source17To17_1MigrationPolicy.swift */; };
|
||||
A82067842D03DC0600645C0D /* OperatingSystemVersion+Comparable.swift in Sources */ = {isa = PBXBuildFile; fileRef = D5708416292448DA00D42D34 /* OperatingSystemVersion+Comparable.swift */; };
|
||||
A82067C42D03E0DE00645C0D /* SemanticVersion in Frameworks */ = {isa = PBXBuildFile; productRef = A82067C32D03E0DE00645C0D /* SemanticVersion */; };
|
||||
A8228B5B2D6E2C0C00F7CE0E /* (null) in Sources */ = {isa = PBXBuildFile; };
|
||||
@@ -77,7 +86,6 @@
|
||||
A859ED5D2D1EE827003DCC58 /* OpenSSL.xcframework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = A859ED5B2D1EE80D003DCC58 /* OpenSSL.xcframework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
|
||||
A86315DF2D3EB2DE0048FA40 /* ErrorProcessing.swift in Sources */ = {isa = PBXBuildFile; fileRef = A86315DE2D3EB2D80048FA40 /* ErrorProcessing.swift */; };
|
||||
A8696EE42D34512C00E96389 /* RemoveAppExtensionsOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = A8696EE32D34512C00E96389 /* RemoveAppExtensionsOperation.swift */; };
|
||||
A881E7C72D6EF58C00954AD2 /* AltStore11ToAltStore17.xcmappingmodel in Sources */ = {isa = PBXBuildFile; fileRef = A881E7C62D6EF58C00954AD2 /* AltStore11ToAltStore17.xcmappingmodel */; };
|
||||
A881E7CB2D6EF5AB00954AD2 /* StoreApp11To17MigrationPolicy.swift in Sources */ = {isa = PBXBuildFile; fileRef = A881E7CA2D6EF5AB00954AD2 /* StoreApp11To17MigrationPolicy.swift */; };
|
||||
A88B8C492D35AD3200F53F9D /* OperationsLoggingContolView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A88B8C482D35AD3200F53F9D /* OperationsLoggingContolView.swift */; };
|
||||
A88B8C552D35F1EC00F53F9D /* OperationsLoggingControl.swift in Sources */ = {isa = PBXBuildFile; fileRef = A88B8C542D35F1EC00F53F9D /* OperationsLoggingControl.swift */; };
|
||||
@@ -87,6 +95,9 @@
|
||||
A8AD35592D31BF2C003A28B4 /* PageInfoManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = A8AD35582D31BF29003A28B4 /* PageInfoManager.swift */; };
|
||||
A8B516E32D2666CA0047047C /* CoreDataHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = A8B516E22D2666CA0047047C /* CoreDataHelper.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 */; };
|
||||
A8C38C242D206A3A00E83DBD /* ConsoleLogger.swift in Sources */ = {isa = PBXBuildFile; fileRef = A8C38C1D2D206A3A00E83DBD /* ConsoleLogger.swift */; };
|
||||
A8C38C262D206A3A00E83DBD /* ConsoleLog.swift in Sources */ = {isa = PBXBuildFile; fileRef = A8C38C1E2D206A3A00E83DBD /* ConsoleLog.swift */; };
|
||||
@@ -100,13 +111,13 @@
|
||||
A8C6D5142D1EE8D700DF01F1 /* OpenSSL.xcframework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = A859ED5B2D1EE80D003DCC58 /* OpenSSL.xcframework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
|
||||
A8C6D5172D1EE95B00DF01F1 /* OpenSSL.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = A859ED5B2D1EE80D003DCC58 /* OpenSSL.xcframework */; };
|
||||
A8C6D5182D1EE95B00DF01F1 /* OpenSSL.xcframework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = A859ED5B2D1EE80D003DCC58 /* OpenSSL.xcframework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
|
||||
A8C924242D8E73B1009D9953 /* AltStore11ToAltStore17.xcmappingmodel in Sources */ = {isa = PBXBuildFile; fileRef = A8C924232D8E73B1009D9953 /* AltStore11ToAltStore17.xcmappingmodel */; };
|
||||
A8D484D82D0CD306002C691D /* AltBackup.ipa in Resources */ = {isa = PBXBuildFile; fileRef = A8D484D72D0CD306002C691D /* AltBackup.ipa */; };
|
||||
A8D49F532D3D2F9400844B92 /* ProcessInfo+AltStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = A8D49F522D3D2F9400844B92 /* ProcessInfo+AltStore.swift */; };
|
||||
A8E2DB312D684E2A009E5D31 /* UITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = A8E2DB2E2D684E2A009E5D31 /* UITests.swift */; };
|
||||
A8E2DB322D684E2A009E5D31 /* UITestsLaunchTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = A8E2DB2F2D684E2A009E5D31 /* UITestsLaunchTests.swift */; };
|
||||
A8E2DB342D68507F009E5D31 /* SideStoreTests.xctestplan in Resources */ = {isa = PBXBuildFile; fileRef = A8E2DB332D68507F009E5D31 /* SideStoreTests.xctestplan */; };
|
||||
A8EA195F2D4982D600DC6322 /* BaseEntity.swift in Sources */ = {isa = PBXBuildFile; fileRef = A8EA195E2D4982D600DC6322 /* BaseEntity.swift */; };
|
||||
A8F838922D048E8F00ED425D /* libEmotionalDamage.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 19104DB22909C06C00C49C7B /* libEmotionalDamage.a */; };
|
||||
A8F838932D048E8F00ED425D /* libminimuxer.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 191E5FAB290A5D92001A3B7C /* libminimuxer.a */; };
|
||||
A8F838942D048ECE00ED425D /* libimobiledevice.a in Frameworks */ = {isa = PBXBuildFile; fileRef = BF45872B2298D31600BD7491 /* libimobiledevice.a */; };
|
||||
A8FD915E2D046F5200322782 /* UserInfoValue.swift in Sources */ = {isa = PBXBuildFile; fileRef = A8FD915D2D046F5200322782 /* UserInfoValue.swift */; };
|
||||
@@ -116,7 +127,6 @@
|
||||
A8FD917C2D0478D200322782 /* VerificationError.swift in Sources */ = {isa = PBXBuildFile; fileRef = D5CF56812A0D83F9006D93E2 /* VerificationError.swift */; };
|
||||
B33FFBAA295F8F78002259E6 /* preboard.c in Sources */ = {isa = PBXBuildFile; fileRef = B33FFBA9295F8F78002259E6 /* preboard.c */; };
|
||||
B33FFBAC295F8F98002259E6 /* companion_proxy.c in Sources */ = {isa = PBXBuildFile; fileRef = B33FFBAB295F8F98002259E6 /* companion_proxy.c */; };
|
||||
B343F86D295F759E002B1159 /* libresolv.tbd in Frameworks */ = {isa = PBXBuildFile; fileRef = B343F86C295F759E002B1159 /* libresolv.tbd */; };
|
||||
B343F87C295F7C5D002B1159 /* opack.c in Sources */ = {isa = PBXBuildFile; fileRef = B343F872295F7C5C002B1159 /* opack.c */; };
|
||||
B343F87D295F7C5D002B1159 /* cbuf.c in Sources */ = {isa = PBXBuildFile; fileRef = B343F873295F7C5C002B1159 /* cbuf.c */; };
|
||||
B343F87E295F7C5D002B1159 /* collection.c in Sources */ = {isa = PBXBuildFile; fileRef = B343F874295F7C5D002B1159 /* collection.c */; };
|
||||
@@ -437,13 +447,6 @@
|
||||
/* End PBXBuildFile section */
|
||||
|
||||
/* Begin PBXContainerItemProxy section */
|
||||
19104DB92909C0F200C49C7B /* PBXContainerItemProxy */ = {
|
||||
isa = PBXContainerItemProxy;
|
||||
containerPortal = BFD247622284B9A500981D42 /* Project object */;
|
||||
proxyType = 1;
|
||||
remoteGlobalIDString = 19104DB12909C06C00C49C7B;
|
||||
remoteInfo = EmotionalDamage;
|
||||
};
|
||||
191E5FB2290A5D9B001A3B7C /* PBXContainerItemProxy */ = {
|
||||
isa = PBXContainerItemProxy;
|
||||
containerPortal = BFD247622284B9A500981D42 /* Project object */;
|
||||
@@ -458,20 +461,6 @@
|
||||
remoteGlobalIDString = CA609C732349C7AAD9FA67C4;
|
||||
remoteInfo = "minimuxer-staticlib";
|
||||
};
|
||||
A809F6932D04D71200F0F0F3 /* PBXContainerItemProxy */ = {
|
||||
isa = PBXContainerItemProxy;
|
||||
containerPortal = B343F84D295F6323002B1159 /* em_proxy.xcodeproj */;
|
||||
proxyType = 2;
|
||||
remoteGlobalIDString = CA60C44C93D7916DE57E6EBD;
|
||||
remoteInfo = "em_proxy-staticlib";
|
||||
};
|
||||
A809F6952D04D71200F0F0F3 /* PBXContainerItemProxy */ = {
|
||||
isa = PBXContainerItemProxy;
|
||||
containerPortal = B343F84D295F6323002B1159 /* em_proxy.xcodeproj */;
|
||||
proxyType = 2;
|
||||
remoteGlobalIDString = CA60058A9FBE4D17AF51A7D5;
|
||||
remoteInfo = "run-bin";
|
||||
};
|
||||
A809F6A02D04D7CB00F0F0F3 /* PBXContainerItemProxy */ = {
|
||||
isa = PBXContainerItemProxy;
|
||||
containerPortal = A8F66C602D04D464009689E6 /* minimuxer.xcodeproj */;
|
||||
@@ -635,8 +624,6 @@
|
||||
0EE7FDC22BE8BC4200D1E390 /* ALTWrappedError.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ALTWrappedError.h; sourceTree = "<group>"; };
|
||||
0EE7FDC32BE8BC7900D1E390 /* ALTLocalizedError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ALTLocalizedError.swift; sourceTree = "<group>"; };
|
||||
0EE7FDCC2BE9124400D1E390 /* ErrorDetailsViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ErrorDetailsViewController.swift; sourceTree = "<group>"; };
|
||||
19104DB22909C06C00C49C7B /* libEmotionalDamage.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libEmotionalDamage.a; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
19104DB42909C06D00C49C7B /* EmotionalDamage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EmotionalDamage.swift; sourceTree = "<group>"; };
|
||||
191E5FAB290A5D92001A3B7C /* libminimuxer.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libminimuxer.a; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
19B9B7442845E6DF0076EF69 /* SelectTeamViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SelectTeamViewController.swift; sourceTree = "<group>"; };
|
||||
32741831F952989EC7E74FFA /* Pods-SideStore.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-SideStore.debug.xcconfig"; path = "Target Support Files/Pods-SideStore/Pods-SideStore.debug.xcconfig"; sourceTree = "<group>"; };
|
||||
@@ -657,6 +644,15 @@
|
||||
A80D60D12D3D705F00CEF65D /* ReleaseTrack.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReleaseTrack.swift; sourceTree = "<group>"; };
|
||||
A80D790C2D2F20AF00A40F40 /* PaginationIntent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PaginationIntent.swift; sourceTree = "<group>"; };
|
||||
A80D790E2D2F217000A40F40 /* PaginationDataHolder.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PaginationDataHolder.swift; sourceTree = "<group>"; };
|
||||
A815AA942D90D2A100929A9E /* StoreApp17To17_1MigrationPolicy.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StoreApp17To17_1MigrationPolicy.swift; sourceTree = "<group>"; };
|
||||
A815AA962D90E16400929A9E /* ReleaseTrack17To17_1MigrationPolicy.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReleaseTrack17To17_1MigrationPolicy.swift; sourceTree = "<group>"; };
|
||||
A815AA982D90E5E500929A9E /* AltStore17ToAltStore17_1.xcmappingmodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcmappingmodel; path = AltStore17ToAltStore17_1.xcmappingmodel; sourceTree = "<group>"; };
|
||||
A815AA9E2D9104DD00929A9E /* BuildInfo.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BuildInfo.swift; sourceTree = "<group>"; };
|
||||
A815AAA02D9108BC00929A9E /* AltStore11ToAltStore17_1.xcmappingmodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcmappingmodel; path = AltStore11ToAltStore17_1.xcmappingmodel; sourceTree = "<group>"; };
|
||||
A815AAA22D9108CB00929A9E /* Source11To17_1MigrationPolicy.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Source11To17_1MigrationPolicy.swift; sourceTree = "<group>"; };
|
||||
A815AAA32D9108CB00929A9E /* StoreApp11To17_1MigrationPolicy.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StoreApp11To17_1MigrationPolicy.swift; sourceTree = "<group>"; };
|
||||
A815AAA42D9108CB00929A9E /* ReleaseTrack11To17_1MigrationPolicy.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReleaseTrack11To17_1MigrationPolicy.swift; sourceTree = "<group>"; };
|
||||
A815AAA52D9108CB00929A9E /* AppPermission11To17_1MigrationPolicy.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppPermission11To17_1MigrationPolicy.swift; sourceTree = "<group>"; };
|
||||
A81A8CB02D68B0320086C96F /* TreeMap.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TreeMap.swift; sourceTree = "<group>"; };
|
||||
A81A8CB42D68B2180086C96F /* TreeMapTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TreeMapTests.swift; sourceTree = "<group>"; };
|
||||
A81A8CBC2D68B43F0086C96F /* LinkedHashMap.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LinkedHashMap.swift; sourceTree = "<group>"; };
|
||||
@@ -664,6 +660,9 @@
|
||||
A81A8CC52D68BA610086C96F /* DataStructureTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = DataStructureTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
A81A8CC72D68BA610086C96F /* DataStructuresTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DataStructuresTests.swift; sourceTree = "<group>"; };
|
||||
A81A8CD32D68BAFF0086C96F /* DataStructureTests.xctestplan */ = {isa = PBXFileReference; lastKnownFileType = text; path = DataStructureTests.xctestplan; sourceTree = "<group>"; };
|
||||
A81BF9E12D84C9E900768940 /* AltStore 17_1.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = "AltStore 17_1.xcdatamodel"; sourceTree = "<group>"; };
|
||||
A81BF9E22D84CB0C00768940 /* AppPermission17To17_1MigrationPolicy.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppPermission17To17_1MigrationPolicy.swift; sourceTree = "<group>"; };
|
||||
A81BF9E32D84CB0C00768940 /* Source17To17_1MigrationPolicy.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Source17To17_1MigrationPolicy.swift; sourceTree = "<group>"; };
|
||||
A859ED5B2D1EE80D003DCC58 /* OpenSSL.xcframework */ = {isa = PBXFileReference; expectedSignature = "AppleDeveloperProgram:67RAULRX93:Marcin Krzyzanowski"; lastKnownFileType = wrapper.xcframework; name = OpenSSL.xcframework; path = SideStore/AltSign/Dependencies/OpenSSL/Frameworks/OpenSSL.xcframework; sourceTree = "<group>"; };
|
||||
A85ACB8E2D1F31C400AA3DE7 /* AltBackup.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = AltBackup.xcconfig; sourceTree = "<group>"; };
|
||||
A85ACB8F2D1F31C400AA3DE7 /* AltStore.debug.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = AltStore.debug.xcconfig; sourceTree = "<group>"; };
|
||||
@@ -676,8 +675,8 @@
|
||||
A86315DE2D3EB2D80048FA40 /* ErrorProcessing.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ErrorProcessing.swift; sourceTree = "<group>"; };
|
||||
A868CFE32D319988002F1201 /* SingletonGenericMap.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SingletonGenericMap.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>"; };
|
||||
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>"; };
|
||||
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; };
|
||||
@@ -685,12 +684,14 @@
|
||||
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>"; };
|
||||
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>"; };
|
||||
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>"; };
|
||||
A8C38C2B2D206AD900E83DBD /* AbstractClassError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AbstractClassError.swift; sourceTree = "<group>"; };
|
||||
A8C38C312D206B2500E83DBD /* FileOutputStream.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FileOutputStream.swift; sourceTree = "<group>"; };
|
||||
A8C38C372D2084D000E83DBD /* ConsoleLogView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConsoleLogView.swift; sourceTree = "<group>"; };
|
||||
A8C924232D8E73B1009D9953 /* AltStore11ToAltStore17.xcmappingmodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcmappingmodel; path = AltStore11ToAltStore17.xcmappingmodel; sourceTree = "<group>"; };
|
||||
A8D484D72D0CD306002C691D /* AltBackup.ipa */ = {isa = PBXFileReference; lastKnownFileType = file; path = AltBackup.ipa; sourceTree = "<group>"; };
|
||||
A8D49F522D3D2F9400844B92 /* ProcessInfo+AltStore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ProcessInfo+AltStore.swift"; sourceTree = "<group>"; };
|
||||
A8E2DB212D684CBD009E5D31 /* UITests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = UITests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
@@ -699,14 +700,12 @@
|
||||
A8E2DB2F2D684E2A009E5D31 /* UITestsLaunchTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UITestsLaunchTests.swift; sourceTree = "<group>"; };
|
||||
A8E2DB332D68507F009E5D31 /* SideStoreTests.xctestplan */ = {isa = PBXFileReference; lastKnownFileType = text; path = SideStoreTests.xctestplan; sourceTree = "<group>"; };
|
||||
A8EA195E2D4982D600DC6322 /* BaseEntity.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BaseEntity.swift; sourceTree = "<group>"; };
|
||||
A8F66C3C2D04D433009689E6 /* em_proxy.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = em_proxy.h; sourceTree = "<group>"; };
|
||||
A8F66C602D04D464009689E6 /* minimuxer.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; path = minimuxer.xcodeproj; sourceTree = "<group>"; };
|
||||
A8FD915B2D046EF100322782 /* ProcessError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProcessError.swift; sourceTree = "<group>"; };
|
||||
A8FD915D2D046F5200322782 /* UserInfoValue.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserInfoValue.swift; sourceTree = "<group>"; };
|
||||
A8FD917A2D0472DD00322782 /* DeprecatedAPIs.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeprecatedAPIs.swift; sourceTree = "<group>"; };
|
||||
B33FFBA9295F8F78002259E6 /* preboard.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = preboard.c; path = src/preboard.c; sourceTree = "<group>"; };
|
||||
B33FFBAB295F8F98002259E6 /* companion_proxy.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = companion_proxy.c; path = src/companion_proxy.c; sourceTree = "<group>"; };
|
||||
B343F84D295F6323002B1159 /* em_proxy.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = em_proxy.xcodeproj; path = SideStore/em_proxy.xcodeproj; sourceTree = SOURCE_ROOT; };
|
||||
B343F86C295F759E002B1159 /* libresolv.tbd */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.text-based-dylib-definition"; name = libresolv.tbd; path = Platforms/MacOSX.platform/Developer/SDKs/MacOSX13.1.sdk/usr/lib/libresolv.tbd; sourceTree = DEVELOPER_DIR; };
|
||||
B343F872295F7C5C002B1159 /* opack.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = opack.c; sourceTree = "<group>"; };
|
||||
B343F873295F7C5C002B1159 /* cbuf.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = cbuf.c; sourceTree = "<group>"; };
|
||||
@@ -1062,15 +1061,6 @@
|
||||
/* End PBXFileReference section */
|
||||
|
||||
/* Begin PBXFrameworksBuildPhase section */
|
||||
19104DAF2909C06C00C49C7B /* Frameworks */ = {
|
||||
isa = PBXFrameworksBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
A809F69F2D04D7B300F0F0F3 /* libem_proxy_static.a in Frameworks */,
|
||||
B343F86D295F759E002B1159 /* libresolv.tbd in Frameworks */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
191E5FA8290A5D92001A3B7C /* Frameworks */ = {
|
||||
isa = PBXFrameworksBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
@@ -1136,13 +1126,14 @@
|
||||
files = (
|
||||
A8C6D5172D1EE95B00DF01F1 /* OpenSSL.xcframework in Frameworks */,
|
||||
A8A543302D04F14400D72399 /* libfragmentzip.a in Frameworks */,
|
||||
A8B646012D70C23E00125819 /* MarkdownKit in Frameworks */,
|
||||
A805C3CD2D0C316A00E76BDD /* Pods_SideStore.framework in Frameworks */,
|
||||
A8F838942D048ECE00ED425D /* libimobiledevice.a in Frameworks */,
|
||||
A8F838922D048E8F00ED425D /* libEmotionalDamage.a in Frameworks */,
|
||||
A8F838932D048E8F00ED425D /* libminimuxer.a in Frameworks */,
|
||||
D533E8B72727841800A9B5DD /* libAppleArchive.tbd in Frameworks */,
|
||||
D533E8BE2727BBF800A9B5DD /* libcurl.a in Frameworks */,
|
||||
BF1614F1250822F100767AEA /* Roxas.framework in Frameworks */,
|
||||
A8B645FF2D70C1AD00125819 /* MarkdownKit in Frameworks */,
|
||||
BF66EE852501AE50007EE018 /* AltStoreCore.framework in Frameworks */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
@@ -1162,14 +1153,6 @@
|
||||
path = Errors;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
19104DB32909C06D00C49C7B /* EmotionalDamage */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
19104DB42909C06D00C49C7B /* EmotionalDamage.swift */,
|
||||
);
|
||||
path = EmotionalDamage;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
191E5FF4290B2663001A3B7C /* libimobiledevice-glue */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
@@ -1221,15 +1204,6 @@
|
||||
name = Products;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
A809F68F2D04D71200F0F0F3 /* Products */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
A809F6942D04D71200F0F0F3 /* libem_proxy_static.a */,
|
||||
A809F6962D04D71200F0F0F3 /* run */,
|
||||
);
|
||||
name = Products;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
A80D790B2D2F209700A40F40 /* Intents */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
@@ -1239,6 +1213,17 @@
|
||||
path = Intents;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
A815AAA62D9108CB00929A9E /* v11-to-v17_1 */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
A815AAA22D9108CB00929A9E /* Source11To17_1MigrationPolicy.swift */,
|
||||
A815AAA32D9108CB00929A9E /* StoreApp11To17_1MigrationPolicy.swift */,
|
||||
A815AAA42D9108CB00929A9E /* ReleaseTrack11To17_1MigrationPolicy.swift */,
|
||||
A815AAA52D9108CB00929A9E /* AppPermission11To17_1MigrationPolicy.swift */,
|
||||
);
|
||||
path = "v11-to-v17_1";
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
A81A8CB22D68B2030086C96F /* UnitTests */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
@@ -1269,10 +1254,31 @@
|
||||
A85ACB8E2D1F31C400AA3DE7 /* AltBackup.xcconfig */,
|
||||
A85ACB932D1F31C400AA3DE7 /* AltWidgetExtension.xcconfig */,
|
||||
A8E2DB2C2D684D39009E5D31 /* UITests.xcconfig */,
|
||||
A881E8562D6FBBAF00954AD2 /* DataStructureTests.xcconfig */,
|
||||
);
|
||||
path = xcconfigs;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
A85E43732D8AF82100E89240 /* v11-to-v17 */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
D5185B812AE1E71D00646E33 /* Source11To17MigrationPolicy.swift */,
|
||||
A881E7CA2D6EF5AB00954AD2 /* StoreApp11To17MigrationPolicy.swift */,
|
||||
);
|
||||
path = "v11-to-v17";
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
A85E43742D8AF84200E89240 /* v17-to-v17_1 */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
A81BF9E32D84CB0C00768940 /* Source17To17_1MigrationPolicy.swift */,
|
||||
A815AA942D90D2A100929A9E /* StoreApp17To17_1MigrationPolicy.swift */,
|
||||
A815AA962D90E16400929A9E /* ReleaseTrack17To17_1MigrationPolicy.swift */,
|
||||
A81BF9E22D84CB0C00768940 /* AppPermission17To17_1MigrationPolicy.swift */,
|
||||
);
|
||||
path = "v17-to-v17_1";
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
A86315DD2D3EB2BD0048FA40 /* errors */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
@@ -1344,6 +1350,22 @@
|
||||
path = database;
|
||||
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 */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
@@ -1369,6 +1391,7 @@
|
||||
A8C38C272D206AA500E83DBD /* common */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
A815AA9E2D9104DD00929A9E /* BuildInfo.swift */,
|
||||
A8B516E52D2668020047047C /* DateTimeUtil.swift */,
|
||||
A8C38C282D206AC100E83DBD /* OutputStream.swift */,
|
||||
A8C38C312D206B2500E83DBD /* FileOutputStream.swift */,
|
||||
@@ -1425,26 +1448,16 @@
|
||||
A8F66C072D04C025009689E6 /* SideStore */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
A8B645F82D70C0DD00125819 /* Views */,
|
||||
A8E2DB352D6850A9009E5D31 /* Tests */,
|
||||
A8F66C5C2D04D433009689E6 /* minimuxer */,
|
||||
A8F66C602D04D464009689E6 /* minimuxer.xcodeproj */,
|
||||
A8F66C412D04D433009689E6 /* em_proxy */,
|
||||
B343F84D295F6323002B1159 /* em_proxy.xcodeproj */,
|
||||
19104DB32909C06D00C49C7B /* EmotionalDamage */,
|
||||
B343F886295F7F9B002B1159 /* libfragmentzip.xcodeproj */,
|
||||
A8C38C1C2D2068D100E83DBD /* Utils */,
|
||||
);
|
||||
path = SideStore;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
A8F66C412D04D433009689E6 /* em_proxy */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
A8F66C3C2D04D433009689E6 /* em_proxy.h */,
|
||||
);
|
||||
path = em_proxy;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
A8F66C5C2D04D433009689E6 /* minimuxer */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
@@ -1796,10 +1809,11 @@
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
BF66EEAF2501AECA007EE018 /* InstalledAppPolicy.swift */,
|
||||
D5185B812AE1E71D00646E33 /* Source11To17MigrationPolicy.swift */,
|
||||
BF66EEAE2501AECA007EE018 /* StoreAppPolicy.swift */,
|
||||
D5F99A1928D12B1400476A16 /* StoreApp10ToStoreApp11Policy.swift */,
|
||||
A881E7CA2D6EF5AB00954AD2 /* StoreApp11To17MigrationPolicy.swift */,
|
||||
A85E43732D8AF82100E89240 /* v11-to-v17 */,
|
||||
A85E43742D8AF84200E89240 /* v17-to-v17_1 */,
|
||||
A815AAA62D9108CB00929A9E /* v11-to-v17_1 */,
|
||||
);
|
||||
path = Policies;
|
||||
sourceTree = "<group>";
|
||||
@@ -1807,7 +1821,9 @@
|
||||
BF66EEB02501AECA007EE018 /* Mapping Models */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
A881E7C62D6EF58C00954AD2 /* AltStore11ToAltStore17.xcmappingmodel */,
|
||||
A815AAA02D9108BC00929A9E /* AltStore11ToAltStore17_1.xcmappingmodel */,
|
||||
A815AA982D90E5E500929A9E /* AltStore17ToAltStore17_1.xcmappingmodel */,
|
||||
A8C924232D8E73B1009D9953 /* AltStore11ToAltStore17.xcmappingmodel */,
|
||||
D5F99A1728D11DB500476A16 /* AltStore10ToAltStore11.xcmappingmodel */,
|
||||
D5CA0C4D280E249E00469595 /* AltStore9ToAltStore10.xcmappingmodel */,
|
||||
BFBF331A2526762200B7B8C9 /* AltStore8ToAltStore9.xcmappingmodel */,
|
||||
@@ -1988,7 +2004,6 @@
|
||||
BF58047B246A28F7008AE704 /* AltBackup.app */,
|
||||
BF66EE7E2501AE50007EE018 /* AltStoreCore.framework */,
|
||||
BF989167250AABF3002ACF50 /* AltWidgetExtension.appex */,
|
||||
19104DB22909C06C00C49C7B /* libEmotionalDamage.a */,
|
||||
191E5FAB290A5D92001A3B7C /* libminimuxer.a */,
|
||||
D586D39828EF58B0000E101F /* AltTests.xctest */,
|
||||
A8E2DB212D684CBD009E5D31 /* UITests.xctest */,
|
||||
@@ -2440,22 +2455,6 @@
|
||||
/* End PBXHeadersBuildPhase section */
|
||||
|
||||
/* Begin PBXNativeTarget section */
|
||||
19104DB12909C06C00C49C7B /* EmotionalDamage */ = {
|
||||
isa = PBXNativeTarget;
|
||||
buildConfigurationList = 19104DB62909C06D00C49C7B /* Build configuration list for PBXNativeTarget "EmotionalDamage" */;
|
||||
buildPhases = (
|
||||
19104DAE2909C06C00C49C7B /* Sources */,
|
||||
19104DAF2909C06C00C49C7B /* Frameworks */,
|
||||
);
|
||||
buildRules = (
|
||||
);
|
||||
dependencies = (
|
||||
);
|
||||
name = EmotionalDamage;
|
||||
productName = EmotionalDamage;
|
||||
productReference = 19104DB22909C06C00C49C7B /* libEmotionalDamage.a */;
|
||||
productType = "com.apple.product-type.library.static";
|
||||
};
|
||||
191E5FAA290A5D92001A3B7C /* minimuxer */ = {
|
||||
isa = PBXNativeTarget;
|
||||
buildConfigurationList = 191E5FAF290A5D92001A3B7C /* Build configuration list for PBXNativeTarget "minimuxer" */;
|
||||
@@ -2608,7 +2607,6 @@
|
||||
dependencies = (
|
||||
A8E00D3E2D0C95B5000DD2C7 /* PBXTargetDependency */,
|
||||
191E5FB3290A5D9B001A3B7C /* PBXTargetDependency */,
|
||||
19104DBA2909C0F200C49C7B /* PBXTargetDependency */,
|
||||
BF66EE842501AE50007EE018 /* PBXTargetDependency */,
|
||||
BF989176250AABF4002ACF50 /* PBXTargetDependency */,
|
||||
);
|
||||
@@ -2627,9 +2625,6 @@
|
||||
LastUpgradeCheck = 1020;
|
||||
ORGANIZATIONNAME = SideStore;
|
||||
TargetAttributes = {
|
||||
19104DB12909C06C00C49C7B = {
|
||||
CreatedOnToolsVersion = 14.0;
|
||||
};
|
||||
191E5FAA290A5D92001A3B7C = {
|
||||
CreatedOnToolsVersion = 14.0;
|
||||
};
|
||||
@@ -2680,14 +2675,11 @@
|
||||
D58D5F2C26DFE68E00E55E38 /* XCRemoteSwiftPackageReference "LaunchAtLogin" */,
|
||||
D5FB7A2C2AA2859400EF863D /* XCRemoteSwiftPackageReference "swift-argument-parser" */,
|
||||
A82067C22D03E0DE00645C0D /* XCRemoteSwiftPackageReference "SemanticVersion" */,
|
||||
A8B645FD2D70C1AD00125819 /* XCRemoteSwiftPackageReference "MarkdownKit" */,
|
||||
);
|
||||
productRefGroup = BFD2476B2284B9A500981D42 /* Products */;
|
||||
projectDirPath = "";
|
||||
projectReferences = (
|
||||
{
|
||||
ProductGroup = A809F68F2D04D71200F0F0F3 /* Products */;
|
||||
ProjectRef = B343F84D295F6323002B1159 /* em_proxy.xcodeproj */;
|
||||
},
|
||||
{
|
||||
ProductGroup = A8A543222D04F0C100D72399 /* Products */;
|
||||
ProjectRef = B343F886295F7F9B002B1159 /* libfragmentzip.xcodeproj */;
|
||||
@@ -2704,7 +2696,6 @@
|
||||
BF58047A246A28F7008AE704 /* AltBackup */,
|
||||
BF66EE7D2501AE50007EE018 /* AltStoreCore */,
|
||||
BF989166250AABF3002ACF50 /* AltWidgetExtension */,
|
||||
19104DB12909C06C00C49C7B /* EmotionalDamage */,
|
||||
191E5FAA290A5D92001A3B7C /* minimuxer */,
|
||||
A8E2DB202D684CBD009E5D31 /* UITests */,
|
||||
A81A8CC42D68BA610086C96F /* DataStructureTests */,
|
||||
@@ -2720,20 +2711,6 @@
|
||||
remoteRef = A809F68D2D04D71200F0F0F3 /* PBXContainerItemProxy */;
|
||||
sourceTree = BUILT_PRODUCTS_DIR;
|
||||
};
|
||||
A809F6942D04D71200F0F0F3 /* libem_proxy_static.a */ = {
|
||||
isa = PBXReferenceProxy;
|
||||
fileType = archive.ar;
|
||||
path = libem_proxy_static.a;
|
||||
remoteRef = A809F6932D04D71200F0F0F3 /* PBXContainerItemProxy */;
|
||||
sourceTree = BUILT_PRODUCTS_DIR;
|
||||
};
|
||||
A809F6962D04D71200F0F0F3 /* run */ = {
|
||||
isa = PBXReferenceProxy;
|
||||
fileType = "compiled.mach-o.executable";
|
||||
path = run;
|
||||
remoteRef = A809F6952D04D71200F0F0F3 /* PBXContainerItemProxy */;
|
||||
sourceTree = BUILT_PRODUCTS_DIR;
|
||||
};
|
||||
A8A543292D04F0C100D72399 /* libfragmentzip */ = {
|
||||
isa = PBXReferenceProxy;
|
||||
fileType = "compiled.mach-o.executable";
|
||||
@@ -2935,14 +2912,6 @@
|
||||
/* End PBXShellScriptBuildPhase section */
|
||||
|
||||
/* Begin PBXSourcesBuildPhase section */
|
||||
19104DAE2909C06C00C49C7B /* Sources */ = {
|
||||
isa = PBXSourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
19104DB52909C06D00C49C7B /* EmotionalDamage.swift in Sources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
191E5FA7290A5D92001A3B7C /* Sources */ = {
|
||||
isa = PBXSourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
@@ -3072,6 +3041,7 @@
|
||||
A82067842D03DC0600645C0D /* OperatingSystemVersion+Comparable.swift in Sources */,
|
||||
D5FB28EE2ADDF89800A1C337 /* KnownSource.swift in Sources */,
|
||||
BF66EED32501AECA007EE018 /* AltStore2ToAltStore3.xcmappingmodel in Sources */,
|
||||
A815AA972D90E16400929A9E /* ReleaseTrack17To17_1MigrationPolicy.swift in Sources */,
|
||||
BF66EEA52501AEC5007EE018 /* Benefit.swift in Sources */,
|
||||
D52B4ABF2AF183F0005991C3 /* WebViewController.swift in Sources */,
|
||||
BF66EED22501AECA007EE018 /* AltStore4ToAltStore5.xcmappingmodel in Sources */,
|
||||
@@ -3080,17 +3050,26 @@
|
||||
BF66EECD2501AECA007EE018 /* StoreAppPolicy.swift in Sources */,
|
||||
BF66EEE82501AED0007EE018 /* UserDefaults+AltStore.swift in Sources */,
|
||||
BF340E9A250AD39500A192CB /* ViewApp.intentdefinition in Sources */,
|
||||
A8C924242D8E73B1009D9953 /* AltStore11ToAltStore17.xcmappingmodel in Sources */,
|
||||
D552EB062AF453F900A3AB4D /* URL+Normalized.swift in Sources */,
|
||||
BFAECC522501B0A400528F27 /* CodableError.swift in Sources */,
|
||||
A8FD915F2D046F5200322782 /* UserInfoValue.swift in Sources */,
|
||||
D5F9821D2AB900060045751F /* AppScreenshot.swift in Sources */,
|
||||
BF66EE9E2501AEC1007EE018 /* Fetchable.swift in Sources */,
|
||||
BF66EEDF2501AECA007EE018 /* PatreonAccount.swift in Sources */,
|
||||
A815AAA12D9108BC00929A9E /* AltStore11ToAltStore17_1.xcmappingmodel in Sources */,
|
||||
BFAECC532501B0A400528F27 /* ServerProtocol.swift in Sources */,
|
||||
A81BF9E52D84CB0C00768940 /* AppPermission17To17_1MigrationPolicy.swift in Sources */,
|
||||
A81BF9E72D84CB0C00768940 /* Source17To17_1MigrationPolicy.swift in Sources */,
|
||||
BFAECC572501B0A400528F27 /* ConnectionManager.swift in Sources */,
|
||||
BF66EE9D2501AEC1007EE018 /* AppProtocol.swift in Sources */,
|
||||
D519AD46292D665B004B12F9 /* Managed.swift in Sources */,
|
||||
D52A2F972ACB40F700BDF8E3 /* Logger+AltStore.swift in Sources */,
|
||||
A815AA952D90D2A100929A9E /* StoreApp17To17_1MigrationPolicy.swift in Sources */,
|
||||
A815AAA72D9108CB00929A9E /* ReleaseTrack11To17_1MigrationPolicy.swift in Sources */,
|
||||
A815AAA82D9108CB00929A9E /* AppPermission11To17_1MigrationPolicy.swift in Sources */,
|
||||
A815AAA92D9108CB00929A9E /* Source11To17_1MigrationPolicy.swift in Sources */,
|
||||
A815AAAA2D9108CB00929A9E /* StoreApp11To17_1MigrationPolicy.swift in Sources */,
|
||||
BFC712C42512D5F100AB5EBE /* XPCConnection.swift in Sources */,
|
||||
D5CA0C4B280E141900469595 /* ManagedPatron.swift in Sources */,
|
||||
BF66EE8C2501AEB2007EE018 /* Keychain.swift in Sources */,
|
||||
@@ -3153,11 +3132,11 @@
|
||||
BF66EECE2501AECA007EE018 /* InstalledAppPolicy.swift in Sources */,
|
||||
BF1FE359251A9FB000C3CE09 /* NSXPCConnection+MachServices.swift in Sources */,
|
||||
BF66EEA62501AEC5007EE018 /* PatreonAPI.swift in Sources */,
|
||||
A881E7C72D6EF58C00954AD2 /* AltStore11ToAltStore17.xcmappingmodel in Sources */,
|
||||
BF66EED02501AECA007EE018 /* AltStore6ToAltStore7.xcmappingmodel in Sources */,
|
||||
BF66EEDC2501AECA007EE018 /* MergePolicy.swift in Sources */,
|
||||
BF66EEE22501AECA007EE018 /* InstalledExtension.swift in Sources */,
|
||||
BF66EED62501AECA007EE018 /* NewsItem.swift in Sources */,
|
||||
A815AA992D90E5E500929A9E /* AltStore17ToAltStore17_1.xcmappingmodel in Sources */,
|
||||
BF66EEA72501AEC5007EE018 /* Campaign.swift in Sources */,
|
||||
D52C8F032AFC56F000CA0BDD /* StoreCategory.swift in Sources */,
|
||||
BF66EE992501AEBC007EE018 /* ALTSourceUserInfoKey.m in Sources */,
|
||||
@@ -3290,6 +3269,7 @@
|
||||
D57FE84428C7DB7100216002 /* ErrorLogViewController.swift in Sources */,
|
||||
A88B8C552D35F1EC00F53F9D /* OperationsLoggingControl.swift in Sources */,
|
||||
D50107EC2ADF2E1A0069F2A1 /* AddSourceTextFieldCell.swift in Sources */,
|
||||
A8B645FC2D70C10300125819 /* CollapsingMarkdownView.swift in Sources */,
|
||||
D5151BD92A8FF64300C96F28 /* RefreshAllAppsIntent.swift in Sources */,
|
||||
BFBE0007250AD0E70080826E /* ViewAppIntentHandler.swift in Sources */,
|
||||
BFDB6A0822AAED73007EA6D6 /* ResignAppOperation.swift in Sources */,
|
||||
@@ -3305,6 +3285,7 @@
|
||||
BF0C4EBD22A1BD8B009A2DD7 /* AppManager.swift in Sources */,
|
||||
BF2901312318F7A800D88A45 /* AppBannerView.swift in Sources */,
|
||||
0EE7FDC42BE8BC7900D1E390 /* ALTLocalizedError.swift in Sources */,
|
||||
A815AA9F2D9104DD00929A9E /* BuildInfo.swift in Sources */,
|
||||
BFF00D342501BDCF00746320 /* IntentHandler.swift in Sources */,
|
||||
BFDBBD80246CB84F004ED2F3 /* RemoveAppBackupOperation.swift in Sources */,
|
||||
A8C38C2A2D206AC100E83DBD /* OutputStream.swift in Sources */,
|
||||
@@ -3342,11 +3323,6 @@
|
||||
/* End PBXSourcesBuildPhase section */
|
||||
|
||||
/* Begin PBXTargetDependency section */
|
||||
19104DBA2909C0F200C49C7B /* PBXTargetDependency */ = {
|
||||
isa = PBXTargetDependency;
|
||||
target = 19104DB12909C06C00C49C7B /* EmotionalDamage */;
|
||||
targetProxy = 19104DB92909C0F200C49C7B /* PBXContainerItemProxy */;
|
||||
};
|
||||
191E5FB3290A5D9B001A3B7C /* PBXTargetDependency */ = {
|
||||
isa = PBXTargetDependency;
|
||||
target = 191E5FAA290A5D92001A3B7C /* minimuxer */;
|
||||
@@ -3412,68 +3388,6 @@
|
||||
/* End PBXVariantGroup section */
|
||||
|
||||
/* Begin XCBuildConfiguration section */
|
||||
19104DB72909C06D00C49C7B /* Debug */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
CLANG_CXX_LANGUAGE_STANDARD = "gnu++20";
|
||||
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
|
||||
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = "$(inherited)";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
|
||||
DEVELOPMENT_TEAM = "$(DEVELOPMENT_TEAM)";
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 15.0;
|
||||
LD_WARN_DUPLICATE_LIBRARIES = YES;
|
||||
LD_WARN_UNUSED_DYLIBS = YES;
|
||||
LIBRARY_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"$(PROJECT_DIR)/SideStore/em_proxy",
|
||||
);
|
||||
MACOSX_DEPLOYMENT_TARGET = "$(RECOMMENDED_MACOSX_DEPLOYMENT_TARGET)";
|
||||
OTHER_LDFLAGS = (
|
||||
"-ObjC",
|
||||
"$(inherited)",
|
||||
);
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SKIP_INSTALL = YES;
|
||||
SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
|
||||
SWIFT_OBJC_BRIDGING_HEADER = SideStore/em_proxy/em_proxy.h;
|
||||
SWIFT_VERSION = 5.0;
|
||||
TARGETED_DEVICE_FAMILY = "1,2";
|
||||
TVOS_DEPLOYMENT_TARGET = 15.0;
|
||||
WATCHOS_DEPLOYMENT_TARGET = 7.0;
|
||||
};
|
||||
name = Debug;
|
||||
};
|
||||
19104DB82909C06D00C49C7B /* Release */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
CLANG_CXX_LANGUAGE_STANDARD = "gnu++20";
|
||||
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
|
||||
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = "$(inherited)";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
DEVELOPMENT_TEAM = "$(DEVELOPMENT_TEAM)";
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 15.0;
|
||||
LD_WARN_DUPLICATE_LIBRARIES = YES;
|
||||
LD_WARN_UNUSED_DYLIBS = YES;
|
||||
LIBRARY_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"$(PROJECT_DIR)/SideStore/em_proxy",
|
||||
);
|
||||
MACOSX_DEPLOYMENT_TARGET = "$(RECOMMENDED_MACOSX_DEPLOYMENT_TARGET)";
|
||||
OTHER_LDFLAGS = (
|
||||
"-ObjC",
|
||||
"$(inherited)",
|
||||
);
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SKIP_INSTALL = YES;
|
||||
SWIFT_OBJC_BRIDGING_HEADER = SideStore/em_proxy/em_proxy.h;
|
||||
SWIFT_VERSION = 5.0;
|
||||
TARGETED_DEVICE_FAMILY = "1,2";
|
||||
TVOS_DEPLOYMENT_TARGET = 15.0;
|
||||
WATCHOS_DEPLOYMENT_TARGET = 7.0;
|
||||
};
|
||||
name = Release;
|
||||
};
|
||||
191E5FB0290A5D92001A3B7C /* Debug */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
@@ -3532,6 +3446,7 @@
|
||||
};
|
||||
A81A8CCA2D68BA610086C96F /* Debug */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
baseConfigurationReference = A881E8562D6FBBAF00954AD2 /* DataStructureTests.xcconfig */;
|
||||
buildSettings = {
|
||||
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
|
||||
CLANG_CXX_LANGUAGE_STANDARD = "gnu++20";
|
||||
@@ -3546,7 +3461,7 @@
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 18.2;
|
||||
LOCALIZATION_PREFERS_STRING_CATALOGS = YES;
|
||||
MARKETING_VERSION = 1.0;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.SideStore.SideStore.DataStructuresTests;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = "$(PRODUCT_BUNDLE_IDENTIFIER)";
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SWIFT_ACTIVE_COMPILATION_CONDITIONS = "DEBUG $(inherited)";
|
||||
SWIFT_EMIT_LOC_STRINGS = NO;
|
||||
@@ -3557,6 +3472,7 @@
|
||||
};
|
||||
A81A8CCB2D68BA610086C96F /* Release */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
baseConfigurationReference = A881E8562D6FBBAF00954AD2 /* DataStructureTests.xcconfig */;
|
||||
buildSettings = {
|
||||
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
|
||||
CLANG_CXX_LANGUAGE_STANDARD = "gnu++20";
|
||||
@@ -3573,7 +3489,7 @@
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 18.2;
|
||||
LOCALIZATION_PREFERS_STRING_CATALOGS = YES;
|
||||
MARKETING_VERSION = 1.0;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.SideStore.SideStore.DataStructuresTests;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = "$(PRODUCT_BUNDLE_IDENTIFIER)";
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
PROVISIONING_PROFILE_SPECIFIER = "";
|
||||
SWIFT_COMPILATION_MODE = wholemodule;
|
||||
@@ -3600,7 +3516,7 @@
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 18.2;
|
||||
LOCALIZATION_PREFERS_STRING_CATALOGS = YES;
|
||||
MARKETING_VERSION = 1.0;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = "--PRODUCT-BUNDLE-IDENTIFIER-.UITests";
|
||||
PRODUCT_BUNDLE_IDENTIFIER = "$(PRODUCT_BUNDLE_IDENTIFIER)";
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SWIFT_ACTIVE_COMPILATION_CONDITIONS = "DEBUG $(inherited)";
|
||||
SWIFT_EMIT_LOC_STRINGS = NO;
|
||||
@@ -3618,6 +3534,7 @@
|
||||
CLANG_CXX_LANGUAGE_STANDARD = "gnu++20";
|
||||
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
|
||||
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
|
||||
"CODE_SIGN_IDENTITY[sdk=macosx*]" = "-";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 1;
|
||||
ENABLE_USER_SCRIPT_SANDBOXING = YES;
|
||||
@@ -3626,7 +3543,7 @@
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 18.2;
|
||||
LOCALIZATION_PREFERS_STRING_CATALOGS = YES;
|
||||
MARKETING_VERSION = 1.0;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = "--PRODUCT-BUNDLE-IDENTIFIER-.UITests";
|
||||
PRODUCT_BUNDLE_IDENTIFIER = "$(PRODUCT_BUNDLE_IDENTIFIER)";
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SWIFT_COMPILATION_MODE = wholemodule;
|
||||
SWIFT_EMIT_LOC_STRINGS = NO;
|
||||
@@ -4153,15 +4070,6 @@
|
||||
/* End XCBuildConfiguration section */
|
||||
|
||||
/* Begin XCConfigurationList section */
|
||||
19104DB62909C06D00C49C7B /* Build configuration list for PBXNativeTarget "EmotionalDamage" */ = {
|
||||
isa = XCConfigurationList;
|
||||
buildConfigurations = (
|
||||
19104DB72909C06D00C49C7B /* Debug */,
|
||||
19104DB82909C06D00C49C7B /* Release */,
|
||||
);
|
||||
defaultConfigurationIsVisible = 0;
|
||||
defaultConfigurationName = Release;
|
||||
};
|
||||
191E5FAF290A5D92001A3B7C /* Build configuration list for PBXNativeTarget "minimuxer" */ = {
|
||||
isa = XCConfigurationList;
|
||||
buildConfigurations = (
|
||||
@@ -4249,9 +4157,17 @@
|
||||
A82067C22D03E0DE00645C0D /* XCRemoteSwiftPackageReference "SemanticVersion" */ = {
|
||||
isa = XCRemoteSwiftPackageReference;
|
||||
repositoryURL = "https://github.com/SwiftPackageIndex/SemanticVersion";
|
||||
requirement = {
|
||||
kind = exactVersion;
|
||||
version = 0.4.0;
|
||||
};
|
||||
};
|
||||
A8B645FD2D70C1AD00125819 /* XCRemoteSwiftPackageReference "MarkdownKit" */ = {
|
||||
isa = XCRemoteSwiftPackageReference;
|
||||
repositoryURL = "https://github.com/bmoliveira/MarkdownKit.git";
|
||||
requirement = {
|
||||
kind = upToNextMajorVersion;
|
||||
minimumVersion = 0.4.0;
|
||||
minimumVersion = 1.7.1;
|
||||
};
|
||||
};
|
||||
D58D5F2C26DFE68E00E55E38 /* XCRemoteSwiftPackageReference "LaunchAtLogin" */ = {
|
||||
@@ -4278,6 +4194,16 @@
|
||||
package = A82067C22D03E0DE00645C0D /* XCRemoteSwiftPackageReference "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 */ = {
|
||||
isa = XCSwiftPackageProductDependency;
|
||||
productName = "AltSign-Static";
|
||||
@@ -4292,6 +4218,7 @@
|
||||
BF66EEB72501AECA007EE018 /* AltStore.xcdatamodeld */ = {
|
||||
isa = XCVersionGroup;
|
||||
children = (
|
||||
A81BF9E12D84C9E900768940 /* AltStore 17_1.xcdatamodel */,
|
||||
D51E83802B86926B0092FC61 /* AltStore 17.xcdatamodel */,
|
||||
D52E988928D002D30032BE6B /* AltStore 11.xcdatamodel */,
|
||||
D5CA0C4C280E242500469595 /* AltStore 10.xcdatamodel */,
|
||||
@@ -4305,7 +4232,7 @@
|
||||
BF66EEBD2501AECA007EE018 /* AltStore 2.xcdatamodel */,
|
||||
BF66EEB92501AECA007EE018 /* AltStore.xcdatamodel */,
|
||||
);
|
||||
currentVersion = D51E83802B86926B0092FC61 /* AltStore 17.xcdatamodel */;
|
||||
currentVersion = A81BF9E12D84C9E900768940 /* AltStore 17_1.xcdatamodel */;
|
||||
path = AltStore.xcdatamodeld;
|
||||
sourceTree = "<group>";
|
||||
versionGroupType = wrapper.xcdatamodel;
|
||||
|
||||
@@ -43,8 +43,10 @@ final class AppContentViewController: UITableViewController
|
||||
}()
|
||||
|
||||
@IBOutlet private var subtitleLabel: UILabel!
|
||||
@IBOutlet private var descriptionTextView: CollapsingTextView!
|
||||
@IBOutlet private var versionDescriptionTextView: CollapsingTextView!
|
||||
// @IBOutlet private var descriptionTextView: CollapsingTextView!
|
||||
@IBOutlet private var descriptionTextView: CollapsingMarkdownView!
|
||||
// @IBOutlet private var versionDescriptionTextView: CollapsingTextView!
|
||||
@IBOutlet private var versionDescriptionTextView: CollapsingMarkdownView!
|
||||
@IBOutlet private var versionLabel: UILabel!
|
||||
@IBOutlet private var versionDateLabel: UILabel!
|
||||
@IBOutlet private var sizeLabel: UILabel!
|
||||
@@ -55,35 +57,32 @@ final class AppContentViewController: UITableViewController
|
||||
@IBOutlet private(set) var appDetailCollectionViewController: AppDetailCollectionViewController!
|
||||
@IBOutlet private var appDetailCollectionViewHeightConstraint: NSLayoutConstraint!
|
||||
|
||||
override func viewDidLoad()
|
||||
{
|
||||
override func viewDidLoad() {
|
||||
super.viewDidLoad()
|
||||
|
||||
self.tableView.contentInset.bottom = 20
|
||||
|
||||
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
|
||||
{
|
||||
self.versionDescriptionTextView.text = version.localizedDescription
|
||||
if let version = self.app.latestAvailableVersion {
|
||||
self.versionDescriptionTextView.text = version.localizedDescription ?? "nil"
|
||||
self.versionLabel.text = String(format: NSLocalizedString("Version %@", comment: ""), version.localizedVersion)
|
||||
self.versionDateLabel.text = Date().relativeDateString(since: version.date)
|
||||
self.sizeLabel.text = self.byteCountFormatter.string(fromByteCount: version.size)
|
||||
}
|
||||
else
|
||||
{
|
||||
self.versionDescriptionTextView.text = nil
|
||||
self.sizeLabel.text = ByteCountFormatter.string(fromByteCount: version.size, countStyle: .file)
|
||||
} else {
|
||||
self.versionDescriptionTextView.text = "nil"
|
||||
self.versionLabel.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.moreButton.addTarget(self, action: #selector(AppContentViewController.toggleCollapsingSection(_:)), for: .primaryActionTriggered)
|
||||
self.versionDescriptionTextView.maximumNumberOfLines = 5
|
||||
|
||||
self.versionDescriptionTextView.maximumNumberOfLines = 3
|
||||
self.versionDescriptionTextView.moreButton.addTarget(self, action: #selector(AppContentViewController.toggleCollapsingSection(_:)), for: .primaryActionTriggered)
|
||||
self.descriptionTextView.toggleButton.addTarget(self, action: #selector(AppContentViewController.toggleCollapsingSection(_:)), for: .primaryActionTriggered)
|
||||
self.versionDescriptionTextView.toggleButton.addTarget(self, action: #selector(AppContentViewController.toggleCollapsingSection(_:)), for: .primaryActionTriggered)
|
||||
}
|
||||
|
||||
override func viewDidLayoutSubviews()
|
||||
@@ -162,8 +161,12 @@ private extension AppContentViewController
|
||||
|
||||
switch sender
|
||||
{
|
||||
case self.descriptionTextView.moreButton: indexPath = IndexPath(row: Row.description.rawValue, section: 0)
|
||||
case self.versionDescriptionTextView.moreButton: indexPath = IndexPath(row: Row.versionDescription.rawValue, section: 0)
|
||||
case self.descriptionTextView.toggleButton:
|
||||
indexPath = IndexPath(row: Row.description.rawValue, section: 0)
|
||||
|
||||
case self.versionDescriptionTextView.toggleButton:
|
||||
indexPath = IndexPath(row: Row.versionDescription.rawValue, section: 0)
|
||||
|
||||
default: return
|
||||
}
|
||||
|
||||
|
||||
@@ -14,7 +14,6 @@ import Intents
|
||||
import AltStoreCore
|
||||
import AltSign
|
||||
import Roxas
|
||||
import EmotionalDamage
|
||||
|
||||
import Nuke
|
||||
|
||||
@@ -27,10 +26,12 @@ extension AppDelegate
|
||||
static let addSourceDeepLinkNotification = Notification.Name(Bundle.Info.appbundleIdentifier + ".AddSourceDeepLinkNotification")
|
||||
|
||||
static let appBackupDidFinish = Notification.Name(Bundle.Info.appbundleIdentifier + ".AppBackupDidFinish")
|
||||
static let exportCertificateNotification = Notification.Name(Bundle.Info.appbundleIdentifier + ".ExportCertificateNotification")
|
||||
|
||||
static let importAppDeepLinkURLKey = "fileURL"
|
||||
static let appBackupResultKey = "result"
|
||||
static let addSourceDeepLinkURLKey = "sourceURL"
|
||||
static let exportCertificateCallbackTemplateKey = "callback"
|
||||
}
|
||||
|
||||
@UIApplicationMain
|
||||
@@ -139,7 +140,6 @@ final class AppDelegate: UIResponder, UIApplicationDelegate {
|
||||
func applicationWillEnterForeground(_ application: UIApplication)
|
||||
{
|
||||
AppManager.shared.update()
|
||||
start_em_proxy(bind_addr: Consts.Proxy.serverURL)
|
||||
|
||||
PatreonAPI.shared.refreshPatreonAccount()
|
||||
}
|
||||
@@ -292,6 +292,26 @@ private extension AppDelegate
|
||||
|
||||
return true
|
||||
|
||||
case "pairing":
|
||||
let queryItems = components.queryItems?.reduce(into: [String: String]()) { $0[$1.name.lowercased()] = $1.value } ?? [:]
|
||||
guard let callbackTemplate = queryItems["urlName"]?.removingPercentEncoding else { return false }
|
||||
|
||||
DispatchQueue.main.async {
|
||||
exportPairingFile(callbackTemplate)
|
||||
}
|
||||
|
||||
return true
|
||||
|
||||
case "certificate":
|
||||
let queryItems = components.queryItems?.reduce(into: [String: String]()) { $0[$1.name.lowercased()] = $1.value } ?? [:]
|
||||
guard let callbackTemplate = queryItems["callback_template"]?.removingPercentEncoding else { return false }
|
||||
|
||||
DispatchQueue.main.async {
|
||||
NotificationCenter.default.post(name: AppDelegate.exportCertificateNotification, object: nil, userInfo: [AppDelegate.exportCertificateCallbackTemplateKey: callbackTemplate])
|
||||
}
|
||||
|
||||
return true
|
||||
|
||||
default: return false
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="32700.99.1234" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES">
|
||||
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="23504" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES">
|
||||
<device id="retina4_7" orientation="portrait" appearance="light"/>
|
||||
<dependencies>
|
||||
<deployment identifier="iOS"/>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="22684"/>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="23506"/>
|
||||
<capability name="Named colors" minToolsVersion="9.0"/>
|
||||
<capability name="Safe area layout guides" minToolsVersion="9.0"/>
|
||||
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
|
||||
@@ -14,7 +14,7 @@
|
||||
<objects>
|
||||
<navigationController storyboardIdentifier="navigationController" id="ZTo-53-dSL" sceneMemberID="viewController">
|
||||
<navigationBar key="navigationBar" contentMode="scaleToFill" insetsLayoutMarginsFromSafeArea="NO" barStyle="black" largeTitles="YES" id="Aej-RF-PfV" customClass="NavigationBar" customModule="SideStore" customModuleProvider="target">
|
||||
<rect key="frame" x="0.0" y="0.0" width="375" height="96"/>
|
||||
<rect key="frame" x="0.0" y="20" width="375" height="96"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<color key="tintColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||
<color key="barTintColor" name="SettingsBackground"/>
|
||||
@@ -42,13 +42,13 @@
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<subviews>
|
||||
<view hidden="YES" userInteractionEnabled="NO" contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="oyW-Fd-ojD" userLabel="Sizing View">
|
||||
<rect key="frame" x="0.0" y="44" width="375" height="623"/>
|
||||
<rect key="frame" x="0.0" y="64" width="375" height="603"/>
|
||||
</view>
|
||||
<scrollView clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" alwaysBounceVertical="YES" indicatorStyle="white" keyboardDismissMode="onDrag" translatesAutoresizingMaskIntoConstraints="NO" id="WXx-hX-AXv">
|
||||
<rect key="frame" x="0.0" y="0.0" width="375" height="667"/>
|
||||
<subviews>
|
||||
<view contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" translatesAutoresizingMaskIntoConstraints="NO" id="2wp-qG-f0Z">
|
||||
<rect key="frame" x="0.0" y="0.0" width="375" height="623"/>
|
||||
<rect key="frame" x="0.0" y="0.0" width="375" height="603"/>
|
||||
<subviews>
|
||||
<stackView opaque="NO" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" axis="vertical" spacing="50" translatesAutoresizingMaskIntoConstraints="NO" id="YmX-7v-pxh">
|
||||
<rect key="frame" x="16" y="6" width="343" height="359.5"/>
|
||||
@@ -57,13 +57,13 @@
|
||||
<rect key="frame" x="0.0" y="0.0" width="343" height="67.5"/>
|
||||
<subviews>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Welcome to SideStore." textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" minimumScaleFactor="0.5" translatesAutoresizingMaskIntoConstraints="NO" id="EI2-V3-zQZ">
|
||||
<rect key="frame" x="0.0" y="0.0" width="333.5" height="41"/>
|
||||
<rect key="frame" x="0.0" y="0.0" width="343" height="41"/>
|
||||
<fontDescription key="fontDescription" type="boldSystem" pointSize="34"/>
|
||||
<color key="textColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Sign in with your Apple ID to get started." textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" minimumScaleFactor="0.5" translatesAutoresizingMaskIntoConstraints="NO" id="SNU-tv-8Au">
|
||||
<rect key="frame" x="0.0" y="47" width="308.5" height="20.5"/>
|
||||
<rect key="frame" x="0.0" y="47" width="306.5" height="20.5"/>
|
||||
<fontDescription key="fontDescription" type="system" pointSize="17"/>
|
||||
<color key="textColor" white="1" alpha="0.59999999999999998" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||
<nil key="highlightedColor"/>
|
||||
@@ -160,7 +160,7 @@
|
||||
</stackView>
|
||||
</subviews>
|
||||
</stackView>
|
||||
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="2N5-zd-fUj">
|
||||
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="system" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="2N5-zd-fUj">
|
||||
<rect key="frame" x="0.0" y="191" width="343" height="51"/>
|
||||
<color key="backgroundColor" name="SettingsHighlighted"/>
|
||||
<constraints>
|
||||
@@ -179,7 +179,7 @@
|
||||
</subviews>
|
||||
</stackView>
|
||||
<stackView opaque="NO" contentMode="scaleToFill" verticalCompressionResistancePriority="250" axis="vertical" spacing="4" translatesAutoresizingMaskIntoConstraints="NO" id="DBk-rT-ZE8">
|
||||
<rect key="frame" x="16" y="518.5" width="343" height="96.5"/>
|
||||
<rect key="frame" x="16" y="498.5" width="343" height="96.5"/>
|
||||
<subviews>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Why do we need this?" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="p9U-0q-Kn8">
|
||||
<rect key="frame" x="0.0" y="0.0" width="343" height="20.5"/>
|
||||
@@ -198,6 +198,10 @@
|
||||
</stackView>
|
||||
</subviews>
|
||||
<constraints>
|
||||
<constraint firstItem="DBk-rT-ZE8" firstAttribute="leading" secondItem="2wp-qG-f0Z" secondAttribute="leadingMargin" id="5AT-nV-ZP9"/>
|
||||
<constraint firstAttribute="bottomMargin" secondItem="DBk-rT-ZE8" secondAttribute="bottom" id="HgY-oY-8KM"/>
|
||||
<constraint firstAttribute="trailingMargin" secondItem="DBk-rT-ZE8" secondAttribute="trailing" id="VCf-bW-2K4"/>
|
||||
<constraint firstItem="YmX-7v-pxh" firstAttribute="top" secondItem="2wp-qG-f0Z" secondAttribute="top" constant="6" id="iUr-Nd-tkt"/>
|
||||
<constraint firstItem="DBk-rT-ZE8" firstAttribute="top" relation="greaterThanOrEqual" secondItem="YmX-7v-pxh" secondAttribute="bottom" constant="8" symbolic="YES" id="zTU-eY-DWd"/>
|
||||
</constraints>
|
||||
</view>
|
||||
@@ -215,19 +219,15 @@
|
||||
<constraints>
|
||||
<constraint firstAttribute="bottom" secondItem="WXx-hX-AXv" secondAttribute="bottom" id="0jL-Ky-ju6"/>
|
||||
<constraint firstAttribute="leadingMargin" secondItem="YmX-7v-pxh" secondAttribute="leading" id="2PO-lG-dmB"/>
|
||||
<constraint firstItem="DBk-rT-ZE8" firstAttribute="leading" secondItem="2wp-qG-f0Z" secondAttribute="leadingMargin" id="5AT-nV-ZP9"/>
|
||||
<constraint firstItem="oyW-Fd-ojD" firstAttribute="top" secondItem="zMn-DV-fpy" secondAttribute="top" id="730-db-ukB"/>
|
||||
<constraint firstItem="2wp-qG-f0Z" firstAttribute="bottomMargin" secondItem="DBk-rT-ZE8" secondAttribute="bottom" id="HgY-oY-8KM"/>
|
||||
<constraint firstItem="zMn-DV-fpy" firstAttribute="trailing" secondItem="oyW-Fd-ojD" secondAttribute="trailing" id="KGE-CN-SWf"/>
|
||||
<constraint firstItem="WXx-hX-AXv" firstAttribute="top" secondItem="mjy-4S-hyH" secondAttribute="top" id="LPQ-bF-ic0"/>
|
||||
<constraint firstItem="zMn-DV-fpy" firstAttribute="trailing" secondItem="WXx-hX-AXv" secondAttribute="trailing" id="MG7-A6-pKp"/>
|
||||
<constraint firstAttribute="trailingMargin" secondItem="YmX-7v-pxh" secondAttribute="trailing" id="O4T-nu-o3e"/>
|
||||
<constraint firstItem="zMn-DV-fpy" firstAttribute="bottom" secondItem="oyW-Fd-ojD" secondAttribute="bottom" id="PuX-ab-cEq"/>
|
||||
<constraint firstItem="oyW-Fd-ojD" firstAttribute="leading" secondItem="zMn-DV-fpy" secondAttribute="leading" id="SzC-gC-Nvi"/>
|
||||
<constraint firstItem="2wp-qG-f0Z" firstAttribute="trailingMargin" secondItem="DBk-rT-ZE8" secondAttribute="trailing" id="VCf-bW-2K4"/>
|
||||
<constraint firstItem="WXx-hX-AXv" firstAttribute="leading" secondItem="zMn-DV-fpy" secondAttribute="leading" id="d08-zF-5X6"/>
|
||||
<constraint firstItem="2wp-qG-f0Z" firstAttribute="height" secondItem="oyW-Fd-ojD" secondAttribute="height" id="dFN-pw-TWt"/>
|
||||
<constraint firstItem="YmX-7v-pxh" firstAttribute="top" secondItem="2wp-qG-f0Z" secondAttribute="top" constant="6" id="iUr-Nd-tkt"/>
|
||||
<constraint firstItem="2wp-qG-f0Z" firstAttribute="width" secondItem="oyW-Fd-ojD" secondAttribute="width" id="rYO-GN-0Lk"/>
|
||||
</constraints>
|
||||
</view>
|
||||
@@ -264,7 +264,7 @@
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<subviews>
|
||||
<stackView opaque="NO" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" axis="vertical" distribution="equalSpacing" translatesAutoresizingMaskIntoConstraints="NO" id="bp6-55-IG2">
|
||||
<rect key="frame" x="0.0" y="44" width="375" height="564"/>
|
||||
<rect key="frame" x="0.0" y="64" width="375" height="544"/>
|
||||
<subviews>
|
||||
<stackView opaque="NO" contentMode="scaleToFill" alignment="center" spacing="20" translatesAutoresizingMaskIntoConstraints="NO" id="FjP-tm-w7K">
|
||||
<rect key="frame" x="16" y="35" width="343" height="95.5"/>
|
||||
@@ -298,7 +298,7 @@
|
||||
</subviews>
|
||||
</stackView>
|
||||
<stackView opaque="NO" contentMode="scaleToFill" alignment="center" spacing="20" translatesAutoresizingMaskIntoConstraints="NO" id="LpI-Jt-SzX">
|
||||
<rect key="frame" x="16" y="168" width="343" height="95.5"/>
|
||||
<rect key="frame" x="16" y="161" width="343" height="95.5"/>
|
||||
<subviews>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="2" textAlignment="right" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="0LW-eE-qHa">
|
||||
<rect key="frame" x="0.0" y="0.0" width="59" height="95.5"/>
|
||||
@@ -310,7 +310,7 @@
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
<stackView opaque="NO" contentMode="scaleToFill" axis="vertical" spacing="5" translatesAutoresizingMaskIntoConstraints="NO" id="dMu-eg-gIO">
|
||||
<rect key="frame" x="79" y="15.5" width="264" height="64"/>
|
||||
<rect key="frame" x="79" y="16" width="264" height="64"/>
|
||||
<subviews>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Connect to Wi-Fi and VPN" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" minimumScaleFactor="0.5" translatesAutoresizingMaskIntoConstraints="NO" id="esj-pD-D4A">
|
||||
<rect key="frame" x="0.0" y="0.0" width="264" height="20.5"/>
|
||||
@@ -318,7 +318,7 @@
|
||||
<color key="textColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Enable SideStore VPN in Wireguard and be able to use Sidestore on the go." textAlignment="natural" lineBreakMode="tailTruncation" numberOfLines="2" baselineAdjustment="alignBaselines" minimumScaleFactor="0.5" translatesAutoresizingMaskIntoConstraints="NO" id="4rk-ge-FSj">
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Enable StosVPN and be able to use Sidestore on the go." textAlignment="natural" lineBreakMode="tailTruncation" numberOfLines="2" baselineAdjustment="alignBaselines" minimumScaleFactor="0.5" translatesAutoresizingMaskIntoConstraints="NO" id="4rk-ge-FSj">
|
||||
<rect key="frame" x="0.0" y="25.5" width="264" height="38.5"/>
|
||||
<fontDescription key="fontDescription" type="system" pointSize="16"/>
|
||||
<color key="textColor" white="1" alpha="0.59999999999999998" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||
@@ -329,7 +329,7 @@
|
||||
</subviews>
|
||||
</stackView>
|
||||
<stackView opaque="NO" contentMode="scaleToFill" alignment="center" spacing="20" translatesAutoresizingMaskIntoConstraints="NO" id="tfb-ja-9UC">
|
||||
<rect key="frame" x="16" y="300.5" width="343" height="95.5"/>
|
||||
<rect key="frame" x="16" y="287.5" width="343" height="95.5"/>
|
||||
<subviews>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="3" textAlignment="right" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="nVr-El-Csi">
|
||||
<rect key="frame" x="0.0" y="0.0" width="59" height="95.5"/>
|
||||
@@ -341,7 +341,7 @@
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
<stackView opaque="NO" contentMode="scaleToFill" axis="vertical" spacing="5" translatesAutoresizingMaskIntoConstraints="NO" id="z6Y-zi-teL">
|
||||
<rect key="frame" x="79" y="16" width="264" height="64"/>
|
||||
<rect key="frame" x="79" y="15.5" width="264" height="64"/>
|
||||
<subviews>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Download Apps" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" minimumScaleFactor="0.5" translatesAutoresizingMaskIntoConstraints="NO" id="JeJ-bk-UCA">
|
||||
<rect key="frame" x="0.0" y="0.0" width="264" height="20.5"/>
|
||||
@@ -360,7 +360,7 @@
|
||||
</subviews>
|
||||
</stackView>
|
||||
<stackView opaque="NO" contentMode="scaleToFill" alignment="center" spacing="20" translatesAutoresizingMaskIntoConstraints="NO" id="X3r-G1-vf2">
|
||||
<rect key="frame" x="16" y="433.5" width="343" height="95.5"/>
|
||||
<rect key="frame" x="16" y="413.5" width="343" height="95.5"/>
|
||||
<subviews>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="4" textAlignment="right" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="i2U-NL-plG">
|
||||
<rect key="frame" x="0.0" y="0.0" width="59" height="95.5"/>
|
||||
@@ -372,7 +372,7 @@
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
<stackView opaque="NO" contentMode="scaleToFill" axis="vertical" spacing="5" translatesAutoresizingMaskIntoConstraints="NO" id="Xs6-pJ-PUz">
|
||||
<rect key="frame" x="79" y="16" width="264" height="64"/>
|
||||
<rect key="frame" x="79" y="17" width="264" height="62"/>
|
||||
<subviews>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Apps Refresh Automatically" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" minimumScaleFactor="0.5" translatesAutoresizingMaskIntoConstraints="NO" id="nvb-Aq-sYa">
|
||||
<rect key="frame" x="0.0" y="0.0" width="264" height="20.5"/>
|
||||
@@ -381,7 +381,7 @@
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Apps are refreshed in the background while you are on SideStore VPN!" textAlignment="natural" lineBreakMode="tailTruncation" numberOfLines="2" baselineAdjustment="alignBaselines" minimumScaleFactor="0.5" translatesAutoresizingMaskIntoConstraints="NO" id="HU5-Hv-E3d">
|
||||
<rect key="frame" x="0.0" y="25.5" width="264" height="38.5"/>
|
||||
<rect key="frame" x="0.0" y="25.5" width="264" height="36.5"/>
|
||||
<fontDescription key="fontDescription" type="system" pointSize="16"/>
|
||||
<color key="textColor" white="1" alpha="0.59999999999999998" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||
<nil key="highlightedColor"/>
|
||||
@@ -431,7 +431,7 @@
|
||||
</objects>
|
||||
<point key="canvasLocation" x="1353" y="736"/>
|
||||
</scene>
|
||||
<!--Refresh AltStore-->
|
||||
<!--Refresh SideStore-->
|
||||
<scene sceneID="9Vh-dM-OqX">
|
||||
<objects>
|
||||
<viewController storyboardIdentifier="refreshAltStoreViewController" id="aoK-yE-UVT" customClass="RefreshAltStoreViewController" customModule="SideStore" customModuleProvider="target" sceneMemberID="viewController">
|
||||
@@ -485,7 +485,7 @@
|
||||
<constraint firstItem="tDQ-ao-1Jg" firstAttribute="leading" secondItem="R83-kV-365" secondAttribute="leadingMargin" id="zEt-Xr-kJx"/>
|
||||
</constraints>
|
||||
</view>
|
||||
<navigationItem key="navigationItem" title="Refresh AltStore" largeTitleDisplayMode="always" id="5nk-NR-jtV"/>
|
||||
<navigationItem key="navigationItem" title="Refresh SideStore" largeTitleDisplayMode="always" id="5nk-NR-jtV"/>
|
||||
<simulatedNavigationBarMetrics key="simulatedTopBarMetrics" prompted="NO"/>
|
||||
<connections>
|
||||
<outlet property="placeholderView" destination="fpO-Bf-gFY" id="q7d-au-d94"/>
|
||||
|
||||
@@ -287,7 +287,7 @@
|
||||
<rect key="frame" x="0.0" y="0.0" width="375" height="98"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<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"/>
|
||||
<color key="backgroundColor" name="Background"/>
|
||||
<fontDescription key="fontDescription" type="system" pointSize="15"/>
|
||||
@@ -353,7 +353,7 @@
|
||||
</stackView>
|
||||
</subviews>
|
||||
</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"/>
|
||||
<color key="backgroundColor" name="Background"/>
|
||||
<fontDescription key="fontDescription" type="system" pointSize="15"/>
|
||||
|
||||
@@ -13,21 +13,18 @@ final class CollapsingTextView: UITextView
|
||||
var isCollapsed = true {
|
||||
didSet {
|
||||
guard self.isCollapsed != oldValue else { return }
|
||||
self.shouldResetLayout = true
|
||||
self.setNeedsLayout()
|
||||
}
|
||||
}
|
||||
|
||||
var maximumNumberOfLines = 2 {
|
||||
didSet {
|
||||
self.shouldResetLayout = true
|
||||
self.setNeedsLayout()
|
||||
}
|
||||
}
|
||||
|
||||
var lineSpacing: Double = 2 {
|
||||
didSet {
|
||||
self.shouldResetLayout = true
|
||||
|
||||
if #available(iOS 16, *)
|
||||
{
|
||||
@@ -42,7 +39,6 @@ final class CollapsingTextView: UITextView
|
||||
|
||||
override var text: String! {
|
||||
didSet {
|
||||
self.shouldResetLayout = true
|
||||
|
||||
guard #available(iOS 16, *) else { return }
|
||||
self.updateText()
|
||||
@@ -51,9 +47,6 @@ final class CollapsingTextView: UITextView
|
||||
|
||||
let moreButton = UIButton(type: .system)
|
||||
|
||||
private var shouldResetLayout: Bool = false
|
||||
private var previousSize: CGSize?
|
||||
|
||||
override init(frame: CGRect, textContainer: NSTextContainer?)
|
||||
{
|
||||
super.init(frame: frame, textContainer: textContainer)
|
||||
@@ -115,45 +108,39 @@ final class CollapsingTextView: UITextView
|
||||
height: font.lineHeight)
|
||||
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)
|
||||
let maximumCollapsedHeight = font.lineHeight * Double(self.maximumNumberOfLines) + self.lineSpacing * Double(self.maximumNumberOfLines - 1)
|
||||
self.textContainer.maximumNumberOfLines = self.maximumNumberOfLines
|
||||
|
||||
if boundingSize.height.rounded() > maximumCollapsedHeight.rounded()
|
||||
{
|
||||
self.textContainer.maximumNumberOfLines = self.maximumNumberOfLines
|
||||
|
||||
var exclusionFrame = moreButtonFrame
|
||||
exclusionFrame.origin.y += self.moreButton.bounds.midY
|
||||
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
|
||||
}
|
||||
var exclusionFrame = moreButtonFrame
|
||||
exclusionFrame.origin.y += self.moreButton.bounds.midY
|
||||
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
|
||||
self.textContainer.maximumNumberOfLines = 0 // Fixes last line having slightly smaller line spacing.
|
||||
self.textContainer.exclusionPaths = []
|
||||
|
||||
self.moreButton.isHidden = true
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
self.textContainer.maximumNumberOfLines = 0
|
||||
self.textContainer.exclusionPaths = []
|
||||
|
||||
self.invalidateIntrinsicContentSize()
|
||||
self.moreButton.isHidden = true
|
||||
}
|
||||
|
||||
self.shouldResetLayout = false
|
||||
self.previousSize = self.bounds.size
|
||||
self.invalidateIntrinsicContentSize()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -8,7 +8,6 @@
|
||||
|
||||
import UIKit
|
||||
import Roxas
|
||||
import EmotionalDamage
|
||||
import minimuxer
|
||||
import WidgetKit
|
||||
|
||||
@@ -88,7 +87,6 @@ final class LaunchViewController: RSTLaunchViewController, UIDocumentPickerDeleg
|
||||
|
||||
|
||||
#if !targetEnvironment(simulator)
|
||||
start_em_proxy(bind_addr: Consts.Proxy.serverURL)
|
||||
|
||||
guard let pf = fetchPairingFile() else {
|
||||
displayError("Device pairing file not found.")
|
||||
@@ -177,7 +175,7 @@ final class LaunchViewController: RSTLaunchViewController, UIDocumentPickerDeleg
|
||||
|
||||
//Add "help" button to take user to wiki
|
||||
let wikiOption = UIAlertAction(title: "Help", style: .default) { (action) in
|
||||
let wikiURL: String = "https://docs.sidestore.io/docs/getting-started/pairing-file"
|
||||
let wikiURL: String = "https://docs.sidestore.io/docs/installation/pairing-file"
|
||||
if let url = URL(string: wikiURL) {
|
||||
UIApplication.shared.open(url)
|
||||
}
|
||||
@@ -255,13 +253,7 @@ final class LaunchViewController: RSTLaunchViewController, UIDocumentPickerDeleg
|
||||
try! FileManager.default.removeItem(at: FileManager.default.documentsDirectory.appendingPathComponent("\(pairingFileName)"))
|
||||
displayError("minimuxer failed to start, please restart SideStore. \((error as? LocalizedError)?.failureReason ?? "UNKNOWN ERROR!!!!!! REPORT TO GITHUB ISSUES!")")
|
||||
}
|
||||
if #available(iOS 17, *) {
|
||||
// TODO: iOS 17 and above have a new JIT implementation that is completely broken in SideStore :(
|
||||
}
|
||||
else {
|
||||
start_auto_mounter(documentsDirectory)
|
||||
}
|
||||
|
||||
start_auto_mounter(documentsDirectory)
|
||||
// Create destinationViewController now so view controllers can register for receiving Notifications.
|
||||
self.destinationViewController = self.storyboard!.instantiateViewController(withIdentifier: "tabBarController") as? TabBarController
|
||||
}
|
||||
|
||||
@@ -1519,6 +1519,24 @@ private extension AppManager
|
||||
patchAppOperation.addDependency(deactivateAppsOperation)
|
||||
|
||||
|
||||
let modifyAppExBundleIdOperation = RSTAsyncBlockOperation { operation in
|
||||
if !context.useMainProfile {
|
||||
operation.finish()
|
||||
return
|
||||
}
|
||||
|
||||
if let app = context.app, let profile = context.provisioningProfiles?[app.bundleIdentifier] {
|
||||
var appexBundleIds: [String: String] = [:]
|
||||
for appex in app.appExtensions {
|
||||
appexBundleIds[appex.bundleIdentifier] = appex.bundleIdentifier.replacingOccurrences(of: app.bundleIdentifier, with: profile.bundleIdentifier)
|
||||
}
|
||||
context.appexBundleIds = appexBundleIds
|
||||
}
|
||||
operation.finish()
|
||||
|
||||
}
|
||||
modifyAppExBundleIdOperation.addDependency(fetchProvisioningProfilesOperation)
|
||||
|
||||
/* Resign */
|
||||
let resignAppOperation = ResignAppOperation(context: context)
|
||||
resignAppOperation.resultHandler = { (result) in
|
||||
@@ -1533,6 +1551,7 @@ private extension AppManager
|
||||
}
|
||||
}
|
||||
resignAppOperation.addDependency(patchAppOperation)
|
||||
resignAppOperation.addDependency(modifyAppExBundleIdOperation)
|
||||
progress.addChild(resignAppOperation.progress, withPendingUnitCount: 20)
|
||||
|
||||
|
||||
@@ -1586,6 +1605,7 @@ private extension AppManager
|
||||
patchAppOperation,
|
||||
refreshAnisetteDataOperation,
|
||||
fetchProvisioningProfilesOperation,
|
||||
modifyAppExBundleIdOperation,
|
||||
resignAppOperation,
|
||||
sendAppOperation,
|
||||
installOperation
|
||||
@@ -1665,8 +1685,8 @@ private extension AppManager
|
||||
|
||||
let context = AppOperationContext(bundleIdentifier: app.bundleIdentifier, authenticatedContext: group.context)
|
||||
context.app = ALTApplication(fileURL: app.fileURL)
|
||||
|
||||
// Since this doesn't involve modifying app bundle which will cause re-install, this is safe in refresh path
|
||||
context.useMainProfile = app.useMainProfile
|
||||
// Since this doesn't involve modifying app bundle which will cause re-install, this is safe in refresh path
|
||||
//App-Extensions: Ensure DB data and disk state must match
|
||||
let dbAppEx: Set<InstalledExtension> = Set(app.appExtensions)
|
||||
let diskAppEx: Set<ALTApplication> = Set(context.app!.appExtensions)
|
||||
|
||||
@@ -166,9 +166,11 @@ class MyAppsViewController: UICollectionViewController, PeekPopPreviewing
|
||||
@IBAction func unwindToMyAppsViewController(_ segue: UIStoryboardSegue)
|
||||
{
|
||||
}
|
||||
|
||||
var minimuxerStatus: Bool {
|
||||
guard minimuxer.ready() else {
|
||||
ToastView(error: (OperationError.noWiFi as NSError).withLocalizedTitle("No WiFi or VPN!")).show(in: self)
|
||||
// added isMinimuxerStatusCheckEnabled to forcefully ignore minimuxer status if status check is disabled in settings
|
||||
guard !UserDefaults.standard.isMinimuxerStatusCheckEnabled || minimuxer.ready() else {
|
||||
ToastView(error: (OperationError.noWiFi as NSError).withLocalizedTitle("No Wi-Fi or VPN!")).show(in: self)
|
||||
return false
|
||||
}
|
||||
return true
|
||||
@@ -235,7 +237,8 @@ private extension MyAppsViewController
|
||||
cell.layoutMargins.right = self.view.layoutMargins.right
|
||||
|
||||
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.isIndicatingActivity = true
|
||||
@@ -281,12 +284,9 @@ private extension MyAppsViewController
|
||||
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()
|
||||
|
||||
// Below lines are necessary to avoid "more" button layout issues.
|
||||
cell.versionDescriptionTextView.setNeedsLayout()
|
||||
cell.layoutIfNeeded()
|
||||
}
|
||||
dataSource.prefetchHandler = { (installedApp, indexPath, completionHandler) in
|
||||
@@ -724,22 +724,29 @@ private extension MyAppsViewController
|
||||
|
||||
let cell = self.collectionView.cellForItem(at: indexPath) as? UpdateCollectionViewCell
|
||||
|
||||
// Toggle the state
|
||||
if self.expandedAppUpdates.contains(installedApp.bundleIdentifier)
|
||||
{
|
||||
self.expandedAppUpdates.remove(installedApp.bundleIdentifier)
|
||||
// Set collapsed mode on the cell
|
||||
cell?.mode = .collapsed
|
||||
}
|
||||
else
|
||||
{
|
||||
self.expandedAppUpdates.insert(installedApp.bundleIdentifier)
|
||||
// Set expanded mode on the cell
|
||||
cell?.mode = .expanded
|
||||
}
|
||||
|
||||
// Clear cached size so it's recalculated
|
||||
self.cachedUpdateSizes[installedApp.bundleIdentifier] = nil
|
||||
|
||||
self.collectionView.performBatchUpdates({
|
||||
self.collectionView.collectionViewLayout.invalidateLayout()
|
||||
}, completion: nil)
|
||||
// Animate the change smoothly with a duration
|
||||
UIView.animate(withDuration: 0.25) {
|
||||
self.collectionView.performBatchUpdates({
|
||||
self.collectionView.collectionViewLayout.invalidateLayout()
|
||||
}, completion: nil)
|
||||
}
|
||||
}
|
||||
|
||||
@IBAction func refreshApp(_ sender: UIButton)
|
||||
@@ -1480,15 +1487,6 @@ private extension MyAppsViewController
|
||||
guard minimuxerStatus else { return }
|
||||
}
|
||||
|
||||
if #available(iOS 17, *), !sidejitenabled {
|
||||
let error = OperationError.tooNewError as NSError
|
||||
let localizedError = error.withLocalizedTitle("No iOS 17 On Device JIT!")
|
||||
|
||||
ToastView(error: localizedError, opensLog: true).show(in: self)
|
||||
AppManager.shared.log(error, operation: .enableJIT, app: installedApp)
|
||||
return
|
||||
}
|
||||
|
||||
AppManager.shared.enableJIT(for: installedApp) { result in
|
||||
DispatchQueue.main.async {
|
||||
switch result {
|
||||
|
||||
@@ -21,12 +21,19 @@ extension UpdateCollectionViewCell
|
||||
{
|
||||
var mode: Mode = .expanded {
|
||||
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 versionDescriptionTextView: CollapsingTextView!
|
||||
// @IBOutlet var versionDescriptionTextView: CollapsingTextView!
|
||||
@IBOutlet var versionDescriptionTextView: CollapsingMarkdownView!
|
||||
|
||||
@IBOutlet private var blurView: UIVisualEffectView!
|
||||
|
||||
@@ -85,16 +92,16 @@ extension UpdateCollectionViewCell
|
||||
}
|
||||
}
|
||||
|
||||
override func systemLayoutSizeFitting(_ targetSize: CGSize, withHorizontalFittingPriority horizontalFittingPriority: UILayoutPriority, verticalFittingPriority: UILayoutPriority) -> CGSize
|
||||
{
|
||||
// Ensure cell is laid out so it will report correct size.
|
||||
self.versionDescriptionTextView.setNeedsLayout()
|
||||
self.versionDescriptionTextView.layoutIfNeeded()
|
||||
|
||||
let size = super.systemLayoutSizeFitting(targetSize, withHorizontalFittingPriority: horizontalFittingPriority, verticalFittingPriority: verticalFittingPriority)
|
||||
|
||||
return size
|
||||
}
|
||||
// override func systemLayoutSizeFitting(_ targetSize: CGSize, withHorizontalFittingPriority horizontalFittingPriority: UILayoutPriority, verticalFittingPriority: UILayoutPriority) -> CGSize
|
||||
// {
|
||||
// // Ensure cell is laid out so it will report correct size.
|
||||
// self.versionDescriptionTextView.setNeedsLayout()
|
||||
// self.versionDescriptionTextView.layoutIfNeeded()
|
||||
//
|
||||
// let size = super.systemLayoutSizeFitting(targetSize, withHorizontalFittingPriority: horizontalFittingPriority, verticalFittingPriority: verticalFittingPriority)
|
||||
//
|
||||
// return size
|
||||
// }
|
||||
}
|
||||
|
||||
private extension UpdateCollectionViewCell
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
<?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"/>
|
||||
<dependencies>
|
||||
<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="System colors in document resources" minToolsVersion="11.0"/>
|
||||
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
|
||||
@@ -11,7 +11,7 @@
|
||||
<objects>
|
||||
<placeholder placeholderIdentifier="IBFilesOwner" id="-1" userLabel="File's Owner"/>
|
||||
<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"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
|
||||
<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">
|
||||
<rect key="frame" x="0.0" y="0.0" width="343" height="125"/>
|
||||
<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"/>
|
||||
<constraints>
|
||||
<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">
|
||||
<rect key="frame" x="0.0" y="50" width="343" height="75"/>
|
||||
<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"/>
|
||||
<color key="textColor" systemColor="secondaryLabelColor"/>
|
||||
<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"/>
|
||||
</namedColor>
|
||||
<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>
|
||||
</resources>
|
||||
</document>
|
||||
|
||||
@@ -10,7 +10,6 @@ import UIKit
|
||||
import CoreData
|
||||
|
||||
import AltStoreCore
|
||||
import EmotionalDamage
|
||||
import minimuxer
|
||||
|
||||
typealias RefreshError = RefreshErrorCode.Error
|
||||
@@ -99,7 +98,6 @@ final class BackgroundRefreshAppsOperation: ResultOperation<[String: Result<Inst
|
||||
self.finish(.failure(RefreshError(.noInstalledApps)))
|
||||
return
|
||||
}
|
||||
start_em_proxy(bind_addr: Consts.Proxy.serverURL)
|
||||
target_minimuxer_address()
|
||||
let documentsDirectory = FileManager.default.documentsDirectory.absoluteString
|
||||
do {
|
||||
|
||||
@@ -220,16 +220,16 @@ struct OperationError: ALTLocalizedError {
|
||||
case .openAppFailed:
|
||||
let appName = self.appName ?? NSLocalizedString("The app", comment: "")
|
||||
return String(format: NSLocalizedString("SideStore was denied permission to launch %@.", comment: ""), appName)
|
||||
case .noWiFi: return NSLocalizedString("You do not appear to be connected to WiFi and/or the WireGuard VPN!\nSideStore will never be able to install or refresh applications without WiFi and the WireGuard VPN.", comment: "")
|
||||
case .tooNewError: return NSLocalizedString("iOS 17 has changed how JIT is enabled therefore SideStore cannot enable it without SideJITServer at this time, sorry for any inconvenience.\nWe will let everyone know once we have a solution!", comment: "")
|
||||
case .unableToConnectSideJIT: return NSLocalizedString("Unable to connect to SideJITServer Please check that you are on the Same Wi-Fi and your Firewall has been set correctly", comment: "")
|
||||
case .unableToRespondSideJITDevice: return NSLocalizedString("SideJITServer is unable to connect to your iDevice Please make sure you have paired your Device by doing 'SideJITServer -y' or try Refreshing SideJITServer from Settings", comment: "")
|
||||
case .wrongSideJITIP: return NSLocalizedString("Incorrect SideJITServer IP Please make sure that you are on the Samw Wifi as SideJITServer", comment: "")
|
||||
case .refreshsidejit: return NSLocalizedString("Unable to find App Please try Refreshing SideJITServer from Settings", comment: "")
|
||||
case .anisetteV1Error: return NSLocalizedString("An error occurred when getting anisette data from a V1 server: %@. Try using another anisette server.", comment: "")
|
||||
case .provisioningError: return NSLocalizedString("An error occurred when provisioning: %@ %@. Please try again. If the issue persists, report it on GitHub Issues!", comment: "")
|
||||
case .anisetteV3Error: return NSLocalizedString("An error occurred when getting anisette data from a V3 server: %@. Please try again. If the issue persists, report it on GitHub Issues!", comment: "")
|
||||
case .cacheClearError: return NSLocalizedString("An error occurred while clearing cache: %@", comment: "")
|
||||
case .noWiFi: return NSLocalizedString("You do not appear to be connected to Wi-Fi and/or StosVPN!\nSideStore will never be able to install or refresh applications without Wi-Fi and StosVPN.", comment: "")
|
||||
case .tooNewError: return NSLocalizedString("iOS 17 has changed how JIT is enabled, therefore, SideStore cannot enable JIT without SideJITServer, StikDebug, or SideStore-nightly at this time, sorry for any inconvenience.", comment: "")
|
||||
case .unableToConnectSideJIT: return NSLocalizedString("Unable to connect to SideJITServer. Please check that you are on the same Wi-Fi of and your Firewall has been set correctly on your server", comment: "")
|
||||
case .unableToRespondSideJITDevice: return NSLocalizedString("SideJITServer is unable to connect to your iDevice. Please make sure you have paired your Device by running 'SideJITServer -y', or try refreshing SideJITServer from Settings", comment: "")
|
||||
case .wrongSideJITIP: return NSLocalizedString("Incorrect SideJITServer IP. Please make sure that you are on the same Wi-Fi as SideJITServer", comment: "")
|
||||
case .refreshsidejit: return NSLocalizedString("Unable to find app; Please try refreshing SideJITServer from Settings", comment: "")
|
||||
case .anisetteV1Error: return NSLocalizedString("An error occurred while getting anisette data from a V1 server: %@. Try using another anisette server.", comment: "")
|
||||
case .provisioningError: return NSLocalizedString("An error occurred while provisioning: %@ %@. Please try again. If the issue persists, report it on GitHub Issues!", comment: "")
|
||||
case .anisetteV3Error: return NSLocalizedString("An error occurred while getting anisette data from a V3 server: %@. Please try again. If the issue persists, report it on GitHub Issues!", comment: "")
|
||||
case .cacheClearError: return NSLocalizedString("An error occurred while clearing the cache: %@", comment: "")
|
||||
case .SideJITIssue: return NSLocalizedString("An error occurred while using SideJIT: %@", comment: "")
|
||||
|
||||
case .refreshAppFailed:
|
||||
@@ -260,7 +260,7 @@ struct OperationError: ALTLocalizedError {
|
||||
var recoverySuggestion: String? {
|
||||
switch self.code
|
||||
{
|
||||
case .noWiFi: return NSLocalizedString("Make sure the VPN is toggled on and you are connected to any WiFi network!", comment: "")
|
||||
case .noWiFi: return NSLocalizedString("Make sure StosVPN is toggled on and you are connected to any Wi-Fi network!", comment: "")
|
||||
case .serverNotFound: return NSLocalizedString("Make sure you're on the same Wi-Fi network as a computer running AltServer, or try connecting this device to your computer via USB.", comment: "")
|
||||
case .maximumAppIDLimitReached:
|
||||
let baseMessage = NSLocalizedString("Delete sideloaded apps to free up App ID slots.", comment: "")
|
||||
@@ -308,9 +308,9 @@ extension MinimuxerError: LocalizedError {
|
||||
case .NoDevice:
|
||||
return NSLocalizedString("Cannot fetch the device from the muxer", comment: "")
|
||||
case .NoConnection:
|
||||
return NSLocalizedString("Unable to connect to the device, make sure Wireguard is enabled and you're connected to WiFi. This could mean an invalid pairing.", comment: "")
|
||||
return NSLocalizedString("Unable to connect to the device, make sure StosVPN is enabled and you're connected to Wi-Fi. This could mean an invalid pairing.", comment: "")
|
||||
case .PairingFile:
|
||||
return NSLocalizedString("Invalid pairing file. Your pairing file either didn't have a UDID, or it wasn't a valid plist. Please use jitterbugpair to generate it", comment: "")
|
||||
return NSLocalizedString("Invalid pairing file. Your pairing file either didn't have a UDID, or it wasn't a valid plist. Please use idevice_pair to regenerate it", comment: "")
|
||||
|
||||
case .CreateDebug:
|
||||
return self.createService(name: "debug")
|
||||
@@ -350,6 +350,38 @@ extension MinimuxerError: LocalizedError {
|
||||
return NSLocalizedString("Unable to manage profiles on the device", comment: "")
|
||||
case .ProfileRemove:
|
||||
return NSLocalizedString("Unable to manage profiles on the device", comment: "")
|
||||
case .CreateLockdown:
|
||||
return NSLocalizedString("Unable to connect to lockdown", comment: "")
|
||||
case .CreateCoreDevice:
|
||||
return NSLocalizedString("Unable to connect to core device proxy", comment: "")
|
||||
case .CreateSoftwareTunnel:
|
||||
return NSLocalizedString("Unable to create software tunnel", comment: "")
|
||||
case .CreateRemoteServer:
|
||||
return NSLocalizedString("Unable to connect to remote server", comment: "")
|
||||
case .CreateProcessControl:
|
||||
return NSLocalizedString("Unable to connect to process control", comment: "")
|
||||
case .GetLockdownValue:
|
||||
return NSLocalizedString("Unable to get value from lockdown", comment: "")
|
||||
case .Connect:
|
||||
return NSLocalizedString("Unable to connect to TCP port", comment: "")
|
||||
case .Close:
|
||||
return NSLocalizedString("Unable to close TCP port", comment: "")
|
||||
case .XpcHandshake:
|
||||
return NSLocalizedString("Unable to get services from XPC", comment: "")
|
||||
case .NoService:
|
||||
return NSLocalizedString("Device did not contain service", comment: "")
|
||||
case .InvalidProductVersion:
|
||||
return NSLocalizedString("Service version was in an unexpected format", comment: "")
|
||||
case .CreateFolder:
|
||||
return NSLocalizedString("Unable to create DDI folder", comment: "")
|
||||
case .DownloadImage:
|
||||
return NSLocalizedString("Unable to download DDI", comment: "")
|
||||
case .ImageLookup:
|
||||
return NSLocalizedString("Unable to lookup DDI images", comment: "")
|
||||
case .ImageRead:
|
||||
return NSLocalizedString("Unable to read images to memory", comment: "")
|
||||
case .Mount:
|
||||
return NSLocalizedString("Mount failed", comment: "")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -67,20 +67,22 @@ class FetchProvisioningProfilesOperation: ResultOperation<[String: ALTProvisioni
|
||||
|
||||
let dispatchGroup = DispatchGroup()
|
||||
|
||||
for appExtension in app.appExtensions
|
||||
{
|
||||
dispatchGroup.enter()
|
||||
|
||||
self.prepareProvisioningProfile(for: appExtension, parentApp: app, team: team, session: session) { (result) in
|
||||
switch result
|
||||
{
|
||||
case .failure(let e): error = e
|
||||
case .success(let profile): profiles[appExtension.bundleIdentifier] = profile
|
||||
if !self.context.useMainProfile {
|
||||
for appExtension in app.appExtensions
|
||||
{
|
||||
dispatchGroup.enter()
|
||||
|
||||
self.prepareProvisioningProfile(for: appExtension, parentApp: app, team: team, session: session) { (result) in
|
||||
switch result
|
||||
{
|
||||
case .failure(let e): error = e
|
||||
case .success(let profile): profiles[appExtension.bundleIdentifier] = profile
|
||||
}
|
||||
|
||||
dispatchGroup.leave()
|
||||
|
||||
self.progress.completedUnitCount += 1
|
||||
}
|
||||
|
||||
dispatchGroup.leave()
|
||||
|
||||
self.progress.completedUnitCount += 1
|
||||
}
|
||||
}
|
||||
|
||||
@@ -360,13 +362,6 @@ extension FetchProvisioningProfilesOperation
|
||||
}
|
||||
}
|
||||
|
||||
class FetchProvisioningProfilesRefreshOperation: FetchProvisioningProfilesOperation, @unchecked Sendable {
|
||||
override init(context: AppOperationContext)
|
||||
{
|
||||
super.init(context: context)
|
||||
}
|
||||
}
|
||||
|
||||
class FetchProvisioningProfilesInstallOperation: FetchProvisioningProfilesOperation, @unchecked Sendable{
|
||||
override init(context: AppOperationContext)
|
||||
{
|
||||
@@ -613,3 +608,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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,6 +11,7 @@ import CoreData
|
||||
|
||||
import AltStoreCore
|
||||
import Roxas
|
||||
import SemanticVersion
|
||||
|
||||
@objc(FetchSourceOperation)
|
||||
final class FetchSourceOperation: ResultOperation<Source>
|
||||
@@ -246,9 +247,20 @@ private extension FetchSourceOperation
|
||||
#endif
|
||||
}
|
||||
|
||||
if let previousSourceID = self.$source.identifier
|
||||
let incomingSourceID = source.identifier
|
||||
if let previousSourceID = self.$source.identifier,
|
||||
incomingSourceID != previousSourceID
|
||||
{
|
||||
guard source.identifier == previousSourceID else { throw SourceError.changedID(source.identifier, previousID: previousSourceID, source: source) }
|
||||
// if let version = BuildInfo().marketing_version,
|
||||
// SemanticVersion(version)! <= SemanticVersion("0.6.1")!
|
||||
// {
|
||||
// // delete the source, so that incoming will be saved.
|
||||
// self.source?.managedObjectContext?.delete(self.source!)
|
||||
// }
|
||||
// else
|
||||
// {
|
||||
throw SourceError.changedID(source.identifier, previousID: self.$source.identifier ?? "nil", source: source)
|
||||
// }
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -72,6 +72,8 @@ final class InstallAppOperation: ResultOperation<InstalledApp>
|
||||
}
|
||||
|
||||
installedApp.update(resignedApp: resignedApp, certificateSerialNumber: certificate.serialNumber, storeBuildVersion: storeBuildVersion)
|
||||
installedApp.useMainProfile = self.context.useMainProfile
|
||||
|
||||
installedApp.needsResign = false
|
||||
|
||||
if let team = DatabaseManager.shared.activeTeam(in: backgroundContext)
|
||||
|
||||
@@ -66,6 +66,8 @@ class AppOperationContext
|
||||
|
||||
var app: ALTApplication?
|
||||
var provisioningProfiles: [String: ALTProvisioningProfile]?
|
||||
var appexBundleIds: [String: String]?
|
||||
var useMainProfile = false
|
||||
|
||||
var isFinished = false
|
||||
|
||||
|
||||
@@ -136,7 +136,11 @@ final class RemoveAppExtensionsOperation: ResultOperation<Void>
|
||||
alertController.addAction(UIAlertAction(title: UIAlertAction.cancel.title, style: UIAlertAction.cancel.style, handler: { (action) in
|
||||
self.finish(.failure(OperationError.cancelled))
|
||||
}))
|
||||
alertController.addAction(UIAlertAction(title: NSLocalizedString("Keep App Extensions", comment: ""), style: .default) { (action) in
|
||||
alertController.addAction(UIAlertAction(title: NSLocalizedString("Keep App Extensions (Use Main Profile)", comment: ""), style: .default) { (action) in
|
||||
self.context.useMainProfile = true
|
||||
self.finish(.success(()))
|
||||
})
|
||||
alertController.addAction(UIAlertAction(title: NSLocalizedString("Keep App Extensions (Register App ID for Each Extension)", comment: ""), style: .default) { (action) in
|
||||
self.finish(.success(()))
|
||||
})
|
||||
alertController.addAction(UIAlertAction(title: NSLocalizedString("Remove App Extensions", comment: ""), style: .destructive) { (action) in
|
||||
|
||||
@@ -55,7 +55,7 @@ final class ResignAppOperation: ResultOperation<ALTApplication>
|
||||
let prepareAppProgress = Progress.discreteProgress(totalUnitCount: 2)
|
||||
self.progress.addChild(prepareAppProgress, withPendingUnitCount: 3)
|
||||
|
||||
let prepareAppBundleProgress = self.prepareAppBundle(for: app, profiles: profiles) { (result) in
|
||||
let prepareAppBundleProgress = self.prepareAppBundle(for: app, profiles: profiles, appexBundleIds: context.appexBundleIds ?? [:]) { (result) in
|
||||
guard let appBundleURL = self.process(result) else { return }
|
||||
|
||||
// Resign app bundle
|
||||
@@ -107,7 +107,7 @@ final class ResignAppOperation: ResultOperation<ALTApplication>
|
||||
|
||||
private extension ResignAppOperation
|
||||
{
|
||||
func prepareAppBundle(for app: ALTApplication, profiles: [String: ALTProvisioningProfile], completionHandler: @escaping (Result<URL, Error>) -> Void) -> Progress
|
||||
func prepareAppBundle(for app: ALTApplication, profiles: [String: ALTProvisioningProfile], appexBundleIds: [String: String], completionHandler: @escaping (Result<URL, Error>) -> Void) -> Progress
|
||||
{
|
||||
let progress = Progress.discreteProgress(totalUnitCount: 1)
|
||||
|
||||
@@ -119,10 +119,15 @@ private extension ResignAppOperation
|
||||
func prepare(_ bundle: Bundle, additionalInfoDictionaryValues: [String: Any] = [:]) throws
|
||||
{
|
||||
guard let identifier = bundle.bundleIdentifier else { throw ALTError(.missingAppBundle) }
|
||||
guard let profile = profiles[identifier] else { throw ALTError(.missingProvisioningProfile) }
|
||||
guard let profile = context.useMainProfile ? profiles.values.first : profiles[identifier] else { throw ALTError(.missingProvisioningProfile) }
|
||||
guard var infoDictionary = bundle.completeInfoDictionary else { throw ALTError(.missingInfoPlist) }
|
||||
|
||||
infoDictionary[kCFBundleIdentifierKey as String] = profile.bundleIdentifier
|
||||
if let forcedBundleIdentifier = appexBundleIds[identifier] {
|
||||
infoDictionary[kCFBundleIdentifierKey as String] = forcedBundleIdentifier
|
||||
} else {
|
||||
infoDictionary[kCFBundleIdentifierKey as String] = profile.bundleIdentifier
|
||||
}
|
||||
|
||||
infoDictionary[Bundle.Info.altBundleID] = identifier
|
||||
infoDictionary[Bundle.Info.devicePairingString] = "<insert pairing file here>"
|
||||
infoDictionary.removeValue(forKey: "DTXcode")
|
||||
|
||||
@@ -8,7 +8,6 @@
|
||||
|
||||
import UIKit
|
||||
import AltStoreCore
|
||||
import EmotionalDamage
|
||||
|
||||
@available(iOS 13, *)
|
||||
final class SceneDelegate: UIResponder, UIWindowSceneDelegate
|
||||
@@ -40,7 +39,6 @@ final class SceneDelegate: UIResponder, UIWindowSceneDelegate
|
||||
guard DatabaseManager.shared.isStarted else { return }
|
||||
|
||||
AppManager.shared.update()
|
||||
start_em_proxy(bind_addr: Consts.Proxy.serverURL)
|
||||
|
||||
PatreonAPI.shared.refreshPatreonAccount()
|
||||
}
|
||||
@@ -142,8 +140,61 @@ private extension SceneDelegate
|
||||
NotificationCenter.default.post(name: AppDelegate.addSourceDeepLinkNotification, object: nil, userInfo: [AppDelegate.addSourceDeepLinkURLKey: sourceURL])
|
||||
}
|
||||
|
||||
case "pairing":
|
||||
let queryItems = components.queryItems?.reduce(into: [String: String]()) { $0[$1.name.lowercased()] = $1.value } ?? [:]
|
||||
Logger.main.info("queryItems \(queryItems)")
|
||||
guard let callbackTemplate = queryItems["urlname"]?.removingPercentEncoding else { return }
|
||||
|
||||
DispatchQueue.main.async {
|
||||
exportPairingFile(callbackTemplate)
|
||||
}
|
||||
|
||||
case "certificate":
|
||||
let queryItems = components.queryItems?.reduce(into: [String: String]()) { $0[$1.name.lowercased()] = $1.value } ?? [:]
|
||||
guard let callbackTemplate = queryItems["callback_template"]?.removingPercentEncoding else { return }
|
||||
|
||||
DispatchQueue.main.async {
|
||||
NotificationCenter.default.post(name: AppDelegate.exportCertificateNotification, object: nil, userInfo: [AppDelegate.exportCertificateCallbackTemplateKey: callbackTemplate])
|
||||
}
|
||||
|
||||
default: break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
func exportPairingFile(_ urlname: String) {
|
||||
if let windowScene = UIApplication.shared.connectedScenes.first as? UIWindowScene,
|
||||
let window = windowScene.windows.first, let viewcontroller = window.rootViewController {
|
||||
let fm = FileManager.default
|
||||
let documentsPath = fm.documentsDirectory.appendingPathComponent("ALTPairingFile.mobiledevicepairing")
|
||||
|
||||
|
||||
guard let data = try? Data(contentsOf: documentsPath) else {
|
||||
let toastView = ToastView(text: NSLocalizedString("Failed to find Pairing File!", comment: ""), detailText: nil)
|
||||
toastView.show(in: viewcontroller)
|
||||
return
|
||||
}
|
||||
|
||||
let base64encodedCert = data.base64EncodedString()
|
||||
var allowedQueryParamAndKey = NSCharacterSet.urlQueryAllowed
|
||||
allowedQueryParamAndKey.remove(charactersIn: ";/?:@&=+$, ")
|
||||
guard let encodedCert = base64encodedCert.addingPercentEncoding(withAllowedCharacters: allowedQueryParamAndKey) else {
|
||||
let toastView = ToastView(text: NSLocalizedString("Failed to encode pairingFile!", comment: ""), detailText: nil)
|
||||
toastView.show(in: viewcontroller)
|
||||
return
|
||||
}
|
||||
|
||||
var urlStr = "\(urlname)://pairingFile?data=$(BASE64_PAIRING)"
|
||||
let finished = urlStr.replacingOccurrences(of: "$(BASE64_PAIRING)", with: encodedCert, options: .literal, range: nil)
|
||||
|
||||
print(finished)
|
||||
guard let callbackUrl = URL(string: finished) else {
|
||||
let toastView = ToastView(text: NSLocalizedString("Failed to initialize callback URL!", comment: ""), detailText: nil)
|
||||
toastView.show(in: viewcontroller)
|
||||
return
|
||||
}
|
||||
UIApplication.shared.open(callbackUrl)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -380,13 +380,8 @@ private extension ErrorLogViewController
|
||||
|
||||
func searchFAQ(for loggedError: LoggedError)
|
||||
{
|
||||
let baseURL = URL(string: "https://faq.altstore.io/getting-started/error-codes")!
|
||||
var components = URLComponents(url: baseURL, resolvingAgainstBaseURL: false)!
|
||||
|
||||
let query = [loggedError.domain, "\(loggedError.error.displayCode)"].joined(separator: "+")
|
||||
components.queryItems = [URLQueryItem(name: "q", value: query)]
|
||||
|
||||
let safariViewController = SFSafariViewController(url: components.url ?? baseURL)
|
||||
let staticURL = URL(string: "https://docs.sidestore.io/docs/troubleshooting/error-codes")!
|
||||
let safariViewController = SFSafariViewController(url: staticURL)
|
||||
safariViewController.preferredControlTintColor = .altPrimary
|
||||
self.present(safariViewController, animated: true)
|
||||
}
|
||||
|
||||
@@ -82,7 +82,7 @@ final class AboutPatreonHeaderView: UICollectionReusableView
|
||||
imageView.layer.cornerRadius = imageView.bounds.midY
|
||||
}
|
||||
|
||||
for button in [self.supportButton, self.accountButton].compactMap({$0})
|
||||
for button in [self.supportButton, self.accountButton, self.twitterButton, self.instagramButton].compactMap({$0})
|
||||
{
|
||||
button.clipsToBounds = true
|
||||
button.layer.cornerRadius = 16
|
||||
|
||||
@@ -22,7 +22,7 @@
|
||||
<color key="tintColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||
<color key="separatorColor" white="1" alpha="0.25" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||
<stackView key="tableFooterView" opaque="NO" contentMode="scaleToFill" axis="vertical" distribution="equalCentering" alignment="center" spacing="15" id="48g-cT-stR">
|
||||
<rect key="frame" x="0.0" y="1986.3333377838135" width="402" height="125"/>
|
||||
<rect key="frame" x="0.0" y="2040.3333358764648" width="402" height="125"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
|
||||
<subviews>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="900" text="Follow SideStore for updates" textAlignment="center" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="XFa-MY-7cV">
|
||||
@@ -698,7 +698,7 @@
|
||||
</userDefinedRuntimeAttributes>
|
||||
</tableViewCell>
|
||||
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="default" indentationWidth="10" rowHeight="51" id="O5R-Al-lGj" customClass="InsetGroupTableViewCell" customModule="SideStore" customModuleProvider="target">
|
||||
<rect key="frame" x="0.0" y="1071.6666698455811" width="402" height="51"/>
|
||||
<rect key="frame" x="0.0" y="1107.6666698455811" width="402" height="51"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="O5R-Al-lGj" id="CrG-Mr-xQq">
|
||||
<rect key="frame" x="0.0" y="0.0" width="402" height="51"/>
|
||||
@@ -739,19 +739,19 @@
|
||||
<tableViewSection headerTitle="" id="OMa-EK-hRI">
|
||||
<cells>
|
||||
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="default" indentationWidth="10" rowHeight="51" id="FMZ-as-Ljo" customClass="InsetGroupTableViewCell" customModule="SideStore" customModuleProvider="target">
|
||||
<rect key="frame" x="0.0" y="1163.0000038146973" width="402" height="51"/>
|
||||
<rect key="frame" x="0.0" y="1199.0000038146973" width="402" height="51"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="FMZ-as-Ljo" id="JzL-Of-A3T">
|
||||
<rect key="frame" x="0.0" y="0.0" width="402" height="51"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<subviews>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Send Feedback" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="pMI-Aj-nQF">
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" ambiguous="YES" text="Send Feedback" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="pMI-Aj-nQF">
|
||||
<rect key="frame" x="29.999999999999993" y="15.333333333333334" width="125.33333333333331" height="20.333333333333329"/>
|
||||
<fontDescription key="fontDescription" type="boldSystem" pointSize="17"/>
|
||||
<color key="textColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
<imageView clipsSubviews="YES" userInteractionEnabled="NO" contentMode="scaleAspectFit" horizontalHuggingPriority="251" verticalHuggingPriority="251" translatesAutoresizingMaskIntoConstraints="NO" id="Jyy-x0-Owj">
|
||||
<imageView clipsSubviews="YES" userInteractionEnabled="NO" contentMode="scaleAspectFit" horizontalHuggingPriority="251" verticalHuggingPriority="251" ambiguous="YES" translatesAutoresizingMaskIntoConstraints="NO" id="Jyy-x0-Owj">
|
||||
<rect key="frame" x="356.33333333333331" y="14.333333333333334" width="15.666666666666686" height="22.333333333333329"/>
|
||||
<imageReference key="image" image="chevron.right" catalog="system" symbolScale="large"/>
|
||||
</imageView>
|
||||
@@ -773,19 +773,19 @@
|
||||
</userDefinedRuntimeAttributes>
|
||||
</tableViewCell>
|
||||
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="default" indentationWidth="10" rowHeight="51" id="Qca-pU-sJh" customClass="InsetGroupTableViewCell" customModule="SideStore" customModuleProvider="target">
|
||||
<rect key="frame" x="0.0" y="1214.0000038146973" width="402" height="51"/>
|
||||
<rect key="frame" x="0.0" y="1250.0000038146973" width="402" height="51"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="Qca-pU-sJh" id="QtU-8J-VQN">
|
||||
<rect key="frame" x="0.0" y="0.0" width="402" height="51"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<subviews>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="View Refresh Attempts" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="sni-07-q0M">
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" ambiguous="YES" text="View Refresh Attempts" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="sni-07-q0M">
|
||||
<rect key="frame" x="30" y="15.333333333333334" width="187.66666666666666" height="20.333333333333329"/>
|
||||
<fontDescription key="fontDescription" type="boldSystem" pointSize="17"/>
|
||||
<color key="textColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
<imageView clipsSubviews="YES" userInteractionEnabled="NO" contentMode="scaleAspectFit" horizontalHuggingPriority="251" verticalHuggingPriority="251" translatesAutoresizingMaskIntoConstraints="NO" id="4d3-me-Hqc">
|
||||
<imageView clipsSubviews="YES" userInteractionEnabled="NO" contentMode="scaleAspectFit" horizontalHuggingPriority="251" verticalHuggingPriority="251" ambiguous="YES" translatesAutoresizingMaskIntoConstraints="NO" id="4d3-me-Hqc">
|
||||
<rect key="frame" x="356.33333333333331" y="14.333333333333334" width="15.666666666666686" height="22.333333333333329"/>
|
||||
<imageReference key="image" image="chevron.right" catalog="system" symbolScale="large"/>
|
||||
</imageView>
|
||||
@@ -810,19 +810,19 @@
|
||||
</connections>
|
||||
</tableViewCell>
|
||||
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="default" indentationWidth="10" rowHeight="51" id="VrV-qI-zXF" customClass="InsetGroupTableViewCell" customModule="SideStore" customModuleProvider="target">
|
||||
<rect key="frame" x="0.0" y="1265.0000038146973" width="402" height="51"/>
|
||||
<rect key="frame" x="0.0" y="1301.0000038146973" width="402" height="51"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="VrV-qI-zXF" id="w1r-uY-4pD">
|
||||
<rect key="frame" x="0.0" y="0.0" width="402" height="51"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<subviews>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="SideJITServer" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="46q-DB-5nc">
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" ambiguous="YES" text="SideJITServer" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="46q-DB-5nc">
|
||||
<rect key="frame" x="29.999999999999993" y="15.333333333333334" width="115.33333333333331" height="20.333333333333329"/>
|
||||
<fontDescription key="fontDescription" type="boldSystem" pointSize="17"/>
|
||||
<color key="textColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
<imageView clipsSubviews="YES" userInteractionEnabled="NO" contentMode="scaleAspectFit" horizontalHuggingPriority="251" verticalHuggingPriority="251" translatesAutoresizingMaskIntoConstraints="NO" id="wvD-eZ-nQI">
|
||||
<imageView clipsSubviews="YES" userInteractionEnabled="NO" contentMode="scaleAspectFit" horizontalHuggingPriority="251" verticalHuggingPriority="251" ambiguous="YES" translatesAutoresizingMaskIntoConstraints="NO" id="wvD-eZ-nQI">
|
||||
<rect key="frame" x="356.33333333333331" y="14.333333333333334" width="15.666666666666686" height="22.333333333333329"/>
|
||||
<imageReference key="image" image="chevron.right" catalog="system" symbolScale="large"/>
|
||||
</imageView>
|
||||
@@ -844,19 +844,19 @@
|
||||
</userDefinedRuntimeAttributes>
|
||||
</tableViewCell>
|
||||
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="default" indentationWidth="10" rowHeight="51" id="VNn-u4-cN8" customClass="InsetGroupTableViewCell" customModule="SideStore" customModuleProvider="target">
|
||||
<rect key="frame" x="0.0" y="1316.0000038146973" width="402" height="51"/>
|
||||
<rect key="frame" x="0.0" y="1352.0000038146973" width="402" height="51"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="VNn-u4-cN8" id="4bh-qe-l2N">
|
||||
<rect key="frame" x="0.0" y="0.0" width="402" height="51"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<subviews>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Reset Pairing File" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="ysS-9s-dXm">
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" ambiguous="YES" text="Reset Pairing File" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="ysS-9s-dXm">
|
||||
<rect key="frame" x="30" y="15.333333333333334" width="140" height="20.333333333333329"/>
|
||||
<fontDescription key="fontDescription" type="boldSystem" pointSize="17"/>
|
||||
<color key="textColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
<imageView clipsSubviews="YES" userInteractionEnabled="NO" contentMode="scaleAspectFit" horizontalHuggingPriority="251" verticalHuggingPriority="251" translatesAutoresizingMaskIntoConstraints="NO" id="r09-mH-pOD">
|
||||
<imageView clipsSubviews="YES" userInteractionEnabled="NO" contentMode="scaleAspectFit" horizontalHuggingPriority="251" verticalHuggingPriority="251" ambiguous="YES" translatesAutoresizingMaskIntoConstraints="NO" id="r09-mH-pOD">
|
||||
<rect key="frame" x="356.33333333333331" y="14.333333333333334" width="15.666666666666686" height="22.333333333333329"/>
|
||||
<imageReference key="image" image="chevron.right" catalog="system" symbolScale="large"/>
|
||||
</imageView>
|
||||
@@ -878,19 +878,19 @@
|
||||
</userDefinedRuntimeAttributes>
|
||||
</tableViewCell>
|
||||
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="default" indentationWidth="10" rowHeight="51" id="e7s-hL-kv9" customClass="InsetGroupTableViewCell" customModule="SideStore" customModuleProvider="target">
|
||||
<rect key="frame" x="0.0" y="1367.0000038146973" width="402" height="51"/>
|
||||
<rect key="frame" x="0.0" y="1403.0000038146973" width="402" height="51"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="e7s-hL-kv9" id="yjL-Mu-HTk">
|
||||
<rect key="frame" x="0.0" y="0.0" width="402" height="51"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<subviews>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Anisette Servers" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="eds-Dj-36y">
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" ambiguous="YES" text="Anisette Servers" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="eds-Dj-36y">
|
||||
<rect key="frame" x="30" y="15.333333333333334" width="135.66666666666666" height="20.333333333333329"/>
|
||||
<fontDescription key="fontDescription" type="boldSystem" pointSize="17"/>
|
||||
<color key="textColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
<imageView clipsSubviews="YES" userInteractionEnabled="NO" contentMode="scaleAspectFit" horizontalHuggingPriority="251" verticalHuggingPriority="251" translatesAutoresizingMaskIntoConstraints="NO" id="0dh-yd-7i9">
|
||||
<imageView clipsSubviews="YES" userInteractionEnabled="NO" contentMode="scaleAspectFit" horizontalHuggingPriority="251" verticalHuggingPriority="251" ambiguous="YES" translatesAutoresizingMaskIntoConstraints="NO" id="0dh-yd-7i9">
|
||||
<rect key="frame" x="356.33333333333331" y="14.333333333333334" width="15.666666666666686" height="22.333333333333329"/>
|
||||
<imageReference key="image" image="chevron.right" catalog="system" symbolScale="large"/>
|
||||
</imageView>
|
||||
@@ -912,7 +912,7 @@
|
||||
</userDefinedRuntimeAttributes>
|
||||
</tableViewCell>
|
||||
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="default" indentationWidth="10" rowHeight="51" id="XW5-Zc-nXH" customClass="InsetGroupTableViewCell" customModule="SideStore" customModuleProvider="target">
|
||||
<rect key="frame" x="0.0" y="1418.0000038146973" width="402" height="51"/>
|
||||
<rect key="frame" x="0.0" y="1454.0000038146973" width="402" height="51"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="XW5-Zc-nXH" id="AtM-bL-8pS">
|
||||
<rect key="frame" x="0.0" y="0.0" width="402" height="51"/>
|
||||
@@ -947,7 +947,7 @@
|
||||
</userDefinedRuntimeAttributes>
|
||||
</tableViewCell>
|
||||
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="default" indentationWidth="10" rowHeight="51" id="qbY-8c-LYT" customClass="InsetGroupTableViewCell" customModule="SideStore" customModuleProvider="target">
|
||||
<rect key="frame" x="0.0" y="1469.0000038146973" width="402" height="51"/>
|
||||
<rect key="frame" x="0.0" y="1505.0000038146973" width="402" height="51"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="qbY-8c-LYT" id="NxK-qB-w7Q">
|
||||
<rect key="frame" x="0.0" y="0.0" width="402" height="51"/>
|
||||
@@ -985,10 +985,70 @@
|
||||
</tableViewCell>
|
||||
</cells>
|
||||
</tableViewSection>
|
||||
<tableViewSection id="ZhW-yK-wdJ">
|
||||
<cells>
|
||||
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="default" indentationWidth="10" rowHeight="51" id="Y6h-Bo-yec" customClass="InsetGroupTableViewCell" customModule="SideStore" customModuleProvider="target">
|
||||
<rect key="frame" x="0.0" y="1592.0000038146973" width="402" height="51"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="Y6h-Bo-yec" id="4Jf-I6-v7z">
|
||||
<rect key="frame" x="0.0" y="0.0" width="402" height="51"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<subviews>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Import Signing Certificate..." textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="rv6-S1-2gw">
|
||||
<rect key="frame" x="30.000000000000014" y="15.333333333333334" width="227.33333333333337" height="20.333333333333329"/>
|
||||
<fontDescription key="fontDescription" type="boldSystem" pointSize="17"/>
|
||||
<color key="textColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
</subviews>
|
||||
<constraints>
|
||||
<constraint firstItem="rv6-S1-2gw" firstAttribute="leading" secondItem="4Jf-I6-v7z" secondAttribute="leadingMargin" id="7zH-bg-kLS"/>
|
||||
<constraint firstItem="rv6-S1-2gw" firstAttribute="centerY" secondItem="4Jf-I6-v7z" secondAttribute="centerY" id="Yls-DF-HHr"/>
|
||||
</constraints>
|
||||
</tableViewCellContentView>
|
||||
<color key="backgroundColor" white="1" alpha="0.14999999999999999" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||
<edgeInsets key="layoutMargins" top="8" left="30" bottom="8" right="30"/>
|
||||
<userDefinedRuntimeAttributes>
|
||||
<userDefinedRuntimeAttribute type="number" keyPath="style">
|
||||
<integer key="value" value="1"/>
|
||||
</userDefinedRuntimeAttribute>
|
||||
<userDefinedRuntimeAttribute type="boolean" keyPath="isSelectable" value="YES"/>
|
||||
</userDefinedRuntimeAttributes>
|
||||
</tableViewCell>
|
||||
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="default" indentationWidth="10" rowHeight="51" id="dLk-d6-X4T" customClass="InsetGroupTableViewCell" customModule="SideStore" customModuleProvider="target">
|
||||
<rect key="frame" x="0.0" y="1643.0000038146973" width="402" height="51"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="dLk-d6-X4T" id="Okl-3m-rde">
|
||||
<rect key="frame" x="0.0" y="0.0" width="402" height="51"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<subviews>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Export Signing Certificate..." textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="WPe-mj-W7t">
|
||||
<rect key="frame" x="29.999999999999986" y="15.333333333333334" width="226.66666666666663" height="20.333333333333329"/>
|
||||
<fontDescription key="fontDescription" type="boldSystem" pointSize="17"/>
|
||||
<color key="textColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
</subviews>
|
||||
<constraints>
|
||||
<constraint firstItem="WPe-mj-W7t" firstAttribute="leading" secondItem="Okl-3m-rde" secondAttribute="leadingMargin" id="9g7-9Z-ZZQ"/>
|
||||
<constraint firstItem="WPe-mj-W7t" firstAttribute="centerY" secondItem="Okl-3m-rde" secondAttribute="centerY" id="RiS-WT-srl"/>
|
||||
</constraints>
|
||||
</tableViewCellContentView>
|
||||
<color key="backgroundColor" white="1" alpha="0.14999999999999999" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||
<edgeInsets key="layoutMargins" top="8" left="30" bottom="8" right="30"/>
|
||||
<userDefinedRuntimeAttributes>
|
||||
<userDefinedRuntimeAttribute type="number" keyPath="style">
|
||||
<integer key="value" value="3"/>
|
||||
</userDefinedRuntimeAttribute>
|
||||
<userDefinedRuntimeAttribute type="boolean" keyPath="isSelectable" value="YES"/>
|
||||
</userDefinedRuntimeAttributes>
|
||||
</tableViewCell>
|
||||
</cells>
|
||||
</tableViewSection>
|
||||
<tableViewSection headerTitle="" id="lLQ-K0-XSb">
|
||||
<cells>
|
||||
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="default" indentationWidth="10" rowHeight="51" id="daQ-mk-yqC" customClass="InsetGroupTableViewCell" customModule="SideStore" customModuleProvider="target">
|
||||
<rect key="frame" x="0.0" y="1560.3333377838135" width="402" height="51"/>
|
||||
<rect key="frame" x="0.0" y="1734.3333377838135" width="402" height="51"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="daQ-mk-yqC" id="ZkW-ZR-twy">
|
||||
<rect key="frame" x="0.0" y="0.0" width="402" height="51"/>
|
||||
@@ -1023,7 +1083,7 @@
|
||||
</userDefinedRuntimeAttributes>
|
||||
</tableViewCell>
|
||||
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="default" indentationWidth="10" rowHeight="51" id="hRP-jU-2hd" customClass="InsetGroupTableViewCell" customModule="SideStore" customModuleProvider="target">
|
||||
<rect key="frame" x="0.0" y="1611.3333377838135" width="402" height="51"/>
|
||||
<rect key="frame" x="0.0" y="1785.3333377838135" width="402" height="51"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="hRP-jU-2hd" id="JhE-O4-pRg">
|
||||
<rect key="frame" x="0.0" y="0.0" width="402" height="51"/>
|
||||
@@ -1058,7 +1118,7 @@
|
||||
</userDefinedRuntimeAttributes>
|
||||
</tableViewCell>
|
||||
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="default" indentationWidth="10" rowHeight="51" id="JoN-Aj-XtZ" customClass="InsetGroupTableViewCell" customModule="SideStore" customModuleProvider="target">
|
||||
<rect key="frame" x="0.0" y="1662.3333377838135" width="402" height="51"/>
|
||||
<rect key="frame" x="0.0" y="1836.3333377838135" width="402" height="51"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="JoN-Aj-XtZ" id="v8Q-VQ-Q1h">
|
||||
<rect key="frame" x="0.0" y="0.0" width="402" height="51"/>
|
||||
@@ -1093,7 +1153,7 @@
|
||||
</userDefinedRuntimeAttributes>
|
||||
</tableViewCell>
|
||||
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="default" indentationWidth="10" rowHeight="51" id="QOO-bO-4M5" customClass="InsetGroupTableViewCell" customModule="SideStore" customModuleProvider="target">
|
||||
<rect key="frame" x="0.0" y="1713.3333377838135" width="402" height="51"/>
|
||||
<rect key="frame" x="0.0" y="1887.3333377838135" width="402" height="51"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="QOO-bO-4M5" id="VTT-z5-C89">
|
||||
<rect key="frame" x="0.0" y="0.0" width="402" height="51"/>
|
||||
@@ -1121,7 +1181,7 @@
|
||||
</userDefinedRuntimeAttributes>
|
||||
</tableViewCell>
|
||||
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="default" indentationWidth="10" rowHeight="51" id="ToB-H7-2lR" customClass="InsetGroupTableViewCell" customModule="SideStore" customModuleProvider="target">
|
||||
<rect key="frame" x="0.0" y="1764.3333377838135" width="402" height="51"/>
|
||||
<rect key="frame" x="0.0" y="1938.3333377838135" width="402" height="51"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="ToB-H7-2lR" id="Acf-xV-Isn">
|
||||
<rect key="frame" x="0.0" y="0.0" width="402" height="51"/>
|
||||
@@ -1149,7 +1209,7 @@
|
||||
</userDefinedRuntimeAttributes>
|
||||
</tableViewCell>
|
||||
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="default" indentationWidth="10" rowHeight="51" id="xtI-eU-LFb" customClass="InsetGroupTableViewCell" customModule="SideStore" customModuleProvider="target">
|
||||
<rect key="frame" x="0.0" y="1815.3333377838135" width="402" height="51"/>
|
||||
<rect key="frame" x="0.0" y="1989.3333377838135" width="402" height="51"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="xtI-eU-LFb" id="bc9-41-6mE">
|
||||
<rect key="frame" x="0.0" y="0.0" width="402" height="51"/>
|
||||
@@ -1183,7 +1243,7 @@
|
||||
</userDefinedRuntimeAttributes>
|
||||
</tableViewCell>
|
||||
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="default" indentationWidth="10" rowHeight="51" id="pvu-IV-Poa" customClass="InsetGroupTableViewCell" customModule="SideStore" customModuleProvider="target">
|
||||
<rect key="frame" x="0.0" y="1866.3333377838135" width="402" height="51"/>
|
||||
<rect key="frame" x="0.0" y="2040.3333377838135" width="402" height="51"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="pvu-IV-Poa" id="zck-an-8cK">
|
||||
<rect key="frame" x="0.0" y="0.0" width="402" height="51"/>
|
||||
@@ -1218,7 +1278,7 @@
|
||||
</userDefinedRuntimeAttributes>
|
||||
</tableViewCell>
|
||||
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="default" indentationWidth="10" rowHeight="51" id="9By-QW-Jw9" customClass="InsetGroupTableViewCell" customModule="SideStore" customModuleProvider="target">
|
||||
<rect key="frame" x="0.0" y="1917.3333377838135" width="402" height="51"/>
|
||||
<rect key="frame" x="0.0" y="2091.3333377838135" width="402" height="51"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="9By-QW-Jw9" id="Dzq-gE-zyT">
|
||||
<rect key="frame" x="0.0" y="0.0" width="402" height="51"/>
|
||||
@@ -1246,6 +1306,41 @@
|
||||
</tableViewCellContentView>
|
||||
<color key="backgroundColor" white="1" alpha="0.14999999999999999" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||
<edgeInsets key="layoutMargins" top="8" left="30" bottom="8" right="30"/>
|
||||
<userDefinedRuntimeAttributes>
|
||||
<userDefinedRuntimeAttribute type="number" keyPath="style">
|
||||
<integer key="value" value="2"/>
|
||||
</userDefinedRuntimeAttribute>
|
||||
</userDefinedRuntimeAttributes>
|
||||
</tableViewCell>
|
||||
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="default" indentationWidth="10" rowHeight="51" id="LzP-Qb-bmC" customClass="InsetGroupTableViewCell" customModule="SideStore" customModuleProvider="target">
|
||||
<rect key="frame" x="0.0" y="2142.3333377838135" width="402" height="51"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="LzP-Qb-bmC" id="3rE-h0-8kb">
|
||||
<rect key="frame" x="0.0" y="0.0" width="402" height="51"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<subviews>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Minimuxer Status Check" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="3BY-c1-QEV" userLabel="Minimuxer Status Check">
|
||||
<rect key="frame" x="30.000000000000014" y="15.333333333333334" width="198.33333333333337" height="20.333333333333329"/>
|
||||
<fontDescription key="fontDescription" type="boldSystem" pointSize="17"/>
|
||||
<color key="textColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
<switch opaque="NO" contentMode="scaleToFill" horizontalHuggingPriority="750" verticalHuggingPriority="750" contentHorizontalAlignment="center" contentVerticalAlignment="center" translatesAutoresizingMaskIntoConstraints="NO" id="AB6-Ok-Faf" userLabel="Minimuxer Check Switch">
|
||||
<rect key="frame" x="323" y="10" width="51" height="31"/>
|
||||
<connections>
|
||||
<action selector="toggleMinimuxerStatusCheck:" destination="aMk-Xp-UL8" eventType="valueChanged" id="Kwh-Km-aLj"/>
|
||||
</connections>
|
||||
</switch>
|
||||
</subviews>
|
||||
<constraints>
|
||||
<constraint firstAttribute="trailingMargin" secondItem="AB6-Ok-Faf" secondAttribute="trailing" id="EtH-X0-plP"/>
|
||||
<constraint firstItem="AB6-Ok-Faf" firstAttribute="centerY" secondItem="3rE-h0-8kb" secondAttribute="centerY" id="Tc3-56-q7s"/>
|
||||
<constraint firstItem="3BY-c1-QEV" firstAttribute="centerY" secondItem="3rE-h0-8kb" secondAttribute="centerY" id="kIV-xS-Ava"/>
|
||||
<constraint firstItem="3BY-c1-QEV" firstAttribute="leading" secondItem="3rE-h0-8kb" secondAttribute="leadingMargin" id="pJS-6V-6vh"/>
|
||||
</constraints>
|
||||
</tableViewCellContentView>
|
||||
<color key="backgroundColor" white="1" alpha="0.14999999999999999" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||
<edgeInsets key="layoutMargins" top="8" left="30" bottom="8" right="30"/>
|
||||
<userDefinedRuntimeAttributes>
|
||||
<userDefinedRuntimeAttribute type="number" keyPath="style">
|
||||
<integer key="value" value="3"/>
|
||||
|
||||
@@ -16,6 +16,8 @@ import IntentsUI
|
||||
import SemanticVersion
|
||||
|
||||
import AltStoreCore
|
||||
import CAltSign
|
||||
import UniformTypeIdentifiers
|
||||
|
||||
extension SettingsViewController
|
||||
{
|
||||
@@ -30,6 +32,7 @@ extension SettingsViewController
|
||||
case techyThings
|
||||
case credits
|
||||
case advancedSettings
|
||||
case signing
|
||||
// diagnostics section, will be enabled on release builds only on swipe down with 3 fingers 3 times
|
||||
case diagnostics
|
||||
// case macDirtyCow
|
||||
@@ -79,6 +82,11 @@ extension SettingsViewController
|
||||
case betaTrack
|
||||
// case hiddenSettings
|
||||
}
|
||||
|
||||
fileprivate enum SigningSettingsRow: Int, CaseIterable {
|
||||
case importCert
|
||||
case exportCert
|
||||
}
|
||||
|
||||
fileprivate enum DiagnosticsRow: Int, CaseIterable
|
||||
{
|
||||
@@ -143,6 +151,7 @@ final class SettingsViewController: UITableViewController
|
||||
|
||||
NotificationCenter.default.addObserver(self, selector: #selector(SettingsViewController.openPatreonSettings(_:)), name: AppDelegate.openPatreonSettingsDeepLinkNotification, object: nil)
|
||||
NotificationCenter.default.addObserver(self, selector: #selector(SettingsViewController.openErrorLog(_:)), name: ToastView.openErrorLogNotification, object: nil)
|
||||
NotificationCenter.default.addObserver(self, selector: #selector(SettingsViewController.openExportCertificateConfirm(_:)), name: AppDelegate.exportCertificateNotification, object: nil)
|
||||
}
|
||||
|
||||
|
||||
@@ -263,50 +272,6 @@ final class SettingsViewController: UITableViewController
|
||||
|
||||
}
|
||||
|
||||
private class BuildInfo{
|
||||
private static let MARKETING_VERSION_TAG = "CFBundleShortVersionString"
|
||||
private static let CURRENT_PROJECT_VERSION_TAG = kCFBundleVersionKey as String
|
||||
|
||||
private static let XCODE_VERSION_TAG = "DTXcode"
|
||||
private static let XCODE_REVISION_TAG = "DTXcodeBuild"
|
||||
|
||||
let bundle: Bundle
|
||||
|
||||
public init(){
|
||||
bundle = Bundle.main
|
||||
}
|
||||
|
||||
enum BundleError: Swift.Error {
|
||||
case invalidURL
|
||||
}
|
||||
|
||||
public init(url: URL) throws {
|
||||
guard let bundle = Bundle(url: url) else {
|
||||
throw BundleError.invalidURL
|
||||
}
|
||||
self.bundle = bundle
|
||||
}
|
||||
|
||||
public lazy var project_version: String? = {
|
||||
let version = bundle.object(forInfoDictionaryKey: Self.CURRENT_PROJECT_VERSION_TAG) as? String
|
||||
return version
|
||||
}()
|
||||
|
||||
public lazy var marketing_version: String? = {
|
||||
let version = bundle.object(forInfoDictionaryKey: Self.MARKETING_VERSION_TAG) as? String
|
||||
return version
|
||||
}()
|
||||
|
||||
public lazy var xcode: String? = {
|
||||
let xcode = bundle.object(forInfoDictionaryKey: Self.XCODE_VERSION_TAG) as? String
|
||||
return xcode
|
||||
}()
|
||||
|
||||
public lazy var xcode_revision: String? = {
|
||||
let revision = bundle.object(forInfoDictionaryKey: Self.XCODE_REVISION_TAG) as? String
|
||||
return revision
|
||||
}()
|
||||
}
|
||||
|
||||
private extension SettingsViewController
|
||||
{
|
||||
@@ -477,7 +442,19 @@ private extension SettingsViewController
|
||||
|
||||
case .advancedSettings:
|
||||
settingsHeaderFooterView.primaryLabel.text = NSLocalizedString("ADVANCED SETTINGS", comment: "")
|
||||
|
||||
case .signing:
|
||||
// FIXME: Why "Enable Background Refresh ..." appear here if secondaryLabel is not specified???
|
||||
if isHeader
|
||||
{
|
||||
settingsHeaderFooterView.primaryLabel.text = NSLocalizedString("SIGNING", comment: "")
|
||||
}
|
||||
else
|
||||
{
|
||||
settingsHeaderFooterView.secondaryLabel.text = NSLocalizedString("", comment: "")
|
||||
}
|
||||
|
||||
|
||||
case .diagnostics:
|
||||
settingsHeaderFooterView.primaryLabel.text = NSLocalizedString("DIAGNOSTICS", comment: "")
|
||||
|
||||
@@ -597,6 +574,11 @@ private extension SettingsViewController
|
||||
UserDefaults.standard.isMinimuxerConsoleLoggingEnabled = sender.isOn
|
||||
}
|
||||
|
||||
@IBAction func toggleMinimuxerStatusCheck(_ sender: UISwitch) {
|
||||
// update it in database
|
||||
UserDefaults.standard.isMinimuxerStatusCheckEnabled = sender.isOn
|
||||
}
|
||||
|
||||
@IBAction func toggleRecreateDatabaseSwitch(_ sender: UISwitch) {
|
||||
// Update the setting in UserDefaults
|
||||
UserDefaults.standard.recreateDatabaseOnNextStart = sender.isOn
|
||||
@@ -800,6 +782,48 @@ private extension SettingsViewController
|
||||
self.performSegue(withIdentifier: "showErrorLog", sender: nil)
|
||||
}
|
||||
}
|
||||
|
||||
@objc func openExportCertificateConfirm(_ notification: Notification)
|
||||
{
|
||||
func export()
|
||||
{
|
||||
guard let template = notification.userInfo?[AppDelegate.exportCertificateCallbackTemplateKey] as? String,
|
||||
template.contains("$(BASE64_CERT)") else {
|
||||
let toastView = ToastView(text: NSLocalizedString("No $(BASE64_CERT) placeholder found", comment: ""), detailText: nil)
|
||||
toastView.show(in: self)
|
||||
return
|
||||
}
|
||||
guard let data = Keychain.shared.signingCertificate,
|
||||
let password = Keychain.shared.signingCertificatePassword else {
|
||||
let toastView = ToastView(text: NSLocalizedString("Failed to find certificate or password", comment: ""), detailText: nil)
|
||||
toastView.show(in: self)
|
||||
return
|
||||
}
|
||||
let base64encodedCert = data.base64EncodedString()
|
||||
var allowedQueryParamAndKey = NSCharacterSet.urlQueryAllowed
|
||||
allowedQueryParamAndKey.remove(charactersIn: ";/?:@&=+$, ")
|
||||
guard let encodedCert = base64encodedCert.addingPercentEncoding(withAllowedCharacters: allowedQueryParamAndKey) else {
|
||||
let toastView = ToastView(text: NSLocalizedString("Failed to encode certificate!", comment: ""), detailText: nil)
|
||||
toastView.show(in: self)
|
||||
return
|
||||
}
|
||||
var urlStr = template.replacingOccurrences(of: "$(BASE64_CERT)", with: encodedCert, options: .literal, range: nil)
|
||||
urlStr = urlStr.replacingOccurrences(of: "$(PASSWORD)", with: password, options: .literal, range: nil)
|
||||
|
||||
print(urlStr)
|
||||
guard let callbackUrl = URL(string: urlStr) else {
|
||||
let toastView = ToastView(text: NSLocalizedString("Failed to initialize callback URL!", comment: ""), detailText: nil)
|
||||
toastView.show(in: self)
|
||||
return
|
||||
}
|
||||
UIApplication.shared.open(callbackUrl)
|
||||
}
|
||||
|
||||
let alertController = UIAlertController(title: NSLocalizedString("Export Certificate", comment: ""), message: NSLocalizedString("Do you want to export your certificate to an external app? That app will be able to sign apps using your certificate.", comment: ""), preferredStyle: .alert)
|
||||
alertController.addAction(UIAlertAction(title: NSLocalizedString("Export", comment: ""), style: .default) { _ in export() })
|
||||
alertController.addAction(.cancel)
|
||||
self.present(alertController, animated: true, completion: nil)
|
||||
}
|
||||
}
|
||||
|
||||
extension SettingsViewController
|
||||
@@ -871,7 +895,7 @@ extension SettingsViewController
|
||||
case _ where isSectionHidden(section): return nil
|
||||
case .signIn where self.activeTeam != nil: return nil
|
||||
case .account where self.activeTeam == nil: return nil
|
||||
case .signIn, .account, .patreon, .display, .appRefresh, .techyThings, .credits, .advancedSettings, .diagnostics /* ,.macDirtyCow */:
|
||||
case .signIn, .account, .patreon, .display, .appRefresh, .techyThings, .credits, .advancedSettings, .signing ,.diagnostics /* ,.macDirtyCow */:
|
||||
let headerView = tableView.dequeueReusableHeaderFooterView(withIdentifier: "HeaderFooterView") as! SettingsHeaderFooterView
|
||||
self.prepare(headerView, for: section, isHeader: true)
|
||||
return headerView
|
||||
@@ -888,7 +912,7 @@ extension SettingsViewController
|
||||
case _ where isSectionHidden(section): return nil
|
||||
case .signIn where self.activeTeam != nil: return nil
|
||||
// case .signIn, .patreon, .display, .appRefresh, .techyThings, .macDirtyCow:
|
||||
case .signIn, .patreon, .display, .appRefresh, .techyThings:
|
||||
case .signIn, .patreon, .display, .appRefresh, .techyThings, .signing:
|
||||
let footerView = tableView.dequeueReusableHeaderFooterView(withIdentifier: "HeaderFooterView") as! SettingsHeaderFooterView
|
||||
self.prepare(footerView, for: section, isHeader: false)
|
||||
return footerView
|
||||
@@ -906,7 +930,7 @@ extension SettingsViewController
|
||||
case .signIn where self.activeTeam != nil: return 1.0
|
||||
case .account where self.activeTeam == nil: return 1.0
|
||||
// case .signIn, .account, .patreon, .display, .appRefresh, .techyThings, .credits, .macDirtyCow, .advanced:
|
||||
case .signIn, .account, .patreon, .display, .appRefresh, .techyThings, .credits, .advancedSettings, .diagnostics:
|
||||
case .signIn, .account, .patreon, .display, .appRefresh, .techyThings, .credits, .advancedSettings, .signing, .diagnostics:
|
||||
let height = self.preferredHeight(for: self.prototypeHeaderFooterView, in: section, isHeader: true)
|
||||
return height
|
||||
|
||||
@@ -923,7 +947,7 @@ extension SettingsViewController
|
||||
case .signIn where self.activeTeam != nil: return 1.0
|
||||
case .account where self.activeTeam == nil: return 1.0
|
||||
// case .signIn, .patreon, .display, .appRefresh, .techyThings, .macDirtyCow:
|
||||
case .signIn, .patreon, .display, .appRefresh, .techyThings, .diagnostics:
|
||||
case .signIn, .patreon, .display, .appRefresh, .techyThings, .signing, .diagnostics:
|
||||
let height = self.preferredHeight(for: self.prototypeHeaderFooterView, in: section, isHeader: false)
|
||||
return height
|
||||
|
||||
@@ -993,7 +1017,7 @@ extension SettingsViewController
|
||||
|
||||
// Option 2: Discord
|
||||
alertController.addAction(UIAlertAction(title: "Discord", style: .default) { _ in
|
||||
if let discordURL = URL(string: "https://discord.gg/sidestore") {
|
||||
if let discordURL = URL(string: "https://discord.gg/sidestore-949183273383395328") {
|
||||
let safariViewController = SFSafariViewController(url: discordURL)
|
||||
safariViewController.preferredControlTintColor = .altPrimary
|
||||
self.present(safariViewController, animated: true, completion: nil)
|
||||
@@ -1198,7 +1222,135 @@ extension SettingsViewController
|
||||
case .refreshAttempts, .betaUpdates, .betaTrack: break
|
||||
|
||||
}
|
||||
|
||||
case .signing:
|
||||
let row = SigningSettingsRow.allCases[indexPath.row]
|
||||
switch row {
|
||||
case .importCert:
|
||||
Task {
|
||||
let certUrl = await withUnsafeContinuation { c in
|
||||
let importVc = UIDocumentPickerViewController(forOpeningContentTypes: [UTType(filenameExtension: "p12")!], asCopy: false)
|
||||
ImportExport.documentPickerHandler = DocumentPickerHandler { url in
|
||||
c.resume(returning: url)
|
||||
}
|
||||
importVc.delegate = ImportExport.documentPickerHandler
|
||||
|
||||
self.present(importVc, animated: true)
|
||||
|
||||
}
|
||||
guard let certUrl else {
|
||||
return
|
||||
}
|
||||
|
||||
let password = await withUnsafeContinuation { (c: UnsafeContinuation<String?,Never>) in
|
||||
let alertController = UIAlertController(title: NSLocalizedString("Please enter the password for the certificate.", comment: ""), message: nil, preferredStyle: .alert)
|
||||
|
||||
alertController.addTextField { (textField) in
|
||||
textField.autocorrectionType = .no
|
||||
textField.autocapitalizationType = .none
|
||||
}
|
||||
|
||||
let submitAction = UIAlertAction(title: NSLocalizedString("OK", comment: ""), style: .default) { (action) in
|
||||
let textField = alertController.textFields?.first
|
||||
|
||||
let code = textField?.text ?? ""
|
||||
c.resume(returning: code)
|
||||
}
|
||||
alertController.addAction(submitAction)
|
||||
alertController.addAction(UIAlertAction(title: NSLocalizedString("Cancel", comment: ""), style: .cancel) { (action) in
|
||||
c.resume(returning: nil)
|
||||
})
|
||||
|
||||
self.present(alertController, animated: true)
|
||||
}
|
||||
|
||||
guard let password else {
|
||||
return
|
||||
}
|
||||
let _ = certUrl.startAccessingSecurityScopedResource()
|
||||
defer {
|
||||
certUrl.stopAccessingSecurityScopedResource()
|
||||
}
|
||||
let certData : Data
|
||||
do {
|
||||
certData = try Data(contentsOf: certUrl)
|
||||
} catch {
|
||||
let toastView = ToastView(text: NSLocalizedString("Failed to import certificate!", comment: ""), detailText: error.localizedDescription)
|
||||
toastView.show(in: self)
|
||||
return
|
||||
}
|
||||
|
||||
guard let altCert = ALTCertificate(p12Data: certData, password: password) else {
|
||||
let toastView = ToastView(text: NSLocalizedString("Failed to import certificate!", comment: ""), detailText: "Failed to create ALTCertificate. Check if the password is correct.")
|
||||
toastView.show(in: self)
|
||||
return
|
||||
}
|
||||
|
||||
Keychain.shared.signingCertificate = altCert.encryptedP12Data(withPassword: "")!
|
||||
let toastView = ToastView(text: NSLocalizedString("Certificate imported successfully!", comment: ""), detailText: nil)
|
||||
toastView.show(in: self)
|
||||
}
|
||||
|
||||
break
|
||||
case .exportCert:
|
||||
Task {
|
||||
guard let certData = Keychain.shared.signingCertificate else {
|
||||
let toastView = ToastView(text: NSLocalizedString("Failed to export certificate!", comment: ""), detailText: "Certificate not found.")
|
||||
toastView.show(in: self)
|
||||
return
|
||||
}
|
||||
|
||||
let password = await withUnsafeContinuation { (c: UnsafeContinuation<String?,Never>) in
|
||||
let alertController = UIAlertController(title: NSLocalizedString("Please enter the password for the certificate.", comment: ""), message: nil, preferredStyle: .alert)
|
||||
|
||||
alertController.addTextField { (textField) in
|
||||
textField.autocorrectionType = .no
|
||||
textField.autocapitalizationType = .none
|
||||
}
|
||||
|
||||
let submitAction = UIAlertAction(title: NSLocalizedString("OK", comment: ""), style: .default) { (action) in
|
||||
let textField = alertController.textFields?.first
|
||||
|
||||
let code = textField?.text ?? ""
|
||||
c.resume(returning: code)
|
||||
}
|
||||
alertController.addAction(submitAction)
|
||||
alertController.addAction(UIAlertAction(title: NSLocalizedString("Cancel", comment: ""), style: .cancel) { (action) in
|
||||
c.resume(returning: nil)
|
||||
})
|
||||
|
||||
self.present(alertController, animated: true)
|
||||
}
|
||||
|
||||
guard let password else {
|
||||
return
|
||||
}
|
||||
|
||||
guard let altCert = ALTCertificate(p12Data: certData, password: nil) else {
|
||||
let toastView = ToastView(text: NSLocalizedString("Failed to export certificate!", comment: ""), detailText: "Failed to create ALTCertificate. Check if the password is correct.")
|
||||
toastView.show(in: self)
|
||||
return
|
||||
}
|
||||
|
||||
guard let newCertData = altCert.encryptedP12Data(withPassword: password) else {
|
||||
let toastView = ToastView(text: NSLocalizedString("Failed to export certificate!", comment: ""), detailText: "Failed to encrypt ALTCertificate.")
|
||||
toastView.show(in: self)
|
||||
return
|
||||
}
|
||||
|
||||
let newCertTmpPath = FileManager.default.temporaryDirectory.appendingPathComponent("SideStoreSigningCertificate.p12")
|
||||
do {
|
||||
try newCertData.write(to: newCertTmpPath)
|
||||
} catch {
|
||||
let toastView = ToastView(text: NSLocalizedString("Failed to export certificate!", comment: ""), detailText: error.localizedDescription)
|
||||
toastView.show(in: self)
|
||||
return
|
||||
}
|
||||
let exportVC = UIDocumentPickerViewController(forExporting: [newCertTmpPath], asCopy: false)
|
||||
self.present(exportVC, animated: true)
|
||||
}
|
||||
break
|
||||
}
|
||||
|
||||
case .diagnostics:
|
||||
let row = DiagnosticsRow.allCases[indexPath.row]
|
||||
switch row {
|
||||
|
||||
@@ -514,27 +514,27 @@ extension SourcesViewController: NSFetchedResultsControllerDelegate
|
||||
let context = DatabaseManager.shared.persistentContainer.newBackgroundContext()
|
||||
context.performAndWait {
|
||||
_ = Source.make(name: "OatmealDome's AltStore Source",
|
||||
identifier: "me.oatmealdome.altstore",
|
||||
groupID: "me.oatmealdome.altstore",
|
||||
sourceURL: URL(string: "https://altstore.oatmealdome.me")!,
|
||||
context: context)
|
||||
|
||||
_ = Source.make(name: "UTM Repository",
|
||||
identifier: "com.utmapp.repos.UTM",
|
||||
groupID: "com.utmapp.repos.UTM",
|
||||
sourceURL: URL(string: "https://alt.getutm.app")!,
|
||||
context: context)
|
||||
|
||||
_ = Source.make(name: "Flyinghead",
|
||||
identifier: "com.flyinghead.source",
|
||||
groupID: "com.flyinghead.source",
|
||||
sourceURL: URL(string: "https://flyinghead.github.io/flycast-builds/altstore.json")!,
|
||||
context: context)
|
||||
|
||||
_ = Source.make(name: "Provenance",
|
||||
identifier: "org.provenance-emu.AltStore",
|
||||
groupID: "org.provenance-emu.AltStore",
|
||||
sourceURL: URL(string: "https://provenance-emu.com/apps.json")!,
|
||||
context: context)
|
||||
|
||||
_ = Source.make(name: "PojavLauncher Repository",
|
||||
identifier: "dev.crystall1ne.repos.PojavLauncher",
|
||||
groupID: "dev.crystall1ne.repos.PojavLauncher",
|
||||
sourceURL: URL(string: "http://alt.crystall1ne.dev")!,
|
||||
context: context)
|
||||
|
||||
|
||||
@@ -36,6 +36,7 @@ final class TabBarController: UITabBarController
|
||||
NotificationCenter.default.addObserver(self, selector: #selector(TabBarController.openPatreonSettings(_:)), name: AppDelegate.openPatreonSettingsDeepLinkNotification, object: nil)
|
||||
NotificationCenter.default.addObserver(self, selector: #selector(TabBarController.importApp(_:)), name: AppDelegate.importAppDeepLinkNotification, object: nil)
|
||||
NotificationCenter.default.addObserver(self, selector: #selector(TabBarController.presentSources(_:)), name: AppDelegate.addSourceDeepLinkNotification, object: nil)
|
||||
NotificationCenter.default.addObserver(self, selector: #selector(TabBarController.exportFiles(_:)), name: AppDelegate.exportCertificateNotification, object: nil)
|
||||
NotificationCenter.default.addObserver(self, selector: #selector(TabBarController.openErrorLog(_:)), name: ToastView.openErrorLogNotification, object: nil)
|
||||
}
|
||||
|
||||
@@ -141,4 +142,9 @@ private extension TabBarController
|
||||
{
|
||||
self.selectedIndex = Tab.settings.rawValue
|
||||
}
|
||||
|
||||
@objc func exportFiles(_ notification: Notification)
|
||||
{
|
||||
self.selectedIndex = Tab.settings.rawValue
|
||||
}
|
||||
}
|
||||
|
||||
@@ -36,6 +36,8 @@ public extension UserDefaults
|
||||
@NSManaged var isExportResignedAppEnabled: Bool
|
||||
@NSManaged var isVerboseOperationsLoggingEnabled: Bool
|
||||
@NSManaged var isMinimuxerConsoleLoggingEnabled: Bool
|
||||
@NSManaged var isMinimuxerStatusCheckEnabled: Bool
|
||||
|
||||
@NSManaged var recreateDatabaseOnNextStart: Bool
|
||||
@NSManaged var isPairingReset: Bool
|
||||
@NSManaged var isDebugModeEnabled: Bool
|
||||
@@ -132,6 +134,7 @@ public extension UserDefaults
|
||||
#keyPath(UserDefaults.isDebugModeEnabled): false,
|
||||
#keyPath(UserDefaults.isVerboseOperationsLoggingEnabled): false,
|
||||
#keyPath(UserDefaults.isMinimuxerConsoleLoggingEnabled): false, // minimuxer logging is disabled by default for console loggin
|
||||
#keyPath(UserDefaults.isMinimuxerStatusCheckEnabled): false, // minimuxer status check is disabled by default to support stosVPN based cellular refresh
|
||||
#keyPath(UserDefaults.recreateDatabaseOnNextStart): false,
|
||||
#keyPath(UserDefaults.isBackgroundRefreshEnabled): true,
|
||||
#keyPath(UserDefaults.isIdleTimeoutDisableEnabled): true,
|
||||
|
||||
@@ -3,6 +3,6 @@
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>_XCCurrentVersionName</key>
|
||||
<string>AltStore 17.xcdatamodel</string>
|
||||
<string>AltStore 17_1.xcdatamodel</string>
|
||||
</dict>
|
||||
</plist>
|
||||
|
||||
@@ -0,0 +1,316 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
||||
<model type="com.apple.IDECoreDataModeler.DataModel" documentVersion="1.0" lastSavedToolsVersion="23605" systemVersion="24D70" minimumToolsVersion="Automatic" sourceLanguage="Swift" userDefinedModelVersionIdentifier="v17.1">
|
||||
<entity name="Account" representedClassName="Account" syncable="YES">
|
||||
<attribute name="appleID" attributeType="String"/>
|
||||
<attribute name="firstName" attributeType="String"/>
|
||||
<attribute name="identifier" attributeType="String"/>
|
||||
<attribute name="isActiveAccount" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
|
||||
<attribute name="lastName" attributeType="String"/>
|
||||
<relationship name="teams" toMany="YES" deletionRule="Cascade" destinationEntity="Team" inverseName="account" inverseEntity="Team"/>
|
||||
<uniquenessConstraints>
|
||||
<uniquenessConstraint>
|
||||
<constraint value="identifier"/>
|
||||
</uniquenessConstraint>
|
||||
</uniquenessConstraints>
|
||||
</entity>
|
||||
<entity name="AppID" representedClassName="AppID" syncable="YES">
|
||||
<attribute name="bundleIdentifier" attributeType="String"/>
|
||||
<attribute name="expirationDate" optional="YES" attributeType="Date" usesScalarValueType="NO"/>
|
||||
<attribute name="features" attributeType="Transformable" valueTransformerName="ALTSecureValueTransformer"/>
|
||||
<attribute name="identifier" attributeType="String"/>
|
||||
<attribute name="name" attributeType="String"/>
|
||||
<relationship name="team" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="Team" inverseName="appIDs" inverseEntity="Team"/>
|
||||
<uniquenessConstraints>
|
||||
<uniquenessConstraint>
|
||||
<constraint value="identifier"/>
|
||||
</uniquenessConstraint>
|
||||
</uniquenessConstraints>
|
||||
</entity>
|
||||
<entity name="AppPermission" representedClassName="AppPermission" syncable="YES">
|
||||
<attribute name="appBundleID" optional="YES" attributeType="String"/>
|
||||
<attribute name="permission" optional="YES" attributeType="String"/>
|
||||
<attribute name="sourceID" optional="YES" attributeType="String"/>
|
||||
<attribute name="type" attributeType="String"/>
|
||||
<attribute name="usageDescription" attributeType="String"/>
|
||||
<relationship name="app" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="StoreApp" inverseName="permissions" inverseEntity="StoreApp"/>
|
||||
<uniquenessConstraints>
|
||||
<uniquenessConstraint>
|
||||
<constraint value="sourceID"/>
|
||||
<constraint value="appBundleID"/>
|
||||
<constraint value="type"/>
|
||||
<constraint value="permission"/>
|
||||
</uniquenessConstraint>
|
||||
</uniquenessConstraints>
|
||||
</entity>
|
||||
<entity name="AppScreenshot" representedClassName="AppScreenshot" syncable="YES">
|
||||
<attribute name="appBundleID" optional="YES" attributeType="String"/>
|
||||
<attribute name="deviceType" attributeType="Integer 16" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="height" optional="YES" attributeType="Integer 16" usesScalarValueType="NO"/>
|
||||
<attribute name="imageURL" attributeType="URI"/>
|
||||
<attribute name="sourceID" optional="YES" attributeType="String"/>
|
||||
<attribute name="width" optional="YES" attributeType="Integer 16" usesScalarValueType="NO"/>
|
||||
<relationship name="app" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="StoreApp" inverseName="screenshots" inverseEntity="StoreApp"/>
|
||||
<uniquenessConstraints>
|
||||
<uniquenessConstraint>
|
||||
<constraint value="sourceID"/>
|
||||
<constraint value="appBundleID"/>
|
||||
<constraint value="imageURL"/>
|
||||
</uniquenessConstraint>
|
||||
</uniquenessConstraints>
|
||||
</entity>
|
||||
<entity name="AppVersion" representedClassName="AppVersion" syncable="YES">
|
||||
<attribute name="appBundleID" optional="YES" attributeType="String"/>
|
||||
<attribute name="buildVersion" optional="YES" attributeType="String"/>
|
||||
<attribute name="channel" optional="YES" attributeType="String"/>
|
||||
<attribute name="date" attributeType="Date" usesScalarValueType="NO"/>
|
||||
<attribute name="downloadURL" attributeType="URI"/>
|
||||
<attribute name="localizedDescription" optional="YES" attributeType="String"/>
|
||||
<attribute name="maxOSVersion" optional="YES" attributeType="String"/>
|
||||
<attribute name="minOSVersion" optional="YES" attributeType="String"/>
|
||||
<attribute name="sha256" optional="YES" attributeType="String"/>
|
||||
<attribute name="size" attributeType="Integer 64" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="sourceID" optional="YES" attributeType="String"/>
|
||||
<attribute name="version" attributeType="String"/>
|
||||
<relationship name="app" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="StoreApp" inverseName="versions" inverseEntity="StoreApp"/>
|
||||
<relationship name="latestVersionApp" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="StoreApp" inverseName="latestVersion" inverseEntity="StoreApp"/>
|
||||
<relationship name="releaseTrack" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="ReleaseTrack" inverseName="releases" inverseEntity="ReleaseTrack"/>
|
||||
<uniquenessConstraints>
|
||||
<uniquenessConstraint>
|
||||
<constraint value="sourceID"/>
|
||||
<constraint value="appBundleID"/>
|
||||
<constraint value="version"/>
|
||||
<constraint value="buildVersion"/>
|
||||
</uniquenessConstraint>
|
||||
</uniquenessConstraints>
|
||||
</entity>
|
||||
<entity name="InstalledApp" representedClassName="InstalledApp" syncable="YES">
|
||||
<attribute name="buildVersion" optional="YES" attributeType="String"/>
|
||||
<attribute name="bundleIdentifier" attributeType="String"/>
|
||||
<attribute name="certificateSerialNumber" optional="YES" attributeType="String"/>
|
||||
<attribute name="expirationDate" attributeType="Date" usesScalarValueType="NO"/>
|
||||
<attribute name="hasAlternateIcon" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
|
||||
<attribute name="hasUpdate" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
|
||||
<attribute name="installedDate" attributeType="Date" usesScalarValueType="NO"/>
|
||||
<attribute name="isActive" attributeType="Boolean" defaultValueString="YES" usesScalarValueType="YES"/>
|
||||
<attribute name="isRefreshing" transient="YES" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
|
||||
<attribute name="name" attributeType="String"/>
|
||||
<attribute name="needsResign" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
|
||||
<attribute name="refreshedDate" attributeType="Date" usesScalarValueType="NO"/>
|
||||
<attribute name="resignedBundleIdentifier" attributeType="String"/>
|
||||
<attribute name="storeBuildVersion" optional="YES" attributeType="String"/>
|
||||
<attribute name="useMainProfile" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
|
||||
<attribute name="version" attributeType="String"/>
|
||||
<relationship name="appExtensions" toMany="YES" deletionRule="Cascade" destinationEntity="InstalledExtension" inverseName="parentApp" inverseEntity="InstalledExtension"/>
|
||||
<relationship name="loggedErrors" toMany="YES" deletionRule="Nullify" destinationEntity="LoggedError" inverseName="installedApp" inverseEntity="LoggedError"/>
|
||||
<relationship name="storeApp" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="StoreApp" inverseName="installedApp" inverseEntity="StoreApp"/>
|
||||
<relationship name="team" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="Team" inverseName="installedApps" inverseEntity="Team"/>
|
||||
<uniquenessConstraints>
|
||||
<uniquenessConstraint>
|
||||
<constraint value="bundleIdentifier"/>
|
||||
</uniquenessConstraint>
|
||||
</uniquenessConstraints>
|
||||
</entity>
|
||||
<entity name="InstalledExtension" representedClassName="InstalledExtension" syncable="YES">
|
||||
<attribute name="bundleIdentifier" attributeType="String"/>
|
||||
<attribute name="expirationDate" attributeType="Date" usesScalarValueType="NO"/>
|
||||
<attribute name="installedDate" attributeType="Date" usesScalarValueType="NO"/>
|
||||
<attribute name="name" attributeType="String"/>
|
||||
<attribute name="refreshedDate" attributeType="Date" usesScalarValueType="NO"/>
|
||||
<attribute name="resignedBundleIdentifier" attributeType="String"/>
|
||||
<attribute name="version" attributeType="String"/>
|
||||
<relationship name="parentApp" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="InstalledApp" inverseName="appExtensions" inverseEntity="InstalledApp"/>
|
||||
</entity>
|
||||
<entity name="LoggedError" representedClassName="LoggedError" syncable="YES">
|
||||
<attribute name="appBundleID" attributeType="String"/>
|
||||
<attribute name="appName" attributeType="String"/>
|
||||
<attribute name="code" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="date" attributeType="Date" usesScalarValueType="NO"/>
|
||||
<attribute name="domain" attributeType="String"/>
|
||||
<attribute name="operation" optional="YES" attributeType="String"/>
|
||||
<attribute name="userInfo" attributeType="Transformable" valueTransformerName="ALTSecureValueTransformer"/>
|
||||
<relationship name="installedApp" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="InstalledApp" inverseName="loggedErrors" inverseEntity="InstalledApp"/>
|
||||
<relationship name="storeApp" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="StoreApp" inverseName="loggedErrors" inverseEntity="StoreApp"/>
|
||||
</entity>
|
||||
<entity name="NewsItem" representedClassName="NewsItem" syncable="YES">
|
||||
<attribute name="appID" optional="YES" attributeType="String"/>
|
||||
<attribute name="caption" attributeType="String"/>
|
||||
<attribute name="date" attributeType="Date" usesScalarValueType="NO"/>
|
||||
<attribute name="externalURL" optional="YES" attributeType="URI"/>
|
||||
<attribute name="identifier" attributeType="String"/>
|
||||
<attribute name="imageURL" optional="YES" attributeType="URI"/>
|
||||
<attribute name="isSilent" attributeType="Boolean" defaultValueString="YES" usesScalarValueType="YES"/>
|
||||
<attribute name="sortIndex" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="sourceIdentifier" optional="YES" attributeType="String"/>
|
||||
<attribute name="tintColor" optional="YES" attributeType="Transformable" valueTransformerName="ALTSecureValueTransformer"/>
|
||||
<attribute name="title" attributeType="String"/>
|
||||
<relationship name="source" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="Source" inverseName="newsItems" inverseEntity="Source"/>
|
||||
<relationship name="storeApp" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="StoreApp" inverseName="newsItems" inverseEntity="StoreApp"/>
|
||||
<uniquenessConstraints>
|
||||
<uniquenessConstraint>
|
||||
<constraint value="identifier"/>
|
||||
<constraint value="sourceIdentifier"/>
|
||||
</uniquenessConstraint>
|
||||
</uniquenessConstraints>
|
||||
</entity>
|
||||
<entity name="PatreonAccount" representedClassName="PatreonAccount" syncable="YES">
|
||||
<attribute name="firstName" optional="YES" attributeType="String"/>
|
||||
<attribute name="identifier" attributeType="String"/>
|
||||
<attribute name="isPatron" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
|
||||
<attribute name="name" attributeType="String"/>
|
||||
<relationship name="pledges" toMany="YES" deletionRule="Cascade" destinationEntity="Pledge" inverseName="account" inverseEntity="Pledge"/>
|
||||
<uniquenessConstraints>
|
||||
<uniquenessConstraint>
|
||||
<constraint value="identifier"/>
|
||||
</uniquenessConstraint>
|
||||
</uniquenessConstraints>
|
||||
</entity>
|
||||
<entity name="Patron" representedClassName="ManagedPatron" syncable="YES">
|
||||
<attribute name="identifier" attributeType="String"/>
|
||||
<attribute name="name" attributeType="String"/>
|
||||
<uniquenessConstraints>
|
||||
<uniquenessConstraint>
|
||||
<constraint value="identifier"/>
|
||||
</uniquenessConstraint>
|
||||
</uniquenessConstraints>
|
||||
</entity>
|
||||
<entity name="Pledge" representedClassName="Pledge" syncable="YES">
|
||||
<attribute name="amount" attributeType="Decimal" defaultValueString="0"/>
|
||||
<attribute name="campaignURL" attributeType="URI"/>
|
||||
<attribute name="identifier" attributeType="String"/>
|
||||
<relationship name="account" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="PatreonAccount" inverseName="pledges" inverseEntity="PatreonAccount"/>
|
||||
<relationship name="rewards" optional="YES" toMany="YES" deletionRule="Cascade" destinationEntity="PledgeReward" inverseName="pledge" inverseEntity="PledgeReward"/>
|
||||
<relationship name="tiers" optional="YES" toMany="YES" deletionRule="Cascade" destinationEntity="PledgeTier" inverseName="pledge" inverseEntity="PledgeTier"/>
|
||||
<uniquenessConstraints>
|
||||
<uniquenessConstraint>
|
||||
<constraint value="identifier"/>
|
||||
</uniquenessConstraint>
|
||||
</uniquenessConstraints>
|
||||
</entity>
|
||||
<entity name="PledgeReward" representedClassName="PledgeReward" syncable="YES">
|
||||
<attribute name="identifier" attributeType="String"/>
|
||||
<attribute name="name" attributeType="String"/>
|
||||
<relationship name="pledge" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="Pledge" inverseName="rewards" inverseEntity="Pledge"/>
|
||||
<uniquenessConstraints>
|
||||
<uniquenessConstraint>
|
||||
<constraint value="identifier"/>
|
||||
</uniquenessConstraint>
|
||||
</uniquenessConstraints>
|
||||
</entity>
|
||||
<entity name="PledgeTier" representedClassName="PledgeTier" syncable="YES">
|
||||
<attribute name="amount" attributeType="Decimal" defaultValueString="0.0"/>
|
||||
<attribute name="identifier" attributeType="String"/>
|
||||
<attribute name="name" optional="YES" attributeType="String"/>
|
||||
<relationship name="pledge" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="Pledge" inverseName="tiers" inverseEntity="Pledge"/>
|
||||
<uniquenessConstraints>
|
||||
<uniquenessConstraint>
|
||||
<constraint value="identifier"/>
|
||||
</uniquenessConstraint>
|
||||
</uniquenessConstraints>
|
||||
</entity>
|
||||
<entity name="RefreshAttempt" representedClassName="RefreshAttempt" syncable="YES">
|
||||
<attribute name="date" attributeType="Date" usesScalarValueType="NO"/>
|
||||
<attribute name="errorDescription" optional="YES" attributeType="String"/>
|
||||
<attribute name="identifier" attributeType="String"/>
|
||||
<attribute name="isSuccess" attributeType="Boolean" defaultValueString="YES" usesScalarValueType="YES"/>
|
||||
<uniquenessConstraints>
|
||||
<uniquenessConstraint>
|
||||
<constraint value="identifier"/>
|
||||
</uniquenessConstraint>
|
||||
</uniquenessConstraints>
|
||||
</entity>
|
||||
<entity name="ReleaseTrack" representedClassName="ReleaseTrack" syncable="YES">
|
||||
<attribute name="appBundleID" optional="YES" attributeType="String"/>
|
||||
<attribute name="sourceID" optional="YES" attributeType="String"/>
|
||||
<attribute name="track" attributeType="String"/>
|
||||
<relationship name="releases" optional="YES" toMany="YES" deletionRule="Cascade" ordered="YES" destinationEntity="AppVersion" inverseName="releaseTrack" inverseEntity="AppVersion"/>
|
||||
<relationship name="storeApp" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="StoreApp" inverseName="releaseTracks" inverseEntity="StoreApp"/>
|
||||
<uniquenessConstraints>
|
||||
<uniquenessConstraint>
|
||||
<constraint value="sourceID"/>
|
||||
<constraint value="appBundleID"/>
|
||||
<constraint value="track"/>
|
||||
</uniquenessConstraint>
|
||||
</uniquenessConstraints>
|
||||
</entity>
|
||||
<entity name="Source" representedClassName="Source" syncable="YES">
|
||||
<attribute name="error" optional="YES" attributeType="Transformable" valueTransformerName="ALTSecureValueTransformer"/>
|
||||
<attribute name="featuredSortID" optional="YES" attributeType="String"/>
|
||||
<attribute name="groupID" optional="YES" attributeType="String"/>
|
||||
<attribute name="hasFeaturedApps" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
|
||||
<attribute name="headerImageURL" optional="YES" attributeType="URI"/>
|
||||
<attribute name="iconURL" optional="YES" attributeType="URI"/>
|
||||
<attribute name="identifier" attributeType="String"/>
|
||||
<attribute name="localizedDescription" optional="YES" attributeType="String"/>
|
||||
<attribute name="name" attributeType="String"/>
|
||||
<attribute name="patreonURL" optional="YES" attributeType="URI"/>
|
||||
<attribute name="sourceURL" attributeType="URI"/>
|
||||
<attribute name="subtitle" optional="YES" attributeType="String"/>
|
||||
<attribute name="tintColor" optional="YES" attributeType="Transformable" valueTransformerName="ALTSecureValueTransformer"/>
|
||||
<attribute name="version" attributeType="Integer 64" defaultValueString="1" usesScalarValueType="YES"/>
|
||||
<attribute name="websiteURL" optional="YES" attributeType="URI"/>
|
||||
<relationship name="apps" toMany="YES" deletionRule="Cascade" ordered="YES" destinationEntity="StoreApp" inverseName="source" inverseEntity="StoreApp"/>
|
||||
<relationship name="featuredApps" optional="YES" toMany="YES" deletionRule="Nullify" ordered="YES" destinationEntity="StoreApp" inverseName="featuringSource" inverseEntity="StoreApp"/>
|
||||
<relationship name="newsItems" toMany="YES" deletionRule="Cascade" ordered="YES" destinationEntity="NewsItem" inverseName="source" inverseEntity="NewsItem"/>
|
||||
<uniquenessConstraints>
|
||||
<uniquenessConstraint>
|
||||
<constraint value="identifier"/>
|
||||
</uniquenessConstraint>
|
||||
</uniquenessConstraints>
|
||||
</entity>
|
||||
<entity name="StoreApp" representedClassName="StoreApp" syncable="YES">
|
||||
<attribute name="bundleIdentifier" attributeType="String"/>
|
||||
<attribute name="category" optional="YES" attributeType="String"/>
|
||||
<attribute name="developerName" attributeType="String"/>
|
||||
<attribute name="downloadURL" optional="YES" attributeType="URI"/>
|
||||
<attribute name="featuredSortID" optional="YES" attributeType="String"/>
|
||||
<attribute name="iconURL" attributeType="URI"/>
|
||||
<attribute name="isHiddenWithoutPledge" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
|
||||
<attribute name="isPledged" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
|
||||
<attribute name="isPledgeRequired" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
|
||||
<attribute name="localizedDescription" attributeType="String"/>
|
||||
<attribute name="marketplaceID" optional="YES" attributeType="String"/>
|
||||
<attribute name="name" attributeType="String"/>
|
||||
<attribute name="pledgeAmount" optional="YES" attributeType="Decimal"/>
|
||||
<attribute name="pledgeCurrency" optional="YES" attributeType="String"/>
|
||||
<attribute name="prefersCustomPledge" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
|
||||
<attribute name="screenshotURLs" attributeType="Transformable" valueTransformerName="ALTSecureValueTransformer"/>
|
||||
<attribute name="size" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="sortIndex" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="sourceIdentifier" optional="YES" attributeType="String"/>
|
||||
<attribute name="subtitle" optional="YES" attributeType="String"/>
|
||||
<attribute name="tintColor" optional="YES" attributeType="Transformable" valueTransformerName="ALTSecureValueTransformer"/>
|
||||
<attribute name="version" optional="YES" attributeType="String"/>
|
||||
<attribute name="versionDate" optional="YES" attributeType="Date" usesScalarValueType="NO"/>
|
||||
<attribute name="versionDescription" optional="YES" attributeType="String"/>
|
||||
<relationship name="featuringSource" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="Source" inverseName="featuredApps" inverseEntity="Source"/>
|
||||
<relationship name="installedApp" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="InstalledApp" inverseName="storeApp" inverseEntity="InstalledApp"/>
|
||||
<relationship name="latestVersion" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="AppVersion" inverseName="latestVersionApp" inverseEntity="AppVersion"/>
|
||||
<relationship name="loggedErrors" toMany="YES" deletionRule="Nullify" destinationEntity="LoggedError" inverseName="storeApp" inverseEntity="LoggedError"/>
|
||||
<relationship name="newsItems" toMany="YES" deletionRule="Nullify" destinationEntity="NewsItem" inverseName="storeApp" inverseEntity="NewsItem"/>
|
||||
<relationship name="permissions" toMany="YES" deletionRule="Cascade" destinationEntity="AppPermission" inverseName="app" inverseEntity="AppPermission"/>
|
||||
<relationship name="releaseTracks" optional="YES" toMany="YES" deletionRule="Cascade" ordered="YES" destinationEntity="ReleaseTrack" inverseName="storeApp" inverseEntity="ReleaseTrack"/>
|
||||
<relationship name="screenshots" toMany="YES" deletionRule="Cascade" ordered="YES" destinationEntity="AppScreenshot" inverseName="app" inverseEntity="AppScreenshot"/>
|
||||
<relationship name="source" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="Source" inverseName="apps" inverseEntity="Source"/>
|
||||
<relationship name="versions" toMany="YES" deletionRule="Cascade" ordered="YES" destinationEntity="AppVersion" inverseName="app" inverseEntity="AppVersion"/>
|
||||
<uniquenessConstraints>
|
||||
<uniquenessConstraint>
|
||||
<constraint value="sourceIdentifier"/>
|
||||
<constraint value="bundleIdentifier"/>
|
||||
</uniquenessConstraint>
|
||||
</uniquenessConstraints>
|
||||
</entity>
|
||||
<entity name="Team" representedClassName="Team" syncable="YES">
|
||||
<attribute name="identifier" attributeType="String"/>
|
||||
<attribute name="isActiveTeam" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
|
||||
<attribute name="name" attributeType="String"/>
|
||||
<attribute name="type" attributeType="Integer 16" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<relationship name="account" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="Account" inverseName="teams" inverseEntity="Account"/>
|
||||
<relationship name="appIDs" toMany="YES" deletionRule="Cascade" destinationEntity="AppID" inverseName="team" inverseEntity="AppID"/>
|
||||
<relationship name="installedApps" toMany="YES" deletionRule="Nullify" destinationEntity="InstalledApp" inverseName="team" inverseEntity="InstalledApp"/>
|
||||
<uniquenessConstraints>
|
||||
<uniquenessConstraint>
|
||||
<constraint value="identifier"/>
|
||||
</uniquenessConstraint>
|
||||
</uniquenessConstraints>
|
||||
</entity>
|
||||
</model>
|
||||
@@ -33,7 +33,7 @@ public class AppPermission: BaseEntity
|
||||
default: return UnknownAppPermission(rawValue: self._permission)
|
||||
}
|
||||
}
|
||||
@NSManaged @objc(permission) private var _permission: String
|
||||
@NSManaged @objc(permission) private(set) public var _permission: String
|
||||
|
||||
// Set by StoreApp.
|
||||
@NSManaged public var appBundleID: String
|
||||
|
||||
@@ -59,6 +59,8 @@ public class InstalledApp: BaseEntity, InstalledAppProtocol
|
||||
@NSManaged public var needsResign: Bool
|
||||
@NSManaged public var hasAlternateIcon: Bool
|
||||
|
||||
@NSManaged public var useMainProfile: Bool
|
||||
|
||||
@NSManaged public var certificateSerialNumber: String?
|
||||
@NSManaged public var storeBuildVersion: String?
|
||||
|
||||
@@ -141,7 +143,7 @@ public class InstalledApp: BaseEntity, InstalledAppProtocol
|
||||
let latestVer = SemanticVersion("\(latestSemVer!.major).\(latestSemVer!.minor).\(latestSemVer!.patch)")
|
||||
|
||||
// Compare by major.minor.patch
|
||||
if latestVer! > latestVer! {
|
||||
if latestVer! > currentVer! {
|
||||
return true
|
||||
}
|
||||
|
||||
|
||||
@@ -213,6 +213,7 @@ extension MergePolicy{
|
||||
}
|
||||
|
||||
default:
|
||||
// break
|
||||
// Unknown context-level conflict.
|
||||
// assertionFailure("MergePolicy is only intended to work with database-level conflicts.")
|
||||
assertionFailure("Context Conflict Detected: is there ambigious data in your incoming sources?\nConflict:\(conflict)")
|
||||
|
||||
@@ -505,13 +505,14 @@ cxIAAYagXxAPTlNLZXllZEFyY2hpdmVy0QAIAAlUcm9vdIABrxESLAALAAwAGQA1ADYANwBjAGQAZQBm
|
||||
<relationship name="relationshipmappings" type="0/0" destination="XDDEVRELATIONSHIPMAPPING" idrefs="z142 z119 z223 z116 z229 z139 z236 z136 z214 z216"></relationship>
|
||||
</object>
|
||||
<object type="XDDEVENTITYMAPPING" id="z198">
|
||||
<attribute name="migrationpolicyclassname" type="string"></attribute>
|
||||
<attribute name="sourcename" type="string">AppPermission</attribute>
|
||||
<attribute name="mappingtypename" type="string">Undefined</attribute>
|
||||
<attribute name="mappingnumber" type="int16">17</attribute>
|
||||
<attribute name="destinationname" type="string">AppPermission</attribute>
|
||||
<attribute name="autogenerateexpression" type="bool">1</attribute>
|
||||
<relationship name="mappingmodel" type="1/1" destination="XDDEVMAPPINGMODEL" idrefs="z192"></relationship>
|
||||
<relationship name="attributemappings" type="0/0" destination="XDDEVATTRIBUTEMAPPING" idrefs="z213 z240 z191 z123 z287"></relationship>
|
||||
<relationship name="attributemappings" type="0/0" destination="XDDEVATTRIBUTEMAPPING" idrefs="z123 z213 z287 z240 z191"></relationship>
|
||||
<relationship name="relationshipmappings" type="0/0" destination="XDDEVRELATIONSHIPMAPPING" idrefs="z288"></relationship>
|
||||
</object>
|
||||
<object type="XDDEVENTITYMAPPING" id="z199">
|
||||
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -1,5 +1,5 @@
|
||||
//
|
||||
// SourceMigrationPolicy.swift
|
||||
// Source11To17MigrationPolicy.swift
|
||||
// AltStoreCore
|
||||
//
|
||||
// Created by Riley Testut on 10/19/23.
|
||||
@@ -1,5 +1,5 @@
|
||||
//
|
||||
// StoreAppMigration11To17Policy.swift
|
||||
// StoreApp11To17MigrationPolicy.swift
|
||||
// AltStore
|
||||
//
|
||||
// Created by Magesh K on 25/02/25.
|
||||
@@ -108,7 +108,7 @@ class StoreApp11To17MigrationPolicy: NSEntityMigrationPolicy {
|
||||
|
||||
// Create a new ReleaseTrack entity
|
||||
let context = dInstance.managedObjectContext!
|
||||
let releaseTrack = NSEntityDescription.insertNewObject(forEntityName: ReleaseTrack.entity().name!, into: context)
|
||||
let releaseTrack = NSEntityDescription.insertNewObject(forEntityName: ReleaseTrack.entity().name ?? ReleaseTrack.description(), into: context)
|
||||
releaseTrack.setValue(defaultChannel, forKey: #keyPath(ReleaseTrack._track))
|
||||
|
||||
// Connect the releaseTrack to the destination StoreApp
|
||||
@@ -116,7 +116,7 @@ class StoreApp11To17MigrationPolicy: NSEntityMigrationPolicy {
|
||||
|
||||
|
||||
// Find the mapping name for AppVersion (make sure this exactly matches your mapping model)
|
||||
let appVersionMappingName = findEntityMappingName(for: AppVersion.entity().name!, in: manager)
|
||||
let appVersionMappingName = findEntityMappingName(for: AppVersion.entity().name ?? AppVersion.description(), in: manager)
|
||||
|
||||
// Create a mutable ordered set for the destination AppVersion objects
|
||||
let destinationVersionsSet = NSMutableOrderedSet()
|
||||
@@ -145,5 +145,5 @@ class StoreApp11To17MigrationPolicy: NSEntityMigrationPolicy {
|
||||
// dInstance.setValue(NSOrderedSet(), forKey: #keyPath(StoreApp._versions))
|
||||
dInstance.setValue(nil, forKey: #keyPath(StoreApp._versions))
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
//
|
||||
// AppPermission17to17_1MigrationPolicy.swift
|
||||
// AltStore
|
||||
//
|
||||
// Created by Magesh K on 15/03/25.
|
||||
// Copyright © 2025 SideStore. All rights reserved.
|
||||
//
|
||||
|
||||
import CoreData
|
||||
|
||||
@objc(AppPermission11To17_1MigrationPolicy)
|
||||
class AppPermission11To17_1MigrationPolicy: AppPermission17To17_1MigrationPolicy {
|
||||
|
||||
override func createDestinationInstances(forSource sInstance: NSManagedObject, in mapping: NSEntityMapping, manager: NSMigrationManager) throws {
|
||||
// Let the default implementation create the basic destination AppPermission
|
||||
try super.createDestinationInstances(forSource: sInstance, in: mapping, manager: manager)
|
||||
}
|
||||
|
||||
|
||||
|
||||
override func createRelationships(
|
||||
forDestination dInstance: NSManagedObject,
|
||||
in mapping: NSEntityMapping,
|
||||
manager: NSMigrationManager
|
||||
) throws {
|
||||
try super.createRelationships(forDestination: dInstance, in: mapping, manager: manager)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
//
|
||||
// ReleaseTrack11To17_1MigrationPolicy.swift
|
||||
// AltStore
|
||||
//
|
||||
// Created by Magesh K on 15/03/25.
|
||||
// Copyright © 2025 SideStore. All rights reserved.
|
||||
//
|
||||
|
||||
import CoreData
|
||||
|
||||
@objc(ReleaseTrack11To17_1MigrationPolicy)
|
||||
class ReleaseTrack11To17_1MigrationPolicy: ReleaseTrack17To17_1MigrationPolicy {
|
||||
|
||||
override func createDestinationInstances(forSource sInstance: NSManagedObject, in mapping: NSEntityMapping, manager: NSMigrationManager) throws {
|
||||
try super.createDestinationInstances(forSource: sInstance, in: mapping, manager: manager)
|
||||
}
|
||||
|
||||
|
||||
override func createRelationships(
|
||||
forDestination dInstance: NSManagedObject,
|
||||
in mapping: NSEntityMapping,
|
||||
manager: NSMigrationManager
|
||||
) throws {
|
||||
try super.createRelationships(forDestination: dInstance, in: mapping, manager: manager)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
//
|
||||
// Source11To17_1MigrationPolicy.swift
|
||||
// AltStore
|
||||
//
|
||||
// Created by Magesh K on 15/03/25.
|
||||
// Copyright © 2025 SideStore. All rights reserved.
|
||||
//
|
||||
|
||||
import CoreData
|
||||
|
||||
@objc(Source11To17_1MigrationPolicy)
|
||||
class Source11To17_1MigrationPolicy: Source17To17_1MigrationPolicy
|
||||
{
|
||||
override func createDestinationInstances(forSource sInstance: NSManagedObject, in mapping: NSEntityMapping, manager: NSMigrationManager) throws {
|
||||
// Let the default implementation create the basic destination AppPermission
|
||||
try super.createDestinationInstances(forSource: sInstance, in: mapping, manager: manager)
|
||||
}
|
||||
|
||||
override func createRelationships(forDestination dInstance: NSManagedObject, in mapping: NSEntityMapping, manager: NSMigrationManager) throws
|
||||
{
|
||||
try super.createRelationships(forDestination: dInstance, in: mapping, manager: manager)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
//
|
||||
// StoreApp11To17_1MigrationPolicy.swift
|
||||
// AltStore
|
||||
//
|
||||
// Created by Magesh K on 15/03/25.
|
||||
// Copyright © 2025 SideStore. All rights reserved.
|
||||
//
|
||||
|
||||
import CoreData
|
||||
|
||||
fileprivate extension NSManagedObject
|
||||
{
|
||||
var storeAppReleaseTracks: NSOrderedSet? {
|
||||
let tracks = self.value(forKey: #keyPath(StoreApp._releaseTracks)) as? NSOrderedSet
|
||||
return tracks
|
||||
}
|
||||
}
|
||||
|
||||
@objc(StoreApp11To17_1MigrationPolicy)
|
||||
class StoreApp11To17_1MigrationPolicy: StoreApp11To17MigrationPolicy
|
||||
{
|
||||
override func createDestinationInstances(forSource sInstance: NSManagedObject, in mapping: NSEntityMapping, manager: NSMigrationManager) throws {
|
||||
try super.createDestinationInstances(forSource: sInstance, in: mapping, manager: manager)
|
||||
}
|
||||
|
||||
override func createRelationships(forDestination dInstance: NSManagedObject, in mapping: NSEntityMapping, manager: NSMigrationManager) throws
|
||||
{
|
||||
try super.createRelationships(forDestination: dInstance, in: mapping, manager: manager)
|
||||
|
||||
let appBundleID = dInstance.value(forKey: #keyPath(StoreApp.bundleIdentifier))
|
||||
let sourceID = dInstance.value(forKey: #keyPath(StoreApp.sourceIdentifier))
|
||||
|
||||
for case let track as NSManagedObject in dInstance.storeAppReleaseTracks ?? []
|
||||
{
|
||||
track.setValue(appBundleID, forKey: #keyPath(ReleaseTrack._appBundleID))
|
||||
track.setValue(sourceID, forKey: #keyPath(ReleaseTrack._sourceID))
|
||||
|
||||
guard let releases = track.value(forKey: #keyPath(ReleaseTrack._releases)) as? NSOrderedSet else {
|
||||
continue
|
||||
}
|
||||
|
||||
for case let version as NSManagedObject in releases {
|
||||
version.setValue(appBundleID, forKey: #keyPath(AppVersion.appBundleID))
|
||||
version.setValue(sourceID, forKey: #keyPath(AppVersion.sourceID))
|
||||
}
|
||||
}
|
||||
|
||||
if let permissions = dInstance.value(forKey: #keyPath(StoreApp._permissions)) as? NSSet {
|
||||
for case let permission as NSManagedObject in permissions {
|
||||
permission.setValue(appBundleID, forKey: #keyPath(AppPermission.appBundleID))
|
||||
permission.setValue(sourceID, forKey: #keyPath(AppPermission.sourceID))
|
||||
}
|
||||
}
|
||||
|
||||
if let screenshots = dInstance.value(forKey: #keyPath(StoreApp._screenshots)) as? NSOrderedSet {
|
||||
for case let screenshot as NSManagedObject in screenshots {
|
||||
screenshot.setValue(appBundleID, forKey: #keyPath(AppScreenshot.appBundleID))
|
||||
screenshot.setValue(sourceID, forKey: #keyPath(AppScreenshot.sourceID))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,100 @@
|
||||
//
|
||||
// AppPermission17to17_1MigrationPolicy.swift
|
||||
// AltStore
|
||||
//
|
||||
// Created by Magesh K on 15/03/25.
|
||||
// Copyright © 2025 SideStore. All rights reserved.
|
||||
//
|
||||
|
||||
import CoreData
|
||||
|
||||
@objc(AppPermission17To17_1MigrationPolicy)
|
||||
class AppPermission17To17_1MigrationPolicy: NSEntityMigrationPolicy {
|
||||
|
||||
override func createDestinationInstances(forSource sInstance: NSManagedObject, in mapping: NSEntityMapping, manager: NSMigrationManager) throws {
|
||||
// Let the default implementation create the basic destination AppPermission
|
||||
try super.createDestinationInstances(forSource: sInstance, in: mapping, manager: manager)
|
||||
|
||||
// Get the destination AppPermission instance that was created
|
||||
guard let destinationPermission = manager.destinationInstances(forEntityMappingName: mapping.name, sourceInstances: [sInstance]).first else {
|
||||
print("Failed to locate destination AppPermission instance")
|
||||
return
|
||||
}
|
||||
|
||||
// Extract the type value from source
|
||||
if let type = sInstance.value(forKey: #keyPath(AppPermission.type)) as? String {
|
||||
// this is for backwards compatibility <0.5.10
|
||||
// if older type was a valid permission, then consider it as "privacy" in the newer "type".
|
||||
if let permission = self.derivePermissionFromType(type) {
|
||||
destinationPermission.setValue(permission, forKey: #keyPath(AppPermission._permission))
|
||||
destinationPermission.setValue("privacy", forKey: #keyPath(AppPermission.type))
|
||||
}
|
||||
}
|
||||
|
||||
// set initial values copied from source as-is
|
||||
// (will be updated by StoreApp and Source migration policy in its createRelationship() method)
|
||||
if let storeApp = sInstance.value(forKey: #keyPath(AppPermission.app)) as? NSManagedObject{
|
||||
if let appBundle = storeApp.value(forKey: #keyPath(StoreApp.bundleIdentifier)) as? String{
|
||||
destinationPermission.setValue(appBundle, forKey: #keyPath(AppPermission.appBundleID))
|
||||
}
|
||||
|
||||
if let sourceID = storeApp.value(forKey: #keyPath(StoreApp.sourceIdentifier)) as? String {
|
||||
destinationPermission.setValue(sourceID, forKey: #keyPath(AppPermission.sourceID))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
override func createRelationships(
|
||||
forDestination dInstance: NSManagedObject,
|
||||
in mapping: NSEntityMapping,
|
||||
manager: NSMigrationManager
|
||||
) throws {
|
||||
|
||||
try super.createRelationships(forDestination: dInstance, in: mapping, manager: manager)
|
||||
|
||||
// Retrieve the source storeApp from the source appPermission
|
||||
guard let storeApp = dInstance.value(forKey: #keyPath(AppPermission.app)) as? NSManagedObject else {
|
||||
print("Destination \(AppPermission.description()) has no storeApp")
|
||||
return
|
||||
}
|
||||
|
||||
// set initial values copied from source as-is to satisfy unique constraints
|
||||
// (will be updated by StoreApp and Source migration policy in its createRelationship() method)
|
||||
if let appBundle = storeApp.value(forKey: #keyPath(StoreApp.bundleIdentifier)) as? String{
|
||||
dInstance.setValue(appBundle, forKey: #keyPath(AppPermission.appBundleID))
|
||||
}
|
||||
|
||||
if let sourceID = storeApp.value(forKey: #keyPath(StoreApp.sourceIdentifier)) as? String {
|
||||
dInstance.setValue(sourceID, forKey: #keyPath(AppPermission.sourceID))
|
||||
}
|
||||
}
|
||||
|
||||
// Helper method to derive permission string from type
|
||||
private func derivePermissionFromType(_ type: String) -> String? {
|
||||
|
||||
// Based on the code in the documents, we need to map the ALTAppPermissionType to permission strings
|
||||
switch type {
|
||||
case "photos": return "NSPhotosUsageDescription"
|
||||
case "camera": return "NSCameraUsageDescription"
|
||||
case "location": return "NSLocationUsageDescription"
|
||||
case "contacts": return "NSContactsUsageDescription"
|
||||
case "reminders": return "NSRemindersUsageDescription"
|
||||
case "music": return "NSAppleMusicUsageDescription"
|
||||
case "microphone": return "NSMicrophoneUsageDescription"
|
||||
case "speech-recognition": return "NSSpeechRecognitionUsageDescription"
|
||||
case "background-audio": return "NSBackgroundAudioUsageDescription"
|
||||
case "background-fetch": return "NSBackgroundFetchUsageDescription"
|
||||
case "bluetooth": return "NSBluetoothUsageDescription"
|
||||
case "network": return "NSNetworkUsageDescription"
|
||||
case "calendars": return "NSCalendarsUsageDescription"
|
||||
case "touchID": return "NSTouchIDUsageDescription"
|
||||
case "faceID": return "NSFaceIDUsageDescription"
|
||||
case "siri": return "NSSiriUsageDescription"
|
||||
case "motion": return "NSMotionUsageDescription"
|
||||
|
||||
default: return nil // Default fallback
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
//
|
||||
// ReleaseTrack17To17_1MigrationPolicy.swift
|
||||
// AltStore
|
||||
//
|
||||
// Created by Magesh K on 15/03/25.
|
||||
// Copyright © 2025 SideStore. All rights reserved.
|
||||
//
|
||||
|
||||
import CoreData
|
||||
|
||||
@objc(ReleaseTrack17To17_1MigrationPolicy)
|
||||
class ReleaseTrack17To17_1MigrationPolicy: NSEntityMigrationPolicy {
|
||||
|
||||
override func createDestinationInstances(forSource sInstance: NSManagedObject, in mapping: NSEntityMapping, manager: NSMigrationManager) throws {
|
||||
try super.createDestinationInstances(forSource: sInstance, in: mapping, manager: manager)
|
||||
}
|
||||
|
||||
|
||||
override func createRelationships(
|
||||
forDestination dInstance: NSManagedObject,
|
||||
in mapping: NSEntityMapping,
|
||||
manager: NSMigrationManager
|
||||
) throws {
|
||||
|
||||
try super.createRelationships(forDestination: dInstance, in: mapping, manager: manager)
|
||||
|
||||
// Retrieve the source storeApp from the source ReleaseTrack
|
||||
guard let storeApp = dInstance.value(forKey: #keyPath(ReleaseTrack.storeApp)) as? NSManagedObject else {
|
||||
print("Destination \(ReleaseTrack.description()) has no storeApp")
|
||||
return
|
||||
}
|
||||
|
||||
// set initial values copied from source as-is to satisfy unique constraints
|
||||
// (will be updated by StoreApp and Source migration policy in its createRelationship() method)
|
||||
let appBundle = storeApp.value(forKey: #keyPath(StoreApp.bundleIdentifier)) as? String
|
||||
let sourceID = storeApp.value(forKey: #keyPath(StoreApp.sourceIdentifier)) as? String
|
||||
|
||||
if let appBundle {
|
||||
dInstance.setValue(appBundle, forKey: #keyPath(ReleaseTrack._appBundleID))
|
||||
}
|
||||
|
||||
if let sourceID {
|
||||
dInstance.setValue(sourceID, forKey: #keyPath(ReleaseTrack._sourceID))
|
||||
}
|
||||
|
||||
if let releases = dInstance.value(forKey: #keyPath(ReleaseTrack._releases)) as? NSOrderedSet {
|
||||
for case let version as NSManagedObject in releases {
|
||||
if let appBundle {
|
||||
version.setValue(appBundle, forKey: #keyPath(AppVersion.appBundleID))
|
||||
}
|
||||
if let sourceID {
|
||||
version.setValue(sourceID, forKey: #keyPath(AppVersion.sourceID))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,191 @@
|
||||
//
|
||||
// Source17To17_1MigrationPolicy.swift
|
||||
// AltStore
|
||||
//
|
||||
// Created by Magesh K on 15/03/25.
|
||||
// Copyright © 2025 SideStore. All rights reserved.
|
||||
//
|
||||
|
||||
import CoreData
|
||||
|
||||
fileprivate extension NSManagedObject
|
||||
{
|
||||
var sourceSourceURL: URL? {
|
||||
let sourceURL = self.value(forKey: #keyPath(Source.sourceURL)) as? URL
|
||||
return sourceURL
|
||||
}
|
||||
|
||||
var sourceSourceId: String? {
|
||||
let sourceId = self.value(forKey: #keyPath(Source.identifier)) as? String
|
||||
return sourceId
|
||||
}
|
||||
|
||||
var sourceApps: NSOrderedSet? {
|
||||
let apps = self.value(forKey: #keyPath(Source._apps)) as? NSOrderedSet
|
||||
return apps
|
||||
}
|
||||
|
||||
var sourceNewsItems: NSOrderedSet? {
|
||||
let newsItems = self.value(forKey: #keyPath(Source._newsItems)) as? NSOrderedSet
|
||||
return newsItems
|
||||
}
|
||||
}
|
||||
|
||||
fileprivate extension NSManagedObject
|
||||
{
|
||||
func setSourceId(_ sourceID: String)
|
||||
{
|
||||
self.setValue(sourceID, forKey: #keyPath(Source.identifier))
|
||||
}
|
||||
|
||||
func setGroupId(_ groupID: String)
|
||||
{
|
||||
self.setValue(groupID, forKey: #keyPath(Source.groupID))
|
||||
}
|
||||
|
||||
func setSourceSourceUrl(_ sourceURL: URL)
|
||||
{
|
||||
self.setValue(sourceURL, forKey: #keyPath(Source.sourceURL))
|
||||
}
|
||||
|
||||
func setSourceSourceID(_ sourceID: String)
|
||||
{
|
||||
self.setValue(sourceID, forKey: #keyPath(Source.identifier))
|
||||
}
|
||||
|
||||
func setStoreAppSourceID(_ sourceID: String)
|
||||
{
|
||||
self.setValue(sourceID, forKey: #keyPath(StoreApp.sourceIdentifier))
|
||||
}
|
||||
|
||||
func setNewsItemSourceID(_ sourceID: String)
|
||||
{
|
||||
self.setValue(sourceID, forKey: #keyPath(NewsItem.sourceIdentifier))
|
||||
}
|
||||
|
||||
func setAppVersionSourceID(_ sourceID: String)
|
||||
{
|
||||
self.setValue(sourceID, forKey: #keyPath(AppVersion.sourceID))
|
||||
}
|
||||
|
||||
func setAppPermissionSourceID(_ sourceID: String)
|
||||
{
|
||||
self.setValue(sourceID, forKey: #keyPath(AppPermission.sourceID))
|
||||
}
|
||||
|
||||
func setAppScreenshotSourceID(_ sourceID: String)
|
||||
{
|
||||
self.setValue(sourceID, forKey: #keyPath(AppScreenshot.sourceID))
|
||||
}
|
||||
|
||||
func setReleaseTracksSourceID(_ sourceID: String)
|
||||
{
|
||||
self.setValue(sourceID, forKey: #keyPath(ReleaseTrack._sourceID))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
fileprivate extension NSManagedObject
|
||||
{
|
||||
var storeAppVersions: NSOrderedSet? {
|
||||
let versions = self.value(forKey: #keyPath(StoreApp._versions)) as? NSOrderedSet
|
||||
return versions
|
||||
}
|
||||
|
||||
var storeAppPermissions: NSSet? {
|
||||
let permissions = self.value(forKey: #keyPath(StoreApp._permissions)) as? NSSet
|
||||
return permissions
|
||||
}
|
||||
|
||||
var storeAppScreenshots: NSOrderedSet? {
|
||||
let screenshots = self.value(forKey: #keyPath(StoreApp._screenshots)) as? NSOrderedSet
|
||||
return screenshots
|
||||
}
|
||||
|
||||
var storeAppReleaseTracks: NSOrderedSet? {
|
||||
let tracks = self.value(forKey: #keyPath(StoreApp._releaseTracks)) as? NSOrderedSet
|
||||
return tracks
|
||||
}
|
||||
}
|
||||
|
||||
@objc(Source17To17_1MigrationPolicy)
|
||||
class Source17To17_1MigrationPolicy: NSEntityMigrationPolicy
|
||||
{
|
||||
override func createDestinationInstances(forSource sInstance: NSManagedObject, in mapping: NSEntityMapping, manager: NSMigrationManager) throws {
|
||||
// Let the default implementation create the basic destination AppPermission
|
||||
try super.createDestinationInstances(forSource: sInstance, in: mapping, manager: manager)
|
||||
|
||||
// Get the destination Source instance that was created
|
||||
guard let dInstance = manager.destinationInstances(forEntityMappingName: mapping.name, sourceInstances: [sInstance]).first else {
|
||||
print("Failed to locate destination Source instance")
|
||||
return
|
||||
}
|
||||
|
||||
// update new fields with initial values
|
||||
if let sourceID = sInstance.value(forKey: #keyPath(Source.identifier)) as? String {
|
||||
dInstance.setValue(sourceID, forKey: #keyPath(Source.groupID))
|
||||
}
|
||||
|
||||
guard var sourceURL = dInstance.sourceSourceURL else {
|
||||
return
|
||||
}
|
||||
|
||||
// sidestore official source has been moved to sidestore.io/apps-v2.json
|
||||
// if we don't switch, users will end up with 2 offical sources
|
||||
let normalizedSourceURL = try? sourceURL.normalized()
|
||||
if normalizedSourceURL == "apps.sidestore.io" // if using old source url (<0.5.9)
|
||||
{
|
||||
sourceURL = Source.altStoreSourceURL // switch to latest
|
||||
dInstance.setSourceSourceUrl(sourceURL) // and use it for current
|
||||
}
|
||||
|
||||
var sourceID = try Source.sourceID(from: sourceURL)
|
||||
dInstance.setSourceId(sourceID)
|
||||
|
||||
// for older versions migrating to current (their sourceID is their groupID)
|
||||
dInstance.setGroupId(sourceID)
|
||||
|
||||
if sourceID == "apps.sidestore.io" {
|
||||
sourceID = Source.altStoreIdentifier
|
||||
dInstance.setSourceId(sourceID)
|
||||
}
|
||||
}
|
||||
|
||||
override func createRelationships(forDestination dInstance: NSManagedObject, in mapping: NSEntityMapping, manager: NSMigrationManager) throws
|
||||
{
|
||||
try super.createRelationships(forDestination: dInstance, in: mapping, manager: manager)
|
||||
|
||||
guard let sourceID = dInstance.sourceSourceId else { return }
|
||||
|
||||
for case let newsItem as NSManagedObject in dInstance.sourceNewsItems ?? []
|
||||
{
|
||||
newsItem.setNewsItemSourceID(sourceID)
|
||||
}
|
||||
|
||||
for case let app as NSManagedObject in dInstance.sourceApps ?? []
|
||||
{
|
||||
app.setStoreAppSourceID(sourceID)
|
||||
|
||||
for case let screenshot as NSManagedObject in app.storeAppScreenshots ?? []
|
||||
{
|
||||
screenshot.setAppScreenshotSourceID(sourceID)
|
||||
}
|
||||
|
||||
for case let track as NSManagedObject in app.storeAppReleaseTracks ?? []
|
||||
{
|
||||
// print("Source_17_1MigrationPolicy: processing track \(track.value(forKey: "track")!)")
|
||||
track.setValue(sourceID, forKey: #keyPath(ReleaseTrack._sourceID))
|
||||
|
||||
guard let releases = track.value(forKey: #keyPath(ReleaseTrack._releases)) as? NSOrderedSet else {
|
||||
// print("Source_17_1MigrationPolicy: releases not found for track: \(track.value(forKey: "track")!)")
|
||||
continue
|
||||
}
|
||||
|
||||
for case let version as NSManagedObject in releases {
|
||||
// print("Source_17_1MigrationPolicy: updating sourceID for version: \(version.value(forKey: "version")!)")
|
||||
version.setValue(sourceID, forKey: #keyPath(AppVersion.sourceID))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
//
|
||||
// StoreApp17To17_1MigrationPolicy.swift
|
||||
// AltStore
|
||||
//
|
||||
// Created by Magesh K on 15/03/25.
|
||||
// Copyright © 2025 SideStore. All rights reserved.
|
||||
//
|
||||
|
||||
import CoreData
|
||||
|
||||
fileprivate extension NSManagedObject
|
||||
{
|
||||
var storeAppReleaseTracks: NSOrderedSet? {
|
||||
let tracks = self.value(forKey: #keyPath(StoreApp._releaseTracks)) as? NSOrderedSet
|
||||
return tracks
|
||||
}
|
||||
}
|
||||
|
||||
@objc(StoreApp17To17_1MigrationPolicy)
|
||||
class StoreApp17To17_1MigrationPolicy: NSEntityMigrationPolicy
|
||||
{
|
||||
override func createDestinationInstances(forSource sInstance: NSManagedObject, in mapping: NSEntityMapping, manager: NSMigrationManager) throws {
|
||||
try super.createDestinationInstances(forSource: sInstance, in: mapping, manager: manager)
|
||||
}
|
||||
|
||||
override func createRelationships(forDestination dInstance: NSManagedObject, in mapping: NSEntityMapping, manager: NSMigrationManager) throws
|
||||
{
|
||||
try super.createRelationships(forDestination: dInstance, in: mapping, manager: manager)
|
||||
|
||||
let appBundleID = dInstance.value(forKey: #keyPath(StoreApp.bundleIdentifier))
|
||||
|
||||
for case let track as NSManagedObject in dInstance.storeAppReleaseTracks ?? []
|
||||
{
|
||||
track.setValue(appBundleID, forKey: #keyPath(ReleaseTrack._appBundleID))
|
||||
|
||||
guard let releases = track.value(forKey: #keyPath(ReleaseTrack._releases)) as? NSOrderedSet else {
|
||||
continue
|
||||
}
|
||||
|
||||
for case let version as NSManagedObject in releases {
|
||||
version.setValue(appBundleID, forKey: #keyPath(AppVersion.appBundleID))
|
||||
}
|
||||
}
|
||||
|
||||
if let permissions = dInstance.value(forKey: #keyPath(StoreApp._permissions)) as? NSSet {
|
||||
for case let permission as NSManagedObject in permissions {
|
||||
permission.setValue(appBundleID, forKey: #keyPath(AppPermission.appBundleID))
|
||||
}
|
||||
}
|
||||
|
||||
if let screenshots = dInstance.value(forKey: #keyPath(StoreApp._screenshots)) as? NSOrderedSet {
|
||||
for case let screenshot as NSManagedObject in screenshots {
|
||||
screenshot.setValue(appBundleID, forKey: #keyPath(AppScreenshot.appBundleID))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -75,6 +75,9 @@ public class NewsItem: BaseEntity, Decodable
|
||||
self.imageURL = try container.decodeIfPresent(URL.self, forKey: .imageURL)
|
||||
self.externalURL = try container.decodeIfPresent(URL.self, forKey: .externalURL)
|
||||
|
||||
// TODO: app specific news should be moved to appEntity (via refactoring)
|
||||
// This can be done by: 1. having two newsItem schema, one for source and one for StoreApp
|
||||
// 2. move this appID field into the newItem schema which is under StoreApp.
|
||||
self.appID = try container.decodeIfPresent(String.self, forKey: .appID)
|
||||
|
||||
let notify = try container.decodeIfPresent(Bool.self, forKey: .notify) ?? false
|
||||
|
||||
@@ -14,7 +14,9 @@ public class ReleaseTrack: BaseEntity, Decodable
|
||||
{
|
||||
// attributes
|
||||
@NSManaged @objc(track) public private(set) var _track: String?
|
||||
|
||||
@NSManaged @objc(appBundleID) public private(set) var _appBundleID: String?
|
||||
@NSManaged @objc(sourceID) public private(set) var _sourceID: String?
|
||||
|
||||
// RelationShips
|
||||
@NSManaged @objc(releases) public private(set) var _releases: NSOrderedSet?
|
||||
@NSManaged public private(set) var storeApp: StoreApp?
|
||||
@@ -89,7 +91,7 @@ public extension ReleaseTrack{
|
||||
}
|
||||
|
||||
// update it into the appVersion
|
||||
_ = version.mutateForData(channel: track, appBundleID: storeApp.bundleIdentifier)
|
||||
_ = version.mutateForData(channel: track, appBundleID: storeApp.bundleIdentifier, sourceID: storeApp.sourceIdentifier)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -109,6 +111,10 @@ public extension ReleaseTrack{
|
||||
if key == NSExpression(forKeyPath: #keyPath(ReleaseTrack.storeApp)).keyPath
|
||||
{
|
||||
updateVersions(for: storeApp)
|
||||
|
||||
// update unique constraint attribs
|
||||
self._appBundleID = storeApp?.bundleIdentifier
|
||||
self._sourceID = storeApp?.sourceIdentifier
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,7 +11,11 @@ import UIKit
|
||||
|
||||
public extension Source
|
||||
{
|
||||
static let altStoreIdentifier = try! Source.sourceID(from: Source.altStoreSourceURL)
|
||||
#if ALPHA
|
||||
static let altStoreGroupIdentifier = Bundle.Info.appbundleIdentifier
|
||||
#else
|
||||
static let altStoreGroupIdentifier = Bundle.Info.appbundleIdentifier
|
||||
#endif
|
||||
|
||||
#if STAGING
|
||||
|
||||
@@ -24,13 +28,15 @@ public extension Source
|
||||
#else
|
||||
|
||||
#if ALPHA
|
||||
static let altStoreSourceURL = URL(string: "https://apps.sidestore.io/")!
|
||||
static let altStoreSourceURL = URL(string: "https://sidestore.io/apps-v2.json/")!
|
||||
#else
|
||||
// static let altStoreSourceURL = URL(string: "https://apps.sidestore.io/")!
|
||||
static let altStoreSourceURL = URL(string: "https://sidestore.io/apps-v2.json/")! // using v2 for alpha releases
|
||||
static let altStoreSourceURL = URL(string: "https://sidestore.io/apps-v2.json/")!
|
||||
#endif
|
||||
|
||||
#endif
|
||||
|
||||
// normalized url is the source identifier (or) p-key!
|
||||
static let altStoreIdentifier = try! Source.sourceID(from: altStoreSourceURL)
|
||||
}
|
||||
|
||||
// public struct AppPermissionFeed: Codable {
|
||||
@@ -205,7 +211,8 @@ public class Source: BaseEntity, Decodable
|
||||
/* Properties */
|
||||
@NSManaged public var version: Int
|
||||
@NSManaged public var name: String
|
||||
@NSManaged public private(set) var identifier: String
|
||||
@NSManaged public private(set) var identifier: String // NOTE: sourceID is just normalized sourceURL
|
||||
@NSManaged public private(set) var groupID: String?
|
||||
@NSManaged public var sourceURL: URL
|
||||
|
||||
/* Source Detail */
|
||||
@@ -280,6 +287,9 @@ public class Source: BaseEntity, Decodable
|
||||
case news
|
||||
case featuredApps
|
||||
case userInfo
|
||||
|
||||
// case identifier
|
||||
case groupID = "identifier"
|
||||
}
|
||||
|
||||
private override init(entity: NSEntityDescription, insertInto context: NSManagedObjectContext?)
|
||||
@@ -359,6 +369,24 @@ public class Source: BaseEntity, Decodable
|
||||
|
||||
// Updates identifier + apps & newsItems
|
||||
try self.setSourceURL(sourceURL)
|
||||
|
||||
|
||||
// NOTE: Source ID is just normalized sourceURL. coz normalized url is the primary key which needs to be unique
|
||||
// Hence if a source's URL changed, then it means it is a different source now.
|
||||
// This also means that the identifier field in the source is irrelevant (if any)
|
||||
|
||||
// if we want grouping of sources from same author or something like that then we should have used groupID (a new field)
|
||||
// shouldn't use the existing "identifier" field, hence the following is commented out
|
||||
|
||||
// // if an explicit identifier is present, then use it
|
||||
// if let identifier = try container.decodeIfPresent(String.self, forKey: .identifier)
|
||||
// {
|
||||
// self.identifier = identifier
|
||||
// }
|
||||
|
||||
// if an explicit (group)identifier is present, then use it as groupID else use sourceID as groupID too
|
||||
self.groupID = try container.decodeIfPresent(String.self, forKey: .groupID) ?? self.identifier
|
||||
|
||||
}
|
||||
catch
|
||||
{
|
||||
@@ -405,7 +433,7 @@ public extension Source
|
||||
|
||||
// TODO: Support alternate URLs
|
||||
let isRecommended = recommendedSources.contains { source in
|
||||
return source.identifier == self.identifier || source.sourceURL?.absoluteString.lowercased() == self.sourceURL.absoluteString
|
||||
return source.identifier == self.identifier || source.sourceURL?.absoluteString.lowercased() == self.sourceURL.absoluteString.lowercased()
|
||||
}
|
||||
return isRecommended
|
||||
}
|
||||
@@ -418,14 +446,17 @@ public extension Source
|
||||
}
|
||||
}
|
||||
|
||||
internal extension Source
|
||||
public extension Source
|
||||
{
|
||||
class func sourceID(from sourceURL: URL) throws -> String
|
||||
{
|
||||
let sourceID = try sourceURL.normalized()
|
||||
return sourceID
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
internal extension Source
|
||||
{
|
||||
func setFeaturedApps(_ featuredApps: [StoreApp]?)
|
||||
{
|
||||
// Explicitly update relationships for all apps to ensure featuredApps merges correctly.
|
||||
@@ -451,17 +482,23 @@ public extension Source
|
||||
{
|
||||
func setSourceURL(_ sourceURL: URL) throws
|
||||
{
|
||||
let identifier = try Source.sourceID(from: sourceURL)
|
||||
|
||||
self.identifier = identifier
|
||||
self.sourceURL = sourceURL
|
||||
|
||||
// update the normalized sourceURL as the identifier
|
||||
let identifier = try Source.sourceID(from: sourceURL)
|
||||
try self.setSourceID(identifier)
|
||||
}
|
||||
|
||||
func setSourceID(_ identifier: String) throws
|
||||
{
|
||||
self.identifier = identifier
|
||||
|
||||
for app in self.apps
|
||||
{
|
||||
app.sourceIdentifier = identifier
|
||||
}
|
||||
|
||||
for newsItem in self.newsItems
|
||||
for newsItem in self.newsItems
|
||||
{
|
||||
newsItem.sourceIdentifier = identifier
|
||||
}
|
||||
@@ -479,6 +516,7 @@ public extension Source
|
||||
{
|
||||
let source = Source(context: context)
|
||||
source.name = "SideStore Offical"
|
||||
source.groupID = Source.altStoreGroupIdentifier
|
||||
source.identifier = Source.altStoreIdentifier
|
||||
try! source.setSourceURL(Source.altStoreSourceURL)
|
||||
|
||||
@@ -491,13 +529,14 @@ public extension Source
|
||||
return source
|
||||
}
|
||||
|
||||
class func make(name: String, identifier: String, sourceURL: URL, context: NSManagedObjectContext) -> Source
|
||||
class func make(name: String, groupID: String, sourceURL: URL, context: NSManagedObjectContext) -> Source
|
||||
{
|
||||
let source = Source(context: context)
|
||||
source.name = name
|
||||
source.identifier = identifier
|
||||
source.sourceURL = sourceURL
|
||||
|
||||
source.sourceURL = sourceURL
|
||||
source.identifier = try! Source.sourceID(from: sourceURL)
|
||||
|
||||
return source
|
||||
}
|
||||
}
|
||||
|
||||
@@ -25,7 +25,7 @@ struct TextLockScreenWidget: Widget
|
||||
}
|
||||
.supportedFamilies([.accessoryCircular])
|
||||
.configurationDisplayName("AltWidget (Text)")
|
||||
.description("View remaining days until AltStore expires.")
|
||||
.description("View remaining days until SideStore expires.")
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -48,7 +48,7 @@ struct IconLockScreenWidget: Widget
|
||||
}
|
||||
.supportedFamilies([.accessoryCircular])
|
||||
.configurationDisplayName("AltWidget (Icon)")
|
||||
.description("View remaining days until AltStore expires.")
|
||||
.description("View remaining days until SideStore expires.")
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
// Configuration settings file format documentation can be found at:
|
||||
// https://help.apple.com/xcode/#/dev745c5c974
|
||||
|
||||
MARKETING_VERSION = 0.6.0
|
||||
CURRENT_PROJECT_VERSION = 6000
|
||||
MARKETING_VERSION = 0.6.3
|
||||
CURRENT_PROJECT_VERSION = 0603
|
||||
|
||||
// Vars to be overwritten by `CodeSigning.xcconfig` if exists
|
||||
DEVELOPMENT_TEAM = S32Z3HMYVQ
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
DEVELOPMENT_TEAM = XYZ0123456
|
||||
|
||||
// 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.
|
||||
// You need to register:
|
||||
|
||||
2
Dependencies/libimobiledevice
vendored
2
Dependencies/libimobiledevice
vendored
Submodule Dependencies/libimobiledevice updated: 04c023317f...e7cc53a65b
1
Makefile
1
Makefile
@@ -213,6 +213,7 @@ build-tests:
|
||||
@echo "Performing a build-for-testing..."
|
||||
@xcodebuild build-for-testing \
|
||||
-enableCodeCoverage YES \
|
||||
-destination 'platform=iOS Simulator,name=iPhone 16 Pro,OS=18.2' \
|
||||
$(COMMON_BUILD_SETTINGS)
|
||||
|
||||
run-tests:
|
||||
|
||||
@@ -28,10 +28,10 @@ Why iOS 15? Targeting such a recent version of iOS allows us to accelerate devel
|
||||
SideStore is a just regular, sandboxed iOS application. The AltStore app target contains the vast majority of SideStore's functionality, including all the logic for downloading and updating apps through SideStore. SideStore makes heavy use of standard iOS frameworks and technologies most iOS developers are familiar with.
|
||||
|
||||
### EM Proxy
|
||||
[SideServer mobile](https://github.com/jkcoxson/em_proxy) powers the defining feature of SideStore: untethered app installation. By levaraging an App Store app with additional entitlements (WireGuard) to create the VPN tunnel for us, it allows SideStore to take advantage of [Jitterbug](https://github.com/osy/Jitterbug)'s loopback method without requiring a paid developer account.
|
||||
[SideServer mobile](https://github.com/jkcoxson/em_proxy) powers the defining feature of SideStore: untethered app installation. By leveraging a custom-built App Store app with additional entitlements (StosVPN) to create the VPN tunnel for us, it allows SideStore to take advantage of [Jitterbug](https://github.com/osy/Jitterbug)'s loopback method without requiring a paid developer account.
|
||||
|
||||
### Minimuxer
|
||||
[Minimuxer](https://github.com/jkcoxson/minimuxer) is a lockdown muxer that can run inside iOS’s sandbox. It replicates Apple’s usbmuxd protocol on MacOS to “discover” devices to interface with wireguard On-Device.
|
||||
[Minimuxer](https://github.com/jkcoxson/minimuxer) is a lockdown muxer that can run inside iOS’s sandbox. It replicates Apple’s usbmuxd protocol on MacOS to “discover” devices to interface with StosVPN On-Device.
|
||||
|
||||
### Roxas
|
||||
[Roxas](https://github.com/rileytestut/roxas) is Riley Testut's internal framework from AltStore used across many of their iOS projects, developed to simplify a variety of common tasks used in iOS development.
|
||||
|
||||
Submodule SideStore/AltSign updated: 5c278001cc...963066f3a6
@@ -1,18 +0,0 @@
|
||||
//
|
||||
// EmotionalDamage.swift
|
||||
// EmotionalDamage
|
||||
//
|
||||
// Created by Jackson Coxson on 10/26/22.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
public func start_em_proxy(bind_addr: String) {
|
||||
let host = NSString(string: bind_addr)
|
||||
let host_pointer = UnsafeMutablePointer<CChar>(mutating: host.utf8String)
|
||||
let _ = start_emotional_damage(host_pointer)
|
||||
}
|
||||
|
||||
public func stop_em_proxy() {
|
||||
stop_emotional_damage()
|
||||
}
|
||||
54
SideStore/Utils/common/BuildInfo.swift
Normal file
54
SideStore/Utils/common/BuildInfo.swift
Normal file
@@ -0,0 +1,54 @@
|
||||
//
|
||||
// BuildInfo.swift
|
||||
// AltStore
|
||||
//
|
||||
// Created by Magesh K on 23/03/25.
|
||||
// Copyright © 2025 SideStore. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
public class BuildInfo{
|
||||
private static let MARKETING_VERSION_TAG = "CFBundleShortVersionString"
|
||||
private static let CURRENT_PROJECT_VERSION_TAG = kCFBundleVersionKey as String
|
||||
|
||||
private static let XCODE_VERSION_TAG = "DTXcode"
|
||||
private static let XCODE_REVISION_TAG = "DTXcodeBuild"
|
||||
|
||||
let bundle: Bundle
|
||||
|
||||
public init(){
|
||||
bundle = Bundle.main
|
||||
}
|
||||
|
||||
enum BundleError: Swift.Error {
|
||||
case invalidURL
|
||||
}
|
||||
|
||||
public init(url: URL) throws {
|
||||
guard let bundle = Bundle(url: url) else {
|
||||
throw BundleError.invalidURL
|
||||
}
|
||||
self.bundle = bundle
|
||||
}
|
||||
|
||||
public lazy var project_version: String? = {
|
||||
let version = bundle.object(forInfoDictionaryKey: Self.CURRENT_PROJECT_VERSION_TAG) as? String
|
||||
return version
|
||||
}()
|
||||
|
||||
public lazy var marketing_version: String? = {
|
||||
let version = bundle.object(forInfoDictionaryKey: Self.MARKETING_VERSION_TAG) as? String
|
||||
return version
|
||||
}()
|
||||
|
||||
public lazy var xcode: String? = {
|
||||
let xcode = bundle.object(forInfoDictionaryKey: Self.XCODE_VERSION_TAG) as? String
|
||||
return xcode
|
||||
}()
|
||||
|
||||
public lazy var xcode_revision: String? = {
|
||||
let revision = bundle.object(forInfoDictionaryKey: Self.XCODE_REVISION_TAG) as? String
|
||||
return revision
|
||||
}()
|
||||
}
|
||||
@@ -12,7 +12,7 @@ import AltStoreCore
|
||||
|
||||
class ImportExport {
|
||||
|
||||
private static var documentPickerHandler: DocumentPickerHandler?
|
||||
public static var documentPickerHandler: DocumentPickerHandler?
|
||||
|
||||
public static func getPreviousBackupURL(_ backupURL: URL) -> URL {
|
||||
let backupParentDirectory = backupURL.deletingLastPathComponent()
|
||||
|
||||
@@ -59,48 +59,97 @@ public class AbstractConsoleLogger<T: OutputStream>: ConsoleLogger{
|
||||
originalStdout = dup(STDOUT_FILENO)
|
||||
originalStderr = dup(STDERR_FILENO)
|
||||
|
||||
let redirectedOutStream = self.outPipe?.fileHandleForWriting.fileDescriptor ?? -1
|
||||
let redirectedErrStream = self.errPipe?.fileHandleForWriting.fileDescriptor ?? -1
|
||||
|
||||
// Redirect stdout and stderr to our pipes
|
||||
dup2(self.outPipe?.fileHandleForWriting.fileDescriptor ?? -1, STDOUT_FILENO)
|
||||
dup2(self.errPipe?.fileHandleForWriting.fileDescriptor ?? -1, STDERR_FILENO)
|
||||
dup2(redirectedOutStream, STDOUT_FILENO)
|
||||
dup2(redirectedErrStream, STDERR_FILENO)
|
||||
|
||||
// Disable libc-level buffering
|
||||
// (libc by default uses bufferring except its own console/TTYs such as for pipes)
|
||||
// we do have our own buffering so we disable stdlib io level bufferring
|
||||
setvbuf(stdout, nil, _IONBF, 0) // disable buffering for stdout
|
||||
setvbuf(stderr, nil, _IONBF, 0) // disable buffering for stderr
|
||||
|
||||
// Setup readability handlers for raw data
|
||||
setupReadabilityHandler(for: outputHandle, isError: false)
|
||||
setupReadabilityHandler(for: errorHandle, isError: true)
|
||||
}
|
||||
|
||||
let shutdownLock = NSLock()
|
||||
|
||||
private func setupReadabilityHandler(for handle: FileHandle?, isError: Bool) {
|
||||
handle?.readabilityHandler = { [weak self] handle in
|
||||
let data = handle.availableData
|
||||
if !data.isEmpty {
|
||||
self?.writeQueue.async {
|
||||
try? self?.writeData(data)
|
||||
}
|
||||
|
||||
// Forward to original std stream
|
||||
if let originalFD = isError ? self?.originalStderr : self?.originalStdout {
|
||||
data.withUnsafeBytes { (bufferPointer) -> Void in
|
||||
if let baseAddress = bufferPointer.baseAddress, bufferPointer.count > 0 {
|
||||
write(originalFD, baseAddress, bufferPointer.count)
|
||||
}
|
||||
handle?.readabilityHandler = readHandler(isError: isError)
|
||||
}
|
||||
|
||||
private func readHandler(isError: Bool) -> (FileHandle) -> Void {
|
||||
return { [weak self] _ in
|
||||
// Lock first before touching anything
|
||||
self?.shutdownLock.lock()
|
||||
defer { self?.shutdownLock.unlock() }
|
||||
|
||||
// Capture strong self *after* lock is acquired
|
||||
guard let self = self else { return }
|
||||
|
||||
let handle = isError ? self.errorHandle : self.outputHandle
|
||||
guard let data = handle?.availableData else { return }
|
||||
|
||||
writeQueue.async {
|
||||
try? self.writeData(data)
|
||||
}
|
||||
|
||||
// 2. Echo to original stdout/stderr if still valid
|
||||
guard let fd = isError ? self.originalStderr : self.originalStdout else {
|
||||
return
|
||||
}
|
||||
|
||||
let appName = Bundle.main.object(forInfoDictionaryKey: "CFBundleName") as? String ?? "UnknownApp"
|
||||
guard fcntl(fd, F_GETFD) != -1 else {
|
||||
NSLog("[%@] ConsoleLogger: Original FD (%d) is invalid, skipping echo", appName, fd)
|
||||
return
|
||||
}
|
||||
|
||||
data.withUnsafeBytes { rawBufferPointer in
|
||||
guard let base = rawBufferPointer.baseAddress else { return }
|
||||
var remaining = data.count
|
||||
var offset = 0
|
||||
let maxChunkSize = 16 * 1024 // 16 KB chunks
|
||||
|
||||
// write in chunks, else will throw 'Result too large'
|
||||
while remaining > 0 {
|
||||
let chunkSize = min(maxChunkSize, remaining)
|
||||
let written = write(fd, base.advanced(by: offset), chunkSize)
|
||||
|
||||
if written < 0 {
|
||||
NSLog("[%@] ConsoleLogger: Failed to re-echo to FD %d: %s", appName, fd, strerror(errno))
|
||||
break
|
||||
}
|
||||
|
||||
remaining -= written
|
||||
offset += written
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
func writeData(_ data: Data) throws {
|
||||
throw AbstractClassError.abstractMethodInvoked
|
||||
}
|
||||
|
||||
func stopCapturing() {
|
||||
shutdownLock.lock()
|
||||
defer { shutdownLock.unlock() }
|
||||
|
||||
ostream.close()
|
||||
|
||||
// Restore original stdout and stderr
|
||||
if let stdout = originalStdout {
|
||||
if let stdout = originalStdout, stdout != STDOUT_FILENO {
|
||||
dup2(stdout, STDOUT_FILENO)
|
||||
close(stdout)
|
||||
}
|
||||
if let stderr = originalStderr {
|
||||
if let stderr = originalStderr, stderr != STDERR_FILENO {
|
||||
dup2(stderr, STDERR_FILENO)
|
||||
close(stderr)
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
Submodule SideStore/em_proxy deleted from 24fdd245e6
@@ -1,343 +0,0 @@
|
||||
// !$*UTF8*$!
|
||||
{
|
||||
archiveVersion = 1;
|
||||
classes = {
|
||||
};
|
||||
objectVersion = 53;
|
||||
objects = {
|
||||
|
||||
/* Begin PBXBuildFile section */
|
||||
9987603429A4555300818586 /* em_proxy.h in Sources */ = {isa = PBXBuildFile; fileRef = 9999259129A45319005CF020 /* em_proxy.h */; };
|
||||
/* End PBXBuildFile section */
|
||||
|
||||
/* Begin PBXBuildRule section */
|
||||
CA6094FFF692AC6C1400ACA8 /* PBXBuildRule */ = {
|
||||
isa = PBXBuildRule;
|
||||
compilerSpec = com.apple.compilers.proxy.script;
|
||||
filePatterns = "*/em_proxy.h";
|
||||
fileType = pattern.proxy;
|
||||
inputFiles = (
|
||||
);
|
||||
isEditable = 0;
|
||||
name = "Cargo project build";
|
||||
outputFiles = (
|
||||
"$(OBJECT_FILE_DIR)/$(CARGO_XCODE_TARGET_ARCH)-$(EXECUTABLE_NAME)",
|
||||
);
|
||||
script = "# generated with cargo-xcode 1.5.0\n# modified to use prebuilt binaries\n\nset -eu;\n\nBUILT_SRC=\"./em_proxy/$LIB_FILE_NAME.a\"\necho Generating Static lib: $BUILT_SRC\nln -f -- \"$BUILT_SRC\" \"$TARGET_BUILD_DIR/$EXECUTABLE_PATH\" || cp \"$BUILT_SRC\" \"$TARGET_BUILD_DIR/$EXECUTABLE_PATH\"\necho \"$BUILT_SRC -> $TARGET_BUILD_DIR/$EXECUTABLE_PATH\"\n\n# xcode generates dep file, but for its own path, so append our rename to it\n#DEP_FILE_SRC=\"minimuxer/target/${CARGO_XCODE_TARGET_TRIPLE}/release/${CARGO_XCODE_CARGO_DEP_FILE_NAME}\"\n#if [ -f \"$DEP_FILE_SRC\" ]; then\n# DEP_FILE_DST=\"${DERIVED_FILE_DIR}/${CARGO_XCODE_TARGET_ARCH}-${EXECUTABLE_NAME}.d\"\n# cp -f \"$DEP_FILE_SRC\" \"$DEP_FILE_DST\"\n# echo >> \"$DEP_FILE_DST\" \"$SCRIPT_OUTPUT_FILE_0: $BUILT_SRC\"\n#fi\n\n# lipo script needs to know all the platform-specific files that have been built\n# archs is in the file name, so that paths don't stay around after archs change\n# must match input for LipoScript\n#FILE_LIST=\"${DERIVED_FILE_DIR}/${ARCHS}-${EXECUTABLE_NAME}.xcfilelist\"\n#touch \"$FILE_LIST\"\n#if ! egrep -q \"$SCRIPT_OUTPUT_FILE_0\" \"$FILE_LIST\" ; then\n# echo >> \"$FILE_LIST\" \"$SCRIPT_OUTPUT_FILE_0\"\n#fi\n";
|
||||
};
|
||||
/* End PBXBuildRule section */
|
||||
|
||||
/* Begin PBXFileReference section */
|
||||
9999259129A45319005CF020 /* em_proxy.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = em_proxy.h; path = em_proxy/em_proxy.h; sourceTree = "<group>"; };
|
||||
ADDEDBA66A6E1 /* libresolv.tbd */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.text-based-dylib-definition"; name = libresolv.tbd; path = usr/lib/libresolv.tbd; sourceTree = SDKROOT; };
|
||||
CA60058A9FBE4D17AF51A7D5 /* run */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.executable"; includeInIndex = 0; path = run; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
CA60C44C93D7916DE57E6EBD /* libem_proxy_static.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libem_proxy_static.a; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
/* End PBXFileReference section */
|
||||
|
||||
/* Begin PBXGroup section */
|
||||
ADDEDBA66A6E2 /* Required for static linking */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
ADDEDBA66A6E1 /* libresolv.tbd */,
|
||||
);
|
||||
name = "Required for static linking";
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
CA6094FFF69222869D176AE5 /* Products */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
CA60C44C93D7916DE57E6EBD /* libem_proxy_static.a */,
|
||||
CA60058A9FBE4D17AF51A7D5 /* run */,
|
||||
);
|
||||
name = Products;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
CA6094FFF69298AF0B5890DB /* Frameworks */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
ADDEDBA66A6E2 /* Required for static linking */,
|
||||
);
|
||||
name = Frameworks;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
CA6094FFF692D65BC3C892A8 = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
9999259129A45319005CF020 /* em_proxy.h */,
|
||||
CA6094FFF69222869D176AE5 /* Products */,
|
||||
CA6094FFF69298AF0B5890DB /* Frameworks */,
|
||||
);
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
/* End PBXGroup section */
|
||||
|
||||
/* Begin PBXNativeTarget section */
|
||||
CA60058A9FBE37FC563E4BCC /* run-bin */ = {
|
||||
isa = PBXNativeTarget;
|
||||
buildConfigurationList = CA603DD75FB437FC563E4BCC /* Build configuration list for PBXNativeTarget "run-bin" */;
|
||||
buildPhases = (
|
||||
CA60445C303637FC563E4BCC /* Sources */,
|
||||
CA6094FFF692AF6EBB7F357C /* Universal Binary lipo */,
|
||||
);
|
||||
buildRules = (
|
||||
CA6094FFF692AC6C1400ACA8 /* PBXBuildRule */,
|
||||
);
|
||||
dependencies = (
|
||||
);
|
||||
name = "run-bin";
|
||||
productName = run;
|
||||
productReference = CA60058A9FBE4D17AF51A7D5 /* run */;
|
||||
productType = "com.apple.product-type.tool";
|
||||
};
|
||||
CA60C44C93D7A30E3695DD59 /* em_proxy-staticlib */ = {
|
||||
isa = PBXNativeTarget;
|
||||
buildConfigurationList = CA603DD75FB4A30E3695DD59 /* Build configuration list for PBXNativeTarget "em_proxy-staticlib" */;
|
||||
buildPhases = (
|
||||
9987603529A4610700818586 /* ShellScript */,
|
||||
CA60445C3036A30E3695DD59 /* Sources */,
|
||||
CA6094FFF692AF6EBB7F357C /* Universal Binary lipo */,
|
||||
);
|
||||
buildRules = (
|
||||
CA6094FFF692AC6C1400ACA8 /* PBXBuildRule */,
|
||||
);
|
||||
dependencies = (
|
||||
);
|
||||
name = "em_proxy-staticlib";
|
||||
productName = libem_proxy_static.a;
|
||||
productReference = CA60C44C93D7916DE57E6EBD /* libem_proxy_static.a */;
|
||||
productType = "com.apple.product-type.library.static";
|
||||
};
|
||||
/* End PBXNativeTarget section */
|
||||
|
||||
/* Begin PBXProject section */
|
||||
CA6094FFF692E04653AD465F /* Project object */ = {
|
||||
isa = PBXProject;
|
||||
attributes = {
|
||||
LastUpgradeCheck = 1300;
|
||||
TargetAttributes = {
|
||||
CA60058A9FBE37FC563E4BCC = {
|
||||
CreatedOnToolsVersion = 9.2;
|
||||
ProvisioningStyle = Automatic;
|
||||
};
|
||||
CA60C44C93D7A30E3695DD59 = {
|
||||
CreatedOnToolsVersion = 9.2;
|
||||
ProvisioningStyle = Automatic;
|
||||
};
|
||||
};
|
||||
};
|
||||
buildConfigurationList = CA6094FFF69280E02D6C7F57 /* Build configuration list for PBXProject "em_proxy" */;
|
||||
compatibilityVersion = "Xcode 11.4";
|
||||
developmentRegion = en;
|
||||
hasScannedForEncodings = 0;
|
||||
knownRegions = (
|
||||
en,
|
||||
Base,
|
||||
);
|
||||
mainGroup = CA6094FFF692D65BC3C892A8;
|
||||
productRefGroup = CA6094FFF69222869D176AE5 /* Products */;
|
||||
projectDirPath = "";
|
||||
projectRoot = "";
|
||||
targets = (
|
||||
CA60C44C93D7A30E3695DD59 /* em_proxy-staticlib */,
|
||||
CA60058A9FBE37FC563E4BCC /* run-bin */,
|
||||
);
|
||||
};
|
||||
/* End PBXProject section */
|
||||
|
||||
/* Begin PBXShellScriptBuildPhase section */
|
||||
9987603529A4610700818586 /* ShellScript */ = {
|
||||
isa = PBXShellScriptBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
);
|
||||
inputFileListPaths = (
|
||||
);
|
||||
inputPaths = (
|
||||
);
|
||||
outputFileListPaths = (
|
||||
);
|
||||
outputPaths = (
|
||||
./em_proxy/em_proxy.h,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
shellPath = /bin/sh;
|
||||
shellScript = "bash ./fetch-prebuilt.sh em_proxy\n";
|
||||
};
|
||||
CA6094FFF692AF6EBB7F357C /* Universal Binary lipo */ = {
|
||||
isa = PBXShellScriptBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
);
|
||||
inputFileListPaths = (
|
||||
);
|
||||
inputPaths = (
|
||||
"$(DERIVED_FILE_DIR)/$(ARCHS)-$(EXECUTABLE_NAME).xcfilelist",
|
||||
);
|
||||
name = "Universal Binary lipo";
|
||||
outputFileListPaths = (
|
||||
);
|
||||
outputPaths = (
|
||||
"$(TARGET_BUILD_DIR)/$(EXECUTABLE_PATH)",
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
shellPath = /bin/sh;
|
||||
shellScript = "# generated with cargo-xcode 1.5.0\n\n#set -eux; cat \"$DERIVED_FILE_DIR/$ARCHS-$EXECUTABLE_NAME.xcfilelist\" | tr '\\n' '\\0' | xargs -0 lipo -create -output \"$TARGET_BUILD_DIR/$EXECUTABLE_PATH\"\n#if [ ${LD_DYLIB_INSTALL_NAME:+1} ]; then\n# install_name_tool -id \"$LD_DYLIB_INSTALL_NAME\" \"$TARGET_BUILD_DIR/$EXECUTABLE_PATH\"\n#fi\n";
|
||||
};
|
||||
/* End PBXShellScriptBuildPhase section */
|
||||
|
||||
/* Begin PBXSourcesBuildPhase section */
|
||||
CA60445C303637FC563E4BCC /* Sources */ = {
|
||||
isa = PBXSourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
CA60445C3036A30E3695DD59 /* Sources */ = {
|
||||
isa = PBXSourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
9987603429A4555300818586 /* em_proxy.h in Sources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
/* End PBXSourcesBuildPhase section */
|
||||
|
||||
/* Begin XCBuildConfiguration section */
|
||||
CA604DFE779B37FC563E4BCC /* Release */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
CARGO_XCODE_CARGO_DEP_FILE_NAME = run.d;
|
||||
CARGO_XCODE_CARGO_FILE_NAME = run;
|
||||
PRODUCT_NAME = run;
|
||||
SUPPORTED_PLATFORMS = macosx;
|
||||
};
|
||||
name = Release;
|
||||
};
|
||||
CA604DFE779BA30E3695DD59 /* Release */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
CARGO_XCODE_CARGO_DEP_FILE_NAME = libem_proxy.d;
|
||||
CARGO_XCODE_CARGO_FILE_NAME = libem_proxy.a;
|
||||
INSTALL_GROUP = "";
|
||||
INSTALL_MODE_FLAG = "";
|
||||
INSTALL_OWNER = "";
|
||||
LIB_FILE_NAME = "";
|
||||
"LIB_FILE_NAME[sdk=iphoneos*]" = "libem_proxy-ios";
|
||||
"LIB_FILE_NAME[sdk=iphonesimulator*]" = "libem_proxy-sim";
|
||||
PRODUCT_NAME = em_proxy_static;
|
||||
SKIP_INSTALL = YES;
|
||||
SUPPORTED_PLATFORMS = "macosx iphonesimulator iphoneos appletvsimulator appletvos";
|
||||
};
|
||||
name = Release;
|
||||
};
|
||||
CA609A517351228BE02872F8 /* Debug */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
ALWAYS_SEARCH_USER_PATHS = NO;
|
||||
CARGO_TARGET_DIR = "$(PROJECT_TEMP_DIR)/cargo_target";
|
||||
CARGO_XCODE_BUILD_MODE = debug;
|
||||
CARGO_XCODE_FEATURES = "";
|
||||
"CARGO_XCODE_TARGET_ARCH[arch=arm64*]" = aarch64;
|
||||
"CARGO_XCODE_TARGET_ARCH[arch=i386]" = i686;
|
||||
"CARGO_XCODE_TARGET_ARCH[arch=x86_64*]" = x86_64;
|
||||
"CARGO_XCODE_TARGET_OS[sdk=appletvos*]" = tvos;
|
||||
"CARGO_XCODE_TARGET_OS[sdk=appletvsimulator*]" = tvos;
|
||||
"CARGO_XCODE_TARGET_OS[sdk=iphoneos*]" = ios;
|
||||
"CARGO_XCODE_TARGET_OS[sdk=iphonesimulator*]" = "ios-sim";
|
||||
"CARGO_XCODE_TARGET_OS[sdk=iphonesimulator*][arch=x86_64*]" = ios;
|
||||
"CARGO_XCODE_TARGET_OS[sdk=macosx*]" = darwin;
|
||||
CURRENT_PROJECT_VERSION = 0.1;
|
||||
MARKETING_VERSION = 0.1.0;
|
||||
ONLY_ACTIVE_ARCH = YES;
|
||||
PRODUCT_NAME = em_proxy;
|
||||
SDKROOT = macosx;
|
||||
SUPPORTS_MACCATALYST = YES;
|
||||
};
|
||||
name = Debug;
|
||||
};
|
||||
CA609A5173513CC16B37690B /* Release */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
ALWAYS_SEARCH_USER_PATHS = NO;
|
||||
CARGO_TARGET_DIR = "$(PROJECT_TEMP_DIR)/cargo_target";
|
||||
CARGO_XCODE_BUILD_MODE = release;
|
||||
CARGO_XCODE_FEATURES = "";
|
||||
"CARGO_XCODE_TARGET_ARCH[arch=arm64*]" = aarch64;
|
||||
"CARGO_XCODE_TARGET_ARCH[arch=i386]" = i686;
|
||||
"CARGO_XCODE_TARGET_ARCH[arch=x86_64*]" = x86_64;
|
||||
"CARGO_XCODE_TARGET_OS[sdk=appletvos*]" = tvos;
|
||||
"CARGO_XCODE_TARGET_OS[sdk=appletvsimulator*]" = tvos;
|
||||
"CARGO_XCODE_TARGET_OS[sdk=iphoneos*]" = ios;
|
||||
"CARGO_XCODE_TARGET_OS[sdk=iphonesimulator*]" = "ios-sim";
|
||||
"CARGO_XCODE_TARGET_OS[sdk=iphonesimulator*][arch=x86_64*]" = ios;
|
||||
"CARGO_XCODE_TARGET_OS[sdk=macosx*]" = darwin;
|
||||
CURRENT_PROJECT_VERSION = 0.1;
|
||||
MARKETING_VERSION = 0.1.0;
|
||||
PRODUCT_NAME = em_proxy;
|
||||
SDKROOT = macosx;
|
||||
SUPPORTS_MACCATALYST = YES;
|
||||
};
|
||||
name = Release;
|
||||
};
|
||||
CA60DE07A83F37FC563E4BCC /* Debug */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
CARGO_XCODE_CARGO_DEP_FILE_NAME = run.d;
|
||||
CARGO_XCODE_CARGO_FILE_NAME = run;
|
||||
PRODUCT_NAME = run;
|
||||
SUPPORTED_PLATFORMS = macosx;
|
||||
};
|
||||
name = Debug;
|
||||
};
|
||||
CA60DE07A83FA30E3695DD59 /* Debug */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
CARGO_XCODE_CARGO_DEP_FILE_NAME = libem_proxy.d;
|
||||
CARGO_XCODE_CARGO_FILE_NAME = libem_proxy.a;
|
||||
INSTALL_GROUP = "";
|
||||
INSTALL_MODE_FLAG = "";
|
||||
INSTALL_OWNER = "";
|
||||
LIB_FILE_NAME = "";
|
||||
"LIB_FILE_NAME[sdk=iphoneos*]" = "libem_proxy-ios";
|
||||
"LIB_FILE_NAME[sdk=iphonesimulator*]" = "libem_proxy-sim";
|
||||
PRODUCT_NAME = em_proxy_static;
|
||||
SKIP_INSTALL = YES;
|
||||
SUPPORTED_PLATFORMS = "macosx iphonesimulator iphoneos appletvsimulator appletvos";
|
||||
};
|
||||
name = Debug;
|
||||
};
|
||||
/* End XCBuildConfiguration section */
|
||||
|
||||
/* Begin XCConfigurationList section */
|
||||
CA603DD75FB437FC563E4BCC /* Build configuration list for PBXNativeTarget "run-bin" */ = {
|
||||
isa = XCConfigurationList;
|
||||
buildConfigurations = (
|
||||
CA604DFE779B37FC563E4BCC /* Release */,
|
||||
CA60DE07A83F37FC563E4BCC /* Debug */,
|
||||
);
|
||||
defaultConfigurationIsVisible = 0;
|
||||
defaultConfigurationName = Release;
|
||||
};
|
||||
CA603DD75FB4A30E3695DD59 /* Build configuration list for PBXNativeTarget "em_proxy-staticlib" */ = {
|
||||
isa = XCConfigurationList;
|
||||
buildConfigurations = (
|
||||
CA604DFE779BA30E3695DD59 /* Release */,
|
||||
CA60DE07A83FA30E3695DD59 /* Debug */,
|
||||
);
|
||||
defaultConfigurationIsVisible = 0;
|
||||
defaultConfigurationName = Release;
|
||||
};
|
||||
CA6094FFF69280E02D6C7F57 /* Build configuration list for PBXProject "em_proxy" */ = {
|
||||
isa = XCConfigurationList;
|
||||
buildConfigurations = (
|
||||
CA609A5173513CC16B37690B /* Release */,
|
||||
CA609A517351228BE02872F8 /* Debug */,
|
||||
);
|
||||
defaultConfigurationIsVisible = 0;
|
||||
defaultConfigurationName = Release;
|
||||
};
|
||||
/* End XCConfigurationList section */
|
||||
};
|
||||
rootObject = CA6094FFF692E04653AD465F /* Project object */;
|
||||
}
|
||||
Submodule SideStore/minimuxer updated: baa22acbff...6ae94902a9
381
update_release_notes.py
Normal file
381
update_release_notes.py
Normal file
@@ -0,0 +1,381 @@
|
||||
#!/usr/bin/env python3
|
||||
import subprocess
|
||||
import sys
|
||||
import os
|
||||
import re
|
||||
|
||||
IGNORED_AUTHORS = [
|
||||
|
||||
]
|
||||
|
||||
TAG_MARKER = "###"
|
||||
HEADER_MARKER = "####"
|
||||
|
||||
def run_command(cmd):
|
||||
"""Run a shell command and return its trimmed output."""
|
||||
return subprocess.check_output(cmd, shell=True, text=True).strip()
|
||||
|
||||
def get_head_commit():
|
||||
"""Return the HEAD commit SHA."""
|
||||
return run_command("git rev-parse HEAD")
|
||||
|
||||
def get_commit_messages(last_successful, current="HEAD"):
|
||||
"""Return a list of commit messages between last_successful and current."""
|
||||
cmd = f"git log {last_successful}..{current} --pretty=format:%s"
|
||||
output = run_command(cmd)
|
||||
if not output:
|
||||
return []
|
||||
return output.splitlines()
|
||||
|
||||
def get_authors_in_range(commit_range, fmt="%an"):
|
||||
"""Return a set of commit authors in the given commit range using the given format."""
|
||||
cmd = f"git log {commit_range} --pretty=format:{fmt}"
|
||||
output = run_command(cmd)
|
||||
if not output:
|
||||
return set()
|
||||
authors = set(line.strip() for line in output.splitlines() if line.strip())
|
||||
authors = set(authors) - set(IGNORED_AUTHORS)
|
||||
return authors
|
||||
|
||||
def get_first_commit_of_repo():
|
||||
"""Return the first commit in the repository (root commit)."""
|
||||
cmd = "git rev-list --max-parents=0 HEAD"
|
||||
output = run_command(cmd)
|
||||
return output.splitlines()[0]
|
||||
|
||||
def get_branch():
|
||||
"""
|
||||
Attempt to determine the branch base (the commit where the current branch diverged
|
||||
from the default remote branch). Falls back to the repo's first commit.
|
||||
"""
|
||||
try:
|
||||
default_ref = run_command("git rev-parse --abbrev-ref origin/HEAD")
|
||||
default_branch = default_ref.split('/')[-1]
|
||||
base_commit = run_command(f"git merge-base HEAD origin/{default_branch}")
|
||||
return base_commit
|
||||
except Exception:
|
||||
return get_first_commit_of_repo()
|
||||
|
||||
def get_repo_url():
|
||||
"""Extract and clean the repository URL from the remote 'origin'."""
|
||||
url = run_command("git config --get remote.origin.url")
|
||||
if url.startswith("git@"):
|
||||
url = url.replace("git@", "https://").replace(":", "/")
|
||||
if url.endswith(".git"):
|
||||
url = url[:-4]
|
||||
return url
|
||||
|
||||
def format_contributor(author):
|
||||
"""
|
||||
Convert an author name to a GitHub username or first name.
|
||||
If the author already starts with '@', return it;
|
||||
otherwise, take the first token and prepend '@'.
|
||||
"""
|
||||
if author.startswith('@'):
|
||||
return author
|
||||
return f"@{author.split()[0]}"
|
||||
|
||||
def format_commit_message(msg):
|
||||
"""Format a commit message as a bullet point for the release notes."""
|
||||
msg_clean = msg.lstrip() # remove leading spaces
|
||||
if msg_clean.startswith("-"):
|
||||
msg_clean = msg_clean[1:].strip() # remove leading '-' and spaces
|
||||
return f"- {msg_clean}"
|
||||
|
||||
# def generate_release_notes(last_successful, tag, branch):
|
||||
"""Generate release notes for the given tag."""
|
||||
current_commit = get_head_commit()
|
||||
messages = get_commit_messages(last_successful, current_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 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):
|
||||
"""
|
||||
Update input based on rules:
|
||||
1. If tag exists, update it
|
||||
2. Special tags (alpha, beta, nightly) stay at the top in that order
|
||||
3. Numbered tags follow special tags
|
||||
4. Remove duplicate tags
|
||||
5. Insert new numbered tags at the top of the numbered section
|
||||
"""
|
||||
tag_lower = tag.lower()
|
||||
is_special_tag = tag_lower in ["alpha", "beta", "nightly"]
|
||||
|
||||
# Parse the existing content into sections
|
||||
if not existing_content:
|
||||
return new_section
|
||||
|
||||
# Split the content into sections by headers
|
||||
pattern = fr'(^{TAG_MARKER} .*$)'
|
||||
sections = re.split(pattern, existing_content, flags=re.MULTILINE)
|
||||
|
||||
# Create a list to store the processed content
|
||||
processed_sections = []
|
||||
|
||||
# Track special tag positions and whether tag was found
|
||||
special_tags_map = {"alpha": False, "beta": False, "nightly": False}
|
||||
last_special_index = -1
|
||||
tag_found = False
|
||||
numbered_tag_index = -1
|
||||
|
||||
i = 0
|
||||
while i < len(sections):
|
||||
# Check if this is a header
|
||||
if i % 2 == 1: # Headers are at odd indices
|
||||
header = sections[i]
|
||||
content = sections[i+1] if i+1 < len(sections) else ""
|
||||
current_tag = header[3:].strip().lower()
|
||||
|
||||
# Check for special tags to track their positions
|
||||
if current_tag in special_tags_map:
|
||||
special_tags_map[current_tag] = True
|
||||
last_special_index = len(processed_sections)
|
||||
|
||||
# Check if this is the first numbered tag
|
||||
elif re.match(r'^[0-9]+\.[0-9]+(\.[0-9]+)?$', current_tag) and numbered_tag_index == -1:
|
||||
numbered_tag_index = len(processed_sections)
|
||||
|
||||
# If this is the tag we're updating, mark it but don't add yet
|
||||
if current_tag == tag_lower:
|
||||
if not tag_found: # Replace the first occurrence
|
||||
tag_found = True
|
||||
i += 2 # Skip the content
|
||||
continue
|
||||
else: # Skip duplicate occurrences
|
||||
i += 2
|
||||
continue
|
||||
|
||||
# Add the current section
|
||||
processed_sections.append(sections[i])
|
||||
i += 1
|
||||
|
||||
# Determine where to insert the new section
|
||||
if tag_found:
|
||||
# We need to determine the insertion point
|
||||
if is_special_tag:
|
||||
# For special tags, insert after last special tag or at beginning
|
||||
desired_index = -1
|
||||
for pos, t in enumerate(["alpha", "beta", "nightly"]):
|
||||
if t == tag_lower:
|
||||
desired_index = pos
|
||||
|
||||
# Find position to insert
|
||||
insert_pos = 0
|
||||
for pos, t in enumerate(["alpha", "beta", "nightly"]):
|
||||
if t == tag_lower:
|
||||
break
|
||||
if special_tags_map[t]:
|
||||
insert_pos = processed_sections.index(f"{TAG_MARKER} {t}")
|
||||
insert_pos += 2 # Move past the header and content
|
||||
|
||||
# Insert at the determined position
|
||||
processed_sections.insert(insert_pos, new_section)
|
||||
if insert_pos > 0 and not processed_sections[insert_pos-1].endswith('\n\n'):
|
||||
processed_sections.insert(insert_pos, '\n\n')
|
||||
else:
|
||||
# For numbered tags, insert after special tags but before other numbered tags
|
||||
insert_pos = 0
|
||||
|
||||
if last_special_index >= 0:
|
||||
# Insert after the last special tag
|
||||
insert_pos = last_special_index + 2 # +2 to skip header and content
|
||||
|
||||
processed_sections.insert(insert_pos, new_section)
|
||||
if insert_pos > 0 and not processed_sections[insert_pos-1].endswith('\n\n'):
|
||||
processed_sections.insert(insert_pos, '\n\n')
|
||||
else:
|
||||
# Tag doesn't exist yet, determine insertion point
|
||||
if is_special_tag:
|
||||
# For special tags, maintain alpha, beta, nightly order
|
||||
special_tags = ["alpha", "beta", "nightly"]
|
||||
insert_pos = 0
|
||||
|
||||
for i, t in enumerate(special_tags):
|
||||
if t == tag_lower:
|
||||
# Check if preceding special tags exist
|
||||
for prev_tag in special_tags[:i]:
|
||||
if special_tags_map[prev_tag]:
|
||||
# Find the position after this tag
|
||||
prev_index = processed_sections.index(f"{TAG_MARKER} {prev_tag}")
|
||||
insert_pos = prev_index + 2 # Skip header and content
|
||||
|
||||
processed_sections.insert(insert_pos, new_section)
|
||||
if insert_pos > 0 and not processed_sections[insert_pos-1].endswith('\n\n'):
|
||||
processed_sections.insert(insert_pos, '\n\n')
|
||||
else:
|
||||
# For numbered tags, insert after special tags but before other numbered tags
|
||||
insert_pos = 0
|
||||
|
||||
if last_special_index >= 0:
|
||||
# Insert after the last special tag
|
||||
insert_pos = last_special_index + 2 # +2 to skip header and content
|
||||
|
||||
processed_sections.insert(insert_pos, new_section)
|
||||
if insert_pos > 0 and not processed_sections[insert_pos-1].endswith('\n\n'):
|
||||
processed_sections.insert(insert_pos, '\n\n')
|
||||
|
||||
# Combine sections ensuring proper spacing
|
||||
result = ""
|
||||
for i, section in enumerate(processed_sections):
|
||||
if i > 0 and section.startswith(f"{TAG_MARKER} "):
|
||||
# Ensure single blank line before headers
|
||||
if not result.endswith("\n\n"):
|
||||
result = result.rstrip("\n") + "\n\n"
|
||||
result += section
|
||||
|
||||
return result.rstrip() + "\n"
|
||||
|
||||
|
||||
def retrieve_tag_content(tag, file_path):
|
||||
if not os.path.exists(file_path):
|
||||
return ""
|
||||
|
||||
with open(file_path, "r") as f:
|
||||
content = f.read()
|
||||
|
||||
# Create a pattern for the tag header (case-insensitive)
|
||||
pattern = re.compile(fr'^{TAG_MARKER} ' + re.escape(tag) + r'$', re.MULTILINE | re.IGNORECASE)
|
||||
|
||||
# Find the tag header
|
||||
match = pattern.search(content)
|
||||
if not match:
|
||||
return ""
|
||||
|
||||
# Start after the tag line
|
||||
start_pos = match.end()
|
||||
|
||||
# Skip a newline if present
|
||||
if start_pos < len(content) and content[start_pos] == "\n":
|
||||
start_pos += 1
|
||||
|
||||
# Find the next tag header after the current tag's content
|
||||
next_tag_match = re.search(fr'^{TAG_MARKER} ', content[start_pos:], re.MULTILINE)
|
||||
|
||||
if next_tag_match:
|
||||
end_pos = start_pos + next_tag_match.start()
|
||||
return content[start_pos:end_pos].strip()
|
||||
else:
|
||||
# Return until the end of the file if this is the last tag
|
||||
return content[start_pos:].strip()
|
||||
|
||||
def main():
|
||||
# Update input file
|
||||
release_file = "release-notes.md"
|
||||
|
||||
# Usage: python release.py <last_successful_commit> [tag] [branch]
|
||||
# Or: python release.py --retrieve <tagname>
|
||||
args = sys.argv[1:]
|
||||
|
||||
if len(args) < 1:
|
||||
print("Usage: python release.py <last_successful_commit> [tag] [branch]")
|
||||
print(" or: python release.py --retrieve <tagname>")
|
||||
sys.exit(1)
|
||||
|
||||
# Check if we're retrieving a tag
|
||||
if args[0] == "--retrieve":
|
||||
if len(args) < 2:
|
||||
print("Error: Missing tag name after --retrieve")
|
||||
sys.exit(1)
|
||||
|
||||
tag_content = retrieve_tag_content(args[1], file_path=release_file)
|
||||
if tag_content:
|
||||
print(tag_content)
|
||||
else:
|
||||
print(f"Tag '{args[1]}' not found in '{release_file}'")
|
||||
return
|
||||
|
||||
# Original functionality for generating release notes
|
||||
last_successful = args[0]
|
||||
tag = args[1] if len(args) > 1 else get_head_commit()
|
||||
branch = args[2] if len(args) > 2 else (os.environ.get("GITHUB_REF") or get_branch())
|
||||
|
||||
# Generate release notes
|
||||
new_section = generate_release_notes(last_successful, tag, branch)
|
||||
|
||||
existing_content = ""
|
||||
if os.path.exists(release_file):
|
||||
with open(release_file, "r") as f:
|
||||
existing_content = f.read()
|
||||
|
||||
updated_content = update_release_md(existing_content, new_section, tag)
|
||||
|
||||
with open(release_file, "w") as f:
|
||||
f.write(updated_content)
|
||||
|
||||
# Output the new section for display
|
||||
print(new_section)
|
||||
|
||||
if __name__ == "__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