├── .circleci
└── config.yml
├── .github
└── workflows
│ ├── add_identifiers.yml
│ ├── build_loop.yml
│ ├── create_certs.yml
│ └── validate_secrets.yml
├── .gitignore
├── .gitmodules
├── Gemfile
├── Gemfile.lock
├── InfoCustomizations.txt
├── LoopConfigOverride.xcconfig
├── LoopWorkspace.xcworkspace
├── contents.xcworkspacedata
└── xcshareddata
│ ├── IDEWorkspaceChecks.plist
│ ├── WorkspaceSettings.xcsettings
│ ├── swiftpm
│ └── Package.resolved
│ └── xcschemes
│ └── LoopWorkspace.xcscheme
├── OverrideAssetsLoop.xcassets
├── AppIcon.appiconset
│ ├── Contents.json
│ ├── Icon.png
│ ├── icon_20pt.png
│ ├── icon_20pt@2x-1.png
│ ├── icon_20pt@2x.png
│ ├── icon_20pt@3x.png
│ ├── icon_29pt.png
│ ├── icon_29pt@2x-1.png
│ ├── icon_29pt@2x.png
│ ├── icon_29pt@3x.png
│ ├── icon_40pt.png
│ ├── icon_40pt@2x-1.png
│ ├── icon_40pt@2x.png
│ ├── icon_40pt@3x.png
│ ├── icon_60pt@2x.png
│ ├── icon_60pt@3x.png
│ ├── icon_76pt.png
│ ├── icon_76pt@2x.png
│ └── icon_83.5@2x.png
└── Contents.json
├── OverrideAssetsWatchApp.xcassets
├── AppIcon.appiconset
│ ├── Contents.json
│ ├── Icon.png
│ ├── icon_108pt@2x.png
│ ├── icon_24pt@2x.png
│ ├── icon_27.5pt@2x.png
│ ├── icon_29pt@2x.png
│ ├── icon_29pt@3x.png
│ ├── icon_40pt@2x.png
│ ├── icon_44pt@2x.png
│ ├── icon_50pt@2x.png
│ ├── icon_86pt@2x.png
│ └── icon_98pt@2x.png
└── Contents.json
├── README.md
├── Scripts
├── export_localizations.sh
├── import_localizations.sh
├── sync.swift
└── update_submodule_refs.sh
├── VersionOverride.xcconfig
├── docs
└── scheme-selection.png
├── fastlane
├── Fastfile
├── Matchfile
└── testflight.md
└── patches
└── save_patches_here.md
/.circleci/config.yml:
--------------------------------------------------------------------------------
1 | jobs:
2 | build_and_test:
3 | macos:
4 | xcode: 15.4.0
5 | steps:
6 | - checkout
7 | - run:
8 | name: Checkout submodules
9 | command: git submodule update --init --recursive --depth 1
10 | - run:
11 | name: Build Loop
12 | command: set -o pipefail && time xcodebuild -workspace LoopWorkspace.xcworkspace -scheme 'LoopWorkspace' -destination 'platform=iOS Simulator,name=iPhone 15,OS=17.5' build | xcpretty
13 | - run:
14 | name: Run Tests
15 | command: set -o pipefail && time xcodebuild -workspace LoopWorkspace.xcworkspace -scheme 'LoopWorkspace' -destination 'platform=iOS Simulator,name=iPhone 15,OS=17.5' test | xcpretty
16 | workflows:
17 | version: 2
18 | build_and_test:
19 | jobs:
20 | - build_and_test
21 |
--------------------------------------------------------------------------------
/.github/workflows/add_identifiers.yml:
--------------------------------------------------------------------------------
1 | name: 2. Add Identifiers
2 | run-name: Add Identifiers (${{ github.ref_name }})
3 | on:
4 | workflow_dispatch:
5 |
6 | jobs:
7 | validate:
8 | name: Validate
9 | uses: ./.github/workflows/validate_secrets.yml
10 | secrets: inherit
11 |
12 | identifiers:
13 | name: Add Identifiers
14 | needs: validate
15 | runs-on: macos-15
16 | steps:
17 | # Checks-out the repo
18 | - name: Checkout Repo
19 | uses: actions/checkout@v4
20 |
21 | # Patch Fastlane Match to not print tables
22 | - name: Patch Match Tables
23 | run: |
24 | TABLE_PRINTER_PATH=$(ruby -e 'puts Gem::Specification.find_by_name("fastlane").gem_dir')/match/lib/match/table_printer.rb
25 | if [ -f "$TABLE_PRINTER_PATH" ]; then
26 | sed -i "" "/puts(Terminal::Table.new(params))/d" "$TABLE_PRINTER_PATH"
27 | else
28 | echo "table_printer.rb not found"
29 | exit 1
30 | fi
31 |
32 | # Install project dependencies
33 | - name: Install Project Dependencies
34 | run: bundle install
35 |
36 | # Sync the GitHub runner clock with the Windows time server (workaround as suggested in https://github.com/actions/runner/issues/2996)
37 | - name: Sync clock
38 | run: sudo sntp -sS time.windows.com
39 |
40 | # Create or update identifiers for app
41 | - name: Fastlane Provision
42 | run: bundle exec fastlane identifiers
43 | env:
44 | TEAMID: ${{ secrets.TEAMID }}
45 | GH_PAT: ${{ secrets.GH_PAT }}
46 | MATCH_PASSWORD: ${{ secrets.MATCH_PASSWORD }}
47 | FASTLANE_KEY_ID: ${{ secrets.FASTLANE_KEY_ID }}
48 | FASTLANE_ISSUER_ID: ${{ secrets.FASTLANE_ISSUER_ID }}
49 | FASTLANE_KEY: ${{ secrets.FASTLANE_KEY }}
50 |
--------------------------------------------------------------------------------
/.github/workflows/build_loop.yml:
--------------------------------------------------------------------------------
1 | name: 4. Build Loop
2 | run-name: Build Loop (${{ github.ref_name }})
3 | on:
4 | workflow_dispatch:
5 |
6 | ## Remove the "#" sign from the beginning of the line below to get automated builds on push (code changes in your repository)
7 | #push:
8 |
9 | # Automated builds now include automatic certificate update - the nuke certs part of that process could
10 | # affect other OS apps if run simultaneously.
11 | # Each OS needs a time of day distinct from other apps, LoopWorkspace uses 9 every Wed and 7 every 1st of month
12 | schedule:
13 | - cron: "0 9 * * 3" # Checks for updates at 09:00 UTC every Wednesday
14 | - cron: "0 7 1 * *" # Builds the app on the 1st of every month at 07:00 UTC
15 |
16 | env:
17 | UPSTREAM_REPO: LoopKit/LoopWorkspace
18 | UPSTREAM_BRANCH: ${{ github.ref_name }} # branch on upstream repository to sync from (replace with specific branch name if needed)
19 | TARGET_BRANCH: ${{ github.ref_name }} # target branch on fork to be kept in sync, and target branch on upstream to be kept alive (replace with specific branch name if needed)
20 | ALIVE_BRANCH_MAIN: alive-main
21 | ALIVE_BRANCH_DEV: alive-dev
22 |
23 | jobs:
24 | # Checks if Distribution certificate is present and valid, optionally nukes and
25 | # creates new certs if the repository variable ENABLE_NUKE_CERTS == 'true'
26 | check_certs:
27 | name: Check certificates
28 | uses: ./.github/workflows/create_certs.yml
29 | secrets: inherit
30 |
31 | # Checks if GH_PAT holds workflow permissions
32 | # Checks for existence of alive branch; if non-existent creates it
33 | check_alive_and_permissions:
34 | needs: check_certs
35 | runs-on: ubuntu-latest
36 | name: Check alive branch and permissions
37 | permissions:
38 | contents: write
39 | outputs:
40 | WORKFLOW_PERMISSION: ${{ steps.workflow-permission.outputs.has_permission }}
41 |
42 | steps:
43 | - name: Check for workflow permissions
44 | id: workflow-permission
45 | env:
46 | TOKEN_TO_CHECK: ${{ secrets.GH_PAT }}
47 | run: |
48 | PERMISSIONS=$(curl -sS -f -I -H "Authorization: token ${{ env.TOKEN_TO_CHECK }}" https://api.github.com | grep ^x-oauth-scopes: | cut -d' ' -f2-);
49 |
50 | if [[ $PERMISSIONS =~ "workflow" || $PERMISSIONS == "" ]]; then
51 | echo "GH_PAT holds workflow permissions or is fine-grained PAT."
52 | echo "has_permission=true" >> $GITHUB_OUTPUT # Set WORKFLOW_PERMISSION to false.
53 | else
54 | echo "GH_PAT lacks workflow permissions."
55 | echo "Automated build features will be skipped!"
56 | echo "has_permission=false" >> $GITHUB_OUTPUT # Set WORKFLOW_PERMISSION to false.
57 | fi
58 |
59 | - name: Check for alive branches
60 | if: steps.workflow-permission.outputs.has_permission == 'true'
61 | env:
62 | GITHUB_TOKEN: ${{ secrets.GH_PAT }}
63 | run: |
64 | if [[ $(gh api -H "Accept: application/vnd.github+json" /repos/${{ github.repository_owner }}/LoopWorkspace/branches | jq --raw-output '[.[] | select(.name == "alive-main" or .name == "alive-dev")] | length > 0') == "true" ]]; then
65 | echo "Branches 'alive-main' or 'alive-dev' exist."
66 | echo "ALIVE_BRANCH_EXISTS=true" >> $GITHUB_ENV
67 | else
68 | echo "Branches 'alive-main' and 'alive-dev' do not exist."
69 | echo "ALIVE_BRANCH_EXISTS=false" >> $GITHUB_ENV
70 | fi
71 |
72 | - name: Create alive branches
73 | if: env.ALIVE_BRANCH_EXISTS == 'false'
74 | env:
75 | GITHUB_TOKEN: ${{ secrets.GH_PAT }}
76 | run: |
77 | # Get ref for LoopKit/LoopWorkspace:main
78 | SHA_MAIN=$(curl -sS -H "Authorization: token $GITHUB_TOKEN" https://api.github.com/repos/${{ env.UPSTREAM_REPO }}/git/refs/heads/main | jq -r '.object.sha')
79 |
80 | # Get ref for LoopKit/LoopWorkspace:dev
81 | SHA_DEV=$(curl -sS -H "Authorization: token $GITHUB_TOKEN" https://api.github.com/repos/${{ env.UPSTREAM_REPO }}/git/refs/heads/dev | jq -r '.object.sha')
82 |
83 | # Create alive-main branch based on LoopKit/LoopWorkspace:main
84 | gh api \
85 | --method POST \
86 | -H "Authorization: token $GITHUB_TOKEN" \
87 | -H "Accept: application/vnd.github.v3+json" \
88 | /repos/${{ github.repository_owner }}/LoopWorkspace/git/refs \
89 | -f ref='refs/heads/alive-main' \
90 | -f sha=$SHA_MAIN
91 |
92 | # Create alive-dev branch based on LoopKit/LoopWorkspace:dev
93 | gh api \
94 | --method POST \
95 | -H "Authorization: token $GITHUB_TOKEN" \
96 | -H "Accept: application/vnd.github.v3+json" \
97 | /repos/${{ github.repository_owner }}/LoopWorkspace/git/refs \
98 | -f ref='refs/heads/alive-dev' \
99 | -f sha=$SHA_DEV
100 |
101 | # Checks for changes in upstream repository; if changes exist prompts sync for build
102 | # Performs keepalive to avoid stale fork
103 | check_latest_from_upstream:
104 | needs: [check_certs, check_alive_and_permissions]
105 | runs-on: ubuntu-latest
106 | name: Check upstream and keep alive
107 | outputs:
108 | NEW_COMMITS: ${{ steps.sync.outputs.has_new_commits }}
109 | ABORT_SYNC: ${{ steps.check_branch.outputs.ABORT_SYNC }}
110 |
111 | steps:
112 | - name: Check if running on main or dev branch
113 | if: |
114 | needs.check_alive_and_permissions.outputs.WORKFLOW_PERMISSION == 'true' &&
115 | (vars.SCHEDULED_BUILD != 'false' || vars.SCHEDULED_SYNC != 'false')
116 | id: check_branch
117 | run: |
118 | if [ "${GITHUB_REF##*/}" = "main" ]; then
119 | echo "Running on main branch"
120 | echo "ALIVE_BRANCH=${ALIVE_BRANCH_MAIN}" >> $GITHUB_OUTPUT
121 | echo "ABORT_SYNC=false" >> $GITHUB_OUTPUT
122 | elif [ "${GITHUB_REF##*/}" = "dev" ]; then
123 | echo "Running on dev branch"
124 | echo "ALIVE_BRANCH=${ALIVE_BRANCH_DEV}" >> $GITHUB_OUTPUT
125 | echo "ABORT_SYNC=false" >> $GITHUB_OUTPUT
126 | else
127 | echo "Not running on main or dev branch"
128 | echo "ABORT_SYNC=true" >> $GITHUB_OUTPUT
129 | fi
130 |
131 | - name: Checkout target repo
132 | if: |
133 | needs.check_alive_and_permissions.outputs.WORKFLOW_PERMISSION == 'true' &&
134 | (vars.SCHEDULED_BUILD != 'false' || vars.SCHEDULED_SYNC != 'false')
135 | uses: actions/checkout@v4
136 | with:
137 | token: ${{ secrets.GH_PAT }}
138 | ref: ${{ steps.check_branch.outputs.ALIVE_BRANCH }}
139 |
140 | - name: Sync upstream changes
141 | if: | # do not run the upstream sync action on the upstream repository
142 | needs.check_alive_and_permissions.outputs.WORKFLOW_PERMISSION == 'true' &&
143 | vars.SCHEDULED_SYNC != 'false' && github.repository_owner != 'LoopKit' && steps.check_branch.outputs.ABORT_SYNC == 'false'
144 | id: sync
145 | uses: aormsby/Fork-Sync-With-Upstream-action@v3.4.1
146 | with:
147 | target_sync_branch: ${{ steps.check_branch.outputs.ALIVE_BRANCH }}
148 | shallow_since: 6 months ago
149 | target_repo_token: ${{ secrets.GH_PAT }}
150 | upstream_sync_branch: ${{ env.UPSTREAM_BRANCH }}
151 | upstream_sync_repo: ${{ env.UPSTREAM_REPO }}
152 |
153 | # Display a sample message based on the sync output var 'has_new_commits'
154 | - name: New commits found
155 | if: |
156 | needs.check_alive_and_permissions.outputs.WORKFLOW_PERMISSION == 'true' &&
157 | vars.SCHEDULED_SYNC != 'false' && steps.sync.outputs.has_new_commits == 'true'
158 | run: echo "New commits were found to sync."
159 |
160 | - name: No new commits
161 | if: |
162 | needs.check_alive_and_permissions.outputs.WORKFLOW_PERMISSION == 'true' &&
163 | vars.SCHEDULED_SYNC != 'false' && steps.sync.outputs.has_new_commits == 'false'
164 | run: echo "There were no new commits."
165 |
166 | - name: Show value of 'has_new_commits'
167 | if: needs.check_alive_and_permissions.outputs.WORKFLOW_PERMISSION == 'true' && vars.SCHEDULED_SYNC != 'false' && steps.check_branch.outputs.ABORT_SYNC == 'false'
168 | run: |
169 | echo ${{ steps.sync.outputs.has_new_commits }}
170 | echo "NEW_COMMITS=${{ steps.sync.outputs.has_new_commits }}" >> $GITHUB_OUTPUT
171 |
172 | # Keep repository "alive": add empty commits to ALIVE_BRANCH after "time_elapsed" days of inactivity to avoid inactivation of scheduled workflows
173 | - name: Keep alive
174 | run: |
175 | echo "Keep Alive is no longer available"
176 | # if: |
177 | # needs.check_alive_and_permissions.outputs.WORKFLOW_PERMISSION == 'true' &&
178 | # (vars.SCHEDULED_BUILD != 'false' || vars.SCHEDULED_SYNC != 'false')
179 | # uses: gautamkrishnar/keepalive-workflow@v1 # using the workflow with default settings
180 | # with:
181 | # time_elapsed: 20 # Time elapsed from the previous commit to trigger a new automated commit (in days)
182 |
183 | - name: Show scheduled build configuration message
184 | if: needs.check_alive_and_permissions.outputs.WORKFLOW_PERMISSION != 'true'
185 | run: |
186 | echo "### :calendar: Scheduled Sync and Build Disabled :mobile_phone_off:" >> $GITHUB_STEP_SUMMARY
187 | echo "You have not yet configured the scheduled sync and build for Loop's browser build." >> $GITHUB_STEP_SUMMARY
188 | echo "Synchronizing your fork of LoopWorkspace
with the upstream repository LoopKit/LoopWorkspace
will be skipped." >> $GITHUB_STEP_SUMMARY
189 | echo "If you want to enable automatic builds and updates for your Loop, please follow the instructions \
190 | under the following path LoopWorkspace/fastlane/testflight.md
." >> $GITHUB_STEP_SUMMARY
191 |
192 | # Builds Loop
193 | build:
194 | name: Build
195 | needs: [check_certs, check_alive_and_permissions, check_latest_from_upstream]
196 | runs-on: macos-15
197 | permissions:
198 | contents: write
199 | if:
200 | | # runs if started manually, or if sync schedule is set and enabled and scheduled on the first Saturday each month, or if sync schedule is set and enabled and new commits were found
201 | github.event_name == 'workflow_dispatch' ||
202 | (needs.check_alive_and_permissions.outputs.WORKFLOW_PERMISSION == 'true' &&
203 | (vars.SCHEDULED_BUILD != 'false' && github.event.schedule == '0 7 1 * *') ||
204 | (vars.SCHEDULED_SYNC != 'false' && needs.check_latest_from_upstream.outputs.NEW_COMMITS == 'true' )
205 | )
206 | steps:
207 | - name: Select Xcode version
208 | run: "sudo xcode-select --switch /Applications/Xcode_16.3.app/Contents/Developer"
209 |
210 | - name: Checkout Repo for syncing
211 | if: |
212 | needs.check_alive_and_permissions.outputs.WORKFLOW_PERMISSION == 'true' &&
213 | vars.SCHEDULED_SYNC != 'false'
214 | uses: actions/checkout@v4
215 | with:
216 | token: ${{ secrets.GH_PAT }}
217 | ref: ${{ env.TARGET_BRANCH }}
218 |
219 | - name: Sync upstream changes
220 | if: | # do not run the upstream sync action on the upstream repository
221 | needs.check_alive_and_permissions.outputs.WORKFLOW_PERMISSION == 'true' &&
222 | vars.SCHEDULED_SYNC != 'false' && github.repository_owner != 'LoopKit' && needs.check_latest_from_upstream.outputs.ABORT_SYNC == 'false'
223 | id: sync
224 | uses: aormsby/Fork-Sync-With-Upstream-action@v3.4.1
225 | with:
226 | target_sync_branch: ${{ env.TARGET_BRANCH }}
227 | shallow_since: 6 months ago
228 | target_repo_token: ${{ secrets.GH_PAT }}
229 | upstream_sync_branch: ${{ env.UPSTREAM_BRANCH }}
230 | upstream_sync_repo: ${{ env.UPSTREAM_REPO }}
231 |
232 | # Display a sample message based on the sync output var 'has_new_commits'
233 | - name: New commits found
234 | if: |
235 | needs.check_alive_and_permissions.outputs.WORKFLOW_PERMISSION == 'true' &&
236 | vars.SCHEDULED_SYNC != 'false' && steps.sync.outputs.has_new_commits == 'true' && needs.check_latest_from_upstream.outputs.ABORT_SYNC == 'false'
237 | run: echo "New commits were found to sync."
238 |
239 | - name: No new commits
240 | if: |
241 | needs.check_alive_and_permissions.outputs.WORKFLOW_PERMISSION == 'true' &&
242 | vars.SCHEDULED_SYNC != 'false' && steps.sync.outputs.has_new_commits == 'false' && needs.check_latest_from_upstream.outputs.ABORT_SYNC == 'false'
243 | run: echo "There were no new commits."
244 |
245 | - name: Show value of 'has_new_commits'
246 | if: |
247 | needs.check_alive_and_permissions.outputs.WORKFLOW_PERMISSION == 'true'
248 | && vars.SCHEDULED_SYNC != 'false' && needs.check_latest_from_upstream.outputs.ABORT_SYNC == 'false'
249 | run: |
250 | echo ${{ steps.sync.outputs.has_new_commits }}
251 | echo "NEW_COMMITS=${{ steps.sync.outputs.has_new_commits }}" >> $GITHUB_OUTPUT
252 |
253 | - name: Checkout Repo for building
254 | uses: actions/checkout@v4
255 | with:
256 | token: ${{ secrets.GH_PAT }}
257 | submodules: recursive
258 | ref: ${{ env.TARGET_BRANCH }}
259 |
260 | # Customize Loop: Download and apply patches
261 | - name: Customize Loop
262 | run: |
263 |
264 | # LoopWorkspace patches
265 | # -applies any patches located in the LoopWorkspace/patches/ directory
266 | if $(ls ./patches/* &> /dev/null); then
267 | git apply ./patches/* --allow-empty -v --whitespace=fix
268 | fi
269 |
270 | # Submodule Loop patches:
271 | # Template for customizing submodule Loop (changes Loop app name to "CustomLoop")
272 | # Remove the "#" sign from the beginning of the line below to activate:
273 | #curl https://github.com/loopnlearn/Loop/commit/d206432b024279ef710df462b20bd464cd9682d4.patch | git apply --directory=Loop -v --whitespace=fix
274 |
275 | # Submodule LoopKit patches:
276 | # General template for customizing submodule LoopKit
277 | # Copy url from a GitHub commit or pull request and insert below, and remove the "#" sign from the beginning of the line to activate:
278 | #curl url_to_github_commit.patch | git apply --directory=LoopKit -v --whitespace=fix
279 |
280 | # Submodule xxxxx patches:
281 |
282 | # Add patches for customization of additional submodules by following the templates above,
283 | # and make sure to specify the submodule by setting "--directory=(submodule_name)".
284 | # Several patches may be added per submodule.
285 | # Adding comments (#) may be useful to easily tell the individual patches apart.
286 |
287 | # Patch Fastlane Match to not print tables
288 | - name: Patch Match Tables
289 | run: |
290 | TABLE_PRINTER_PATH=$(ruby -e 'puts Gem::Specification.find_by_name("fastlane").gem_dir')/match/lib/match/table_printer.rb
291 | if [ -f "$TABLE_PRINTER_PATH" ]; then
292 | sed -i "" "/puts(Terminal::Table.new(params))/d" "$TABLE_PRINTER_PATH"
293 | else
294 | echo "table_printer.rb not found"
295 | exit 1
296 | fi
297 |
298 | # Install project dependencies
299 | - name: Install Project Dependencies
300 | run: bundle install
301 |
302 | # Sync the GitHub runner clock with the Windows time server (workaround as suggested in https://github.com/actions/runner/issues/2996)
303 | - name: Sync clock
304 | run: sudo sntp -sS time.windows.com
305 |
306 | # Build signed Loop IPA file
307 | - name: Fastlane Build & Archive
308 | run: bundle exec fastlane build_loop
309 | env:
310 | TEAMID: ${{ secrets.TEAMID }}
311 | GH_PAT: ${{ secrets.GH_PAT }}
312 | FASTLANE_KEY_ID: ${{ secrets.FASTLANE_KEY_ID }}
313 | FASTLANE_ISSUER_ID: ${{ secrets.FASTLANE_ISSUER_ID }}
314 | FASTLANE_KEY: ${{ secrets.FASTLANE_KEY }}
315 | MATCH_PASSWORD: ${{ secrets.MATCH_PASSWORD }}
316 |
317 | # Upload to TestFlight
318 | - name: Fastlane upload to TestFlight
319 | run: bundle exec fastlane release
320 | env:
321 | TEAMID: ${{ secrets.TEAMID }}
322 | GH_PAT: ${{ secrets.GH_PAT }}
323 | FASTLANE_KEY_ID: ${{ secrets.FASTLANE_KEY_ID }}
324 | FASTLANE_ISSUER_ID: ${{ secrets.FASTLANE_ISSUER_ID }}
325 | FASTLANE_KEY: ${{ secrets.FASTLANE_KEY }}
326 | MATCH_PASSWORD: ${{ secrets.MATCH_PASSWORD }}
327 |
328 | # Upload Build artifacts
329 | - name: Upload build log, IPA and Symbol artifacts
330 | if: always()
331 | uses: actions/upload-artifact@v4
332 | with:
333 | name: build-artifacts
334 | path: |
335 | artifacts
336 | buildlog
--------------------------------------------------------------------------------
/.github/workflows/create_certs.yml:
--------------------------------------------------------------------------------
1 | name: 3. Create Certificates
2 | run-name: Create Certificates (${{ github.ref_name }})
3 |
4 | on: [workflow_call, workflow_dispatch]
5 |
6 | env:
7 | TEAMID: ${{ secrets.TEAMID }}
8 | GH_PAT: ${{ secrets.GH_PAT }}
9 | GH_TOKEN: ${{ secrets.GH_PAT }}
10 | MATCH_PASSWORD: ${{ secrets.MATCH_PASSWORD }}
11 | FASTLANE_KEY_ID: ${{ secrets.FASTLANE_KEY_ID }}
12 | FASTLANE_ISSUER_ID: ${{ secrets.FASTLANE_ISSUER_ID }}
13 | FASTLANE_KEY: ${{ secrets.FASTLANE_KEY }}
14 |
15 | jobs:
16 | validate:
17 | name: Validate
18 | uses: ./.github/workflows/validate_secrets.yml
19 | secrets: inherit
20 |
21 |
22 | create_certs:
23 | name: Certificates
24 | needs: validate
25 | runs-on: macos-15
26 | outputs:
27 | new_certificate_needed: ${{ steps.set_output.outputs.new_certificate_needed }}
28 |
29 | steps:
30 | # Checks-out the repo
31 | - name: Checkout Repo
32 | uses: actions/checkout@v4
33 |
34 | # Patch Fastlane Match to not print tables
35 | - name: Patch Match Tables
36 | run: |
37 | TABLE_PRINTER_PATH=$(ruby -e 'puts Gem::Specification.find_by_name("fastlane").gem_dir')/match/lib/match/table_printer.rb
38 | if [ -f "$TABLE_PRINTER_PATH" ]; then
39 | sed -i "" "/puts(Terminal::Table.new(params))/d" "$TABLE_PRINTER_PATH"
40 | else
41 | echo "table_printer.rb not found"
42 | exit 1
43 | fi
44 |
45 | # Install project dependencies
46 | - name: Install Project Dependencies
47 | run: bundle install
48 |
49 | # Sync the GitHub runner clock with the Windows time server (workaround as suggested in https://github.com/actions/runner/issues/2996)
50 | - name: Sync clock
51 | run: sudo sntp -sS time.windows.com
52 |
53 | # Create or update Distribution certificate and provisioning profiles
54 | - name: Check and create or update Distribution certificate and profiles if needed
55 | run: |
56 | echo "Running Fastlane certs lane..."
57 | bundle exec fastlane certs || true # ignore and continue on errors without annotating an exit code
58 | - name: Check Distribution certificate and launch Nuke certificates if needed
59 | run: bundle exec fastlane check_and_renew_certificates
60 | id: check_certs
61 |
62 | - name: Set output and annotations based on Fastlane result
63 | id: set_output
64 | run: |
65 | CERT_STATUS_FILE="${{ github.workspace }}/fastlane/new_certificate_needed.txt"
66 | ENABLE_NUKE_CERTS=${{ vars.ENABLE_NUKE_CERTS }}
67 |
68 | if [ -f "$CERT_STATUS_FILE" ]; then
69 | CERT_STATUS=$(cat "$CERT_STATUS_FILE" | tr -d '\n' | tr -d '\r') # Read file content and strip newlines
70 | echo "new_certificate_needed: $CERT_STATUS"
71 | echo "new_certificate_needed=$CERT_STATUS" >> $GITHUB_OUTPUT
72 | else
73 | echo "Certificate status file not found. Defaulting to false."
74 | echo "new_certificate_needed=false" >> $GITHUB_OUTPUT
75 | fi
76 | # Check if ENABLE_NUKE_CERTS is not set to true when certs are valid
77 | if [ "$CERT_STATUS" != "true" ] && [ "$ENABLE_NUKE_CERTS" != "true" ]; then
78 | echo "::notice::🔔 Automated renewal of certificates is disabled because the repository variable ENABLE_NUKE_CERTS is not set to 'true'."
79 | fi
80 | # Check if ENABLE_NUKE_CERTS is not set to true when certs are not valid
81 | if [ "$CERT_STATUS" = "true" ] && [ "$ENABLE_NUKE_CERTS" != "true" ]; then
82 | echo "::error::❌ No valid distribution certificate found. Automated renewal of certificates was skipped because the repository variable ENABLE_NUKE_CERTS is not set to 'true'."
83 | exit 1
84 | fi
85 | # Check if vars.FORCE_NUKE_CERTS is not set to true
86 | if [ vars.FORCE_NUKE_CERTS = "true" ]; then
87 | echo "::warning::‼️ Nuking of certificates was forced because the repository variable FORCE_NUKE_CERTS is set to 'true'."
88 | fi
89 | # Nuke Certs if needed, and if the repository variable ENABLE_NUKE_CERTS is set to 'true', or if FORCE_NUKE_CERTS is set to 'true', which will always force certs to be nuked
90 | nuke_certs:
91 | name: Nuke certificates
92 | needs: [validate, create_certs]
93 | runs-on: macos-15
94 | if: ${{ (needs.create_certs.outputs.new_certificate_needed == 'true' && vars.ENABLE_NUKE_CERTS == 'true') || vars.FORCE_NUKE_CERTS == 'true' }}
95 | steps:
96 | - name: Output from step id 'check_certs'
97 | run: echo "new_certificate_needed=${{ needs.create_certs.outputs.new_certificate_needed }}"
98 |
99 | - name: Checkout repository
100 | uses: actions/checkout@v4
101 |
102 | - name: Install dependencies
103 | run: bundle install
104 |
105 | - name: Run Fastlane nuke_certs
106 | run: |
107 | set -e # Set error immediately after this step if error occurs
108 | bundle exec fastlane nuke_certs
109 | - name: Recreate Distribution certificate after nuking
110 | run: |
111 | set -e # Set error immediately after this step if error occurs
112 | bundle exec fastlane certs
113 | - name: Add success annotations for nuke and certificate recreation
114 | if: ${{ success() }}
115 | run: |
116 | echo "::warning::⚠️ All Distribution certificates and TestFlight profiles have been revoked and recreated."
117 | echo "::warning::❗️ If you have other apps being distributed by GitHub Actions / Fastlane / TestFlight that does not renew certificates automatically, please run the '3. Create Certificates' workflow for each of these apps to allow these apps to be built."
118 | echo "::warning::✅ But don't worry about your existing TestFlight builds, they will keep working!"
119 |
--------------------------------------------------------------------------------
/.github/workflows/validate_secrets.yml:
--------------------------------------------------------------------------------
1 | name: 1. Validate Secrets
2 | run-name: Validate Secrets (${{ github.ref_name }})
3 | on: [workflow_call, workflow_dispatch]
4 |
5 | jobs:
6 | validate-access-token:
7 | name: Access
8 | runs-on: macos-15
9 | env:
10 | GH_PAT: ${{ secrets.GH_PAT }}
11 | GH_TOKEN: ${{ secrets.GH_PAT }}
12 | outputs:
13 | HAS_WORKFLOW_PERMISSION: ${{ steps.access-token.outputs.has_workflow_permission }}
14 | steps:
15 | - name: Validate Access Token
16 | id: access-token
17 | run: |
18 | # Validate Access Token
19 |
20 | # Ensure that gh exit codes are handled when output is piped.
21 | set -o pipefail
22 |
23 | # Define patterns to validate the access token (GH_PAT) and distinguish between classic and fine-grained tokens.
24 | GH_PAT_CLASSIC_PATTERN='^ghp_[a-zA-Z0-9]{36}$'
25 | GH_PAT_FINE_GRAINED_PATTERN='^github_pat_[a-zA-Z0-9]{22}_[a-zA-Z0-9]{59}$'
26 |
27 | # Validate Access Token (GH_PAT)
28 | if [ -z "$GH_PAT" ]; then
29 | failed=true
30 | echo "::error::The GH_PAT secret is unset or empty. Set it and try again."
31 | else
32 | if [[ $GH_PAT =~ $GH_PAT_CLASSIC_PATTERN ]]; then
33 | provides_scopes=true
34 | echo "The GH_PAT secret is a structurally valid classic token."
35 | elif [[ $GH_PAT =~ $GH_PAT_FINE_GRAINED_PATTERN ]]; then
36 | echo "The GH_PAT secret is a structurally valid fine-grained token."
37 | else
38 | unknown_format=true
39 | echo "The GH_PAT secret does not have a known token format."
40 | fi
41 |
42 | # Attempt to capture the x-oauth-scopes scopes of the token.
43 | if ! scopes=$(curl -sS -f -I -H "Authorization: token $GH_PAT" https://api.github.com | { grep -i '^x-oauth-scopes:' || true; } | cut -d ' ' -f2- | tr -d '\r'); then
44 | failed=true
45 | if [ $unknown_format ]; then
46 | echo "::error::Unable to connect to GitHub using the GH_PAT secret. Verify that it is set correctly (including the 'ghp_' or 'github_pat_' prefix) and try again."
47 | else
48 | echo "::error::Unable to connect to GitHub using the GH_PAT secret. Verify that the token exists and has not expired at https://github.com/settings/tokens. If necessary, regenerate or create a new token (and update the secret), then try again."
49 | fi
50 | elif [[ $scopes =~ workflow ]]; then
51 | echo "The GH_PAT secret has repo and workflow permissions."
52 | echo "has_workflow_permission=true" >> $GITHUB_OUTPUT
53 | elif [[ $scopes =~ repo ]]; then
54 | echo "The GH_PAT secret has repo (but not workflow) permissions."
55 | elif [ $provides_scopes ]; then
56 | failed=true
57 | if [ -z "$scopes" ]; then
58 | echo "The GH_PAT secret is valid and can be used to connect to GitHub, but it does not provide any permission scopes."
59 | else
60 | echo "The GH_PAT secret is valid and can be used to connect to GitHub, but it only provides the following permission scopes: $scopes"
61 | fi
62 | echo "::error::The GH_PAT secret is lacking at least the 'repo' permission scope required to access the Match-Secrets repository. Update the token permissions at https://github.com/settings/tokens (to include the 'repo' and 'workflow' scopes) and try again."
63 | else
64 | echo "The GH_PAT secret is valid and can be used to connect to GitHub, but it does not provide inspectable scopes. Assuming that the 'repo' and 'workflow' permission scopes required to access the Match-Secrets repository and perform automations are present."
65 | echo "has_workflow_permission=true" >> $GITHUB_OUTPUT
66 | fi
67 | fi
68 |
69 | # Exit unsuccessfully if secret validation failed.
70 | if [ $failed ]; then
71 | exit 2
72 | fi
73 |
74 | validate-match-secrets:
75 | name: Match-Secrets
76 | needs: validate-access-token
77 | runs-on: macos-15
78 | env:
79 | GH_TOKEN: ${{ secrets.GH_PAT }}
80 | steps:
81 | - name: Validate Match-Secrets
82 | run: |
83 | # Validate Match-Secrets
84 |
85 | # Ensure that gh exit codes are handled when output is piped.
86 | set -o pipefail
87 |
88 | # If a Match-Secrets repository does not exist, attempt to create one.
89 | if ! visibility=$(gh repo view ${{ github.repository_owner }}/Match-Secrets --json visibility | jq --raw-output '.visibility | ascii_downcase'); then
90 | echo "A '${{ github.repository_owner }}/Match-Secrets' repository could not be found using the GH_PAT secret. Attempting to create one..."
91 |
92 | # Create a private Match-Secrets repository and verify that it exists and that it is private.
93 | if gh repo create ${{ github.repository_owner }}/Match-Secrets --private >/dev/null && [ "$(gh repo view ${{ github.repository_owner }}/Match-Secrets --json visibility | jq --raw-output '.visibility | ascii_downcase')" == "private" ]; then
94 | echo "Created a private '${{ github.repository_owner }}/Match-Secrets' repository."
95 | else
96 | failed=true
97 | echo "::error::Unable to create a private '${{ github.repository_owner }}/Match-Secrets' repository. Create a private 'Match-Secrets' repository manually and try again. If a private 'Match-Secrets' repository already exists, verify that the token permissions of the GH_PAT are set correctly (or update them) at https://github.com/settings/tokens and try again."
98 | fi
99 | # Otherwise, if a Match-Secrets repository exists, but it is public, cause validation to fail.
100 | elif [[ "$visibility" == "public" ]]; then
101 | failed=true
102 | echo "::error::A '${{ github.repository_owner }}/Match-Secrets' repository was found, but it is public. Change the repository visibility to private (or delete it) and try again. If necessary, a private repository will be created for you."
103 | else
104 | echo "Found a private '${{ github.repository_owner }}/Match-Secrets' repository to use."
105 | fi
106 |
107 | # Exit unsuccessfully if secret validation failed.
108 | if [ $failed ]; then
109 | exit 2
110 | fi
111 |
112 | validate-fastlane-secrets:
113 | name: Fastlane
114 | needs: [validate-access-token, validate-match-secrets]
115 | runs-on: macos-15
116 | env:
117 | GH_PAT: ${{ secrets.GH_PAT }}
118 | GH_TOKEN: ${{ secrets.GH_PAT }}
119 | FASTLANE_ISSUER_ID: ${{ secrets.FASTLANE_ISSUER_ID }}
120 | FASTLANE_KEY_ID: ${{ secrets.FASTLANE_KEY_ID }}
121 | FASTLANE_KEY: ${{ secrets.FASTLANE_KEY }}
122 | MATCH_PASSWORD: ${{ secrets.MATCH_PASSWORD }}
123 | TEAMID: ${{ secrets.TEAMID }}
124 | steps:
125 | - name: Checkout Repo
126 | uses: actions/checkout@v4
127 |
128 | - name: Install Project Dependencies
129 | run: bundle install
130 |
131 | # Sync the GitHub runner clock with the Windows time server (workaround as suggested in https://github.com/actions/runner/issues/2996)
132 | - name: Sync clock
133 | run: sudo sntp -sS time.windows.com
134 |
135 | - name: Validate Fastlane Secrets
136 | run: |
137 | # Validate Fastlane Secrets
138 |
139 | # Validate TEAMID
140 | if [ -z "$TEAMID" ]; then
141 | failed=true
142 | echo "::error::The TEAMID secret is unset or empty. Set it and try again."
143 | elif [ ${#TEAMID} -ne 10 ]; then
144 | failed=true
145 | echo "::error::The TEAMID secret is set but has wrong length. Verify that it is set correctly and try again."
146 | elif ! [[ $TEAMID =~ ^[A-Z0-9]+$ ]]; then
147 | failed=true
148 | echo "::error::The TEAMID secret is set but invalid. Verify that it is set correctly (only uppercase letters and numbers) and try again."
149 | fi
150 |
151 | # Validate MATCH_PASSWORD
152 | if [ -z "$MATCH_PASSWORD" ]; then
153 | failed=true
154 | echo "::error::The MATCH_PASSWORD secret is unset or empty. Set it and try again."
155 | fi
156 |
157 | # Ensure that fastlane exit codes are handled when output is piped.
158 | set -o pipefail
159 |
160 | # Validate FASTLANE_ISSUER_ID, FASTLANE_KEY_ID, and FASTLANE_KEY
161 | FASTLANE_KEY_ID_PATTERN='^[A-Z0-9]+$'
162 | FASTLANE_ISSUER_ID_PATTERN='^\{?[A-F0-9a-f]{8}-[A-F0-9a-f]{4}-[A-F0-9a-f]{4}-[A-F0-9a-f]{4}-[A-F0-9a-f]{12}\}?$'
163 |
164 | if [ -z "$FASTLANE_ISSUER_ID" ] || [ -z "$FASTLANE_KEY_ID" ] || [ -z "$FASTLANE_KEY" ]; then
165 | failed=true
166 | [ -z "$FASTLANE_ISSUER_ID" ] && echo "::error::The FASTLANE_ISSUER_ID secret is unset or empty. Set it and try again."
167 | [ -z "$FASTLANE_KEY_ID" ] && echo "::error::The FASTLANE_KEY_ID secret is unset or empty. Set it and try again."
168 | [ -z "$FASTLANE_KEY" ] && echo "::error::The FASTLANE_KEY secret is unset or empty. Set it and try again."
169 | elif [ ${#FASTLANE_KEY_ID} -ne 10 ]; then
170 | failed=true
171 | echo "::error::The FASTLANE_KEY_ID secret is set but has wrong length. Verify that you copied it correctly from the 'Keys' tab at https://appstoreconnect.apple.com/access/integrations/api and try again."
172 | elif ! [[ $FASTLANE_KEY_ID =~ $FASTLANE_KEY_ID_PATTERN ]]; then
173 | failed=true
174 | echo "::error::The FASTLANE_KEY_ID secret is set but invalid. Verify that you copied it correctly from the 'Keys' tab at https://appstoreconnect.apple.com/access/integrations/api and try again."
175 | elif ! [[ $FASTLANE_ISSUER_ID =~ $FASTLANE_ISSUER_ID_PATTERN ]]; then
176 | failed=true
177 | echo "::error::The FASTLANE_ISSUER_ID secret is set but invalid. Verify that you copied it correctly from the 'Keys' tab at https://appstoreconnect.apple.com/access/integrations/api and try again."
178 | elif ! echo "$FASTLANE_KEY" | openssl pkcs8 -nocrypt >/dev/null; then
179 | failed=true
180 | echo "::error::The FASTLANE_KEY secret is set but invalid. Verify that you copied it correctly from the API Key file (*.p8) you downloaded and try again."
181 | elif ! (bundle exec fastlane validate_secrets 2>&1 || true) | tee fastlane.log; then # ignore "fastlane validate_secrets" errors and continue on errors without annotating an exit code
182 | if grep -q "bad decrypt" fastlane.log; then
183 | failed=true
184 | echo "::error::Unable to decrypt the Match-Secrets repository using the MATCH_PASSWORD secret. Verify that it is set correctly and try again."
185 | elif grep -q -e "required agreement" -e "license agreement" fastlane.log; then
186 | failed=true
187 | echo "::error::❗️ Verify that the latest developer program license agreement has been accepted at https://developer.apple.com/account (review and accept any updated agreement), then wait a few minutes for changes to take effect and try again."
188 | elif grep -q "Your certificate .* is not valid" fastlane.log; then
189 | echo "::notice::Your Distribution certificate is invalid or expired. Automated renewal of the certificate will be attempted."
190 | fi
191 | fi
192 |
193 | # Exit unsuccessfully if secret validation failed.
194 | if [ $failed ]; then
195 | exit 2
196 | fi
197 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | ## Build
2 | DerivedData/
3 |
4 | ## Settings
5 | *.pbxuser
6 | !default.pbxuser
7 | *.mode1v3
8 | !default.mode1v3
9 | *.mode2v3
10 | !default.mode2v3
11 | *.perspectivev3
12 | !default.perspectivev3
13 | xcuserdata/
14 |
15 | ## Other
16 | *.moved-aside
17 | *.xccheckout
18 | *.xcscmblueprint
19 | *.xcuserstate
20 | .DS_Store
21 |
22 | ## Obj-C/Swift specific
23 | *.hmap
24 | *.ipa
25 |
26 | ## Playgrounds
27 | *.playground
28 | playground.xcworkspace
29 | timeline.xctimeline
30 |
--------------------------------------------------------------------------------
/.gitmodules:
--------------------------------------------------------------------------------
1 | [submodule "Loop"]
2 | path = Loop
3 | url = https://github.com/LoopKit/Loop.git
4 | [submodule "LoopKit"]
5 | path = LoopKit
6 | url = https://github.com/LoopKit/LoopKit.git
7 | [submodule "CGMBLEKit"]
8 | path = CGMBLEKit
9 | url = https://github.com/LoopKit/CGMBLEKit.git
10 | [submodule "dexcom-share-client-swift"]
11 | path = dexcom-share-client-swift
12 | url = https://github.com/LoopKit/dexcom-share-client-swift.git
13 | [submodule "RileyLinkKit"]
14 | path = RileyLinkKit
15 | url = https://github.com/LoopKit/RileyLinkKit
16 | [submodule "NightscoutService"]
17 | path = NightscoutService
18 | url = https://github.com/LoopKit/NightscoutService.git
19 | [submodule "Minizip"]
20 | path = Minizip
21 | url = https://github.com/LoopKit/Minizip.git
22 | [submodule "TrueTime.swift"]
23 | path = TrueTime.swift
24 | url = https://github.com/LoopKit/TrueTime.swift.git
25 | [submodule "LoopOnboarding"]
26 | path = LoopOnboarding
27 | url = https://github.com/LoopKit/LoopOnboarding.git
28 | [submodule "AmplitudeService"]
29 | path = AmplitudeService
30 | url = https://github.com/LoopKit/AmplitudeService.git
31 | [submodule "LogglyService"]
32 | path = LogglyService
33 | url = https://github.com/LoopKit/LogglyService.git
34 | [submodule "OmniBLE"]
35 | path = OmniBLE
36 | url = https://github.com/LoopKit/OmniBLE.git
37 | [submodule "NightscoutRemoteCGM"]
38 | path = NightscoutRemoteCGM
39 | url = https://github.com/LoopKit/NightscoutRemoteCGM.git
40 | [submodule "LoopSupport"]
41 | path = LoopSupport
42 | url = https://github.com/LoopKit/LoopSupport
43 | [submodule "G7SensorKit"]
44 | path = G7SensorKit
45 | url = https://github.com/LoopKit/G7SensorKit.git
46 | [submodule "TidepoolService"]
47 | path = TidepoolService
48 | url = https://github.com/LoopKit/TidepoolService.git
49 | [submodule "OmniKit"]
50 | path = OmniKit
51 | url = https://github.com/LoopKit/OmniKit.git
52 | [submodule "MinimedKit"]
53 | path = MinimedKit
54 | url = https://github.com/LoopKit/MinimedKit.git
55 | [submodule "MixpanelService"]
56 | path = MixpanelService
57 | url = https://github.com/LoopKit/MixpanelService
58 | [submodule "LibreTransmitter"]
59 | path = LibreTransmitter
60 | url = https://github.com/LoopKit/LibreTransmitter.git
61 |
--------------------------------------------------------------------------------
/Gemfile:
--------------------------------------------------------------------------------
1 | source "https://rubygems.org"
2 |
3 | gem "fastlane"
4 |
--------------------------------------------------------------------------------
/Gemfile.lock:
--------------------------------------------------------------------------------
1 | GEM
2 | remote: https://rubygems.org/
3 | specs:
4 | CFPropertyList (3.0.7)
5 | base64
6 | nkf
7 | rexml
8 | addressable (2.8.7)
9 | public_suffix (>= 2.0.2, < 7.0)
10 | artifactory (3.0.17)
11 | atomos (0.1.3)
12 | aws-eventstream (1.3.2)
13 | aws-partitions (1.1086.0)
14 | aws-sdk-core (3.222.1)
15 | aws-eventstream (~> 1, >= 1.3.0)
16 | aws-partitions (~> 1, >= 1.992.0)
17 | aws-sigv4 (~> 1.9)
18 | base64
19 | jmespath (~> 1, >= 1.6.1)
20 | logger
21 | aws-sdk-kms (1.99.0)
22 | aws-sdk-core (~> 3, >= 3.216.0)
23 | aws-sigv4 (~> 1.5)
24 | aws-sdk-s3 (1.183.0)
25 | aws-sdk-core (~> 3, >= 3.216.0)
26 | aws-sdk-kms (~> 1)
27 | aws-sigv4 (~> 1.5)
28 | aws-sigv4 (1.11.0)
29 | aws-eventstream (~> 1, >= 1.0.2)
30 | babosa (1.0.4)
31 | base64 (0.2.0)
32 | claide (1.1.0)
33 | colored (1.2)
34 | colored2 (3.1.2)
35 | commander (4.6.0)
36 | highline (~> 2.0.0)
37 | declarative (0.0.20)
38 | digest-crc (0.7.0)
39 | rake (>= 12.0.0, < 14.0.0)
40 | domain_name (0.6.20240107)
41 | dotenv (2.8.1)
42 | emoji_regex (3.2.3)
43 | excon (0.112.0)
44 | faraday (1.10.4)
45 | faraday-em_http (~> 1.0)
46 | faraday-em_synchrony (~> 1.0)
47 | faraday-excon (~> 1.1)
48 | faraday-httpclient (~> 1.0)
49 | faraday-multipart (~> 1.0)
50 | faraday-net_http (~> 1.0)
51 | faraday-net_http_persistent (~> 1.0)
52 | faraday-patron (~> 1.0)
53 | faraday-rack (~> 1.0)
54 | faraday-retry (~> 1.0)
55 | ruby2_keywords (>= 0.0.4)
56 | faraday-cookie_jar (0.0.7)
57 | faraday (>= 0.8.0)
58 | http-cookie (~> 1.0.0)
59 | faraday-em_http (1.0.0)
60 | faraday-em_synchrony (1.0.0)
61 | faraday-excon (1.1.0)
62 | faraday-httpclient (1.0.1)
63 | faraday-multipart (1.1.0)
64 | multipart-post (~> 2.0)
65 | faraday-net_http (1.0.2)
66 | faraday-net_http_persistent (1.2.0)
67 | faraday-patron (1.0.0)
68 | faraday-rack (1.0.0)
69 | faraday-retry (1.0.3)
70 | faraday_middleware (1.2.1)
71 | faraday (~> 1.0)
72 | fastimage (2.4.0)
73 | fastlane (2.227.1)
74 | CFPropertyList (>= 2.3, < 4.0.0)
75 | addressable (>= 2.8, < 3.0.0)
76 | artifactory (~> 3.0)
77 | aws-sdk-s3 (~> 1.0)
78 | babosa (>= 1.0.3, < 2.0.0)
79 | bundler (>= 1.12.0, < 3.0.0)
80 | colored (~> 1.2)
81 | commander (~> 4.6)
82 | dotenv (>= 2.1.1, < 3.0.0)
83 | emoji_regex (>= 0.1, < 4.0)
84 | excon (>= 0.71.0, < 1.0.0)
85 | faraday (~> 1.0)
86 | faraday-cookie_jar (~> 0.0.6)
87 | faraday_middleware (~> 1.0)
88 | fastimage (>= 2.1.0, < 3.0.0)
89 | fastlane-sirp (>= 1.0.0)
90 | gh_inspector (>= 1.1.2, < 2.0.0)
91 | google-apis-androidpublisher_v3 (~> 0.3)
92 | google-apis-playcustomapp_v1 (~> 0.1)
93 | google-cloud-env (>= 1.6.0, < 2.0.0)
94 | google-cloud-storage (~> 1.31)
95 | highline (~> 2.0)
96 | http-cookie (~> 1.0.5)
97 | json (< 3.0.0)
98 | jwt (>= 2.1.0, < 3)
99 | mini_magick (>= 4.9.4, < 5.0.0)
100 | multipart-post (>= 2.0.0, < 3.0.0)
101 | naturally (~> 2.2)
102 | optparse (>= 0.1.1, < 1.0.0)
103 | plist (>= 3.1.0, < 4.0.0)
104 | rubyzip (>= 2.0.0, < 3.0.0)
105 | security (= 0.1.5)
106 | simctl (~> 1.6.3)
107 | terminal-notifier (>= 2.0.0, < 3.0.0)
108 | terminal-table (~> 3)
109 | tty-screen (>= 0.6.3, < 1.0.0)
110 | tty-spinner (>= 0.8.0, < 1.0.0)
111 | word_wrap (~> 1.0.0)
112 | xcodeproj (>= 1.13.0, < 2.0.0)
113 | xcpretty (~> 0.4.1)
114 | xcpretty-travis-formatter (>= 0.0.3, < 2.0.0)
115 | fastlane-sirp (1.0.0)
116 | sysrandom (~> 1.0)
117 | gh_inspector (1.1.3)
118 | google-apis-androidpublisher_v3 (0.54.0)
119 | google-apis-core (>= 0.11.0, < 2.a)
120 | google-apis-core (0.11.3)
121 | addressable (~> 2.5, >= 2.5.1)
122 | googleauth (>= 0.16.2, < 2.a)
123 | httpclient (>= 2.8.1, < 3.a)
124 | mini_mime (~> 1.0)
125 | representable (~> 3.0)
126 | retriable (>= 2.0, < 4.a)
127 | rexml
128 | google-apis-iamcredentials_v1 (0.17.0)
129 | google-apis-core (>= 0.11.0, < 2.a)
130 | google-apis-playcustomapp_v1 (0.13.0)
131 | google-apis-core (>= 0.11.0, < 2.a)
132 | google-apis-storage_v1 (0.31.0)
133 | google-apis-core (>= 0.11.0, < 2.a)
134 | google-cloud-core (1.8.0)
135 | google-cloud-env (>= 1.0, < 3.a)
136 | google-cloud-errors (~> 1.0)
137 | google-cloud-env (1.6.0)
138 | faraday (>= 0.17.3, < 3.0)
139 | google-cloud-errors (1.5.0)
140 | google-cloud-storage (1.47.0)
141 | addressable (~> 2.8)
142 | digest-crc (~> 0.4)
143 | google-apis-iamcredentials_v1 (~> 0.1)
144 | google-apis-storage_v1 (~> 0.31.0)
145 | google-cloud-core (~> 1.6)
146 | googleauth (>= 0.16.2, < 2.a)
147 | mini_mime (~> 1.0)
148 | googleauth (1.8.1)
149 | faraday (>= 0.17.3, < 3.a)
150 | jwt (>= 1.4, < 3.0)
151 | multi_json (~> 1.11)
152 | os (>= 0.9, < 2.0)
153 | signet (>= 0.16, < 2.a)
154 | highline (2.0.3)
155 | http-cookie (1.0.8)
156 | domain_name (~> 0.5)
157 | httpclient (2.9.0)
158 | mutex_m
159 | jmespath (1.6.2)
160 | json (2.10.2)
161 | jwt (2.10.1)
162 | base64
163 | logger (1.7.0)
164 | mini_magick (4.13.2)
165 | mini_mime (1.1.5)
166 | multi_json (1.15.0)
167 | multipart-post (2.4.1)
168 | mutex_m (0.3.0)
169 | nanaimo (0.4.0)
170 | naturally (2.2.1)
171 | nkf (0.2.0)
172 | optparse (0.6.0)
173 | os (1.1.4)
174 | plist (3.7.2)
175 | public_suffix (6.0.1)
176 | rake (13.2.1)
177 | representable (3.2.0)
178 | declarative (< 0.1.0)
179 | trailblazer-option (>= 0.1.1, < 0.2.0)
180 | uber (< 0.2.0)
181 | retriable (3.1.2)
182 | rexml (3.4.1)
183 | rouge (3.28.0)
184 | ruby2_keywords (0.0.5)
185 | rubyzip (2.4.1)
186 | security (0.1.5)
187 | signet (0.19.0)
188 | addressable (~> 2.8)
189 | faraday (>= 0.17.5, < 3.a)
190 | jwt (>= 1.5, < 3.0)
191 | multi_json (~> 1.10)
192 | simctl (1.6.10)
193 | CFPropertyList
194 | naturally
195 | sysrandom (1.0.5)
196 | terminal-notifier (2.0.0)
197 | terminal-table (3.0.2)
198 | unicode-display_width (>= 1.1.1, < 3)
199 | trailblazer-option (0.1.2)
200 | tty-cursor (0.7.1)
201 | tty-screen (0.8.2)
202 | tty-spinner (0.9.3)
203 | tty-cursor (~> 0.7)
204 | uber (0.1.0)
205 | unicode-display_width (2.6.0)
206 | word_wrap (1.0.0)
207 | xcodeproj (1.27.0)
208 | CFPropertyList (>= 2.3.3, < 4.0)
209 | atomos (~> 0.1.3)
210 | claide (>= 1.0.2, < 2.0)
211 | colored2 (~> 3.1)
212 | nanaimo (~> 0.4.0)
213 | rexml (>= 3.3.6, < 4.0)
214 | xcpretty (0.4.1)
215 | rouge (~> 3.28.0)
216 | xcpretty-travis-formatter (1.0.1)
217 | xcpretty (~> 0.2, >= 0.0.7)
218 |
219 | PLATFORMS
220 | arm64-darwin-21
221 | arm64-darwin-22
222 | arm64-darwin-23
223 | arm64-darwin-24
224 | x86_64-darwin-19
225 | x86_64-darwin-24
226 | x86_64-linux
227 |
228 | DEPENDENCIES
229 | fastlane
230 |
231 | BUNDLED WITH
232 | 2.6.2
233 |
--------------------------------------------------------------------------------
/InfoCustomizations.txt:
--------------------------------------------------------------------------------
1 | TidepoolServiceClientId=diy-loop
2 |
--------------------------------------------------------------------------------
/LoopConfigOverride.xcconfig:
--------------------------------------------------------------------------------
1 | #include? "../../LoopConfigOverride.xcconfig"
2 |
3 | // Override this if you don't want the default com.${DEVELOPMENT_TEAM}.loopkit that loop uses
4 | // MAIN_APP_BUNDLE_IDENTIFIER = com.myname.loop
5 |
6 | // Customize this to change the app name displayed
7 | //MAIN_APP_DISPLAY_NAME = Loop
8 |
9 | // Customize this to change the URL to open Loop to something other than the display name
10 | //URL_SCHEME_NAME = $(MAIN_APP_DISPLAY_NAME)
11 |
12 | // Features
13 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = $(inherited) EXPERIMENTAL_FEATURES_ENABLED SIMULATORS_ENABLED ALLOW_ALGORITHM_EXPERIMENTS DEBUG_FEATURES_ENABLED
14 |
15 | // Put your team id here for signing
16 | //LOOP_DEVELOPMENT_TEAM = UY678SP37Q
17 |
--------------------------------------------------------------------------------
/LoopWorkspace.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
7 |
9 |
10 |
12 |
13 |
15 |
16 |
18 |
19 |
20 |
22 |
23 |
25 |
26 |
29 |
31 |
32 |
34 |
35 |
37 |
38 |
39 |
41 |
42 |
44 |
45 |
47 |
48 |
50 |
51 |
53 |
54 |
56 |
57 |
59 |
60 |
62 |
63 |
65 |
66 |
68 |
69 |
71 |
72 |
74 |
75 |
77 |
78 |
80 |
81 |
83 |
84 |
86 |
87 |
89 |
90 |
92 |
93 |
95 |
96 |
98 |
99 |
101 |
102 |
103 |
--------------------------------------------------------------------------------
/LoopWorkspace.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | IDEDidComputeMac32BitWarning
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/LoopWorkspace.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | IDEWorkspaceSharedSettings_AutocreateContextsIfNeeded
6 |
7 | PreviewsEnabled
8 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/LoopWorkspace.xcworkspace/xcshareddata/swiftpm/Package.resolved:
--------------------------------------------------------------------------------
1 | {
2 | "originHash" : "f8d1e9c237647ab612da7f2bd3ae26946f39410508314c00cf54509a673f147e",
3 | "pins" : [
4 | {
5 | "identity" : "amplitude-ios",
6 | "kind" : "remoteSourceControl",
7 | "location" : "https://github.com/amplitude/Amplitude-iOS.git",
8 | "state" : {
9 | "branch" : "main",
10 | "revision" : "e818b182f5c3d5ce5035deab90bca108175b3561"
11 | }
12 | },
13 | {
14 | "identity" : "analytics-connector-ios",
15 | "kind" : "remoteSourceControl",
16 | "location" : "https://github.com/amplitude/analytics-connector-ios.git",
17 | "state" : {
18 | "revision" : "d3d682a26ca6f4947ece2c2e627971bb41b940fa",
19 | "version" : "1.0.1"
20 | }
21 | },
22 | {
23 | "identity" : "base32",
24 | "kind" : "remoteSourceControl",
25 | "location" : "https://github.com/mattrubin/Base32.git",
26 | "state" : {
27 | "branch" : "1.1.2+spm",
28 | "revision" : "d185e44c8b355d34d5c6c6ad502c60cba4599f69"
29 | }
30 | },
31 | {
32 | "identity" : "cryptoswift",
33 | "kind" : "remoteSourceControl",
34 | "location" : "https://github.com/krzyzanowskim/CryptoSwift",
35 | "state" : {
36 | "revision" : "eee9ad754926c40a0f7e73f152357d37b119b7fa",
37 | "version" : "1.7.1"
38 | }
39 | },
40 | {
41 | "identity" : "mixpanel-swift",
42 | "kind" : "remoteSourceControl",
43 | "location" : "https://github.com/mixpanel/mixpanel-swift.git",
44 | "state" : {
45 | "branch" : "master",
46 | "revision" : "c676a9737c76e127e3ae5776247b226bc6d7652d"
47 | }
48 | },
49 | {
50 | "identity" : "mkringprogressview",
51 | "kind" : "remoteSourceControl",
52 | "location" : "https://github.com/maxkonovalov/MKRingProgressView.git",
53 | "state" : {
54 | "branch" : "master",
55 | "revision" : "660888aab1d2ab0ed7eb9eb53caec12af4955fa7"
56 | }
57 | },
58 | {
59 | "identity" : "nightscoutkit",
60 | "kind" : "remoteSourceControl",
61 | "location" : "https://github.com/LoopKit/NightscoutKit",
62 | "state" : {
63 | "branch" : "main",
64 | "revision" : "ca8e2cea82ab465282cd180ce01d64c1cf25478d"
65 | }
66 | },
67 | {
68 | "identity" : "onetimepassword",
69 | "kind" : "remoteSourceControl",
70 | "location" : "https://github.com/mattrubin/OneTimePassword",
71 | "state" : {
72 | "revision" : "8e4022f2852d77240d0a17482cbfe325354aac70"
73 | }
74 | },
75 | {
76 | "identity" : "slidebutton",
77 | "kind" : "remoteSourceControl",
78 | "location" : "https://github.com/no-comment/SlideButton",
79 | "state" : {
80 | "branch" : "main",
81 | "revision" : "5eacebba4d7deeb693592bc9a62ab2d2181e133b"
82 | }
83 | },
84 | {
85 | "identity" : "swiftcharts",
86 | "kind" : "remoteSourceControl",
87 | "location" : "https://github.com/ivanschuetz/SwiftCharts",
88 | "state" : {
89 | "branch" : "master",
90 | "revision" : "c354c1945bb35a1f01b665b22474f6db28cba4a2"
91 | }
92 | },
93 | {
94 | "identity" : "tidepoolkit",
95 | "kind" : "remoteSourceControl",
96 | "location" : "https://github.com/tidepool-org/TidepoolKit",
97 | "state" : {
98 | "branch" : "dev",
99 | "revision" : "54045c2e7d720dcd8a0909037772dcd6f54f0158"
100 | }
101 | },
102 | {
103 | "identity" : "zipfoundation",
104 | "kind" : "remoteSourceControl",
105 | "location" : "https://github.com/LoopKit/ZIPFoundation.git",
106 | "state" : {
107 | "branch" : "stream-entry",
108 | "revision" : "c67b7509ec82ee2b4b0ab3f97742b94ed9692494"
109 | }
110 | }
111 | ],
112 | "version" : 3
113 | }
114 |
--------------------------------------------------------------------------------
/LoopWorkspace.xcworkspace/xcshareddata/xcschemes/LoopWorkspace.xcscheme:
--------------------------------------------------------------------------------
1 |
2 |
5 |
8 |
9 |
15 |
21 |
22 |
23 |
29 |
35 |
36 |
37 |
43 |
49 |
50 |
51 |
57 |
63 |
64 |
65 |
71 |
77 |
78 |
79 |
85 |
91 |
92 |
93 |
99 |
105 |
106 |
107 |
113 |
119 |
120 |
121 |
127 |
133 |
134 |
135 |
141 |
147 |
148 |
149 |
155 |
161 |
162 |
163 |
169 |
175 |
176 |
177 |
183 |
189 |
190 |
191 |
197 |
203 |
204 |
205 |
211 |
217 |
218 |
219 |
225 |
231 |
232 |
233 |
239 |
245 |
246 |
247 |
253 |
259 |
260 |
261 |
267 |
273 |
274 |
275 |
281 |
287 |
288 |
289 |
295 |
301 |
302 |
303 |
309 |
315 |
316 |
317 |
323 |
329 |
330 |
331 |
337 |
343 |
344 |
345 |
351 |
357 |
358 |
359 |
365 |
371 |
372 |
373 |
379 |
385 |
386 |
387 |
393 |
399 |
400 |
401 |
407 |
413 |
414 |
415 |
416 |
417 |
422 |
423 |
429 |
430 |
431 |
432 |
434 |
440 |
441 |
442 |
444 |
450 |
451 |
452 |
454 |
460 |
461 |
462 |
464 |
470 |
471 |
472 |
474 |
480 |
481 |
482 |
484 |
490 |
491 |
492 |
494 |
500 |
501 |
502 |
504 |
510 |
511 |
512 |
514 |
520 |
521 |
522 |
524 |
530 |
531 |
532 |
534 |
540 |
541 |
542 |
543 |
544 |
554 |
556 |
562 |
563 |
564 |
565 |
571 |
573 |
579 |
580 |
581 |
582 |
584 |
585 |
588 |
589 |
590 |
--------------------------------------------------------------------------------
/OverrideAssetsLoop.xcassets/AppIcon.appiconset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "size" : "20x20",
5 | "idiom" : "iphone",
6 | "filename" : "icon_20pt@2x.png",
7 | "scale" : "2x"
8 | },
9 | {
10 | "size" : "20x20",
11 | "idiom" : "iphone",
12 | "filename" : "icon_20pt@3x.png",
13 | "scale" : "3x"
14 | },
15 | {
16 | "size" : "29x29",
17 | "idiom" : "iphone",
18 | "filename" : "icon_29pt@2x.png",
19 | "scale" : "2x"
20 | },
21 | {
22 | "size" : "29x29",
23 | "idiom" : "iphone",
24 | "filename" : "icon_29pt@3x.png",
25 | "scale" : "3x"
26 | },
27 | {
28 | "size" : "40x40",
29 | "idiom" : "iphone",
30 | "filename" : "icon_40pt@2x.png",
31 | "scale" : "2x"
32 | },
33 | {
34 | "size" : "40x40",
35 | "idiom" : "iphone",
36 | "filename" : "icon_40pt@3x.png",
37 | "scale" : "3x"
38 | },
39 | {
40 | "size" : "60x60",
41 | "idiom" : "iphone",
42 | "filename" : "icon_60pt@2x.png",
43 | "scale" : "2x"
44 | },
45 | {
46 | "size" : "60x60",
47 | "idiom" : "iphone",
48 | "filename" : "icon_60pt@3x.png",
49 | "scale" : "3x"
50 | },
51 | {
52 | "size" : "20x20",
53 | "idiom" : "ipad",
54 | "filename" : "icon_20pt.png",
55 | "scale" : "1x"
56 | },
57 | {
58 | "size" : "20x20",
59 | "idiom" : "ipad",
60 | "filename" : "icon_20pt@2x-1.png",
61 | "scale" : "2x"
62 | },
63 | {
64 | "size" : "29x29",
65 | "idiom" : "ipad",
66 | "filename" : "icon_29pt.png",
67 | "scale" : "1x"
68 | },
69 | {
70 | "size" : "29x29",
71 | "idiom" : "ipad",
72 | "filename" : "icon_29pt@2x-1.png",
73 | "scale" : "2x"
74 | },
75 | {
76 | "size" : "40x40",
77 | "idiom" : "ipad",
78 | "filename" : "icon_40pt.png",
79 | "scale" : "1x"
80 | },
81 | {
82 | "size" : "40x40",
83 | "idiom" : "ipad",
84 | "filename" : "icon_40pt@2x-1.png",
85 | "scale" : "2x"
86 | },
87 | {
88 | "size" : "76x76",
89 | "idiom" : "ipad",
90 | "filename" : "icon_76pt.png",
91 | "scale" : "1x"
92 | },
93 | {
94 | "size" : "76x76",
95 | "idiom" : "ipad",
96 | "filename" : "icon_76pt@2x.png",
97 | "scale" : "2x"
98 | },
99 | {
100 | "size" : "83.5x83.5",
101 | "idiom" : "ipad",
102 | "filename" : "icon_83.5@2x.png",
103 | "scale" : "2x"
104 | },
105 | {
106 | "size" : "1024x1024",
107 | "idiom" : "ios-marketing",
108 | "filename" : "Icon.png",
109 | "scale" : "1x"
110 | }
111 | ],
112 | "info" : {
113 | "version" : 1,
114 | "author" : "xcode"
115 | }
116 | }
--------------------------------------------------------------------------------
/OverrideAssetsLoop.xcassets/AppIcon.appiconset/Icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/LoopKit/LoopWorkspace/5bac6d2c62c2708d0e68810860b73ca9c22ebe06/OverrideAssetsLoop.xcassets/AppIcon.appiconset/Icon.png
--------------------------------------------------------------------------------
/OverrideAssetsLoop.xcassets/AppIcon.appiconset/icon_20pt.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/LoopKit/LoopWorkspace/5bac6d2c62c2708d0e68810860b73ca9c22ebe06/OverrideAssetsLoop.xcassets/AppIcon.appiconset/icon_20pt.png
--------------------------------------------------------------------------------
/OverrideAssetsLoop.xcassets/AppIcon.appiconset/icon_20pt@2x-1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/LoopKit/LoopWorkspace/5bac6d2c62c2708d0e68810860b73ca9c22ebe06/OverrideAssetsLoop.xcassets/AppIcon.appiconset/icon_20pt@2x-1.png
--------------------------------------------------------------------------------
/OverrideAssetsLoop.xcassets/AppIcon.appiconset/icon_20pt@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/LoopKit/LoopWorkspace/5bac6d2c62c2708d0e68810860b73ca9c22ebe06/OverrideAssetsLoop.xcassets/AppIcon.appiconset/icon_20pt@2x.png
--------------------------------------------------------------------------------
/OverrideAssetsLoop.xcassets/AppIcon.appiconset/icon_20pt@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/LoopKit/LoopWorkspace/5bac6d2c62c2708d0e68810860b73ca9c22ebe06/OverrideAssetsLoop.xcassets/AppIcon.appiconset/icon_20pt@3x.png
--------------------------------------------------------------------------------
/OverrideAssetsLoop.xcassets/AppIcon.appiconset/icon_29pt.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/LoopKit/LoopWorkspace/5bac6d2c62c2708d0e68810860b73ca9c22ebe06/OverrideAssetsLoop.xcassets/AppIcon.appiconset/icon_29pt.png
--------------------------------------------------------------------------------
/OverrideAssetsLoop.xcassets/AppIcon.appiconset/icon_29pt@2x-1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/LoopKit/LoopWorkspace/5bac6d2c62c2708d0e68810860b73ca9c22ebe06/OverrideAssetsLoop.xcassets/AppIcon.appiconset/icon_29pt@2x-1.png
--------------------------------------------------------------------------------
/OverrideAssetsLoop.xcassets/AppIcon.appiconset/icon_29pt@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/LoopKit/LoopWorkspace/5bac6d2c62c2708d0e68810860b73ca9c22ebe06/OverrideAssetsLoop.xcassets/AppIcon.appiconset/icon_29pt@2x.png
--------------------------------------------------------------------------------
/OverrideAssetsLoop.xcassets/AppIcon.appiconset/icon_29pt@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/LoopKit/LoopWorkspace/5bac6d2c62c2708d0e68810860b73ca9c22ebe06/OverrideAssetsLoop.xcassets/AppIcon.appiconset/icon_29pt@3x.png
--------------------------------------------------------------------------------
/OverrideAssetsLoop.xcassets/AppIcon.appiconset/icon_40pt.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/LoopKit/LoopWorkspace/5bac6d2c62c2708d0e68810860b73ca9c22ebe06/OverrideAssetsLoop.xcassets/AppIcon.appiconset/icon_40pt.png
--------------------------------------------------------------------------------
/OverrideAssetsLoop.xcassets/AppIcon.appiconset/icon_40pt@2x-1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/LoopKit/LoopWorkspace/5bac6d2c62c2708d0e68810860b73ca9c22ebe06/OverrideAssetsLoop.xcassets/AppIcon.appiconset/icon_40pt@2x-1.png
--------------------------------------------------------------------------------
/OverrideAssetsLoop.xcassets/AppIcon.appiconset/icon_40pt@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/LoopKit/LoopWorkspace/5bac6d2c62c2708d0e68810860b73ca9c22ebe06/OverrideAssetsLoop.xcassets/AppIcon.appiconset/icon_40pt@2x.png
--------------------------------------------------------------------------------
/OverrideAssetsLoop.xcassets/AppIcon.appiconset/icon_40pt@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/LoopKit/LoopWorkspace/5bac6d2c62c2708d0e68810860b73ca9c22ebe06/OverrideAssetsLoop.xcassets/AppIcon.appiconset/icon_40pt@3x.png
--------------------------------------------------------------------------------
/OverrideAssetsLoop.xcassets/AppIcon.appiconset/icon_60pt@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/LoopKit/LoopWorkspace/5bac6d2c62c2708d0e68810860b73ca9c22ebe06/OverrideAssetsLoop.xcassets/AppIcon.appiconset/icon_60pt@2x.png
--------------------------------------------------------------------------------
/OverrideAssetsLoop.xcassets/AppIcon.appiconset/icon_60pt@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/LoopKit/LoopWorkspace/5bac6d2c62c2708d0e68810860b73ca9c22ebe06/OverrideAssetsLoop.xcassets/AppIcon.appiconset/icon_60pt@3x.png
--------------------------------------------------------------------------------
/OverrideAssetsLoop.xcassets/AppIcon.appiconset/icon_76pt.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/LoopKit/LoopWorkspace/5bac6d2c62c2708d0e68810860b73ca9c22ebe06/OverrideAssetsLoop.xcassets/AppIcon.appiconset/icon_76pt.png
--------------------------------------------------------------------------------
/OverrideAssetsLoop.xcassets/AppIcon.appiconset/icon_76pt@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/LoopKit/LoopWorkspace/5bac6d2c62c2708d0e68810860b73ca9c22ebe06/OverrideAssetsLoop.xcassets/AppIcon.appiconset/icon_76pt@2x.png
--------------------------------------------------------------------------------
/OverrideAssetsLoop.xcassets/AppIcon.appiconset/icon_83.5@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/LoopKit/LoopWorkspace/5bac6d2c62c2708d0e68810860b73ca9c22ebe06/OverrideAssetsLoop.xcassets/AppIcon.appiconset/icon_83.5@2x.png
--------------------------------------------------------------------------------
/OverrideAssetsLoop.xcassets/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "author" : "xcode",
4 | "version" : 1
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/OverrideAssetsWatchApp.xcassets/AppIcon.appiconset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "filename" : "icon_24pt@2x.png",
5 | "idiom" : "watch",
6 | "role" : "notificationCenter",
7 | "scale" : "2x",
8 | "size" : "24x24",
9 | "subtype" : "38mm"
10 | },
11 | {
12 | "filename" : "icon_27.5pt@2x.png",
13 | "idiom" : "watch",
14 | "role" : "notificationCenter",
15 | "scale" : "2x",
16 | "size" : "27.5x27.5",
17 | "subtype" : "42mm"
18 | },
19 | {
20 | "filename" : "icon_29pt@2x.png",
21 | "idiom" : "watch",
22 | "role" : "companionSettings",
23 | "scale" : "2x",
24 | "size" : "29x29"
25 | },
26 | {
27 | "filename" : "icon_29pt@3x.png",
28 | "idiom" : "watch",
29 | "role" : "companionSettings",
30 | "scale" : "3x",
31 | "size" : "29x29"
32 | },
33 | {
34 | "idiom" : "watch",
35 | "role" : "notificationCenter",
36 | "scale" : "2x",
37 | "size" : "33x33",
38 | "subtype" : "45mm"
39 | },
40 | {
41 | "filename" : "icon_40pt@2x.png",
42 | "idiom" : "watch",
43 | "role" : "appLauncher",
44 | "scale" : "2x",
45 | "size" : "40x40",
46 | "subtype" : "38mm"
47 | },
48 | {
49 | "filename" : "icon_44pt@2x.png",
50 | "idiom" : "watch",
51 | "role" : "appLauncher",
52 | "scale" : "2x",
53 | "size" : "44x44",
54 | "subtype" : "40mm"
55 | },
56 | {
57 | "idiom" : "watch",
58 | "role" : "appLauncher",
59 | "scale" : "2x",
60 | "size" : "46x46",
61 | "subtype" : "41mm"
62 | },
63 | {
64 | "filename" : "icon_50pt@2x.png",
65 | "idiom" : "watch",
66 | "role" : "appLauncher",
67 | "scale" : "2x",
68 | "size" : "50x50",
69 | "subtype" : "44mm"
70 | },
71 | {
72 | "idiom" : "watch",
73 | "role" : "appLauncher",
74 | "scale" : "2x",
75 | "size" : "51x51",
76 | "subtype" : "45mm"
77 | },
78 | {
79 | "idiom" : "watch",
80 | "role" : "appLauncher",
81 | "scale" : "2x",
82 | "size" : "54x54",
83 | "subtype" : "49mm"
84 | },
85 | {
86 | "filename" : "icon_86pt@2x.png",
87 | "idiom" : "watch",
88 | "role" : "quickLook",
89 | "scale" : "2x",
90 | "size" : "86x86",
91 | "subtype" : "38mm"
92 | },
93 | {
94 | "filename" : "icon_98pt@2x.png",
95 | "idiom" : "watch",
96 | "role" : "quickLook",
97 | "scale" : "2x",
98 | "size" : "98x98",
99 | "subtype" : "42mm"
100 | },
101 | {
102 | "filename" : "icon_108pt@2x.png",
103 | "idiom" : "watch",
104 | "role" : "quickLook",
105 | "scale" : "2x",
106 | "size" : "108x108",
107 | "subtype" : "44mm"
108 | },
109 | {
110 | "idiom" : "watch",
111 | "role" : "quickLook",
112 | "scale" : "2x",
113 | "size" : "117x117",
114 | "subtype" : "45mm"
115 | },
116 | {
117 | "idiom" : "watch",
118 | "role" : "quickLook",
119 | "scale" : "2x",
120 | "size" : "129x129",
121 | "subtype" : "49mm"
122 | },
123 | {
124 | "filename" : "Icon.png",
125 | "idiom" : "watch-marketing",
126 | "scale" : "1x",
127 | "size" : "1024x1024"
128 | }
129 | ],
130 | "info" : {
131 | "author" : "xcode",
132 | "version" : 1
133 | }
134 | }
135 |
--------------------------------------------------------------------------------
/OverrideAssetsWatchApp.xcassets/AppIcon.appiconset/Icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/LoopKit/LoopWorkspace/5bac6d2c62c2708d0e68810860b73ca9c22ebe06/OverrideAssetsWatchApp.xcassets/AppIcon.appiconset/Icon.png
--------------------------------------------------------------------------------
/OverrideAssetsWatchApp.xcassets/AppIcon.appiconset/icon_108pt@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/LoopKit/LoopWorkspace/5bac6d2c62c2708d0e68810860b73ca9c22ebe06/OverrideAssetsWatchApp.xcassets/AppIcon.appiconset/icon_108pt@2x.png
--------------------------------------------------------------------------------
/OverrideAssetsWatchApp.xcassets/AppIcon.appiconset/icon_24pt@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/LoopKit/LoopWorkspace/5bac6d2c62c2708d0e68810860b73ca9c22ebe06/OverrideAssetsWatchApp.xcassets/AppIcon.appiconset/icon_24pt@2x.png
--------------------------------------------------------------------------------
/OverrideAssetsWatchApp.xcassets/AppIcon.appiconset/icon_27.5pt@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/LoopKit/LoopWorkspace/5bac6d2c62c2708d0e68810860b73ca9c22ebe06/OverrideAssetsWatchApp.xcassets/AppIcon.appiconset/icon_27.5pt@2x.png
--------------------------------------------------------------------------------
/OverrideAssetsWatchApp.xcassets/AppIcon.appiconset/icon_29pt@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/LoopKit/LoopWorkspace/5bac6d2c62c2708d0e68810860b73ca9c22ebe06/OverrideAssetsWatchApp.xcassets/AppIcon.appiconset/icon_29pt@2x.png
--------------------------------------------------------------------------------
/OverrideAssetsWatchApp.xcassets/AppIcon.appiconset/icon_29pt@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/LoopKit/LoopWorkspace/5bac6d2c62c2708d0e68810860b73ca9c22ebe06/OverrideAssetsWatchApp.xcassets/AppIcon.appiconset/icon_29pt@3x.png
--------------------------------------------------------------------------------
/OverrideAssetsWatchApp.xcassets/AppIcon.appiconset/icon_40pt@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/LoopKit/LoopWorkspace/5bac6d2c62c2708d0e68810860b73ca9c22ebe06/OverrideAssetsWatchApp.xcassets/AppIcon.appiconset/icon_40pt@2x.png
--------------------------------------------------------------------------------
/OverrideAssetsWatchApp.xcassets/AppIcon.appiconset/icon_44pt@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/LoopKit/LoopWorkspace/5bac6d2c62c2708d0e68810860b73ca9c22ebe06/OverrideAssetsWatchApp.xcassets/AppIcon.appiconset/icon_44pt@2x.png
--------------------------------------------------------------------------------
/OverrideAssetsWatchApp.xcassets/AppIcon.appiconset/icon_50pt@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/LoopKit/LoopWorkspace/5bac6d2c62c2708d0e68810860b73ca9c22ebe06/OverrideAssetsWatchApp.xcassets/AppIcon.appiconset/icon_50pt@2x.png
--------------------------------------------------------------------------------
/OverrideAssetsWatchApp.xcassets/AppIcon.appiconset/icon_86pt@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/LoopKit/LoopWorkspace/5bac6d2c62c2708d0e68810860b73ca9c22ebe06/OverrideAssetsWatchApp.xcassets/AppIcon.appiconset/icon_86pt@2x.png
--------------------------------------------------------------------------------
/OverrideAssetsWatchApp.xcassets/AppIcon.appiconset/icon_98pt@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/LoopKit/LoopWorkspace/5bac6d2c62c2708d0e68810860b73ca9c22ebe06/OverrideAssetsWatchApp.xcassets/AppIcon.appiconset/icon_98pt@2x.png
--------------------------------------------------------------------------------
/OverrideAssetsWatchApp.xcassets/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "author" : "xcode",
4 | "version" : 1
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # LoopWorkspace
2 |
3 | The Loop app can be built using GitHub in a browser on any computer or using a Mac with Xcode.
4 |
5 | * Non-developers may prefer the GitHub method
6 | * Developers or Loopers who want full build control may prefer the Mac/Xcode method
7 |
8 | ## GitHub Build Instructions
9 |
10 | The GitHub Build Instructions are at this [link](fastlane/testflight.md) and further expanded in [LoopDocs: Browser Build](https://loopkit.github.io/loopdocs/gh-actions/gh-overview/).
11 |
12 | ## Mac/Xcode Build Instructions
13 |
14 | The rest of this README contains information needed for Mac/Xcode build. Additonal instructions are found in [LoopDocs: Mac/Xcode Build](https://loopkit.github.io/loopdocs/build/overview/).
15 |
16 | ### Clone
17 |
18 | This repository uses git submodules to pull in the various workspace dependencies.
19 |
20 | To clone this repo:
21 |
22 | ```
23 | git clone --branch= --recurse-submodules https://github.com/LoopKit/LoopWorkspace
24 | ```
25 |
26 | Replace `` with the initial LoopWorkspace repository branch you wish to checkout.
27 |
28 | ### Open
29 |
30 | Change to the cloned directory and open the workspace in Xcode:
31 |
32 | ```
33 | cd LoopWorkspace
34 | xed .
35 | ```
36 |
37 | ### Input your development team
38 |
39 | You should be able to build to a simulator without changing anything. But if you wish to build to a real device, you'll need a developer account, and you'll need to tell Xcode about your team id, which you can find at https://developer.apple.com/.
40 |
41 | Select the LoopConfigOverride file in Xcode's project navigator, uncomment the `LOOP_DEVELOPMENT_TEAM`, and replace the existing team id with your own id.
42 |
43 | ### Build
44 |
45 | Select the "LoopWorkspace" scheme (not the "Loop" scheme) and Build, Run, or Test.
46 |
--------------------------------------------------------------------------------
/Scripts/export_localizations.sh:
--------------------------------------------------------------------------------
1 | #!/bin/zsh
2 |
3 | set -e
4 | set -u
5 |
6 | : "$LOKALISE_TOKEN"
7 |
8 | LANGUAGES=(ar cs ru en zh-Hans nl fr de it nb pl es ja pt-BR vi da sv fi ro tr he sk hi)
9 |
10 | argstring="${LANGUAGES[@]/#/-exportLanguage }"
11 | IFS=" "; args=( $=argstring )
12 |
13 | xcodebuild -scheme LoopWorkspace -exportLocalizations -localizationPath xclocs $args
14 |
15 | mkdir -p xliff_out
16 | find xclocs -name '*.xliff' -exec cp {} xliff_out \;
17 |
18 |
--------------------------------------------------------------------------------
/Scripts/import_localizations.sh:
--------------------------------------------------------------------------------
1 | #!/bin/zsh
2 |
3 | # Install the Lokalise command line tools from https://github.com/lokalise/lokalise-cli-2-go
4 | # Generate an API Token (not an SDK Token!) following the instructions here: https://docs.lokalise.com/en/articles/1929556-api-tokens
5 | # export LOKALISE_TOKEN=""
6 | # export GH_TOKEN=""
7 |
8 | set -e
9 | set -u
10 |
11 | : "$LOKALISE_TOKEN"
12 | : "$GH_TOKEN"
13 |
14 | date=`date`
15 |
16 | # Fetch translations from Lokalise
17 | rm -rf xliff_in
18 | lokalise2 \
19 | --token "$LOKALISE_TOKEN" \
20 | --project-id "414338966417c70d7055e2.75119857" \
21 | file download \
22 | --format xliff \
23 | --bundle-structure "%LANG_ISO%.%FORMAT%" \
24 | --original-filenames=false \
25 | --placeholder-format ios \
26 | --export-empty-as skip \
27 | --replace-breaks=false \
28 | --unzip-to ./xliff_in
29 |
30 | projects=(LoopKit:AmplitudeService:dev LoopKit:CGMBLEKit:dev LoopKit:G7SensorKit:main LoopKit:LogglyService:dev LoopKit:Loop:dev LoopKit:LoopKit:dev LoopKit:LoopOnboarding:dev LoopKit:LoopSupport:dev LoopKit:NightscoutRemoteCGM:dev LoopKit:NightscoutService:dev LoopKit:OmniBLE:dev LoopKit:TidepoolService:dev LoopKit:dexcom-share-client-swift:dev LoopKit:RileyLinkKit:dev LoopKit:OmniKit:main LoopKit:MinimedKit:main LoopKit:LibreTransmitter:main)
31 |
32 | for project in ${projects}; do
33 | echo "Prepping $project"
34 | IFS=":" read user dir branch <<< "$project"
35 | echo "parts = $user $dir $branch"
36 | cd $dir
37 | git checkout $branch
38 | git pull
39 | git branch -D translations || true
40 | git checkout -b translations || true
41 | cd -
42 | done
43 |
44 | # Build Loop
45 | set -o pipefail && time xcodebuild -workspace LoopWorkspace.xcworkspace -scheme 'LoopWorkspace' build | xcpretty
46 |
47 |
48 | # Apply translations
49 | foreach file in xliff_in/*.xliff
50 | xcodebuild -workspace LoopWorkspace.xcworkspace -scheme "LoopWorkspace" -importLocalizations -localizationPath $file
51 | end
52 |
53 |
54 | # Generate branches, commit and push.
55 | for project in ${projects}; do
56 | echo "Commiting $project"
57 | IFS=":" read user dir branch <<< "$project"
58 | echo "parts = $user $dir $branch"
59 | cd $dir
60 | git add .
61 | if git commit -am "Updated translations from Lokalise on ${date}"; then
62 | git push -f
63 | pr=$(gh pr create -B $branch -R $user/$dir --fill 2>&1 | grep http)
64 | echo "PR = $pr"
65 | open $pr
66 | fi
67 | cd -
68 | done
69 |
70 | # Reset
71 | #for project in ${projects}; do
72 | # echo "Commiting $project"
73 | # IFS=":" read user dir branch <<< "$project"
74 | # echo "parts = $user $dir $branch"
75 | # cd $dir
76 | # git checkout $branch
77 | # git pull
78 | # cd -
79 | #done
80 |
--------------------------------------------------------------------------------
/Scripts/sync.swift:
--------------------------------------------------------------------------------
1 | #!/usr/bin/swift sh
2 |
3 | // Depends on swift-sh. Install with: `brew install swift-sh`
4 |
5 | import Foundation
6 | import Cocoa
7 |
8 | import AsyncSwiftGit // @bdewey
9 | import OctoKit // nerdishbynature/octokit.swift == main
10 |
11 | let createPRs = true
12 |
13 | guard CommandLine.arguments.count == 3 else {
14 | print("usage: sync.swift ")
15 | exit(1)
16 | }
17 | let pullRequestName = CommandLine.arguments[1] // example: "LOOP-4688 DIY Sync"
18 | let syncBranch = CommandLine.arguments[2] // example: "ps/LOOP-4688/diy-sync"
19 |
20 | enum EnvError: Error {
21 | case missing(String)
22 | }
23 |
24 | func getEnv(_ name: String) throws -> String {
25 | guard let value = ProcessInfo.processInfo.environment[name] else {
26 | throw EnvError.missing(name)
27 | }
28 | return value
29 | }
30 |
31 | let ghUsername = try getEnv("GH_USERNAME")
32 | let ghToken = try getEnv("GH_TOKEN")
33 | let ghCommitterName = try getEnv("GH_COMMITTER_NAME")
34 | let ghCommitterEmail = try getEnv("GH_COMMITTER_EMAIL")
35 |
36 | struct Project {
37 | let project: String
38 | let branch: String
39 | let subdir: String
40 |
41 | init(_ project: String, _ branch: String, _ subdir: String = "") {
42 | self.project = project
43 | self.branch = branch
44 | self.subdir = subdir
45 | }
46 |
47 | var path: String {
48 | if subdir.isEmpty {
49 | return project
50 | } else {
51 | return subdir + "/" + project
52 | }
53 | }
54 | }
55 |
56 | let projects = [
57 | Project("Loop", "dev"),
58 | Project("LoopKit", "dev"),
59 | Project("TidepoolService", "dev"),
60 | Project("CGMBLEKit", "dev"),
61 | Project("dexcom-share-client-swift", "dev"),
62 | Project("RileyLinkKit", "dev"),
63 | Project("NightscoutService", "dev"),
64 | Project("LoopOnboarding", "dev"),
65 | Project("AmplitudeService", "dev"),
66 | Project("LogglyService", "dev"),
67 | Project("MixpanelService", "main"),
68 | Project("OmniBLE", "dev"),
69 | Project("NightscoutRemoteCGM", "dev"),
70 | Project("LoopSupport", "dev"),
71 | Project("G7SensorKit", "main"),
72 | Project("OmniKit", "main"),
73 | Project("MinimedKit", "main"),
74 | Project("LibreTransmitter", "main")
75 | ]
76 |
77 | let fm = FileManager.default
78 | let loopkit = URL(string: "https://github.com/LoopKit")!
79 | let tidepool = URL(string: "https://github.com/tidepool-org")!
80 | let incomingRemote = "tidepool"
81 |
82 | let octokit = Octokit(TokenConfiguration(ghToken))
83 |
84 | let credentials = Credentials.plaintext(username: ghUsername, password: ghToken)
85 | let signature = try! Signature(name: ghCommitterName, email: ghCommitterEmail)
86 |
87 | for project in projects {
88 | let dest = URL(string: fm.currentDirectoryPath)!.appendingPathComponent(project.path)
89 | let repository: AsyncSwiftGit.Repository
90 | if !fm.fileExists(atPath: dest.path) {
91 | print("Cloning \(project.project)")
92 | let url = loopkit.appendingPathComponent(project.project)
93 | repository = try await Repository.clone(from: url, to: dest)
94 | print("Cloned \(project.project)")
95 | } else {
96 | print("Already Exists: \(project.path)")
97 | repository = try Repository(openAt: dest)
98 | }
99 |
100 | let incomingRemoteURL = tidepool.appendingPathComponent(project.project)
101 |
102 | // Add remote if it doesn't exist, and fetch latest changes
103 | if (try? repository.remoteURL(for: incomingRemote)) == nil {
104 | try repository.addRemote(incomingRemote, url: incomingRemoteURL)
105 | }
106 | try await repository.fetch(remote: incomingRemote)
107 |
108 | // Create and checkout the branch where sync changesets will go ("tidepool-sync")
109 | if !(try repository.branchExists(named: syncBranch)) {
110 | try repository.createBranch(named: syncBranch, target: "origin/\(project.branch)")
111 | }
112 | try await repository.checkout(revspec: syncBranch)
113 |
114 | // Merge changes from tidepool to diy
115 | try await repository.merge(revisionSpecification: "\(incomingRemote)/\(project.branch)", signature: signature)
116 |
117 | let originTree = try repository.lookupTree(for: "origin/\(project.branch)")
118 | let diff = try repository.diff(originTree, repository.headTree)
119 |
120 | guard diff.count > 0 else {
121 | print("No incoming changes; skipping PR creation.")
122 | try await repository.checkout(revspec: project.branch)
123 | continue
124 | }
125 | print("Found diffs: \(diff)")
126 |
127 | // Push changes up to origin
128 | let refspec = "refs/heads/" + syncBranch + ":refs/heads/" + syncBranch
129 | print("Pushing \(refspec) to \(project.project)")
130 | try await repository.push(remoteName: "origin", refspecs: [refspec], credentials: credentials)
131 |
132 | if createPRs {
133 | // Make sure a PR exists, or create it
134 |
135 | let prs = try await octokit.pullRequests(owner: "LoopKit", repository: project.project, base: project.branch, head:"LoopKit:" + syncBranch)
136 | let pr: PullRequest
137 | if prs.count == 0 {
138 | pr = try await octokit.createPullRequest(owner: "LoopKit", repo: project.project, title: pullRequestName, head: "LoopKit:" + syncBranch, base: project.branch, body: "")
139 | print("PR = \(pr)")
140 | } else {
141 | pr = prs.first!
142 | }
143 | if let url = pr.htmlURL {
144 | if NSWorkspace.shared.open(url) {
145 | print("default browser was successfully opened")
146 | }
147 | }
148 | } else {
149 | print("Skipping PR creation")
150 | }
151 | }
152 |
153 |
--------------------------------------------------------------------------------
/Scripts/update_submodule_refs.sh:
--------------------------------------------------------------------------------
1 | #!/bin/zsh
2 |
3 | projects=(LoopKit:AmplitudeService:dev LoopKit:CGMBLEKit:dev LoopKit:G7SensorKit:main LoopKit:LogglyService:dev LoopKit:Loop:dev LoopKit:LoopKit:dev LoopKit:LoopOnboarding:dev LoopKit:LoopSupport:dev LoopKit:NightscoutRemoteCGM:dev LoopKit:NightscoutService:dev LoopKit:OmniBLE:dev LoopKit:TidepoolService:dev LoopKit:dexcom-share-client-swift:dev ps2:RileyLinkKit:dev LoopKit:OmniKit:main LoopKit:MinimedKit:main LoopKit:LibreTransmitter:main LoopKit:MixpanelService:main)
4 |
5 | for project in ${projects}; do
6 | echo "Updating to $project"
7 | IFS=":" read user dir branch <<< "$project"
8 | echo "Updating to $branch on $user/$project"
9 | cd $dir
10 | git checkout $branch
11 | #git branch -D tidepool-sync
12 | git pull
13 | cd -
14 | done
15 |
16 |
--------------------------------------------------------------------------------
/VersionOverride.xcconfig:
--------------------------------------------------------------------------------
1 | //
2 | // VersionOverride.xcconfig
3 | // LoopWorkspace
4 | //
5 | // Created 3/31/2025
6 | // Copyright © 2020 LoopKit Authors. All rights reserved.
7 | //
8 |
9 | // Version [for DIY Loop]
10 | // configure the version number in LoopWorkspace
11 | LOOP_MARKETING_VERSION = 3.6.0
12 | CURRENT_PROJECT_VERSION = 57
13 |
--------------------------------------------------------------------------------
/docs/scheme-selection.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/LoopKit/LoopWorkspace/5bac6d2c62c2708d0e68810860b73ca9c22ebe06/docs/scheme-selection.png
--------------------------------------------------------------------------------
/fastlane/Fastfile:
--------------------------------------------------------------------------------
1 | # This file contains the fastlane.tools configuration
2 | # You can find the documentation at https://docs.fastlane.tools
3 | #
4 | # For a list of all available actions, check out
5 | #
6 | # https://docs.fastlane.tools/actions
7 | #
8 | # For a list of all available plugins, check out
9 | #
10 | # https://docs.fastlane.tools/plugins/available-plugins
11 | #
12 |
13 | default_platform(:ios)
14 |
15 | TEAMID = ENV["TEAMID"]
16 | GH_PAT = ENV["GH_PAT"]
17 | GITHUB_WORKSPACE = ENV["GITHUB_WORKSPACE"]
18 | GITHUB_REPOSITORY_OWNER = ENV["GITHUB_REPOSITORY_OWNER"]
19 | FASTLANE_KEY_ID = ENV["FASTLANE_KEY_ID"]
20 | FASTLANE_ISSUER_ID = ENV["FASTLANE_ISSUER_ID"]
21 | FASTLANE_KEY = ENV["FASTLANE_KEY"]
22 | DEVICE_NAME = ENV["DEVICE_NAME"]
23 | DEVICE_ID = ENV["DEVICE_ID"]
24 | ENV["FASTLANE_XCODEBUILD_SETTINGS_TIMEOUT"] = "120"
25 |
26 | platform :ios do
27 | desc "Build Loop"
28 | lane :build_loop do
29 | setup_ci if ENV['CI']
30 |
31 | update_project_team(
32 | path: "#{GITHUB_WORKSPACE}/Loop/Loop.xcodeproj",
33 | teamid: "#{TEAMID}"
34 | )
35 |
36 | api_key = app_store_connect_api_key(
37 | key_id: "#{FASTLANE_KEY_ID}",
38 | issuer_id: "#{FASTLANE_ISSUER_ID}",
39 | key_content: "#{FASTLANE_KEY}"
40 | )
41 |
42 | previous_build_number = latest_testflight_build_number(
43 | app_identifier: "com.#{TEAMID}.loopkit.Loop",
44 | api_key: api_key,
45 | )
46 |
47 | current_build_number = previous_build_number + 1
48 |
49 | increment_build_number(
50 | xcodeproj: "#{GITHUB_WORKSPACE}/Loop/Loop.xcodeproj",
51 | build_number: current_build_number
52 | )
53 |
54 | match(
55 | type: "appstore",
56 | git_basic_authorization: Base64.strict_encode64("#{GITHUB_REPOSITORY_OWNER}:#{GH_PAT}"),
57 | app_identifier: [
58 | "com.#{TEAMID}.loopkit.Loop",
59 | "com.#{TEAMID}.loopkit.Loop.statuswidget",
60 | "com.#{TEAMID}.loopkit.Loop.LoopWatch.watchkitextension",
61 | "com.#{TEAMID}.loopkit.Loop.LoopWatch",
62 | "com.#{TEAMID}.loopkit.Loop.Loop-Intent-Extension",
63 | "com.#{TEAMID}.loopkit.Loop.LoopWidgetExtension"
64 | ]
65 | )
66 |
67 | previous_build_number = latest_testflight_build_number(
68 | app_identifier: "com.#{TEAMID}.loopkit.Loop",
69 | api_key: api_key,
70 | )
71 |
72 | current_build_number = previous_build_number + 1
73 |
74 | increment_build_number(
75 | xcodeproj: "#{GITHUB_WORKSPACE}/Loop/Loop.xcodeproj",
76 | build_number: current_build_number
77 | )
78 |
79 | mapping = Actions.lane_context[
80 | SharedValues::MATCH_PROVISIONING_PROFILE_MAPPING
81 | ]
82 |
83 | update_code_signing_settings(
84 | path: "#{GITHUB_WORKSPACE}/Loop/Loop.xcodeproj",
85 | profile_name: mapping["com.#{TEAMID}.loopkit.Loop"],
86 | code_sign_identity: "iPhone Distribution",
87 | targets: ["Loop"]
88 | )
89 |
90 | update_code_signing_settings(
91 | path: "#{GITHUB_WORKSPACE}/Loop/Loop.xcodeproj",
92 | code_sign_identity: "iPhone Distribution",
93 | targets: ["LoopCore", "LoopCore-watchOS", "LoopUI"]
94 | )
95 |
96 | update_code_signing_settings(
97 | path: "#{GITHUB_WORKSPACE}/Loop/Loop.xcodeproj",
98 | profile_name: mapping["com.#{TEAMID}.loopkit.Loop.statuswidget"],
99 | code_sign_identity: "iPhone Distribution",
100 | targets: ["Loop Status Extension"]
101 | )
102 |
103 | update_code_signing_settings(
104 | path: "#{GITHUB_WORKSPACE}/Loop/Loop.xcodeproj",
105 | profile_name: mapping["com.#{TEAMID}.loopkit.Loop.LoopWatch.watchkitextension"],
106 | code_sign_identity: "iPhone Distribution",
107 | targets: ["WatchApp Extension"]
108 | )
109 |
110 | update_code_signing_settings(
111 | path: "#{GITHUB_WORKSPACE}/Loop/Loop.xcodeproj",
112 | profile_name: mapping["com.#{TEAMID}.loopkit.Loop.LoopWatch"],
113 | code_sign_identity: "iPhone Distribution",
114 | targets: ["WatchApp"]
115 | )
116 |
117 | update_code_signing_settings(
118 | path: "#{GITHUB_WORKSPACE}/Loop/Loop.xcodeproj",
119 | profile_name: mapping["com.#{TEAMID}.loopkit.Loop.Loop-Intent-Extension"],
120 | code_sign_identity: "iPhone Distribution",
121 | targets: ["Loop Intent Extension"]
122 | )
123 |
124 | update_code_signing_settings(
125 | path: "#{GITHUB_WORKSPACE}/Loop/Loop.xcodeproj",
126 | profile_name: mapping["com.#{TEAMID}.loopkit.Loop.LoopWidgetExtension"],
127 | code_sign_identity: "iPhone Distribution",
128 | targets: ["Loop Widget Extension"]
129 | )
130 |
131 | gym(
132 | export_method: "app-store",
133 | scheme: "LoopWorkspace",
134 | output_name: "Loop.ipa",
135 | configuration: "Release",
136 | destination: 'generic/platform=iOS',
137 | buildlog_path: 'buildlog'
138 | )
139 |
140 | copy_artifacts(
141 | target_path: "artifacts",
142 | artifacts: ["*.mobileprovision", "*.ipa", "*.dSYM.zip"]
143 | )
144 | end
145 |
146 | desc "Push to TestFlight"
147 | lane :release do
148 | api_key = app_store_connect_api_key(
149 | key_id: "#{FASTLANE_KEY_ID}",
150 | issuer_id: "#{FASTLANE_ISSUER_ID}",
151 | key_content: "#{FASTLANE_KEY}"
152 | )
153 |
154 | upload_to_testflight(
155 | api_key: api_key,
156 | skip_submission: false,
157 | ipa: "Loop.ipa",
158 | skip_waiting_for_build_processing: true,
159 | )
160 | end
161 |
162 | desc "Provision Identifiers and Certificates"
163 | lane :identifiers do
164 | setup_ci if ENV['CI']
165 | ENV["MATCH_READONLY"] = false.to_s
166 |
167 | app_store_connect_api_key(
168 | key_id: "#{FASTLANE_KEY_ID}",
169 | issuer_id: "#{FASTLANE_ISSUER_ID}",
170 | key_content: "#{FASTLANE_KEY}"
171 | )
172 |
173 | def configure_bundle_id(name, identifier, capabilities)
174 | bundle_id = Spaceship::ConnectAPI::BundleId.find(identifier) || Spaceship::ConnectAPI::BundleId.create(name: name, identifier: identifier)
175 | capabilities.each { |capability|
176 | bundle_id.create_capability(capability)
177 | }
178 | end
179 |
180 | configure_bundle_id("Loop", "com.#{TEAMID}.loopkit.Loop", [
181 | Spaceship::ConnectAPI::BundleIdCapability::Type::APP_GROUPS,
182 | Spaceship::ConnectAPI::BundleIdCapability::Type::HEALTHKIT,
183 | Spaceship::ConnectAPI::BundleIdCapability::Type::PUSH_NOTIFICATIONS,
184 | Spaceship::ConnectAPI::BundleIdCapability::Type::SIRIKIT,
185 | Spaceship::ConnectAPI::BundleIdCapability::Type::USERNOTIFICATIONS_TIMESENSITIVE,
186 | Spaceship::ConnectAPI::BundleIdCapability::Type::NFC_TAG_READING
187 | ])
188 |
189 | configure_bundle_id("Loop Intent Extension", "com.#{TEAMID}.loopkit.Loop.Loop-Intent-Extension", [
190 | Spaceship::ConnectAPI::BundleIdCapability::Type::APP_GROUPS
191 | ])
192 |
193 | configure_bundle_id("Loop Status Extension", "com.#{TEAMID}.loopkit.Loop.statuswidget", [
194 | Spaceship::ConnectAPI::BundleIdCapability::Type::APP_GROUPS
195 | ])
196 |
197 | configure_bundle_id("WatchApp", "com.#{TEAMID}.loopkit.Loop.LoopWatch", [])
198 |
199 | configure_bundle_id("WatchApp Extension", "com.#{TEAMID}.loopkit.Loop.LoopWatch.watchkitextension", [
200 | Spaceship::ConnectAPI::BundleIdCapability::Type::HEALTHKIT,
201 | Spaceship::ConnectAPI::BundleIdCapability::Type::SIRIKIT
202 | ])
203 |
204 | configure_bundle_id("Loop Widget Extension", "com.#{TEAMID}.loopkit.Loop.LoopWidgetExtension", [
205 | Spaceship::ConnectAPI::BundleIdCapability::Type::APP_GROUPS
206 | ])
207 |
208 | end
209 |
210 | desc "Provision Certificates"
211 | lane :certs do
212 | setup_ci if ENV['CI']
213 | ENV["MATCH_READONLY"] = false.to_s
214 |
215 | app_store_connect_api_key(
216 | key_id: "#{FASTLANE_KEY_ID}",
217 | issuer_id: "#{FASTLANE_ISSUER_ID}",
218 | key_content: "#{FASTLANE_KEY}"
219 | )
220 |
221 | match(
222 | type: "appstore",
223 | force: false,
224 | verbose: true,
225 | git_basic_authorization: Base64.strict_encode64("#{GITHUB_REPOSITORY_OWNER}:#{GH_PAT}"),
226 | app_identifier: [
227 | "com.#{TEAMID}.loopkit.Loop",
228 | "com.#{TEAMID}.loopkit.Loop.statuswidget",
229 | "com.#{TEAMID}.loopkit.Loop.LoopWatch.watchkitextension",
230 | "com.#{TEAMID}.loopkit.Loop.LoopWatch",
231 | "com.#{TEAMID}.loopkit.Loop.Loop-Intent-Extension",
232 | "com.#{TEAMID}.loopkit.Loop.LoopWidgetExtension",
233 | ]
234 | )
235 | end
236 |
237 | desc "Validate Secrets"
238 | lane :validate_secrets do
239 | setup_ci if ENV['CI']
240 | ENV["MATCH_READONLY"] = true.to_s
241 |
242 | app_store_connect_api_key(
243 | key_id: "#{FASTLANE_KEY_ID}",
244 | issuer_id: "#{FASTLANE_ISSUER_ID}",
245 | key_content: "#{FASTLANE_KEY}"
246 | )
247 |
248 | def find_bundle_id(identifier)
249 | bundle_id = Spaceship::ConnectAPI::BundleId.find(identifier)
250 | end
251 |
252 | find_bundle_id("com.#{TEAMID}.loopkit.Loop")
253 |
254 | match(
255 | type: "appstore",
256 | git_basic_authorization: Base64.strict_encode64("#{GITHUB_REPOSITORY_OWNER}:#{GH_PAT}"),
257 | app_identifier: [],
258 | )
259 |
260 | end
261 |
262 | desc "Nuke Certs"
263 | lane :nuke_certs do
264 | setup_ci if ENV['CI']
265 | ENV["MATCH_READONLY"] = false.to_s
266 |
267 | app_store_connect_api_key(
268 | key_id: "#{FASTLANE_KEY_ID}",
269 | issuer_id: "#{FASTLANE_ISSUER_ID}",
270 | key_content: "#{FASTLANE_KEY}"
271 | )
272 |
273 | match_nuke(
274 | type: "appstore",
275 | team_id: "#{TEAMID}",
276 | skip_confirmation: true,
277 | git_basic_authorization: Base64.strict_encode64("#{GITHUB_REPOSITORY_OWNER}:#{GH_PAT}")
278 | )
279 | end
280 |
281 | desc "Check Certificates and Trigger Workflow for Expired or Missing Certificates"
282 | lane :check_and_renew_certificates do
283 | setup_ci if ENV['CI']
284 | ENV["MATCH_READONLY"] = false.to_s
285 |
286 | # Authenticate using App Store Connect API Key
287 | api_key = app_store_connect_api_key(
288 | key_id: ENV["FASTLANE_KEY_ID"],
289 | issuer_id: ENV["FASTLANE_ISSUER_ID"],
290 | key_content: ENV["FASTLANE_KEY"] # Ensure valid key content
291 | )
292 |
293 | # Initialize flag to track if renewal of certificates is needed
294 | new_certificate_needed = false
295 |
296 | # Fetch all certificates
297 | certificates = Spaceship::ConnectAPI::Certificate.all
298 |
299 | # Filter for Distribution Certificates
300 | distribution_certs = certificates.select { |cert| cert.certificate_type == "DISTRIBUTION" }
301 |
302 | # Handle case where no distribution certificates are found
303 | if distribution_certs.empty?
304 | puts "No Distribution certificates found! Triggering action to create certificate."
305 | new_certificate_needed = true
306 | else
307 | # Check for expiration
308 | distribution_certs.each do |cert|
309 | expiration_date = Time.parse(cert.expiration_date)
310 |
311 | puts "Current Distribution Certificate: #{cert.id}, Expiration date: #{expiration_date}"
312 |
313 | if expiration_date < Time.now
314 | puts "Distribution Certificate #{cert.id} is expired! Triggering action to renew certificate."
315 | new_certificate_needed = true
316 | else
317 | puts "Distribution certificate #{cert.id} is valid. No action required."
318 | end
319 | end
320 | end
321 |
322 | # Write result to new_certificate_needed.txt
323 | file_path = File.expand_path('new_certificate_needed.txt')
324 | File.write(file_path, new_certificate_needed ? 'true' : 'false')
325 |
326 | # Log the absolute path and contents of the new_certificate_needed.txt file
327 | puts ""
328 | puts "Absolute path of new_certificate_needed.txt: #{file_path}"
329 | new_certificate_needed_content = File.read(file_path)
330 | puts "Certificate creation or renewal needed: #{new_certificate_needed_content}"
331 | end
332 | end
--------------------------------------------------------------------------------
/fastlane/Matchfile:
--------------------------------------------------------------------------------
1 |
2 | GITHUB_REPOSITORY_OWNER ||= ENV["GITHUB_REPOSITORY_OWNER"]
3 |
4 | git_url("https://github.com/#{GITHUB_REPOSITORY_OWNER}/Match-Secrets.git")
5 |
6 | storage_mode("git")
7 |
8 | type("appstore")
9 |
10 | # The docs are available on https://docs.fastlane.tools/actions/match
11 |
--------------------------------------------------------------------------------
/fastlane/testflight.md:
--------------------------------------------------------------------------------
1 | # Using GitHub Actions + FastLane to deploy to TestFlight
2 |
3 | These instructions allow you to build your app without having access to a Mac.
4 |
5 | * You can install your app on phones using TestFlight that are not connected to your computer
6 | * You can send builds and updates to those you care for
7 | * You can install your app on your phone using only the TestFlight app if a phone was lost or the app is accidentally deleted
8 | * You do not need to worry about specific Xcode/Mac versions for a given iOS
9 |
10 | ## **Automatic Builds**
11 | >
12 | > The browser build **defaults to** automatically updating and building a new version of Loop according to this schedule:
13 | > - automatically checks for updates weekly on Wednesdays and if updates are found, it will build a new version of the app
14 | > - automatically builds once a month regardless of whether there are updates on the first of the month
15 | > - with each scheduled run (weekly or monthly), a successful Build Loop log appears - if the time is very short, it did not need to build - only the long actions (>20 minutes) built a new Loop app
16 | >
17 | > It also creates an alive branch, if you don't already have one. See [Why do I have an alive branch?](#why-do-i-have-an-alive-branch).
18 | >
19 | > The [**Optional**](#optional) section provides instructions to modify the default behavior if desired.
20 |
21 | > **Repeat Builders**
22 | > - to enable automatic build, your `GH_PAT` token must have `workflow` scope
23 | > - if you previously configured your `GH_PAT` without that scope, see [`GH_PAT` `workflow` permission](#gh_pat-workflow-permission)
24 |
25 | ## Introduction
26 |
27 | The setup steps are somewhat involved, but nearly all are one time steps. Subsequent builds are trivial. Your app must be updated once every 90 days, but it's a simple click to make a new build and can be done from anywhere. The 90-day update is a TestFlight requirement, and with this version of Loop, the build process (once you've successfully built once) is automated to update and build at least once a month.
28 |
29 | There are more detailed instructions in LoopDocs for using GitHub for Browser Builds, including troubleshooting and build errors. Please refer to:
30 |
31 | * [LoopDocs: Browser Overview](https://loopkit.github.io/loopdocs/browser/bb-overview/)
32 | * [LoopDocs: Errors with Browser](https://loopkit.github.io/loopdocs/browser/bb-errors/)
33 |
34 | Note that installing with TestFlight, (in the US), requires the Apple ID account holder to be 13 years or older. For younger Loopers, an adult must log into Media & Purchase on the child's phone to install Loop. More details on this can be found in [LoopDocs](https://loopkit.github.io/loopdocs/browser/phone-install/#testflight-for-a-child).
35 |
36 | If you build multiple apps, it is strongly recommended that you configure a free *GitHub* organization and do all your building in the organization. This means you enter items one time for the organization (6 SECRETS required to build and 1 VARIABLE required to automatically update your certificates annually). Otherwise, those 6 SECRETS must be entered for every repository. Please refer to [LoopDocs: Create a *GitHub* Organization](https://loopkit.github.io/loopdocs/browser/secrets/#create-a-free-github-organization).
37 |
38 | ## Prerequisites
39 |
40 | * A [GitHub account](https://github.com/signup). The free level comes with plenty of storage and free compute time to build loop, multiple times a day, if you wanted to.
41 | * A paid [Apple Developer account](https://developer.apple.com).
42 | * Some time. Set aside a couple of hours to perform the setup.
43 |
44 | ## Save 6 Secrets
45 |
46 | You require 6 Secrets (alphanumeric items) to use the GitHub build method and if you use the GitHub method to build more than Loop, e.g., Loop Follow or LoopCaregiver, you will use the same 6 Secrets for each app you build with this method. Each secret is identified below by `ALL_CAPITAL_LETTER_NAMES`.
47 |
48 | * Four Secrets are from your Apple Account
49 | * Two Secrets are from your GitHub account
50 | * Be sure to save the 6 Secrets in a text file using a text editor
51 | - Do **NOT** use a smart editor, which might auto-correct and change case, because these Secrets are case sensitive
52 |
53 | Refer to [LoopDocs: Make a Secrets Reference File](https://loopkit.github.io/loopdocs/browser/intro-summary/#make-a-secrets-reference-file) for a handy template to use when saving your Secrets.
54 |
55 | ## Generate App Store Connect API Key
56 |
57 | This step is common for all GitHub Browser Builds; do this step only once. You will be saving 4 Secrets from your Apple Account in this step.
58 |
59 | 1. Sign in to the [Apple developer portal page](https://developer.apple.com/account/resources/certificates/list).
60 | 1. Copy the Team ID from the upper right of the screen. Record this as your `TEAMID`.
61 | 1. Go to the [App Store Connect](https://appstoreconnect.apple.com/access/integrations/api) interface, click the "Integrations" tab, and create a new key with "Admin" access. Give it the name: "FastLane API Key".
62 | 1. Record the issuer id; this will be used for `FASTLANE_ISSUER_ID`.
63 | 1. Record the key id; this will be used for `FASTLANE_KEY_ID`.
64 | 1. Download the API key itself, and open it in a text editor. The contents of this file will be used for `FASTLANE_KEY`. Copy the full text, including the "-----BEGIN PRIVATE KEY-----" and "-----END PRIVATE KEY-----" lines.
65 |
66 | ## Create GitHub Personal Access Token
67 |
68 | If you have previously built another app using the "browser build" method, you use the same personal access token (`GH_PAT`), so skip this step. If you use a free GitHub organization to build, you still use the same personal access token. This is created using your personal GitHub username.
69 |
70 | Log into your GitHub account to create a personal access token; this is one of two GitHub secrets needed for your build.
71 |
72 | 1. Create a [new personal access token](https://github.com/settings/tokens/new):
73 | * Enter a name for your token, use "FastLane Access Token".
74 | * Change the Expiration selection to `No expiration`.
75 | * Select the `workflow` permission scope - this also selects `repo` scope.
76 | * Click "Generate token".
77 | * Copy the token and record it. It will be used below as `GH_PAT`.
78 |
79 | ## Make up a Password
80 |
81 | This is the second one of two GitHub secrets needed for your build.
82 |
83 | The first time you build with the GitHub Browser Build method for any DIY app, you will make up a password and record it as `MATCH_PASSWORD`. Note, if you later lose `MATCH_PASSWORD`, you will need to delete and make a new Match-Secrets repository (next step).
84 |
85 | ## GitHub Match-Secrets Repository
86 |
87 | A private Match-Secrets repository is automatically created under your GitHub username the first time you run a GitHub Action. Because it is a private repository - only you can see it. You will not take any direct actions with this repository; it needs to be there for GitHub to use as you progress through the steps.
88 |
89 | ## Setup GitHub LoopWorkspace Repository
90 |
91 | 1. Fork https://github.com/LoopKit/LoopWorkspace into your GitHub username (using your organization if you have one). If you already have a fork of LoopWorkspace in that username, you should not make another one. Do not rename the repository. You can continue to work with your existing fork, or delete that from GitHub and then fork again.
92 | 1. If you are using an organization, do this step at the organization level, e.g., username-org. If you are not using an organization, do this step at the repository level, e.g., username/LoopWorkspace:
93 | * Go to Settings -> Secrets and variables -> Actions and make sure the Secrets tab is open
94 | 1. For each of the following secrets, tap on "New organization secret" or "New repository secret", then add the name of the secret, along with the value you recorded for it:
95 | * `TEAMID`
96 | * `FASTLANE_ISSUER_ID`
97 | * `FASTLANE_KEY_ID`
98 | * `FASTLANE_KEY`
99 | * `GH_PAT`
100 | * `MATCH_PASSWORD`
101 | 1. If you are using an organization, do this step at the organization level, e.g., username-org. If you are not using an organization, do this step at the repository level, e.g., username/LoopWorkspace:
102 | * Go to Settings -> Secrets and variables -> Actions and make sure the Variables tab is open
103 | 1. Tap on "Create new organization variable" or "Create new repository variable", then add the name below and enter the value true. Unlike secrets, variables are visible and can be edited.
104 | * `ENABLE_NUKE_CERTS`
105 |
106 | ## Validate repository secrets
107 |
108 | This step validates most of your six Secrets and provides error messages if it detects an issue with one or more.
109 |
110 | 1. Click on the "Actions" tab of your LoopWorkspace repository and enable workflows if needed
111 | 1. On the left side, select "1. Validate Secrets".
112 | 1. On the right side, click "Run Workflow", and tap the green `Run workflow` button.
113 | 1. Wait, and within a minute or two you should see a green checkmark indicating the workflow succeeded.
114 | 1. The workflow will check if the required secrets are added and that they are correctly formatted. If errors are detected, please check the run log for details.
115 |
116 | There can be a delay after you start a workflow before the screen changes. Refresh your browser to see if it started. And if it seems to take a long time to finish - refresh your browser to see if it is done.
117 |
118 | ## Add Identifiers for Loop App
119 |
120 | 1. Click on the "Actions" tab of your LoopWorkspace repository.
121 | 1. On the left side, select "2. Add Identifiers".
122 | 1. On the right side, click "Run Workflow", and tap the green `Run workflow` button.
123 | 1. Wait, and within a minute or two you should see a green checkmark indicating the workflow succeeded.
124 |
125 | ## Create App Group
126 |
127 | If you have already built Loop via Xcode using this Apple ID, you can skip ahead to [Add App Group to Bundle Identifiers](#add-app-group-to-bundle-identifiers).
128 |
129 | 1. Go to [Register an App Group](https://developer.apple.com/account/resources/identifiers/applicationGroup/add/) on the Apple Developer site.
130 | 1. For Description, use "Loop App Group".
131 | 1. For Identifier, enter "group.com.TEAMID.loopkit.LoopGroup", substituting your team id for `TEAMID`.
132 | 1. Click "Continue" and then "Register".
133 |
134 | ## Add App Group to Bundle Identifiers
135 |
136 | Note 1 - If you previously built with Xcode, the `Names` listed below may be different, but the `Identifiers` will match. A table is provided below the steps to assist. The Add Identifier Action that you completed above generates 6 identifiers, but only 4 need to be modified as indicated in this step.
137 |
138 | Note 2 - Depending on your build history, you may find some of the Identifiers are already configured - and you are just verifying the status; but in other cases, you will need to configure the Identifiers.
139 |
140 | 1. Go to [Certificates, Identifiers & Profiles](https://developer.apple.com/account/resources/identifiers/list) on the Apple Developer site.
141 | 1. For each of the following identifier names:
142 | * Loop
143 | * Loop Intent Extension
144 | * Loop Status Extension
145 | * Loop Widget Extension
146 | 1. Click on the identifier's name.
147 | 1. On the "App Groups" capabilities, click on the "Configure" button.
148 | 1. Select the "Loop App Group"
149 | 1. Click "Continue".
150 | 1. Click "Save".
151 | 1. Click "Confirm".
152 | 1. Remember to do this for each of the identifiers above.
153 |
154 | #### Table with Name and Identifier for Loop
155 |
156 | | NAME | IDENTIFIER |
157 | |-------|------------|
158 | | Loop | com.TEAMID.loopkit.Loop |
159 | | Loop Intent Extension | com.TEAMID.loopkit.Loop.Loop-Intent-Extension |
160 | | Loop Status Extension | com.TEAMID.loopkit.Loop.statuswidget |
161 | | Loop Widget Extension | com.TEAMID.loopkit.Loop.LoopWidgetExtension |
162 | | WatchApp | com.TEAMID.loopkit.Loop.LoopWatch |
163 | | WatchAppExtension | com.TEAMID.loopkit.Loop.LoopWatch.watchkitextension |
164 |
165 |
166 | ## Create Loop App in App Store Connect
167 |
168 | If you have created a Loop app in App Store Connect before, you can skip this section.
169 |
170 | 1. Go to the [apps list](https://appstoreconnect.apple.com/apps) on App Store Connect and click the blue "plus" icon to create a New App.
171 | * Select "iOS".
172 | * Select a name: this will have to be unique, so you may have to try a few different names here, but it will not be the name you see on your phone, so it's not that important.
173 | * Select your primary language.
174 | * Choose the bundle ID that matches `com.TEAMID.loopkit.Loop`, with TEAMID matching your team id.
175 | * SKU can be anything; e.g. "123".
176 | * Select "Full Access".
177 | 1. Click Create
178 |
179 | You do not need to fill out the next form. That is for submitting to the app store.
180 |
181 | ## Create Building Certificates
182 |
183 | This step is no longer required. The Build Loop function now takes care of this for you. It does not hurt to run it but is not needed.
184 |
185 | Once a year, you will get an email from Apple indicating your certificate will expire in 30 days. You can ignore that email. When it does expire, the next time an automatic or manual build happens, the expired certificate information will be removed (nuked) from your Match-Secrets repository and a new one created. This should happen without you needing to take any action.
186 |
187 | ## Build Loop
188 |
189 | 1. Click on the "Actions" tab of your LoopWorkspace repository.
190 | 1. On the left side, select "4. Build Loop".
191 | 1. On the right side, click "Run Workflow", and tap the green `Run workflow` button.
192 | 1. You have some time now. Go enjoy a coffee. The build should take about 20-30 minutes.
193 | 1. Your app should eventually appear on [App Store Connect](https://appstoreconnect.apple.com/apps).
194 | 1. For each phone/person you would like to support Loop on:
195 | * Add them in [Users and Access](https://appstoreconnect.apple.com/access/users) on App Store Connect.
196 | * Add them to your TestFlight Internal Testing group.
197 |
198 | ## TestFlight and Deployment Details
199 |
200 | Please refer to [LoopDocs: TestFlight Overview](https://loopkit.github.io/loopdocs/browser/tf-users) and [LoopDocs: Install on Phone](https://loopkit.github.io/loopdocs/browser/phone-install/)
201 |
202 | ## Automatic Build FAQs
203 |
204 | ### Why do I have an `alive` branch?
205 |
206 | If a GitHub repository has no activity (no commits are made) in 60 days, then GitHub disables the ability to use automated actions for that repository. We need to take action more frequently than that or the automated build process won't work.
207 |
208 | The updated `build_loop.yml` file uses a special branch called `alive` and adds a dummy commit to the `alive` branch at regular intervals. This "trick" keeps the Actions enabled so the automated build works.
209 |
210 | The branch `alive` is created automatically for you. Do not delete or rename it! Do not modify `alive` yourself; it is not used for building the app.
211 |
212 | ## OPTIONAL
213 |
214 | What if you don't want to allow automated updates of the repository or automatic builds?
215 |
216 | You can affect the default behavior:
217 |
218 | 1. [`GH_PAT` `workflow` permission](#gh_pat-workflow-permission)
219 | 1. [Modify scheduled building and synchronization](#modify-scheduled-building-and-synchronization)
220 |
221 | ### `GH_PAT` `workflow` permission
222 |
223 | To enable the scheduled build and sync, the `GH_PAT` must hold the `workflow` permission scopes. This permission serves as the enabler for automatic and scheduled builds with browser build. To verify your token holds this permission, follow these steps.
224 |
225 | 1. Go to your [FastLane Access Token](https://github.com/settings/tokens)
226 | 2. It should say `repo`, `workflow` next to the `FastLane Access Token` link
227 | 3. If it does not, click on the link to open the token detail view
228 | 4. Click to check the `workflow` box. You will see that the checked boxes for the `repo` scope become disabled (change color to dark gray and are not clickable)
229 | 5. Scroll all the way down to and click the green `Update token` button
230 | 6. Your token now holds both required permissions
231 |
232 | If you choose not to have automatic building enabled, be sure the `GH_PAT` has `repo` scope or you won't be able to manually build.
233 |
234 | ### Modify scheduled building and synchronization
235 |
236 | You can modify the automation by creating and using some variables.
237 |
238 | To configure the automated build more granularly involves creating up to two environment variables: `SCHEDULED_BUILD` and/or `SCHEDULED_SYNC`. See [How to configure a variable](#how-to-configure-a-variable).
239 |
240 | Note that the weekly and monthly Build Loop actions will continue, but the actions are modified if one or more of these variables is set to false. **A successful Action Log will still appear, even if no automatic activity happens**.
241 |
242 | * If you want to manually decide when to update your repository to the latest commit, but you want the monthly builds and keep-alive to continue: set `SCHEDULED_SYNC` to false and either do not create `SCHEDULED_BUILD` or set it to true
243 | * If you want to only build when an update has been found: set `SCHEDULED_BUILD` to false and either do not create `SCHEDULED_SYNC` or set it to true
244 | * **Warning**: if no updates to your default branch are detected within 90 days, your previous TestFlight build may expire requiring a manual build
245 |
246 | |`SCHEDULED_SYNC`|`SCHEDULED_BUILD`|Automatic Actions|
247 | |---|---|---|
248 | | `true` (or NA) | `true` (or NA) | keep-alive, weekly update check (auto update/build), monthly build with auto update|
249 | | `true` (or NA) | `false` | keep-alive, weekly update check with auto update, only builds if update detected|
250 | | `false` | `true` (or NA) | keep-alive, monthly build, no auto update |
251 | | `false` | `false` | no automatic activity, no keep-alive|
252 |
253 | ### How to configure a variable
254 |
255 | 1. Go to the "Settings" tab of your repository (to modify a single repository schedule) or your organization to affect all repositories.
256 | 2. Click on `Secrets and Variables`.
257 | 3. Click on `Actions`
258 | 4. You will now see a page titled *Actions secrets and variables*. Click on the `Variables` tab
259 | 5. To disable ONLY scheduled building, do the following:
260 | - Click on the green `New repository variable` button (upper right)
261 | - Type `SCHEDULED_BUILD` in the "Name" field
262 | - Type `false` in the "Value" field
263 | - Click the green `Add variable` button to save.
264 | 7. To disable scheduled syncing, add a variable:
265 | - Click on the green `New repository variable` button (upper right)
266 | - - Type `SCHEDULED_SYNC` in the "Name" field
267 | - Type `false` in the "Value" field
268 | - Click the green `Add variable` button to save
269 |
270 | Your build will run on the following conditions:
271 | - Default behaviour:
272 | - Run weekly, every Wednesday at 08:00 UTC to check for changes; if there are changes, it will update your repository and build
273 | - Run monthly, every first of the month at 06:00 UTC, if there are changes, it will update your repository; regardless of changes, it will build
274 | - Each time the action runs, it makes a keep-alive commit to the `alive` branch if necessary
275 | - If you disable any automation (both variables set to `false`), no updates, keep-alive or building happens when Build Loop runs
276 | - If you disabled just scheduled synchronization (`SCHEDULED_SYNC` set to`false`), it will only run once a month, on the first of the month, no update will happen; keep-alive will run
277 | - If you disabled just scheduled build (`SCHEDULED_BUILD` set to`false`), it will run once weekly, every Wednesday, to check for changes; if there are changes, it will update and build; keep-alive will run
278 |
279 | ## What if I build using more than one GitHub username
280 |
281 | This is not typical. But if you do use more than one GitHub username, follow these steps at the time of the annual certificate renewal.
282 |
283 | 1. After the certificates were removed (nuked) from username1 Match-Secrets storage, you need to switch to username2
284 | 1. Add the variable FORCE_NUKE_CERTS=true to the username2/LoopWorkspace repository
285 | 1. Run the action Create Certificate (or Build, but Create is faster)
286 | 1. Immediately set FORCE_NUKE_CERTS=false or delete the variable
287 |
288 | Now certificates for username2 have been cleared out of Match-Secrets storage for username2. Building can proceed as usual for both username1 and username2.
289 |
--------------------------------------------------------------------------------
/patches/save_patches_here.md:
--------------------------------------------------------------------------------
1 | LoopWorkspace-level patches can be saved in this directory (LoopWorkspace/patches/)
2 |
--------------------------------------------------------------------------------