Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
cfbf80f327 | ||
|
|
96958c53a3 | ||
|
|
13509b68f2 | ||
|
|
f689409990 | ||
|
|
c69f4f7fc7 | ||
|
|
10f993e686 | ||
|
|
a02ddfbfbc | ||
|
|
7a85c93631 | ||
|
|
a3d11cfca3 | ||
|
|
74279fc443 | ||
|
|
7e3a72961f | ||
|
|
ca2179352c | ||
|
|
8ddac065c6 | ||
|
|
a930bd35a5 | ||
|
|
6b8f3ff806 | ||
|
|
3323f392e3 | ||
|
|
f8f2b80641 | ||
|
|
1729443cc3 | ||
|
|
6b5ac8a8b3 | ||
|
|
b12aca2757 | ||
|
|
39cc3a780a | ||
|
|
18302723e8 | ||
|
|
20c383d22d | ||
|
|
77a0c4e018 | ||
|
|
9a5c8b193d | ||
|
|
0562ba8ab0 | ||
|
|
906e860c86 | ||
|
|
516e1fb58d | ||
|
|
7da1307cf6 | ||
|
|
ea4893d815 | ||
|
|
f2e0079b59 | ||
|
|
47d8baa496 | ||
|
|
6d96d64811 | ||
|
|
92fb998e5a | ||
|
|
bed6030e2b | ||
|
|
328a85bad1 | ||
|
|
394375e429 | ||
|
|
ca744adfd9 | ||
|
|
5e42f55570 | ||
|
|
021da1a074 | ||
|
|
0e55f45e91 | ||
|
|
9749837506 | ||
|
|
4fbb0b98c3 | ||
|
|
0edf8afcaf | ||
|
|
e2ee54578c | ||
|
|
b35e4484de | ||
|
|
c8368612eb | ||
|
|
26266e724b | ||
|
|
6ccfea9eb4 | ||
|
|
d4349c20a7 | ||
|
|
63f1cc2450 | ||
|
|
acec2a694a | ||
|
|
f15d7224e0 | ||
|
|
236f552a22 | ||
|
|
321b6f2c64 | ||
|
|
218ead9a31 | ||
|
|
60b6e82c88 | ||
|
|
bbe4ad938a | ||
|
|
9e27380a10 | ||
|
|
baf83fd079 | ||
|
|
dfe3163692 | ||
|
|
4452bd4088 | ||
|
|
6f93b840a9 | ||
|
|
443f50c144 | ||
|
|
5a7f902d01 | ||
|
|
02cc82cee1 | ||
|
|
fa9b05958d | ||
|
|
6fb2c9e7df | ||
|
|
54e59b4b3e | ||
|
|
8a0ea6cae5 | ||
|
|
1bbd816e83 | ||
|
|
4e56191b7e | ||
|
|
4404fb49d3 | ||
|
|
fd4db442e4 | ||
|
|
416f392108 | ||
|
|
dbe9f6b0b2 | ||
|
|
18f06f7a63 | ||
|
|
fb75ddc3e9 | ||
|
|
1326a40033 | ||
|
|
85be8d2ce7 | ||
|
|
b5fb8c8cac | ||
|
|
71b7ec4987 | ||
|
|
ad110d46d9 | ||
|
|
dabe0c13ef | ||
|
|
a5dbfb5cba | ||
|
|
75c4700ac6 | ||
|
|
19ce60fd0a | ||
|
|
d26933b722 | ||
|
|
30dfe2c0c0 | ||
|
|
fa55bcd5f3 | ||
|
|
fbc1dc9406 | ||
|
|
4a4b82c8b6 | ||
|
|
042e477252 | ||
|
|
d1d7b72441 | ||
|
|
48d8d7f527 | ||
|
|
73a71e5262 | ||
|
|
cab779e403 | ||
|
|
e51e874264 | ||
|
|
e399c9f974 | ||
|
|
acd46e5f2a | ||
|
|
59af8453f3 | ||
|
|
d03f4d9aae | ||
|
|
4cc2c3310c | ||
|
|
1123b34e10 | ||
|
|
8c12fce187 | ||
|
|
a82176c855 | ||
|
|
7180bbd6d4 | ||
|
|
b9b4539764 | ||
|
|
96ccc01d08 | ||
|
|
2108dd6c94 | ||
|
|
ca2003f2d6 | ||
|
|
acbbac588f | ||
|
|
f75ff97042 | ||
|
|
4aa7af9716 | ||
|
|
d2682da3cc | ||
|
|
aa1d2ab623 | ||
|
|
929333d264 | ||
|
|
d2cbf823d8 | ||
|
|
ba580e43e6 | ||
|
|
3c49cec213 | ||
|
|
c237fce38d | ||
|
|
8acecc61cd | ||
|
|
63149b7597 | ||
|
|
a331eeadcb | ||
|
|
33e109b131 | ||
|
|
68b4328c72 | ||
|
|
dd8a0a1952 | ||
|
|
b58c1eb9ec | ||
|
|
27994d6ac6 | ||
|
|
9d6978e9c7 | ||
|
|
62ae4bc890 | ||
|
|
c2ce6a859b | ||
|
|
aa5e0a5507 | ||
|
|
6c1c1def78 | ||
|
|
1a232183a8 | ||
|
|
cfa7d5be69 | ||
|
|
bb375eb149 | ||
|
|
7e5e6ce680 | ||
|
|
1032f8b4f1 | ||
|
|
744a56c8b2 | ||
|
|
e58372a190 | ||
|
|
19d44b8666 | ||
|
|
92a52d6194 | ||
|
|
db1c0e25c0 | ||
|
|
04860a7304 | ||
|
|
d8d67301ed | ||
|
|
fa1e915b9c | ||
|
|
da86b69b2c | ||
|
|
44e3167278 | ||
|
|
5cc22f14bc | ||
|
|
8519af5cbb | ||
|
|
3fc4be8335 | ||
|
|
5a5cc0bed3 | ||
|
|
1a9e7503ef | ||
|
|
d84e9e3406 | ||
|
|
c41954993c | ||
|
|
a7c8fc3bff | ||
|
|
36c1f10de8 | ||
|
|
6002f97fb6 | ||
|
|
4378dad2b9 | ||
|
|
278298ecd2 | ||
|
|
b8959cb7bb | ||
|
|
dec90bbbbf | ||
|
|
a4b9d2f04d | ||
|
|
ee41366c08 | ||
|
|
3f42b77d13 | ||
|
|
e22f78a449 | ||
|
|
f2b66ff19f | ||
|
|
2fd0893773 | ||
|
|
0e9292d6d9 | ||
|
|
69250ada66 | ||
|
|
22f8b5f203 | ||
|
|
987acc5b0f | ||
|
|
ce844137a0 | ||
|
|
7f3a8c07e2 | ||
|
|
bd14d58f08 | ||
|
|
b8494cd1d9 | ||
|
|
1a1c3db8e5 | ||
|
|
49772c1334 | ||
|
|
02ddb99000 | ||
|
|
cf1509e34f | ||
|
|
ce2e671768 | ||
|
|
cf2add29dd | ||
|
|
8d2bd958ab | ||
|
|
fcdd8ecbfa | ||
|
|
58beb0d5c5 | ||
|
|
2b1e33b89a | ||
|
|
02d77847da | ||
|
|
ad999b7e71 | ||
|
|
06540f5342 | ||
|
|
f1a34abe87 | ||
|
|
18c3248113 | ||
|
|
aacc39810b | ||
|
|
24f19b8798 | ||
|
|
ff17f4f740 | ||
|
|
2db4497385 | ||
|
|
94036a31e5 | ||
|
|
866baf9f70 | ||
|
|
90cdc55574 | ||
|
|
2f562e82a0 | ||
|
|
e6adc3876e | ||
|
|
475a7504a5 | ||
|
|
7c04622019 | ||
|
|
5609b28e5a | ||
|
|
2dc2a49758 | ||
|
|
e304c66f93 | ||
|
|
db0caf1bea | ||
|
|
8ddb87ddca | ||
|
|
730ddb1c96 | ||
|
|
48f428a4f0 | ||
|
|
806db63e9e | ||
|
|
c2a30c46c3 | ||
|
|
1ce08a7f32 | ||
|
|
4486d26e50 | ||
|
|
e49a3b46e6 | ||
|
|
7bfb341551 | ||
|
|
05e1dae033 | ||
|
|
7f98454965 | ||
|
|
c6ec485303 | ||
|
|
d6d3e3b0e9 | ||
|
|
91b6d6a9dc | ||
|
|
b2f6165ae3 | ||
|
|
4ab49794de | ||
|
|
fe69642a64 | ||
|
|
65829f5e23 | ||
|
|
8713bf8646 | ||
|
|
3ec0e1f992 | ||
|
|
74151c65d0 | ||
|
|
e16777f95c | ||
|
|
8e9e66f846 | ||
|
|
0e9db3ee20 | ||
|
|
220013c4f0 | ||
|
|
d23d5da165 | ||
|
|
d8a2789760 | ||
|
|
53315bbb56 | ||
|
|
c88f5442ab | ||
|
|
7f347b6c90 | ||
|
|
e607ac8232 | ||
|
|
79b4441ffc |
@@ -0,0 +1,58 @@
|
||||
# Default branch protection
|
||||
|
||||
The intended repository ruleset is stored in
|
||||
[`rulesets/protect-default-branch.json`](rulesets/protect-default-branch.json).
|
||||
It targets the configured default branch and requires pull requests, the strict
|
||||
`Required CI` check from GitHub Actions, resolved review conversations,
|
||||
squash-only merges, and linear history. Branch deletion and force pushes are
|
||||
blocked. Qodana remains advisory and is not part of the required gate.
|
||||
|
||||
The ruleset's `RepositoryRole` actor ID `5` is GitHub's built-in Administrator
|
||||
role. Its `pull_request` bypass mode permits an administrator to bypass rules
|
||||
only while merging an existing pull request; it does not permit a direct push.
|
||||
|
||||
## Repository settings
|
||||
|
||||
Keep squash merge enabled and disable merge commits and rebase merge. Enable
|
||||
auto-merge, the update-branch option, and automatic deletion of merged head
|
||||
branches. Keep the Actions token read-only and do not allow Actions to approve
|
||||
pull-request reviews.
|
||||
|
||||
## Activation and verification
|
||||
|
||||
1. Confirm a pull request and a `master` push each produce exactly one
|
||||
successful `Required CI` check from GitHub Actions integration `15368`.
|
||||
2. For the initial ruleset POST, override the committed JSON's `enforcement`
|
||||
value to `disabled`, then compare GitHub's normalized API response with this
|
||||
file.
|
||||
3. PUT the exact committed JSON to the inspected ruleset to activate it.
|
||||
4. Open a canary pull request and confirm that pending or failing CI, unresolved
|
||||
conversations, and an out-of-date branch block merging; only squash merge is
|
||||
available.
|
||||
5. After merging, confirm the head branch is deleted and the post-merge full
|
||||
E2E, frontend release, and mobile release guards still run.
|
||||
|
||||
If validation exposes a blocker, disable the ruleset rather than deleting it so
|
||||
its configuration and history remain available.
|
||||
|
||||
## Activation record
|
||||
|
||||
Repository ruleset `19051697` was activated on 2026-07-16 after preparation
|
||||
PR #172 established `Required CI`, remediation PR #174 passed the strict
|
||||
current-base gate, and merged master run `29501928124` completed with all 43
|
||||
executed jobs successful. This documentation update is the after-activation
|
||||
canary for the normal protected pull-request path.
|
||||
|
||||
## Normal publishing flow
|
||||
|
||||
Create a scoped feature branch, open a pull request to `master`, wait for
|
||||
`Required CI`, update the branch if `master` advanced, resolve every review
|
||||
conversation, and squash-merge. For waits expected to exceed 90 seconds, use
|
||||
the workspace `scripts/ci-watch.sh` helper instead of repeatedly polling GitHub.
|
||||
|
||||
## Break glass
|
||||
|
||||
For an incident, an administrator must still open a pull request. Document the
|
||||
incident and why the normal gate cannot complete, then use the PR-only bypass
|
||||
when merging. Monitor all post-merge workflows and open a follow-up pull request
|
||||
for any validation or remediation deferred during the incident.
|
||||
@@ -0,0 +1,53 @@
|
||||
{
|
||||
"name": "Protect default branch",
|
||||
"target": "branch",
|
||||
"enforcement": "active",
|
||||
"bypass_actors": [
|
||||
{
|
||||
"actor_id": 5,
|
||||
"actor_type": "RepositoryRole",
|
||||
"bypass_mode": "pull_request"
|
||||
}
|
||||
],
|
||||
"conditions": {
|
||||
"ref_name": {
|
||||
"include": ["~DEFAULT_BRANCH"],
|
||||
"exclude": []
|
||||
}
|
||||
},
|
||||
"rules": [
|
||||
{
|
||||
"type": "deletion"
|
||||
},
|
||||
{
|
||||
"type": "non_fast_forward"
|
||||
},
|
||||
{
|
||||
"type": "required_linear_history"
|
||||
},
|
||||
{
|
||||
"type": "pull_request",
|
||||
"parameters": {
|
||||
"allowed_merge_methods": ["squash"],
|
||||
"dismiss_stale_reviews_on_push": false,
|
||||
"require_code_owner_review": false,
|
||||
"require_last_push_approval": false,
|
||||
"required_approving_review_count": 0,
|
||||
"required_review_thread_resolution": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "required_status_checks",
|
||||
"parameters": {
|
||||
"do_not_enforce_on_create": false,
|
||||
"required_status_checks": [
|
||||
{
|
||||
"context": "Required CI",
|
||||
"integration_id": 15368
|
||||
}
|
||||
],
|
||||
"strict_required_status_checks_policy": true
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -1,32 +1,66 @@
|
||||
name: Qodana Configuration Upload
|
||||
name: Qodana
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [main, dev]
|
||||
pull_request:
|
||||
branches: [main]
|
||||
workflow_dispatch:
|
||||
pull_request:
|
||||
branches: [master, beta, canary, internal]
|
||||
types: [opened, synchronize, reopened, ready_for_review]
|
||||
push:
|
||||
branches: [master, beta, canary, internal]
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
checks: write
|
||||
pull-requests: write
|
||||
|
||||
concurrency:
|
||||
group: qodana-${{ github.event_name == 'pull_request' && format('pr-{0}', github.event.pull_request.number) || github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
upload-qodana-config:
|
||||
runs-on: [self-hosted, Linux, X64, default]
|
||||
timeout-minutes: 10
|
||||
qodana:
|
||||
name: Qodana
|
||||
if: >-
|
||||
github.event_name != 'pull_request' ||
|
||||
(
|
||||
github.event.pull_request.draft == false &&
|
||||
github.event.pull_request.head.repo.full_name == github.repository &&
|
||||
github.event.pull_request.user.login != 'dependabot[bot]'
|
||||
)
|
||||
runs-on: ubuntu-24.04
|
||||
timeout-minutes: 60
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v5
|
||||
# v5.0.1
|
||||
uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd
|
||||
with:
|
||||
ref: ${{ github.event_name == 'pull_request' && github.event.pull_request.head.sha || github.sha }}
|
||||
fetch-depth: 0
|
||||
persist-credentials: false
|
||||
|
||||
- name: Run Qodana Configuration Uploader
|
||||
- name: Require Qodana project token
|
||||
shell: bash
|
||||
env:
|
||||
QODANA_CONFIGURATIONS_TOKEN: ${{ secrets.QODANA_CONFIGURATIONS_TOKEN }}
|
||||
QODANA_TOKEN: ${{ secrets.QODANA_TOKEN }}
|
||||
run: |
|
||||
docker run --rm \
|
||||
-v "$(pwd):/workspace" \
|
||||
-w /workspace \
|
||||
-e QODANA_CONFIGURATIONS_TOKEN \
|
||||
jetbrains/qodana-configuration-uploader@sha256:f4786ceea616048c3401cf0b0345d2220d22a2ec7b046fd48cbbfc522e6efe30 \
|
||||
--global-configs-file qodana-global-configurations.yaml \
|
||||
--qodana-host https://qodana.cloud
|
||||
set -euo pipefail
|
||||
if [[ -z "${QODANA_TOKEN:-}" ]]; then
|
||||
echo "::error::QODANA_TOKEN is not configured for this repository."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
- name: Qodana
|
||||
# v2026.1.3
|
||||
uses: JetBrains/qodana-action@4861e015da555e86a72b862892aba6c2b93e6891
|
||||
with:
|
||||
use-caches: true
|
||||
cache-default-branch-only: true
|
||||
upload-result: false
|
||||
use-annotations: true
|
||||
pr-mode: ${{ github.event_name == 'pull_request' }}
|
||||
post-pr-comment: true
|
||||
github-token: ${{ github.token }}
|
||||
push-fixes: none
|
||||
env:
|
||||
QODANA_TOKEN: ${{ secrets.QODANA_TOKEN }}
|
||||
|
||||
@@ -0,0 +1,99 @@
|
||||
name: cPanel Root Audit and Restore
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
mode:
|
||||
description: Audit is read-only; restore exchanges public_html with a retained recovery entry.
|
||||
required: true
|
||||
default: audit
|
||||
type: choice
|
||||
options:
|
||||
- audit
|
||||
- restore
|
||||
recovery:
|
||||
description: Exact recovery entry reported by an audit, for example public_html.recovery-20260720.
|
||||
required: false
|
||||
type: string
|
||||
state_token:
|
||||
description: Exact 64-character audit-metadata state token reported by the audit.
|
||||
required: false
|
||||
type: string
|
||||
confirmation:
|
||||
description: For restore, type RESTORE <recovery> TO <webroot> STATE <state-token> exactly.
|
||||
required: false
|
||||
type: string
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
concurrency:
|
||||
group: frontend-production
|
||||
cancel-in-progress: false
|
||||
|
||||
jobs:
|
||||
audit-or-restore:
|
||||
runs-on: [self-hosted, Linux, X64, default]
|
||||
timeout-minutes: 10
|
||||
environment:
|
||||
name: frontend-production
|
||||
url: ${{ vars.PRODUCTION_FRONTEND_URL || 'https://truckwash.io' }}
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
- uses: actions/setup-node@v5
|
||||
with:
|
||||
node-version: 22
|
||||
|
||||
- name: Audit cPanel primary webroot
|
||||
if: inputs.mode == 'audit'
|
||||
id: audit
|
||||
run: node scripts/release/cpanel-root.mjs audit
|
||||
env:
|
||||
NODE_OPTIONS: --use-system-ca
|
||||
PRODUCTION_CPANEL_USER: ${{ secrets.PRODUCTION_CPANEL_USER }}
|
||||
PRODUCTION_CPANEL_API_TOKEN: ${{ secrets.PRODUCTION_CPANEL_API_TOKEN }}
|
||||
PRODUCTION_CPANEL_API_URL: ${{ vars.PRODUCTION_CPANEL_API_URL }}
|
||||
PRODUCTION_CPANEL_PATH: ${{ vars.PRODUCTION_CPANEL_PATH }}
|
||||
PRODUCTION_CPANEL_WEBROOT: ${{ vars.PRODUCTION_CPANEL_WEBROOT || 'public_html' }}
|
||||
PRODUCTION_FRONTEND_URL: ${{ vars.PRODUCTION_FRONTEND_URL || 'https://truckwash.io' }}
|
||||
CPANEL_ROOT_REPORT_PATH: output/cpanel-root/audit.json
|
||||
|
||||
- name: Validate restore inputs
|
||||
if: inputs.mode == 'restore'
|
||||
env:
|
||||
RECOVERY: ${{ inputs.recovery }}
|
||||
STATE_TOKEN: ${{ inputs.state_token }}
|
||||
CONFIRMATION: ${{ inputs.confirmation }}
|
||||
WEBROOT: ${{ vars.PRODUCTION_CPANEL_WEBROOT || 'public_html' }}
|
||||
run: |
|
||||
test -n "$RECOVERY"
|
||||
[[ "$STATE_TOKEN" =~ ^[a-f0-9]{64}$ ]]
|
||||
test "$CONFIRMATION" = "RESTORE $RECOVERY TO $WEBROOT STATE $STATE_TOKEN"
|
||||
|
||||
- name: Restore retained cPanel webroot
|
||||
if: inputs.mode == 'restore'
|
||||
run: node scripts/release/cpanel-root.mjs restore
|
||||
env:
|
||||
NODE_OPTIONS: --use-system-ca
|
||||
PRODUCTION_CPANEL_USER: ${{ secrets.PRODUCTION_CPANEL_USER }}
|
||||
PRODUCTION_CPANEL_API_TOKEN: ${{ secrets.PRODUCTION_CPANEL_API_TOKEN }}
|
||||
PRODUCTION_CPANEL_API_URL: ${{ vars.PRODUCTION_CPANEL_API_URL }}
|
||||
PRODUCTION_CPANEL_PATH: ${{ vars.PRODUCTION_CPANEL_PATH }}
|
||||
PRODUCTION_CPANEL_WEBROOT: ${{ vars.PRODUCTION_CPANEL_WEBROOT || 'public_html' }}
|
||||
PRODUCTION_FRONTEND_URL: ${{ vars.PRODUCTION_FRONTEND_URL || 'https://truckwash.io' }}
|
||||
CPANEL_ROOT_RECOVERY: ${{ inputs.recovery }}
|
||||
CPANEL_ROOT_STATE_TOKEN: ${{ inputs.state_token }}
|
||||
CPANEL_ROOT_CONFIRMATION: ${{ inputs.confirmation }}
|
||||
CPANEL_ROOT_REPORT_PATH: output/cpanel-root/restore.json
|
||||
|
||||
- name: Upload cPanel root report
|
||||
if: always()
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: cpanel-root-${{ inputs.mode }}-${{ github.run_id }}
|
||||
path: output/cpanel-root
|
||||
if-no-files-found: ignore
|
||||
retention-days: 30
|
||||
@@ -0,0 +1,72 @@
|
||||
name: Frontend Root FTPS Repair
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
mode:
|
||||
description: Audit downloads and hashes .htaccess; repair backs it up and activates the reviewed file.
|
||||
required: true
|
||||
default: audit
|
||||
type: choice
|
||||
options:
|
||||
- audit
|
||||
- repair
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
concurrency:
|
||||
group: frontend-production
|
||||
cancel-in-progress: false
|
||||
|
||||
jobs:
|
||||
audit-or-repair:
|
||||
runs-on: [self-hosted, Linux, X64, default]
|
||||
timeout-minutes: 10
|
||||
environment:
|
||||
name: frontend-production
|
||||
url: ${{ vars.PRODUCTION_FRONTEND_URL || 'https://truckwash.io' }}
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
- uses: actions/setup-node@v5
|
||||
with:
|
||||
node-version: 22
|
||||
|
||||
- name: Install secure FTP client without system changes
|
||||
run: |
|
||||
if command -v lftp >/dev/null 2>&1; then
|
||||
exit 0
|
||||
fi
|
||||
|
||||
package_root="$RUNNER_TEMP/lftp-package"
|
||||
mkdir -p "$package_root"
|
||||
(
|
||||
cd "$package_root"
|
||||
apt-get download lftp
|
||||
dpkg-deb --extract ./lftp_*.deb root
|
||||
)
|
||||
echo "$package_root/root/usr/bin" >> "$GITHUB_PATH"
|
||||
|
||||
- name: Audit or repair live root .htaccess
|
||||
run: bash scripts/release/repair-public-htaccess.sh "${{ inputs.mode }}"
|
||||
env:
|
||||
NODE_OPTIONS: --use-system-ca
|
||||
PRODUCTION_FTP_HOST: ${{ secrets.PRODUCTION_FTP_HOST }}
|
||||
PRODUCTION_FTP_USER: ${{ secrets.PRODUCTION_FTP_USER }}
|
||||
PRODUCTION_FTP_PASSWORD: ${{ secrets.PRODUCTION_FTP_PASSWORD }}
|
||||
PRODUCTION_FTP_PATH: ${{ secrets.PRODUCTION_FTP_PATH }}
|
||||
PRODUCTION_CPANEL_WEBROOT: ${{ vars.PRODUCTION_CPANEL_WEBROOT || 'public_html' }}
|
||||
PRODUCTION_FRONTEND_URL: ${{ vars.PRODUCTION_FRONTEND_URL || 'https://truckwash.io' }}
|
||||
ROOT_REPAIR_REPORT_DIR: output/cpanel-root-ftps
|
||||
|
||||
- name: Upload FTPS root report
|
||||
if: always()
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: frontend-root-ftps-${{ inputs.mode }}-${{ github.run_id }}
|
||||
path: output/cpanel-root-ftps
|
||||
if-no-files-found: ignore
|
||||
retention-days: 30
|
||||
@@ -0,0 +1,624 @@
|
||||
name: iOS Device Debug IPA
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
source_ref:
|
||||
description: Same-repository branch, tag, or commit to build
|
||||
required: true
|
||||
default: master
|
||||
type: string
|
||||
expected_sha:
|
||||
description: Full 40-character SHA that source_ref must resolve to
|
||||
required: true
|
||||
type: string
|
||||
confirmation:
|
||||
description: Type SIGN IOS DEBUG IPA
|
||||
required: true
|
||||
type: string
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
concurrency:
|
||||
group: ios-device-debug-${{ github.run_id }}
|
||||
cancel-in-progress: false
|
||||
|
||||
jobs:
|
||||
resolve:
|
||||
name: Resolve and verify source
|
||||
runs-on: ubuntu-24.04
|
||||
timeout-minutes: 10
|
||||
outputs:
|
||||
source_sha: ${{ steps.resolve.outputs.source_sha }}
|
||||
steps:
|
||||
- name: Validate dispatch confirmation
|
||||
shell: bash
|
||||
env:
|
||||
CONFIRMATION: ${{ inputs.confirmation }}
|
||||
EXPECTED_SHA: ${{ inputs.expected_sha }}
|
||||
SOURCE_REF: ${{ inputs.source_ref }}
|
||||
WORKFLOW_REF: ${{ github.ref }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
if [[ "$WORKFLOW_REF" != "refs/heads/master" ]]; then
|
||||
echo "The signing workflow must be dispatched from the master workflow ref" >&2
|
||||
exit 1
|
||||
fi
|
||||
if [[ "$CONFIRMATION" != "SIGN IOS DEBUG IPA" ]]; then
|
||||
echo "confirmation must exactly match SIGN IOS DEBUG IPA" >&2
|
||||
exit 1
|
||||
fi
|
||||
if [[ ! "$EXPECTED_SHA" =~ ^[0-9a-fA-F]{40}$ ]]; then
|
||||
echo "expected_sha must be a full 40-character commit SHA" >&2
|
||||
exit 1
|
||||
fi
|
||||
if [[ -z "$SOURCE_REF" || "$SOURCE_REF" =~ [[:space:]] ]]; then
|
||||
echo "source_ref must be non-empty and contain no whitespace" >&2
|
||||
exit 1
|
||||
fi
|
||||
if [[ "$SOURCE_REF" == refs/pull/* || "$SOURCE_REF" == pull/* ]]; then
|
||||
echo "Pull-request refs are not eligible for device-debug signing" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
- name: Checkout same-repository history
|
||||
uses: actions/checkout@v5
|
||||
with:
|
||||
fetch-depth: 0
|
||||
persist-credentials: false
|
||||
|
||||
- name: Resolve immutable commit
|
||||
id: resolve
|
||||
shell: bash
|
||||
env:
|
||||
EXPECTED_SHA: ${{ inputs.expected_sha }}
|
||||
SOURCE_REF: ${{ inputs.source_ref }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
expected_sha="${EXPECTED_SHA,,}"
|
||||
|
||||
if [[ "$SOURCE_REF" =~ ^[0-9a-fA-F]{7,40}$ ]]; then
|
||||
candidate="$SOURCE_REF"
|
||||
elif [[ "$SOURCE_REF" == refs/heads/* ]]; then
|
||||
candidate="refs/remotes/origin/${SOURCE_REF#refs/heads/}"
|
||||
elif [[ "$SOURCE_REF" == refs/tags/* ]]; then
|
||||
candidate="$SOURCE_REF"
|
||||
elif git show-ref --verify --quiet "refs/remotes/origin/$SOURCE_REF"; then
|
||||
candidate="refs/remotes/origin/$SOURCE_REF"
|
||||
elif git show-ref --verify --quiet "refs/tags/$SOURCE_REF"; then
|
||||
candidate="refs/tags/$SOURCE_REF"
|
||||
else
|
||||
echo "source_ref does not identify a same-repository branch, tag, or fetched commit" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
source_sha="$(git rev-parse --verify "${candidate}^{commit}" 2>/dev/null || true)"
|
||||
source_sha="${source_sha,,}"
|
||||
if [[ ! "$source_sha" =~ ^[0-9a-f]{40}$ ]]; then
|
||||
echo "source_ref could not be resolved to a commit" >&2
|
||||
exit 1
|
||||
fi
|
||||
if [[ "$source_sha" != "$expected_sha" ]]; then
|
||||
echo "source_ref resolved to a SHA different from expected_sha" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
reachable=false
|
||||
while IFS= read -r repository_ref; do
|
||||
if git merge-base --is-ancestor "$source_sha" "$repository_ref" 2>/dev/null; then
|
||||
reachable=true
|
||||
break
|
||||
fi
|
||||
done < <(git for-each-ref --format='%(refname)' refs/remotes/origin refs/tags)
|
||||
if [[ "$reachable" != true ]]; then
|
||||
echo "The requested commit is not reachable from a same-repository branch or tag" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "source_sha=$source_sha" >> "$GITHUB_OUTPUT"
|
||||
echo "Resolved source_ref to $source_sha" >> "$GITHUB_STEP_SUMMARY"
|
||||
|
||||
build:
|
||||
name: Build development-signed IPA
|
||||
needs: resolve
|
||||
runs-on: macos-26
|
||||
timeout-minutes: 90
|
||||
environment:
|
||||
name: mobile-device-debug
|
||||
env:
|
||||
IOS_PROJECT_PATH: ios/App/App.xcodeproj
|
||||
IOS_SCHEME: App
|
||||
IOS_DEBUG_BUNDLE_ID: ${{ vars.IOS_DEBUG_BUNDLE_ID || 'io.truckwash.app.debug' }}
|
||||
IOS_DEBUG_API_URL: ${{ vars.IOS_DEBUG_API_URL || 'https://api-v2.truckwash.io/master/api' }}
|
||||
APPLE_TEAM_ID: ${{ vars.APPLE_TEAM_ID }}
|
||||
MOBILE_VERSION_NAME: 0.0.${{ github.run_number }}
|
||||
RESOLVED_SOURCE_SHA: ${{ needs.resolve.outputs.source_sha }}
|
||||
steps:
|
||||
- name: Checkout resolved source
|
||||
uses: actions/checkout@v5
|
||||
with:
|
||||
ref: ${{ needs.resolve.outputs.source_sha }}
|
||||
fetch-depth: 1
|
||||
persist-credentials: false
|
||||
|
||||
- name: Verify runner and resolve build number
|
||||
shell: bash
|
||||
run: |
|
||||
set -euo pipefail
|
||||
xcode_version_output="$(xcodebuild -version)"
|
||||
IFS= read -r xcode_version <<< "$xcode_version_output"
|
||||
xcode_major="$(awk '{split($2, version, "."); print version[1]}' <<< "$xcode_version")"
|
||||
if [[ ! "$xcode_major" =~ ^[0-9]+$ ]] || (( xcode_major < 26 )); then
|
||||
echo "Xcode 26 or newer is required; found $xcode_version" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
build_number="$((10#$GITHUB_RUN_NUMBER * 100 + 10#$GITHUB_RUN_ATTEMPT))"
|
||||
if [[ ! "$build_number" =~ ^[1-9][0-9]{0,17}$ ]]; then
|
||||
echo "Derived build number is outside Apple's supported integer format" >&2
|
||||
exit 1
|
||||
fi
|
||||
echo "MOBILE_VERSION_CODE=$build_number" >> "$GITHUB_ENV"
|
||||
echo "XCODE_VERSION=$xcode_version" >> "$GITHUB_ENV"
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v5
|
||||
with:
|
||||
node-version: 22
|
||||
cache: npm
|
||||
|
||||
- name: Install dependencies
|
||||
run: npm ci --legacy-peer-deps
|
||||
|
||||
- name: Verify stable API and Capacitor iOS CORS
|
||||
shell: bash
|
||||
run: |
|
||||
set -euo pipefail
|
||||
api_base="${IOS_DEBUG_API_URL%/}"
|
||||
curl --fail --silent --show-error --location \
|
||||
--connect-timeout 10 --max-time 20 \
|
||||
--header 'Accept: application/json' \
|
||||
--output /dev/null \
|
||||
"$api_base/ping"
|
||||
|
||||
cors_headers="$RUNNER_TEMP/ios-debug-cors-headers.txt"
|
||||
cors_body="$RUNNER_TEMP/ios-debug-cors-body.txt"
|
||||
cors_status="$(curl --silent --show-error \
|
||||
--connect-timeout 10 --max-time 20 \
|
||||
--request OPTIONS \
|
||||
--header 'Origin: capacitor://localhost' \
|
||||
--header 'Access-Control-Request-Method: POST' \
|
||||
--header 'Access-Control-Request-Headers: authorization,content-type' \
|
||||
--dump-header "$cors_headers" \
|
||||
--output "$cors_body" \
|
||||
--write-out '%{http_code}' \
|
||||
"$api_base/ping")"
|
||||
if [[ ! "$cors_status" =~ ^2[0-9][0-9]$ ]]; then
|
||||
echo "Stable API rejected the Capacitor iOS CORS preflight with HTTP $cors_status" >&2
|
||||
sed -n '1,20p' "$cors_body" >&2
|
||||
exit 1
|
||||
fi
|
||||
if ! grep -Eiq '^access-control-allow-origin:[[:space:]]*capacitor://localhost[[:space:]]*$' "$cors_headers"; then
|
||||
echo "Stable API did not allow the exact capacitor://localhost origin" >&2
|
||||
exit 1
|
||||
fi
|
||||
if ! grep -Eiq '^access-control-allow-credentials:[[:space:]]*true[[:space:]]*$' "$cors_headers"; then
|
||||
echo "Stable API did not allow credentialed Capacitor requests" >&2
|
||||
exit 1
|
||||
fi
|
||||
if ! grep -Eiq '^access-control-allow-methods:.*[[:space:],]POST([[:space:],]|$)' "$cors_headers"; then
|
||||
echo "Stable API did not allow POST from the Capacitor origin" >&2
|
||||
exit 1
|
||||
fi
|
||||
if ! grep -Eiq '^access-control-allow-headers:.*[[:space:],]Authorization([[:space:],]|$)' "$cors_headers"; then
|
||||
echo "Stable API did not allow the Authorization header from the Capacitor origin" >&2
|
||||
exit 1
|
||||
fi
|
||||
if ! grep -Eiq '^access-control-allow-headers:.*[[:space:],]Content-Type([[:space:],]|$)' "$cors_headers"; then
|
||||
echo "Stable API did not allow the Content-Type header from the Capacitor origin" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
- name: Build stable production web payload
|
||||
env:
|
||||
RELEASE_COMMIT_SHA: ${{ env.RESOLVED_SOURCE_SHA }}
|
||||
VITE_API_URL: ${{ env.IOS_DEBUG_API_URL }}
|
||||
VITE_RELEASE_MANAGER_CONTROL_API_URL: ${{ env.IOS_DEBUG_API_URL }}
|
||||
VITE_RELEASE_PUBLIC_GATEWAY_API_URL: https://api-v2.truckwash.io
|
||||
run: |
|
||||
npm run build
|
||||
node -e "const manifest = require('./dist/release-manifest.json'); if (manifest.commit_sha !== process.env.RESOLVED_SOURCE_SHA) { throw new Error('Web release manifest source SHA mismatch'); }"
|
||||
|
||||
- name: Sync and validate iOS shell
|
||||
shell: bash
|
||||
run: |
|
||||
set -euo pipefail
|
||||
npx cap sync ios
|
||||
npm run mobile:permissions:check
|
||||
if grep -q 'isa = PBXShellScriptBuildPhase;' "$IOS_PROJECT_PATH/project.pbxproj"; then
|
||||
echo "Unexpected Xcode shell-script build phase detected" >&2
|
||||
exit 1
|
||||
fi
|
||||
xcodebuild -resolvePackageDependencies -project "$IOS_PROJECT_PATH" -scheme "$IOS_SCHEME"
|
||||
|
||||
- name: Validate native Debug and Release settings
|
||||
shell: bash
|
||||
run: |
|
||||
set -euo pipefail
|
||||
debug_settings="$RUNNER_TEMP/ios-debug-build-settings.txt"
|
||||
release_settings="$RUNNER_TEMP/ios-release-build-settings.txt"
|
||||
xcodebuild -showBuildSettings \
|
||||
-project "$IOS_PROJECT_PATH" \
|
||||
-scheme "$IOS_SCHEME" \
|
||||
-configuration Debug \
|
||||
CODE_SIGNING_ALLOWED=NO > "$debug_settings"
|
||||
xcodebuild -showBuildSettings \
|
||||
-project "$IOS_PROJECT_PATH" \
|
||||
-scheme "$IOS_SCHEME" \
|
||||
-configuration Release \
|
||||
CODE_SIGNING_ALLOWED=NO > "$release_settings"
|
||||
|
||||
grep -Eq '^[[:space:]]*PRODUCT_BUNDLE_IDENTIFIER = io\.truckwash\.app\.debug$' "$debug_settings"
|
||||
grep -Eq '^[[:space:]]*APP_DISPLAY_NAME = Truck Wash Debug$' "$debug_settings"
|
||||
grep -Eq '^[[:space:]]*PRODUCT_NAME = TruckWashDebug$' "$debug_settings"
|
||||
grep -Eq '^[[:space:]]*CAPACITOR_DEBUG = true$' "$debug_settings"
|
||||
grep -Eq '^[[:space:]]*DEBUG_INFORMATION_FORMAT = dwarf-with-dsym$' "$debug_settings"
|
||||
grep -Eq '^[[:space:]]*IPHONEOS_DEPLOYMENT_TARGET = 15\.0$' "$debug_settings"
|
||||
grep -Eq '^[[:space:]]*PRODUCT_BUNDLE_IDENTIFIER = io\.truckwash\.app$' "$release_settings"
|
||||
grep -Eq '^[[:space:]]*APP_DISPLAY_NAME = Truck Wash$' "$release_settings"
|
||||
grep -Eq '^[[:space:]]*PRODUCT_NAME = App$' "$release_settings"
|
||||
|
||||
- name: Install and validate Apple development signing assets
|
||||
shell: bash
|
||||
env:
|
||||
IOS_DEBUG_CERTIFICATE_BASE64: ${{ secrets.IOS_DEBUG_CERTIFICATE_BASE64 }}
|
||||
IOS_DEBUG_CERTIFICATE_PASSWORD: ${{ secrets.IOS_DEBUG_CERTIFICATE_PASSWORD }}
|
||||
IOS_DEBUG_PROVISION_PROFILE_BASE64: ${{ secrets.IOS_DEBUG_PROVISION_PROFILE_BASE64 }}
|
||||
IOS_DEBUG_ALLOWED_UDIDS: ${{ secrets.IOS_DEBUG_ALLOWED_UDIDS }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
node scripts/mobile/check-ios-debug-signing-env.mjs
|
||||
|
||||
certificate_path="$RUNNER_TEMP/ios-debug-development.p12"
|
||||
profile_path="$RUNNER_TEMP/ios-debug-development.mobileprovision"
|
||||
profile_plist="$RUNNER_TEMP/ios-debug-development-profile.plist"
|
||||
keychain_path="$RUNNER_TEMP/ios-debug-signing.keychain-db"
|
||||
keychain_password="$(openssl rand -base64 48 | tr -d '\n')"
|
||||
echo "::add-mask::$keychain_password"
|
||||
|
||||
node -e "const fs = require('fs'); fs.writeFileSync(process.argv[1], Buffer.from(process.env.IOS_DEBUG_CERTIFICATE_BASE64.replace(/\\s/g, ''), 'base64'))" "$certificate_path"
|
||||
node -e "const fs = require('fs'); fs.writeFileSync(process.argv[1], Buffer.from(process.env.IOS_DEBUG_PROVISION_PROFILE_BASE64.replace(/\\s/g, ''), 'base64'))" "$profile_path"
|
||||
chmod 600 "$certificate_path" "$profile_path"
|
||||
security cms -D -i "$profile_path" > "$profile_plist"
|
||||
|
||||
security create-keychain -p "$keychain_password" "$keychain_path"
|
||||
security set-keychain-settings -lut 21600 "$keychain_path"
|
||||
security unlock-keychain -p "$keychain_password" "$keychain_path"
|
||||
security import "$certificate_path" \
|
||||
-P "$IOS_DEBUG_CERTIFICATE_PASSWORD" \
|
||||
-A \
|
||||
-t cert \
|
||||
-f pkcs12 \
|
||||
-k "$keychain_path"
|
||||
security list-keychains -d user -s "$keychain_path" $(security list-keychains -d user | tr -d '"')
|
||||
security set-key-partition-list \
|
||||
-S apple-tool:,apple: \
|
||||
-s \
|
||||
-k "$keychain_password" \
|
||||
"$keychain_path"
|
||||
|
||||
signing_identity_sha="$(security find-identity -v -p codesigning "$keychain_path" | awk '/Apple Development/ {print $2; exit}')"
|
||||
if [[ ! "$signing_identity_sha" =~ ^[0-9A-Fa-f]{40}$ ]]; then
|
||||
echo "The PKCS#12 file does not contain a valid Apple Development signing identity" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
IOS_SIGNING_IDENTITY_SHA="$signing_identity_sha" PROFILE_PLIST="$profile_plist" python3 <<'PY'
|
||||
import datetime
|
||||
import hashlib
|
||||
import os
|
||||
import plistlib
|
||||
import re
|
||||
import sys
|
||||
|
||||
with open(os.environ["PROFILE_PLIST"], "rb") as handle:
|
||||
profile = plistlib.load(handle)
|
||||
|
||||
team_id = os.environ["APPLE_TEAM_ID"]
|
||||
bundle_id = os.environ["IOS_DEBUG_BUNDLE_ID"]
|
||||
entitlements = profile.get("Entitlements", {})
|
||||
allowed = {line.strip() for line in os.environ["IOS_DEBUG_ALLOWED_UDIDS"].splitlines() if line.strip()}
|
||||
provisioned = set(profile.get("ProvisionedDevices", []))
|
||||
expiration = profile.get("ExpirationDate")
|
||||
now = datetime.datetime.now(datetime.timezone.utc)
|
||||
if expiration and expiration.tzinfo is None:
|
||||
expiration = expiration.replace(tzinfo=datetime.timezone.utc)
|
||||
|
||||
checks = {
|
||||
"profile team identifier": team_id in profile.get("TeamIdentifier", []),
|
||||
"application identifier": entitlements.get("application-identifier") == f"{team_id}.{bundle_id}",
|
||||
"entitlement team identifier": entitlements.get("com.apple.developer.team-identifier") == team_id,
|
||||
"development entitlement": entitlements.get("get-task-allow") is True,
|
||||
"profile expiration": expiration is not None and expiration > now,
|
||||
"registered devices": bool(allowed) and allowed <= provisioned,
|
||||
"non-enterprise profile": profile.get("ProvisionsAllDevices") is not True,
|
||||
"developer certificate": bool(profile.get("DeveloperCertificates")),
|
||||
"profile UUID": isinstance(profile.get("UUID"), str) and re.fullmatch(r"[0-9A-Fa-f-]{36}", profile["UUID"]) is not None,
|
||||
"safe profile name": isinstance(profile.get("Name"), str) and not any(char in profile["Name"] for char in "\r\n"),
|
||||
}
|
||||
identity_sha = os.environ["IOS_SIGNING_IDENTITY_SHA"].upper()
|
||||
certificate_hashes = {hashlib.sha1(value).hexdigest().upper() for value in profile.get("DeveloperCertificates", [])}
|
||||
checks["certificate belongs to profile"] = identity_sha in certificate_hashes
|
||||
|
||||
failures = [label for label, passed in checks.items() if not passed]
|
||||
if failures:
|
||||
print("Development provisioning profile validation failed:", file=sys.stderr)
|
||||
for failure in failures:
|
||||
print(f"- {failure}", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
PY
|
||||
|
||||
profile_uuid="$(/usr/libexec/PlistBuddy -c 'Print :UUID' "$profile_plist")"
|
||||
profile_name="$(/usr/libexec/PlistBuddy -c 'Print :Name' "$profile_plist")"
|
||||
profile_expiration="$(PROFILE_PLIST="$profile_plist" python3 - <<'PY'
|
||||
import datetime
|
||||
import os
|
||||
import plistlib
|
||||
|
||||
with open(os.environ["PROFILE_PLIST"], "rb") as handle:
|
||||
expiration = plistlib.load(handle)["ExpirationDate"]
|
||||
if expiration.tzinfo is None:
|
||||
expiration = expiration.replace(tzinfo=datetime.timezone.utc)
|
||||
print(expiration.astimezone(datetime.timezone.utc).isoformat().replace("+00:00", "Z"))
|
||||
PY
|
||||
)"
|
||||
profile_install_dir="$HOME/Library/MobileDevice/Provisioning Profiles"
|
||||
profile_install_path="$profile_install_dir/$profile_uuid.mobileprovision"
|
||||
mkdir -p "$profile_install_dir"
|
||||
cp "$profile_path" "$profile_install_path"
|
||||
|
||||
echo "IOS_KEYCHAIN_PATH=$keychain_path" >> "$GITHUB_ENV"
|
||||
echo "IOS_PROFILE_INSTALL_PATH=$profile_install_path" >> "$GITHUB_ENV"
|
||||
echo "IOS_PROFILE_NAME=$profile_name" >> "$GITHUB_ENV"
|
||||
echo "IOS_PROFILE_UUID=$profile_uuid" >> "$GITHUB_ENV"
|
||||
echo "IOS_PROFILE_EXPIRATION=$profile_expiration" >> "$GITHUB_ENV"
|
||||
|
||||
- name: Archive Debug app with Apple Development signing
|
||||
shell: bash
|
||||
run: |
|
||||
set -euo pipefail
|
||||
archive_path="$RUNNER_TEMP/TruckWashDebug.xcarchive"
|
||||
xcodebuild \
|
||||
-project "$IOS_PROJECT_PATH" \
|
||||
-scheme "$IOS_SCHEME" \
|
||||
-configuration Debug \
|
||||
-destination "generic/platform=iOS" \
|
||||
-archivePath "$archive_path" \
|
||||
archive \
|
||||
DEVELOPMENT_TEAM="$APPLE_TEAM_ID" \
|
||||
CODE_SIGN_STYLE=Manual \
|
||||
CODE_SIGN_IDENTITY="Apple Development" \
|
||||
PROVISIONING_PROFILE_SPECIFIER="$IOS_PROFILE_NAME" \
|
||||
PRODUCT_BUNDLE_IDENTIFIER="$IOS_DEBUG_BUNDLE_ID" \
|
||||
MARKETING_VERSION="$MOBILE_VERSION_NAME" \
|
||||
CURRENT_PROJECT_VERSION="$MOBILE_VERSION_CODE" \
|
||||
DEBUG_INFORMATION_FORMAT="dwarf-with-dsym" \
|
||||
ONLY_ACTIVE_ARCH=NO
|
||||
echo "IOS_ARCHIVE_PATH=$archive_path" >> "$GITHUB_ENV"
|
||||
|
||||
- name: Export development IPA
|
||||
shell: bash
|
||||
run: |
|
||||
set -euo pipefail
|
||||
export_options="$RUNNER_TEMP/ios-debug-ExportOptions.plist"
|
||||
export_path="$RUNNER_TEMP/ios-debug-export"
|
||||
EXPORT_OPTIONS="$export_options" python3 <<'PY'
|
||||
import os
|
||||
import plistlib
|
||||
|
||||
options = {
|
||||
"method": "development",
|
||||
"signingStyle": "manual",
|
||||
"teamID": os.environ["APPLE_TEAM_ID"],
|
||||
"provisioningProfiles": {
|
||||
os.environ["IOS_DEBUG_BUNDLE_ID"]: os.environ["IOS_PROFILE_NAME"],
|
||||
},
|
||||
"stripSwiftSymbols": True,
|
||||
"manageAppVersionAndBuildNumber": False,
|
||||
}
|
||||
with open(os.environ["EXPORT_OPTIONS"], "wb") as handle:
|
||||
plistlib.dump(options, handle)
|
||||
PY
|
||||
xcodebuild \
|
||||
-exportArchive \
|
||||
-archivePath "$IOS_ARCHIVE_PATH" \
|
||||
-exportPath "$export_path" \
|
||||
-exportOptionsPlist "$export_options"
|
||||
|
||||
shopt -s nullglob
|
||||
ipa_files=("$export_path"/*.ipa)
|
||||
if [[ ${#ipa_files[@]} -ne 1 ]]; then
|
||||
echo "Expected exactly one exported IPA; found ${#ipa_files[@]}" >&2
|
||||
exit 1
|
||||
fi
|
||||
echo "IOS_EXPORTED_IPA=${ipa_files[0]}" >> "$GITHUB_ENV"
|
||||
|
||||
- name: Validate exported IPA and embedded signature
|
||||
shell: bash
|
||||
env:
|
||||
IOS_DEBUG_ALLOWED_UDIDS: ${{ secrets.IOS_DEBUG_ALLOWED_UDIDS }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
inspect_dir="$RUNNER_TEMP/ios-debug-inspect"
|
||||
mkdir -p "$inspect_dir"
|
||||
unzip -q "$IOS_EXPORTED_IPA" -d "$inspect_dir"
|
||||
shopt -s nullglob
|
||||
app_bundles=("$inspect_dir"/Payload/*.app)
|
||||
if [[ ${#app_bundles[@]} -ne 1 ]]; then
|
||||
echo "Expected exactly one Payload app; found ${#app_bundles[@]}" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
app_path="${app_bundles[0]}"
|
||||
app_info="$app_path/Info.plist"
|
||||
embedded_profile="$RUNNER_TEMP/ios-debug-embedded-profile.plist"
|
||||
signature_entitlements="$RUNNER_TEMP/ios-debug-signature-entitlements.plist"
|
||||
signature_details="$RUNNER_TEMP/ios-debug-signature-details.txt"
|
||||
security cms -D -i "$app_path/embedded.mobileprovision" > "$embedded_profile"
|
||||
codesign --verify --deep --strict "$app_path"
|
||||
codesign -d --entitlements :- "$app_path" > "$signature_entitlements"
|
||||
codesign -dvv "$app_path" > /dev/null 2> "$signature_details"
|
||||
grep -Fq "TeamIdentifier=$APPLE_TEAM_ID" "$signature_details"
|
||||
grep -Eq '^Authority=Apple Development:' "$signature_details"
|
||||
|
||||
executable_name="$(/usr/libexec/PlistBuddy -c 'Print :CFBundleExecutable' "$app_info")"
|
||||
architectures="$(lipo -archs "$app_path/$executable_name")"
|
||||
if [[ " $architectures " != *" arm64 "* ]]; then
|
||||
echo "Exported executable does not contain arm64" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
APP_INFO="$app_info" PROFILE_PLIST="$embedded_profile" SIGNATURE_ENTITLEMENTS="$signature_entitlements" python3 <<'PY'
|
||||
import datetime
|
||||
import os
|
||||
import plistlib
|
||||
import sys
|
||||
|
||||
def load(path):
|
||||
with open(path, "rb") as handle:
|
||||
return plistlib.load(handle)
|
||||
|
||||
info = load(os.environ["APP_INFO"])
|
||||
profile = load(os.environ["PROFILE_PLIST"])
|
||||
signature = load(os.environ["SIGNATURE_ENTITLEMENTS"])
|
||||
profile_entitlements = profile.get("Entitlements", {})
|
||||
team_id = os.environ["APPLE_TEAM_ID"]
|
||||
bundle_id = os.environ["IOS_DEBUG_BUNDLE_ID"]
|
||||
allowed = {line.strip() for line in os.environ["IOS_DEBUG_ALLOWED_UDIDS"].splitlines() if line.strip()}
|
||||
provisioned = set(profile.get("ProvisionedDevices", []))
|
||||
expiration = profile.get("ExpirationDate")
|
||||
now = datetime.datetime.now(datetime.timezone.utc)
|
||||
if expiration and expiration.tzinfo is None:
|
||||
expiration = expiration.replace(tzinfo=datetime.timezone.utc)
|
||||
|
||||
checks = {
|
||||
"bundle identifier": info.get("CFBundleIdentifier") == bundle_id,
|
||||
"display name": info.get("CFBundleDisplayName") == "Truck Wash Debug",
|
||||
"debug executable": info.get("CFBundleExecutable") == "TruckWashDebug",
|
||||
"marketing version": info.get("CFBundleShortVersionString") == os.environ["MOBILE_VERSION_NAME"],
|
||||
"build number": info.get("CFBundleVersion") == os.environ["MOBILE_VERSION_CODE"],
|
||||
"minimum iOS": info.get("MinimumOSVersion") == "15.0",
|
||||
"profile UUID": profile.get("UUID") == os.environ["IOS_PROFILE_UUID"],
|
||||
"profile team": team_id in profile.get("TeamIdentifier", []),
|
||||
"profile application identifier": profile_entitlements.get("application-identifier") == f"{team_id}.{bundle_id}",
|
||||
"development profile": profile_entitlements.get("get-task-allow") is True,
|
||||
"signature application identifier": signature.get("application-identifier") == f"{team_id}.{bundle_id}",
|
||||
"signature team identifier": signature.get("com.apple.developer.team-identifier") == team_id,
|
||||
"debuggable signature": signature.get("get-task-allow") is True,
|
||||
"profile expiration": expiration is not None and expiration > now,
|
||||
"registered devices": bool(allowed) and allowed <= provisioned,
|
||||
"non-enterprise profile": profile.get("ProvisionsAllDevices") is not True,
|
||||
}
|
||||
failures = [label for label, passed in checks.items() if not passed]
|
||||
if failures:
|
||||
print("Exported development IPA validation failed:", file=sys.stderr)
|
||||
for failure in failures:
|
||||
print(f"- {failure}", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
PY
|
||||
|
||||
- name: Assemble debug artifact
|
||||
shell: bash
|
||||
env:
|
||||
SOURCE_REF: ${{ inputs.source_ref }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
short_sha="${RESOLVED_SOURCE_SHA:0:12}"
|
||||
artifact_name="truck-wash-debug-${MOBILE_VERSION_NAME}-${short_sha}"
|
||||
artifact_dir="$RUNNER_TEMP/device-debug-artifact"
|
||||
ipa_filename="$artifact_name.ipa"
|
||||
dsym_filename="$artifact_name.dSYM.zip"
|
||||
mkdir -p "$artifact_dir"
|
||||
cp "$IOS_EXPORTED_IPA" "$artifact_dir/$ipa_filename"
|
||||
|
||||
shopt -s nullglob
|
||||
dsym_bundles=("$IOS_ARCHIVE_PATH"/dSYMs/*.dSYM)
|
||||
if [[ ${#dsym_bundles[@]} -eq 0 ]]; then
|
||||
echo "The Debug archive did not contain any dSYM bundles" >&2
|
||||
exit 1
|
||||
fi
|
||||
ditto -c -k --sequesterRsrc --keepParent "$IOS_ARCHIVE_PATH/dSYMs" "$artifact_dir/$dsym_filename"
|
||||
|
||||
capacitor_version="$(node -p "require('./node_modules/@capacitor/core/package.json').version")"
|
||||
BUILT_AT_UTC="$(date -u '+%Y-%m-%dT%H:%M:%SZ')" \
|
||||
CAPACITOR_VERSION="$capacitor_version" \
|
||||
DSYM_FILENAME="$dsym_filename" \
|
||||
IPA_FILENAME="$ipa_filename" \
|
||||
MANIFEST_PATH="$artifact_dir/manifest.json" \
|
||||
python3 <<'PY'
|
||||
import json
|
||||
import os
|
||||
|
||||
manifest = {
|
||||
"schema_version": 1,
|
||||
"repository": os.environ["GITHUB_REPOSITORY"],
|
||||
"source_ref": os.environ["SOURCE_REF"],
|
||||
"source_sha": os.environ["RESOLVED_SOURCE_SHA"],
|
||||
"workflow_run": int(os.environ["GITHUB_RUN_NUMBER"]),
|
||||
"workflow_attempt": int(os.environ["GITHUB_RUN_ATTEMPT"]),
|
||||
"built_at_utc": os.environ["BUILT_AT_UTC"],
|
||||
"api_url": os.environ["IOS_DEBUG_API_URL"],
|
||||
"release_manager_control_api_url": os.environ["IOS_DEBUG_API_URL"],
|
||||
"bundle_id": os.environ["IOS_DEBUG_BUNDLE_ID"],
|
||||
"display_name": "Truck Wash Debug",
|
||||
"executable_name": "TruckWashDebug",
|
||||
"version": os.environ["MOBILE_VERSION_NAME"],
|
||||
"build": os.environ["MOBILE_VERSION_CODE"],
|
||||
"minimum_ios": "15.0",
|
||||
"capacitor_version": os.environ["CAPACITOR_VERSION"],
|
||||
"xcode_version": os.environ["XCODE_VERSION"],
|
||||
"signing_method": "development",
|
||||
"profile_expiration_utc": os.environ["IOS_PROFILE_EXPIRATION"],
|
||||
"ipa_filename": os.environ["IPA_FILENAME"],
|
||||
"dsym_filename": os.environ["DSYM_FILENAME"],
|
||||
}
|
||||
with open(os.environ["MANIFEST_PATH"], "w", encoding="utf-8") as handle:
|
||||
json.dump(manifest, handle, indent=2, sort_keys=True)
|
||||
handle.write("\n")
|
||||
PY
|
||||
|
||||
(
|
||||
cd "$artifact_dir"
|
||||
shasum -a 256 "$ipa_filename" "$dsym_filename" manifest.json > SHA256SUMS
|
||||
)
|
||||
echo "IOS_DEBUG_ARTIFACT_DIR=$artifact_dir" >> "$GITHUB_ENV"
|
||||
echo "IOS_DEBUG_ARTIFACT_NAME=$artifact_name" >> "$GITHUB_ENV"
|
||||
|
||||
- name: Upload device-debug artifact
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: ${{ env.IOS_DEBUG_ARTIFACT_NAME }}
|
||||
path: ${{ env.IOS_DEBUG_ARTIFACT_DIR }}
|
||||
if-no-files-found: error
|
||||
retention-days: 7
|
||||
|
||||
- name: Clean up Apple signing assets
|
||||
if: always()
|
||||
shell: bash
|
||||
run: |
|
||||
if [[ -n "${IOS_KEYCHAIN_PATH:-}" ]]; then
|
||||
security delete-keychain "$IOS_KEYCHAIN_PATH" || true
|
||||
else
|
||||
security delete-keychain "$RUNNER_TEMP/ios-debug-signing.keychain-db" || true
|
||||
fi
|
||||
if [[ -n "${IOS_PROFILE_INSTALL_PATH:-}" ]]; then
|
||||
rm -f "$IOS_PROFILE_INSTALL_PATH"
|
||||
fi
|
||||
rm -f \
|
||||
"$RUNNER_TEMP/ios-debug-development.p12" \
|
||||
"$RUNNER_TEMP/ios-debug-development.mobileprovision" \
|
||||
"$RUNNER_TEMP/ios-debug-development-profile.plist" \
|
||||
"$RUNNER_TEMP/ios-debug-embedded-profile.plist" \
|
||||
"$RUNNER_TEMP/ios-debug-signature-entitlements.plist" \
|
||||
"$RUNNER_TEMP/ios-debug-signature-details.txt"
|
||||
@@ -0,0 +1,484 @@
|
||||
name: Mobile Store Artifacts
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
version_name:
|
||||
description: Store version name, for example 1.4.0
|
||||
required: false
|
||||
type: string
|
||||
version_code:
|
||||
description: Store build number/version code
|
||||
required: false
|
||||
type: string
|
||||
upload_android_to_play:
|
||||
description: Upload the signed Android App Bundle to Google Play
|
||||
required: false
|
||||
type: boolean
|
||||
default: true
|
||||
upload_ios_to_app_store:
|
||||
description: Upload the signed iOS IPA to App Store Connect
|
||||
required: false
|
||||
type: boolean
|
||||
default: true
|
||||
android_track:
|
||||
description: Google Play track for manual dispatches
|
||||
required: false
|
||||
type: choice
|
||||
default: production
|
||||
options:
|
||||
- production
|
||||
- beta
|
||||
- alpha
|
||||
- internal
|
||||
android_release_status:
|
||||
description: Google Play release status for manual dispatches
|
||||
required: false
|
||||
type: choice
|
||||
default: completed
|
||||
options:
|
||||
- completed
|
||||
- draft
|
||||
- inProgress
|
||||
- halted
|
||||
push:
|
||||
tags:
|
||||
- "mobile-v*"
|
||||
workflow_run:
|
||||
workflows:
|
||||
- Automated Tests
|
||||
types:
|
||||
- completed
|
||||
branches:
|
||||
- master
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
concurrency:
|
||||
group: mobile-store-artifacts-${{ github.event.workflow_run.head_branch || github.ref_name || github.run_id }}
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
android:
|
||||
name: Android AAB and Play upload
|
||||
if: >
|
||||
github.event_name != 'workflow_run' ||
|
||||
(github.event.workflow_run.conclusion == 'success' &&
|
||||
github.event.workflow_run.event == 'push' &&
|
||||
github.event.workflow_run.head_branch == github.event.repository.default_branch)
|
||||
runs-on: ubuntu-24.04
|
||||
environment: mobile-store-production
|
||||
timeout-minutes: 60
|
||||
env:
|
||||
ANDROID_PACKAGE_NAME: ${{ vars.ANDROID_PACKAGE_NAME || 'io.truckwash.twa' }}
|
||||
ANDROID_AAB_PATH: ${{ vars.ANDROID_AAB_PATH || 'android/app/build/outputs/bundle/release/app-release.aab' }}
|
||||
PLAY_STORE_TRACK: ${{ inputs.android_track || vars.PLAY_STORE_TRACK || 'production' }}
|
||||
PLAY_STORE_RELEASE_STATUS: ${{ inputs.android_release_status || vars.PLAY_STORE_RELEASE_STATUS || 'completed' }}
|
||||
PLAY_STORE_USER_FRACTION: ${{ vars.PLAY_STORE_USER_FRACTION || '' }}
|
||||
UPLOAD_ANDROID_TO_PLAY: ${{ github.event_name != 'workflow_dispatch' || inputs.upload_android_to_play }}
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v5
|
||||
with:
|
||||
ref: ${{ github.event.workflow_run.head_sha || github.sha }}
|
||||
|
||||
- name: Guard current master release
|
||||
id: release-guard
|
||||
shell: bash
|
||||
env:
|
||||
EVENT_NAME: ${{ github.event_name }}
|
||||
EXPECTED_SHA: ${{ github.event.workflow_run.head_sha || github.sha }}
|
||||
RELEASE_BRANCH: ${{ github.event.workflow_run.head_branch || github.ref_name }}
|
||||
DEFAULT_BRANCH: ${{ github.event.repository.default_branch }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
current=true
|
||||
if [[ "$EVENT_NAME" == "workflow_run" ]]; then
|
||||
latest_sha="$(git ls-remote origin "refs/heads/$DEFAULT_BRANCH" | awk '{print $1}')"
|
||||
if [[ -z "$latest_sha" ]]; then
|
||||
echo "Could not resolve origin/$DEFAULT_BRANCH." >&2
|
||||
exit 1
|
||||
fi
|
||||
if [[ "$latest_sha" != "$EXPECTED_SHA" ]]; then
|
||||
current=false
|
||||
echo "Skipping stale mobile upload for $EXPECTED_SHA; origin/$DEFAULT_BRANCH is $latest_sha."
|
||||
else
|
||||
echo "Mobile upload commit is current for $DEFAULT_BRANCH."
|
||||
fi
|
||||
else
|
||||
echo "Mobile release guard passed for $EVENT_NAME on $RELEASE_BRANCH."
|
||||
fi
|
||||
echo "current=$current" >> "$GITHUB_OUTPUT"
|
||||
|
||||
- name: Setup Node.js
|
||||
if: steps.release-guard.outputs.current == 'true'
|
||||
uses: actions/setup-node@v5
|
||||
with:
|
||||
node-version: 22
|
||||
cache: npm
|
||||
|
||||
- name: Setup Java
|
||||
if: steps.release-guard.outputs.current == 'true'
|
||||
uses: actions/setup-java@v4
|
||||
with:
|
||||
distribution: temurin
|
||||
java-version: 21
|
||||
|
||||
- name: Setup Android SDK
|
||||
if: steps.release-guard.outputs.current == 'true'
|
||||
uses: android-actions/setup-android@v3
|
||||
|
||||
- name: Install Android SDK packages
|
||||
if: steps.release-guard.outputs.current == 'true'
|
||||
shell: bash
|
||||
run: |
|
||||
set -euo pipefail
|
||||
yes | sdkmanager --licenses >/dev/null || true
|
||||
sdkmanager "platforms;android-36" "build-tools;36.0.0"
|
||||
|
||||
- name: Resolve mobile version
|
||||
if: steps.release-guard.outputs.current == 'true'
|
||||
shell: bash
|
||||
env:
|
||||
INPUT_VERSION_NAME: ${{ inputs.version_name || '' }}
|
||||
INPUT_VERSION_CODE: ${{ inputs.version_code || '' }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
version_name="$INPUT_VERSION_NAME"
|
||||
if [[ -z "$version_name" && "$GITHUB_REF_NAME" == mobile-v* ]]; then
|
||||
version_name="${GITHUB_REF_NAME#mobile-v}"
|
||||
fi
|
||||
if [[ -z "$version_name" ]]; then
|
||||
version_name="0.0.${GITHUB_RUN_NUMBER}"
|
||||
fi
|
||||
version_code="${INPUT_VERSION_CODE:-$GITHUB_RUN_NUMBER}"
|
||||
echo "MOBILE_VERSION_NAME=$version_name" >> "$GITHUB_ENV"
|
||||
echo "MOBILE_VERSION_CODE=$version_code" >> "$GITHUB_ENV"
|
||||
|
||||
- name: Check Android store environment
|
||||
if: steps.release-guard.outputs.current == 'true'
|
||||
env:
|
||||
ANDROID_KEYSTORE_BASE64: ${{ secrets.ANDROID_KEYSTORE_BASE64 }}
|
||||
ANDROID_KEYSTORE_PASSWORD: ${{ secrets.ANDROID_KEYSTORE_PASSWORD }}
|
||||
ANDROID_KEY_ALIAS: ${{ secrets.ANDROID_KEY_ALIAS }}
|
||||
ANDROID_KEY_PASSWORD: ${{ secrets.ANDROID_KEY_PASSWORD }}
|
||||
GOOGLE_PLAY_SERVICE_ACCOUNT_JSON_BASE64: ${{ secrets.GOOGLE_PLAY_SERVICE_ACCOUNT_JSON_BASE64 }}
|
||||
run: node scripts/mobile/check-store-upload-env.mjs --android
|
||||
|
||||
- name: Install dependencies
|
||||
if: steps.release-guard.outputs.current == 'true'
|
||||
run: npm ci --legacy-peer-deps
|
||||
|
||||
- name: Decode Android signing key
|
||||
if: steps.release-guard.outputs.current == 'true'
|
||||
shell: bash
|
||||
env:
|
||||
ANDROID_KEYSTORE_BASE64: ${{ secrets.ANDROID_KEYSTORE_BASE64 }}
|
||||
ANDROID_KEYSTORE_PASSWORD: ${{ secrets.ANDROID_KEYSTORE_PASSWORD }}
|
||||
ANDROID_KEY_ALIAS: ${{ secrets.ANDROID_KEY_ALIAS }}
|
||||
ANDROID_KEY_PASSWORD: ${{ secrets.ANDROID_KEY_PASSWORD }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
keystore_path="$RUNNER_TEMP/android-release.keystore"
|
||||
node -e "const fs = require('fs'); fs.writeFileSync(process.argv[1], Buffer.from(process.env.ANDROID_KEYSTORE_BASE64, 'base64'))" "$keystore_path"
|
||||
echo "ANDROID_KEYSTORE_FILE=$keystore_path" >> "$GITHUB_ENV"
|
||||
echo "ANDROID_KEYSTORE_PASSWORD=$ANDROID_KEYSTORE_PASSWORD" >> "$GITHUB_ENV"
|
||||
echo "ANDROID_KEY_ALIAS=$ANDROID_KEY_ALIAS" >> "$GITHUB_ENV"
|
||||
echo "ANDROID_KEY_PASSWORD=$ANDROID_KEY_PASSWORD" >> "$GITHUB_ENV"
|
||||
|
||||
- name: Build and sync Android shell
|
||||
if: steps.release-guard.outputs.current == 'true'
|
||||
run: |
|
||||
npm run mobile:android:sync
|
||||
npm run mobile:permissions:check
|
||||
npm run mobile:android:signing:check
|
||||
|
||||
- name: Build signed Android App Bundle
|
||||
if: steps.release-guard.outputs.current == 'true'
|
||||
working-directory: android
|
||||
run: ./gradlew --no-daemon bundleRelease
|
||||
|
||||
- name: Verify Android App Bundle signature
|
||||
if: steps.release-guard.outputs.current == 'true'
|
||||
run: jarsigner -verify -certs -verbose "$ANDROID_AAB_PATH" >/dev/null
|
||||
|
||||
- name: Upload Android artifact
|
||||
if: steps.release-guard.outputs.current == 'true'
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: truck-wash-android-${{ env.MOBILE_VERSION_NAME }}-${{ github.event.workflow_run.head_sha || github.sha }}
|
||||
path: ${{ env.ANDROID_AAB_PATH }}
|
||||
if-no-files-found: error
|
||||
retention-days: 14
|
||||
|
||||
- name: Upload Android App Bundle to Google Play
|
||||
if: steps.release-guard.outputs.current == 'true' && env.UPLOAD_ANDROID_TO_PLAY == 'true'
|
||||
env:
|
||||
GOOGLE_PLAY_SERVICE_ACCOUNT_JSON_BASE64: ${{ secrets.GOOGLE_PLAY_SERVICE_ACCOUNT_JSON_BASE64 }}
|
||||
run: npm run mobile:android:play-upload
|
||||
|
||||
ios:
|
||||
name: iOS IPA and App Store upload
|
||||
if: >
|
||||
github.event_name != 'workflow_run' ||
|
||||
(github.event.workflow_run.conclusion == 'success' &&
|
||||
github.event.workflow_run.event == 'push' &&
|
||||
github.event.workflow_run.head_branch == github.event.repository.default_branch)
|
||||
runs-on: macos-15
|
||||
environment: mobile-store-production
|
||||
timeout-minutes: 90
|
||||
env:
|
||||
IOS_PROJECT_PATH: ios/App/App.xcodeproj
|
||||
IOS_SCHEME: App
|
||||
IOS_BUNDLE_ID: io.truckwash.app
|
||||
UPLOAD_IOS_TO_APP_STORE: ${{ github.event_name != 'workflow_dispatch' || inputs.upload_ios_to_app_store }}
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v5
|
||||
with:
|
||||
ref: ${{ github.event.workflow_run.head_sha || github.sha }}
|
||||
|
||||
- name: Guard current master release
|
||||
id: release-guard
|
||||
shell: bash
|
||||
env:
|
||||
EVENT_NAME: ${{ github.event_name }}
|
||||
EXPECTED_SHA: ${{ github.event.workflow_run.head_sha || github.sha }}
|
||||
RELEASE_BRANCH: ${{ github.event.workflow_run.head_branch || github.ref_name }}
|
||||
DEFAULT_BRANCH: ${{ github.event.repository.default_branch }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
current=true
|
||||
if [[ "$EVENT_NAME" == "workflow_run" ]]; then
|
||||
latest_sha="$(git ls-remote origin "refs/heads/$DEFAULT_BRANCH" | awk '{print $1}')"
|
||||
if [[ -z "$latest_sha" ]]; then
|
||||
echo "Could not resolve origin/$DEFAULT_BRANCH." >&2
|
||||
exit 1
|
||||
fi
|
||||
if [[ "$latest_sha" != "$EXPECTED_SHA" ]]; then
|
||||
current=false
|
||||
echo "Skipping stale mobile upload for $EXPECTED_SHA; origin/$DEFAULT_BRANCH is $latest_sha."
|
||||
else
|
||||
echo "Mobile upload commit is current for $DEFAULT_BRANCH."
|
||||
fi
|
||||
else
|
||||
echo "Mobile release guard passed for $EVENT_NAME on $RELEASE_BRANCH."
|
||||
fi
|
||||
echo "current=$current" >> "$GITHUB_OUTPUT"
|
||||
|
||||
- name: Setup Node.js
|
||||
if: steps.release-guard.outputs.current == 'true'
|
||||
uses: actions/setup-node@v5
|
||||
with:
|
||||
node-version: 22
|
||||
cache: npm
|
||||
|
||||
- name: Resolve mobile version
|
||||
if: steps.release-guard.outputs.current == 'true'
|
||||
shell: bash
|
||||
env:
|
||||
INPUT_VERSION_NAME: ${{ inputs.version_name || '' }}
|
||||
INPUT_VERSION_CODE: ${{ inputs.version_code || '' }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
version_name="$INPUT_VERSION_NAME"
|
||||
if [[ -z "$version_name" && "$GITHUB_REF_NAME" == mobile-v* ]]; then
|
||||
version_name="${GITHUB_REF_NAME#mobile-v}"
|
||||
fi
|
||||
if [[ -z "$version_name" ]]; then
|
||||
version_name="0.0.${GITHUB_RUN_NUMBER}"
|
||||
fi
|
||||
version_code="${INPUT_VERSION_CODE:-$GITHUB_RUN_NUMBER}"
|
||||
echo "MOBILE_VERSION_NAME=$version_name" >> "$GITHUB_ENV"
|
||||
echo "MOBILE_VERSION_CODE=$version_code" >> "$GITHUB_ENV"
|
||||
|
||||
- name: Check iOS store environment
|
||||
if: steps.release-guard.outputs.current == 'true'
|
||||
env:
|
||||
IOS_CERTIFICATE_BASE64: ${{ secrets.IOS_CERTIFICATE_BASE64 }}
|
||||
IOS_CERTIFICATE_PASSWORD: ${{ secrets.IOS_CERTIFICATE_PASSWORD }}
|
||||
IOS_PROVISION_PROFILE_BASE64: ${{ secrets.IOS_PROVISION_PROFILE_BASE64 }}
|
||||
IOS_KEYCHAIN_PASSWORD: ${{ secrets.IOS_KEYCHAIN_PASSWORD }}
|
||||
APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }}
|
||||
APP_STORE_CONNECT_API_KEY_ID: ${{ secrets.APP_STORE_CONNECT_API_KEY_ID }}
|
||||
APP_STORE_CONNECT_ISSUER_ID: ${{ secrets.APP_STORE_CONNECT_ISSUER_ID }}
|
||||
APP_STORE_CONNECT_API_PRIVATE_KEY_BASE64: ${{ secrets.APP_STORE_CONNECT_API_PRIVATE_KEY_BASE64 }}
|
||||
run: node scripts/mobile/check-store-upload-env.mjs --ios
|
||||
|
||||
- name: Install dependencies
|
||||
if: steps.release-guard.outputs.current == 'true'
|
||||
run: npm ci --legacy-peer-deps
|
||||
|
||||
- name: Build and sync iOS shell
|
||||
if: steps.release-guard.outputs.current == 'true'
|
||||
run: |
|
||||
npm run build
|
||||
npx cap sync ios
|
||||
npm run mobile:permissions:check
|
||||
|
||||
- name: Install Apple signing assets
|
||||
if: steps.release-guard.outputs.current == 'true'
|
||||
shell: bash
|
||||
env:
|
||||
IOS_CERTIFICATE_BASE64: ${{ secrets.IOS_CERTIFICATE_BASE64 }}
|
||||
IOS_CERTIFICATE_PASSWORD: ${{ secrets.IOS_CERTIFICATE_PASSWORD }}
|
||||
IOS_PROVISION_PROFILE_BASE64: ${{ secrets.IOS_PROVISION_PROFILE_BASE64 }}
|
||||
IOS_KEYCHAIN_PASSWORD: ${{ secrets.IOS_KEYCHAIN_PASSWORD }}
|
||||
APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
certificate_path="$RUNNER_TEMP/apple-distribution.p12"
|
||||
profile_path="$RUNNER_TEMP/app-store.mobileprovision"
|
||||
keychain_path="$RUNNER_TEMP/app-signing.keychain-db"
|
||||
profile_plist="$RUNNER_TEMP/profile.plist"
|
||||
|
||||
node -e "const fs = require('fs'); fs.writeFileSync(process.argv[1], Buffer.from(process.env.IOS_CERTIFICATE_BASE64, 'base64'))" "$certificate_path"
|
||||
node -e "const fs = require('fs'); fs.writeFileSync(process.argv[1], Buffer.from(process.env.IOS_PROVISION_PROFILE_BASE64, 'base64'))" "$profile_path"
|
||||
|
||||
security create-keychain -p "$IOS_KEYCHAIN_PASSWORD" "$keychain_path"
|
||||
security set-keychain-settings -lut 21600 "$keychain_path"
|
||||
security unlock-keychain -p "$IOS_KEYCHAIN_PASSWORD" "$keychain_path"
|
||||
security import "$certificate_path" -P "$IOS_CERTIFICATE_PASSWORD" -A -t cert -f pkcs12 -k "$keychain_path"
|
||||
security list-keychain -d user -s "$keychain_path" $(security list-keychains -d user | tr -d '"')
|
||||
security set-key-partition-list -S apple-tool:,apple: -s -k "$IOS_KEYCHAIN_PASSWORD" "$keychain_path"
|
||||
|
||||
mkdir -p "$HOME/Library/MobileDevice/Provisioning Profiles"
|
||||
security cms -D -i "$profile_path" > "$profile_plist"
|
||||
profile_uuid="$(/usr/libexec/PlistBuddy -c 'Print UUID' "$profile_plist")"
|
||||
profile_name="$(/usr/libexec/PlistBuddy -c 'Print Name' "$profile_plist")"
|
||||
cp "$profile_path" "$HOME/Library/MobileDevice/Provisioning Profiles/$profile_uuid.mobileprovision"
|
||||
|
||||
echo "APPLE_TEAM_ID=$APPLE_TEAM_ID" >> "$GITHUB_ENV"
|
||||
echo "IOS_KEYCHAIN_PATH=$keychain_path" >> "$GITHUB_ENV"
|
||||
echo "IOS_PROFILE_UUID=$profile_uuid" >> "$GITHUB_ENV"
|
||||
echo "IOS_PROFILE_NAME=$profile_name" >> "$GITHUB_ENV"
|
||||
|
||||
- name: Install App Store Connect API key
|
||||
if: steps.release-guard.outputs.current == 'true' && env.UPLOAD_IOS_TO_APP_STORE == 'true'
|
||||
shell: bash
|
||||
env:
|
||||
APP_STORE_CONNECT_API_KEY_ID: ${{ secrets.APP_STORE_CONNECT_API_KEY_ID }}
|
||||
APP_STORE_CONNECT_ISSUER_ID: ${{ secrets.APP_STORE_CONNECT_ISSUER_ID }}
|
||||
APP_STORE_CONNECT_API_PRIVATE_KEY_BASE64: ${{ secrets.APP_STORE_CONNECT_API_PRIVATE_KEY_BASE64 }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
private_keys_dir="$RUNNER_TEMP/private_keys"
|
||||
private_key_path="$private_keys_dir/AuthKey_${APP_STORE_CONNECT_API_KEY_ID}.p8"
|
||||
mkdir -p "$private_keys_dir"
|
||||
node -e "const fs = require('fs'); fs.writeFileSync(process.argv[1], Buffer.from(process.env.APP_STORE_CONNECT_API_PRIVATE_KEY_BASE64, 'base64'))" "$private_key_path"
|
||||
chmod 600 "$private_key_path"
|
||||
echo "API_PRIVATE_KEYS_DIR=$private_keys_dir" >> "$GITHUB_ENV"
|
||||
echo "APP_STORE_CONNECT_API_KEY_ID=$APP_STORE_CONNECT_API_KEY_ID" >> "$GITHUB_ENV"
|
||||
echo "APP_STORE_CONNECT_ISSUER_ID=$APP_STORE_CONNECT_ISSUER_ID" >> "$GITHUB_ENV"
|
||||
echo "APP_STORE_CONNECT_API_KEY_PATH=$private_key_path" >> "$GITHUB_ENV"
|
||||
|
||||
- name: Resolve Swift packages
|
||||
if: steps.release-guard.outputs.current == 'true'
|
||||
run: xcodebuild -resolvePackageDependencies -project "$IOS_PROJECT_PATH" -scheme "$IOS_SCHEME"
|
||||
|
||||
- name: Archive iOS app
|
||||
if: steps.release-guard.outputs.current == 'true'
|
||||
run: |
|
||||
xcodebuild \
|
||||
-project "$IOS_PROJECT_PATH" \
|
||||
-scheme "$IOS_SCHEME" \
|
||||
-configuration Release \
|
||||
-destination "generic/platform=iOS" \
|
||||
-archivePath "$RUNNER_TEMP/TruckWash.xcarchive" \
|
||||
archive \
|
||||
DEVELOPMENT_TEAM="$APPLE_TEAM_ID" \
|
||||
CODE_SIGN_STYLE=Manual \
|
||||
CODE_SIGN_IDENTITY="Apple Distribution" \
|
||||
PROVISIONING_PROFILE_SPECIFIER="$IOS_PROFILE_NAME" \
|
||||
MARKETING_VERSION="$MOBILE_VERSION_NAME" \
|
||||
CURRENT_PROJECT_VERSION="$MOBILE_VERSION_CODE"
|
||||
|
||||
- name: Export iOS IPA
|
||||
if: steps.release-guard.outputs.current == 'true'
|
||||
shell: bash
|
||||
run: |
|
||||
set -euo pipefail
|
||||
export_method="app-store-connect"
|
||||
if ! xcodebuild -help 2>&1 | grep -q "app-store-connect"; then
|
||||
export_method="app-store"
|
||||
fi
|
||||
export_options="$RUNNER_TEMP/ExportOptions.plist"
|
||||
cat > "$export_options" <<EOF
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>method</key>
|
||||
<string>$export_method</string>
|
||||
<key>signingStyle</key>
|
||||
<string>manual</string>
|
||||
<key>teamID</key>
|
||||
<string>$APPLE_TEAM_ID</string>
|
||||
<key>provisioningProfiles</key>
|
||||
<dict>
|
||||
<key>$IOS_BUNDLE_ID</key>
|
||||
<string>$IOS_PROFILE_NAME</string>
|
||||
</dict>
|
||||
<key>stripSwiftSymbols</key>
|
||||
<true/>
|
||||
<key>manageAppVersionAndBuildNumber</key>
|
||||
<false/>
|
||||
</dict>
|
||||
</plist>
|
||||
EOF
|
||||
xcodebuild \
|
||||
-exportArchive \
|
||||
-archivePath "$RUNNER_TEMP/TruckWash.xcarchive" \
|
||||
-exportPath "$RUNNER_TEMP/ios-export" \
|
||||
-exportOptionsPlist "$export_options"
|
||||
ipa_path="$(find "$RUNNER_TEMP/ios-export" -name '*.ipa' -print -quit)"
|
||||
test -n "$ipa_path"
|
||||
echo "IOS_IPA_PATH=$ipa_path" >> "$GITHUB_ENV"
|
||||
|
||||
- name: Upload iOS artifact
|
||||
if: steps.release-guard.outputs.current == 'true'
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: truck-wash-ios-${{ env.MOBILE_VERSION_NAME }}-${{ github.event.workflow_run.head_sha || github.sha }}
|
||||
path: ${{ runner.temp }}/ios-export/*.ipa
|
||||
if-no-files-found: error
|
||||
retention-days: 14
|
||||
|
||||
- name: Validate iOS IPA with App Store Connect
|
||||
if: steps.release-guard.outputs.current == 'true' && env.UPLOAD_IOS_TO_APP_STORE == 'true'
|
||||
shell: bash
|
||||
run: |
|
||||
set -euo pipefail
|
||||
xcrun altool \
|
||||
--validate-app \
|
||||
--type ios \
|
||||
--file "$IOS_IPA_PATH" \
|
||||
--apiKey "$APP_STORE_CONNECT_API_KEY_ID" \
|
||||
--apiIssuer "$APP_STORE_CONNECT_ISSUER_ID"
|
||||
|
||||
- name: Upload iOS IPA to App Store Connect
|
||||
if: steps.release-guard.outputs.current == 'true' && env.UPLOAD_IOS_TO_APP_STORE == 'true'
|
||||
shell: bash
|
||||
run: |
|
||||
set -euo pipefail
|
||||
xcrun altool \
|
||||
--upload-app \
|
||||
--type ios \
|
||||
--file "$IOS_IPA_PATH" \
|
||||
--apiKey "$APP_STORE_CONNECT_API_KEY_ID" \
|
||||
--apiIssuer "$APP_STORE_CONNECT_ISSUER_ID"
|
||||
|
||||
- name: Clean up Apple signing assets
|
||||
if: always()
|
||||
shell: bash
|
||||
run: |
|
||||
if [[ -n "${IOS_KEYCHAIN_PATH:-}" ]]; then
|
||||
security delete-keychain "$IOS_KEYCHAIN_PATH" || true
|
||||
fi
|
||||
if [[ -n "${IOS_PROFILE_UUID:-}" ]]; then
|
||||
rm -f "$HOME/Library/MobileDevice/Provisioning Profiles/$IOS_PROFILE_UUID.mobileprovision"
|
||||
fi
|
||||
if [[ -n "${APP_STORE_CONNECT_API_KEY_PATH:-}" ]]; then
|
||||
rm -f "$APP_STORE_CONNECT_API_KEY_PATH"
|
||||
fi
|
||||
@@ -14,49 +14,57 @@ permissions:
|
||||
actions: read
|
||||
|
||||
concurrency:
|
||||
group: frontend-release-${{ github.event.workflow_run.head_branch }}
|
||||
cancel-in-progress: true
|
||||
group: frontend-production
|
||||
cancel-in-progress: false
|
||||
|
||||
jobs:
|
||||
build-upload-and-verify:
|
||||
if: github.event.workflow_run.conclusion == 'success' && github.event.workflow_run.event == 'push'
|
||||
build-release:
|
||||
if: >-
|
||||
github.event.workflow_run.conclusion == 'success' &&
|
||||
github.event.workflow_run.event == 'push' &&
|
||||
github.event.workflow_run.head_branch == 'master' &&
|
||||
github.event.workflow_run.head_repository.full_name == github.repository
|
||||
runs-on: [self-hosted, Linux, X64, default]
|
||||
env:
|
||||
RELEASE_BASE_URL: https://api-v2.truckwash.io/master/frontend
|
||||
PLAYWRIGHT_BASE_URL: https://dev.truckwash.io
|
||||
PLAYWRIGHT_RELEASE_STATIC_BASE_URL: https://api-v2.truckwash.io/master/frontend
|
||||
PLAYWRIGHT_RELEASE_API_BASE_URL: https://api-v2.truckwash.io
|
||||
PLAYWRIGHT_RELEASE_API_PING_PATHS: /master/api/ping
|
||||
RELEASE_BUILD_ID: ${{ github.run_id }}-${{ github.run_attempt }}
|
||||
RELEASE_EXPECTED_BUILD_ID: ${{ github.run_id }}-${{ github.run_attempt }}
|
||||
RELEASE_COMMIT_SHA: ${{ github.event.workflow_run.head_sha }}
|
||||
RELEASE_EXPECTED_COMMIT: ${{ github.event.workflow_run.head_sha }}
|
||||
RELEASE_WAIT_INITIAL_SECONDS: 45
|
||||
RELEASE_WAIT_TIMEOUT_SECONDS: 600
|
||||
RELEASE_POLL_INTERVAL_SECONDS: 10
|
||||
RELEASE_BUILD_ID: ${{ github.run_id }}-${{ github.run_attempt }}
|
||||
outputs:
|
||||
current: ${{ steps.branch-head.outputs.current }}
|
||||
build_id: ${{ steps.package.outputs.build_id }}
|
||||
artifact_name: ${{ steps.package-names.outputs.artifact_name }}
|
||||
archive_name: ${{ steps.package.outputs.archive_name }}
|
||||
checksum_name: ${{ steps.package-names.outputs.checksum_name }}
|
||||
inventory_name: ${{ steps.package-names.outputs.inventory_name }}
|
||||
release_id: ${{ steps.package.outputs.release_id }}
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
- name: Check release commit is current
|
||||
id: branch-head
|
||||
uses: actions/github-script@v7
|
||||
with:
|
||||
github-token: ${{ github.token }}
|
||||
script: |
|
||||
const { data: branch } = await github.rest.repos.getBranch({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
branch: "master",
|
||||
});
|
||||
const expected = process.env.RELEASE_EXPECTED_COMMIT;
|
||||
const current = branch.commit.sha === expected;
|
||||
core.setOutput("current", String(current));
|
||||
core.info(
|
||||
current
|
||||
? `Release commit ${expected} is current for master.`
|
||||
: `Skipping stale release for ${expected}; origin/master is ${branch.commit.sha}.`,
|
||||
);
|
||||
|
||||
- name: Checkout tested commit
|
||||
if: steps.branch-head.outputs.current == 'true'
|
||||
uses: actions/checkout@v5
|
||||
with:
|
||||
fetch-depth: 0
|
||||
ref: ${{ github.event.workflow_run.head_sha }}
|
||||
|
||||
- name: Check release commit is current
|
||||
id: branch-head
|
||||
run: |
|
||||
latest_sha="$(git ls-remote origin "refs/heads/$RELEASE_BRANCH" | awk '{print $1}')"
|
||||
if [[ -z "$latest_sha" ]]; then
|
||||
echo "Could not resolve origin/$RELEASE_BRANCH." >&2
|
||||
exit 1
|
||||
fi
|
||||
if [[ "$latest_sha" != "$RELEASE_EXPECTED_COMMIT" ]]; then
|
||||
echo "current=false" >> "$GITHUB_OUTPUT"
|
||||
echo "Skipping stale release for $RELEASE_EXPECTED_COMMIT; origin/$RELEASE_BRANCH is $latest_sha."
|
||||
exit 0
|
||||
fi
|
||||
echo "current=true" >> "$GITHUB_OUTPUT"
|
||||
echo "Release commit is current for $RELEASE_BRANCH."
|
||||
env:
|
||||
RELEASE_BRANCH: ${{ github.event.workflow_run.head_branch }}
|
||||
persist-credentials: false
|
||||
ref: ${{ env.RELEASE_COMMIT_SHA }}
|
||||
|
||||
- name: Setup Node.js
|
||||
if: steps.branch-head.outputs.current == 'true'
|
||||
@@ -79,16 +87,22 @@ jobs:
|
||||
npm run text:check-encoding
|
||||
npm run i18n:v2:source-check
|
||||
|
||||
- name: Unit tests
|
||||
if: steps.branch-head.outputs.current == 'true'
|
||||
run: npm run test:unit
|
||||
env:
|
||||
VITEST_BATCH_SIZE: 5
|
||||
|
||||
- name: Build release artifact
|
||||
if: steps.branch-head.outputs.current == 'true'
|
||||
run: npm run build
|
||||
|
||||
- name: Record pre-gate dist inventory
|
||||
if: steps.branch-head.outputs.current == 'true'
|
||||
run: |
|
||||
inventory="$RUNNER_TEMP/dist-before-production-gate.txt"
|
||||
while IFS= read -r -d '' file; do
|
||||
relative_path="${file#dist/}"
|
||||
printf '%s\t%s\t%s\n' \
|
||||
"$(sha256sum "$file" | awk '{print $1}')" \
|
||||
"$(stat --format='%s' "$file")" \
|
||||
"$relative_path"
|
||||
done < <(find dist -type f -print0 | LC_ALL=C sort -z) > "$inventory"
|
||||
|
||||
- name: Install Playwright Chromium
|
||||
if: steps.branch-head.outputs.current == 'true'
|
||||
run: node scripts/install-playwright-browsers.mjs chromium
|
||||
@@ -96,56 +110,186 @@ jobs:
|
||||
- name: Production Playwright gate
|
||||
if: steps.branch-head.outputs.current == 'true'
|
||||
run: npm run test:e2e:prod
|
||||
env:
|
||||
PLAYWRIGHT_PROD_WEBKIT: "0"
|
||||
|
||||
- name: Upload dist artifact
|
||||
if: steps.branch-head.outputs.current == 'true'
|
||||
continue-on-error: true
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: frontend-dist-${{ env.RELEASE_BUILD_ID }}
|
||||
path: dist
|
||||
retention-days: 3
|
||||
|
||||
- name: Request Release Manager auto sync
|
||||
- name: Confirm production gate did not mutate dist
|
||||
if: steps.branch-head.outputs.current == 'true'
|
||||
run: |
|
||||
test -n "$RELEASE_MANAGER_GATE_TOKEN" || (echo "RELEASE_MANAGER_GATE_TOKEN is required" >&2; exit 1)
|
||||
response_file="$(mktemp)"
|
||||
status_code="$(curl --show-error --silent \
|
||||
--output "$response_file" \
|
||||
--write-out "%{http_code}" \
|
||||
-X POST "$RELEASE_MANAGER_GATE_URL" \
|
||||
-H "Authorization: Bearer $RELEASE_MANAGER_GATE_TOKEN" \
|
||||
-H "Content-Type: application/json" \
|
||||
--data "{\"environment_url\":\"$RELEASE_BASE_URL\",\"channel_slug\":\"stable\",\"app\":\"frontend\",\"repository\":\"$RELEASE_REPOSITORY\",\"branch\":\"$RELEASE_BRANCH\",\"expected_commit\":\"$RELEASE_EXPECTED_COMMIT\",\"build_id\":\"$RELEASE_EXPECTED_BUILD_ID\",\"workflow_url\":\"$RELEASE_WORKFLOW_URL\",\"auto_sync\":true,\"wait_timeout_seconds\":300,\"poll_interval_seconds\":10,\"required_checks\":[\"api_gateway\"]}")"
|
||||
if [[ "$status_code" =~ ^2 ]]; then
|
||||
cat "$response_file"
|
||||
elif [[ "$status_code" == "504" ]]; then
|
||||
echo "Release Manager auto sync request reached the gateway timeout; continuing to artifact wait."
|
||||
else
|
||||
cat "$response_file" >&2
|
||||
echo "Release Manager auto sync request failed with HTTP $status_code." >&2
|
||||
exit 1
|
||||
fi
|
||||
env:
|
||||
RELEASE_MANAGER_GATE_URL: ${{ secrets.RELEASE_MANAGER_GATE_URL || 'https://api.truckwash.io/release/gate/test-runs' }}
|
||||
RELEASE_MANAGER_GATE_TOKEN: ${{ secrets.RELEASE_MANAGER_GATE_TOKEN }}
|
||||
RELEASE_REPOSITORY: ${{ github.repository }}
|
||||
RELEASE_BRANCH: ${{ github.event.workflow_run.head_branch }}
|
||||
RELEASE_WORKFLOW_URL: https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}
|
||||
inventory="$RUNNER_TEMP/dist-after-production-gate.txt"
|
||||
while IFS= read -r -d '' file; do
|
||||
relative_path="${file#dist/}"
|
||||
printf '%s\t%s\t%s\n' \
|
||||
"$(sha256sum "$file" | awk '{print $1}')" \
|
||||
"$(stat --format='%s' "$file")" \
|
||||
"$relative_path"
|
||||
done < <(find dist -type f -print0 | LC_ALL=C sort -z) > "$inventory"
|
||||
cmp "$RUNNER_TEMP/dist-before-production-gate.txt" "$inventory"
|
||||
|
||||
- name: Wait for Coolify release artifact
|
||||
- name: Package and validate release
|
||||
if: steps.branch-head.outputs.current == 'true'
|
||||
run: npm run release:verify-upload
|
||||
id: package
|
||||
run: node scripts/release/package-dist.mjs
|
||||
env:
|
||||
RELEASE_OUTPUT_DIR: release-artifacts
|
||||
|
||||
- name: Resolve package metadata
|
||||
if: steps.branch-head.outputs.current == 'true'
|
||||
id: package-names
|
||||
env:
|
||||
BUILD_ID: ${{ steps.package.outputs.build_id }}
|
||||
CHECKSUM_PATH: ${{ steps.package.outputs.checksum_path }}
|
||||
INVENTORY_PATH: ${{ steps.package.outputs.inventory_path }}
|
||||
run: |
|
||||
echo "artifact_name=frontend-release-$BUILD_ID" >> "$GITHUB_OUTPUT"
|
||||
echo "checksum_name=$(basename -- "$CHECKSUM_PATH")" >> "$GITHUB_OUTPUT"
|
||||
echo "inventory_name=$(basename -- "$INVENTORY_PATH")" >> "$GITHUB_OUTPUT"
|
||||
|
||||
- name: Upload release package
|
||||
if: steps.branch-head.outputs.current == 'true'
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: ${{ steps.package-names.outputs.artifact_name }}
|
||||
path: |
|
||||
${{ steps.package.outputs.archive_path }}
|
||||
${{ steps.package.outputs.checksum_path }}
|
||||
${{ steps.package.outputs.inventory_path }}
|
||||
if-no-files-found: error
|
||||
retention-days: 14
|
||||
|
||||
deploy-frontend-production:
|
||||
needs: build-release
|
||||
if: needs.build-release.outputs.current == 'true'
|
||||
runs-on: [self-hosted, Linux, X64, default]
|
||||
timeout-minutes: 90
|
||||
environment:
|
||||
name: frontend-production
|
||||
url: ${{ vars.PRODUCTION_FRONTEND_URL || 'https://truckwash.io' }}
|
||||
env:
|
||||
RELEASE_BASE_URL: ${{ vars.PRODUCTION_FRONTEND_URL || 'https://truckwash.io' }}
|
||||
PLAYWRIGHT_BASE_URL: ${{ vars.PRODUCTION_FRONTEND_URL || 'https://truckwash.io' }}
|
||||
PLAYWRIGHT_RELEASE_STATIC_BASE_URL: ${{ vars.PRODUCTION_FRONTEND_URL || 'https://truckwash.io' }}
|
||||
PLAYWRIGHT_RELEASE_API_BASE_URL: https://api-v2.truckwash.io
|
||||
PLAYWRIGHT_RELEASE_API_PING_PATHS: /master/api/ping
|
||||
RELEASE_COMMIT_SHA: ${{ github.event.workflow_run.head_sha }}
|
||||
RELEASE_EXPECTED_COMMIT: ${{ github.event.workflow_run.head_sha }}
|
||||
RELEASE_BUILD_ID: ${{ needs.build-release.outputs.build_id }}
|
||||
RELEASE_EXPECTED_BUILD_ID: ${{ needs.build-release.outputs.build_id }}
|
||||
RELEASE_ID: ${{ needs.build-release.outputs.release_id }}
|
||||
RELEASE_STRICT_BUILD_ID: "true"
|
||||
RELEASE_REQUIRE_CACHE_HEADERS: "true"
|
||||
RELEASE_WAIT_INITIAL_SECONDS: 0
|
||||
RELEASE_WAIT_TIMEOUT_SECONDS: 300
|
||||
RELEASE_POLL_INTERVAL_SECONDS: 5
|
||||
steps:
|
||||
- name: Checkout tested commit
|
||||
uses: actions/checkout@v5
|
||||
with:
|
||||
fetch-depth: 0
|
||||
persist-credentials: false
|
||||
ref: ${{ env.RELEASE_COMMIT_SHA }}
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v5
|
||||
with:
|
||||
node-version: 22
|
||||
cache: npm
|
||||
|
||||
- name: Install dependencies
|
||||
run: npm ci --legacy-peer-deps
|
||||
|
||||
- name: Install secure FTP client without system changes
|
||||
run: |
|
||||
if command -v lftp >/dev/null 2>&1; then
|
||||
exit 0
|
||||
fi
|
||||
|
||||
package_root="$RUNNER_TEMP/lftp-package"
|
||||
mkdir -p "$package_root"
|
||||
(
|
||||
cd "$package_root"
|
||||
apt-get download lftp
|
||||
dpkg-deb --extract ./lftp_*.deb root
|
||||
)
|
||||
echo "$package_root/root/usr/bin" >> "$GITHUB_PATH"
|
||||
|
||||
- name: Install Playwright Chromium
|
||||
run: node scripts/install-playwright-browsers.mjs chromium
|
||||
|
||||
- name: Download validated release package
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: ${{ needs.build-release.outputs.artifact_name }}
|
||||
path: release-artifacts
|
||||
|
||||
- name: Resolve downloaded release package
|
||||
env:
|
||||
ARCHIVE_NAME: ${{ needs.build-release.outputs.archive_name }}
|
||||
CHECKSUM_NAME: ${{ needs.build-release.outputs.checksum_name }}
|
||||
INVENTORY_NAME: ${{ needs.build-release.outputs.inventory_name }}
|
||||
run: |
|
||||
[[ -n "$ARCHIVE_NAME" && "$ARCHIVE_NAME" == "$(basename -- "$ARCHIVE_NAME")" ]]
|
||||
[[ -n "$CHECKSUM_NAME" && "$CHECKSUM_NAME" == "$(basename -- "$CHECKSUM_NAME")" ]]
|
||||
[[ -n "$INVENTORY_NAME" && "$INVENTORY_NAME" == "$(basename -- "$INVENTORY_NAME")" ]]
|
||||
|
||||
archive_path="$GITHUB_WORKSPACE/release-artifacts/$ARCHIVE_NAME"
|
||||
checksum_path="$GITHUB_WORKSPACE/release-artifacts/$CHECKSUM_NAME"
|
||||
inventory_path="$GITHUB_WORKSPACE/release-artifacts/$INVENTORY_NAME"
|
||||
[[ -f "$archive_path" && -f "$checksum_path" && -f "$inventory_path" ]]
|
||||
|
||||
echo "RELEASE_ARCHIVE_PATH=$archive_path" >> "$GITHUB_ENV"
|
||||
echo "RELEASE_ARCHIVE_SHA256_PATH=$checksum_path" >> "$GITHUB_ENV"
|
||||
echo "RELEASE_INVENTORY_PATH=$inventory_path" >> "$GITHUB_ENV"
|
||||
|
||||
- name: Check release commit is still current
|
||||
id: branch-head
|
||||
uses: actions/github-script@v7
|
||||
with:
|
||||
github-token: ${{ github.token }}
|
||||
script: |
|
||||
const { data: branch } = await github.rest.repos.getBranch({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
branch: "master",
|
||||
});
|
||||
const expected = process.env.RELEASE_EXPECTED_COMMIT;
|
||||
const current = branch.commit.sha === expected;
|
||||
core.setOutput("current", String(current));
|
||||
core.info(
|
||||
current
|
||||
? `Release commit ${expected} is current immediately before activation.`
|
||||
: `Skipping stale release for ${expected}; origin/master is ${branch.commit.sha}.`,
|
||||
);
|
||||
|
||||
- name: Deploy atomically and verify cPanel release
|
||||
if: steps.branch-head.outputs.current == 'true'
|
||||
id: deploy
|
||||
timeout-minutes: 15
|
||||
run: node scripts/release/deploy-cpanel.mjs
|
||||
env:
|
||||
NODE_OPTIONS: --use-system-ca
|
||||
PRODUCTION_FTP_HOST: ${{ secrets.PRODUCTION_FTP_HOST }}
|
||||
PRODUCTION_FTP_USER: ${{ secrets.PRODUCTION_FTP_USER }}
|
||||
PRODUCTION_FTP_PASSWORD: ${{ secrets.PRODUCTION_FTP_PASSWORD }}
|
||||
PRODUCTION_FTP_PATH: ${{ secrets.PRODUCTION_FTP_PATH }}
|
||||
PRODUCTION_CPANEL_USER: ${{ secrets.PRODUCTION_CPANEL_USER }}
|
||||
PRODUCTION_CPANEL_API_TOKEN: ${{ secrets.PRODUCTION_CPANEL_API_TOKEN }}
|
||||
PRODUCTION_CPANEL_API_URL: ${{ vars.PRODUCTION_CPANEL_API_URL }}
|
||||
PRODUCTION_CPANEL_PATH: ${{ vars.PRODUCTION_CPANEL_PATH }}
|
||||
PRODUCTION_FRONTEND_URL: ${{ vars.PRODUCTION_FRONTEND_URL || 'https://truckwash.io' }}
|
||||
RELEASE_GITHUB_REPOSITORY: ${{ github.repository }}
|
||||
RELEASE_GITHUB_TOKEN: ${{ github.token }}
|
||||
|
||||
- name: Public live Playwright gate
|
||||
if: steps.branch-head.outputs.current == 'true'
|
||||
timeout-minutes: 10
|
||||
run: npm run test:e2e:live:public
|
||||
env:
|
||||
NODE_OPTIONS: --use-system-ca
|
||||
|
||||
- name: Credentialed live Playwright gate
|
||||
if: steps.branch-head.outputs.current == 'true'
|
||||
timeout-minutes: 15
|
||||
run: npm run test:e2e:live:roles
|
||||
env:
|
||||
NODE_OPTIONS: --use-system-ca
|
||||
@@ -157,6 +301,23 @@ jobs:
|
||||
PLAYWRIGHT_OPERATOR_PASSWORD: ${{ secrets.PLAYWRIGHT_OPERATOR_PASSWORD }}
|
||||
PLAYWRIGHT_DEPARTMENT_ID: ${{ secrets.PLAYWRIGHT_DEPARTMENT_ID }}
|
||||
|
||||
- name: Roll back after live verification failure
|
||||
if: failure() && steps.branch-head.outputs.current == 'true' && steps.deploy.outcome == 'success'
|
||||
timeout-minutes: 10
|
||||
run: node scripts/release/deploy-cpanel.mjs --rollback
|
||||
env:
|
||||
NODE_OPTIONS: --use-system-ca
|
||||
RELEASE_ROLLBACK_TARGET: ${{ steps.deploy.outputs.rollback_target }}
|
||||
PRODUCTION_FTP_HOST: ${{ secrets.PRODUCTION_FTP_HOST }}
|
||||
PRODUCTION_FTP_USER: ${{ secrets.PRODUCTION_FTP_USER }}
|
||||
PRODUCTION_FTP_PASSWORD: ${{ secrets.PRODUCTION_FTP_PASSWORD }}
|
||||
PRODUCTION_FTP_PATH: ${{ secrets.PRODUCTION_FTP_PATH }}
|
||||
PRODUCTION_CPANEL_USER: ${{ secrets.PRODUCTION_CPANEL_USER }}
|
||||
PRODUCTION_CPANEL_API_TOKEN: ${{ secrets.PRODUCTION_CPANEL_API_TOKEN }}
|
||||
PRODUCTION_CPANEL_API_URL: ${{ vars.PRODUCTION_CPANEL_API_URL }}
|
||||
PRODUCTION_CPANEL_PATH: ${{ vars.PRODUCTION_CPANEL_PATH }}
|
||||
PRODUCTION_FRONTEND_URL: ${{ vars.PRODUCTION_FRONTEND_URL || 'https://truckwash.io' }}
|
||||
|
||||
- name: Record Release Manager gate
|
||||
if: steps.branch-head.outputs.current == 'true'
|
||||
run: |
|
||||
@@ -166,12 +327,11 @@ jobs:
|
||||
-X POST "$RELEASE_MANAGER_GATE_URL" \
|
||||
-H "Authorization: Bearer $RELEASE_MANAGER_GATE_TOKEN" \
|
||||
-H "Content-Type: application/json" \
|
||||
--data "{\"environment_url\":\"$RELEASE_BASE_URL\",\"channel_slug\":\"stable\",\"app\":\"frontend\",\"repository\":\"$RELEASE_REPOSITORY\",\"branch\":\"$RELEASE_BRANCH\",\"expected_commit\":\"$RELEASE_EXPECTED_COMMIT\",\"build_id\":\"$release_gate_build_id\",\"workflow_url\":\"$RELEASE_WORKFLOW_URL\",\"auto_sync\":true,\"wait_timeout_seconds\":300,\"poll_interval_seconds\":10,\"required_checks\":[\"static_artifact\",\"api_gateway\"]}"
|
||||
--data "{\"environment_url\":\"$RELEASE_BASE_URL\",\"channel_slug\":\"stable\",\"app\":\"frontend\",\"repository\":\"$RELEASE_REPOSITORY\",\"branch\":\"master\",\"expected_commit\":\"$RELEASE_EXPECTED_COMMIT\",\"build_id\":\"$release_gate_build_id\",\"workflow_url\":\"$RELEASE_WORKFLOW_URL\",\"auto_sync\":false,\"wait_timeout_seconds\":300,\"poll_interval_seconds\":10,\"required_checks\":[\"static_artifact\",\"api_gateway\"]}"
|
||||
env:
|
||||
RELEASE_MANAGER_GATE_URL: ${{ secrets.RELEASE_MANAGER_GATE_URL || 'https://api.truckwash.io/release/gate/test-runs' }}
|
||||
RELEASE_MANAGER_GATE_TOKEN: ${{ secrets.RELEASE_MANAGER_GATE_TOKEN }}
|
||||
RELEASE_REPOSITORY: ${{ github.repository }}
|
||||
RELEASE_BRANCH: ${{ github.event.workflow_run.head_branch }}
|
||||
RELEASE_WORKFLOW_URL: https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}
|
||||
|
||||
- name: Update server version after verification
|
||||
@@ -189,4 +349,4 @@ jobs:
|
||||
name: frontend-release-playwright-${{ env.RELEASE_BUILD_ID }}
|
||||
path: output/playwright
|
||||
if-no-files-found: ignore
|
||||
retention-days: 3
|
||||
retention-days: 14
|
||||
|
||||
@@ -4,8 +4,43 @@ on:
|
||||
pull_request:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
- master
|
||||
- dev
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
mode:
|
||||
description: "What to run for a manual dispatch."
|
||||
required: true
|
||||
type: choice
|
||||
default: full
|
||||
options:
|
||||
- full
|
||||
- targeted
|
||||
- targeted-then-full
|
||||
target_specs:
|
||||
description: "Comma- or newline-separated Playwright spec paths under tests/e2e."
|
||||
required: false
|
||||
type: string
|
||||
default: "tests/e2e/superuser-department-overview.spec.js"
|
||||
target_projects:
|
||||
description: "JSON array of Playwright projects for targeted mode."
|
||||
required: false
|
||||
type: string
|
||||
default: '["chromium-desktop","chromium-mobile","chromium-tablet","webkit-mobile","webkit-desktop"]'
|
||||
target_grep:
|
||||
description: "Optional Playwright grep pattern for targeted mode."
|
||||
required: false
|
||||
type: string
|
||||
default: ""
|
||||
runner:
|
||||
description: "Runner pool for this manually dispatched test run"
|
||||
required: false
|
||||
default: "self-hosted"
|
||||
type: choice
|
||||
options:
|
||||
- self-hosted
|
||||
- github-hosted
|
||||
schedule:
|
||||
- cron: "0 2 * * *"
|
||||
|
||||
@@ -13,16 +48,22 @@ permissions:
|
||||
contents: read
|
||||
|
||||
concurrency:
|
||||
group: frontend-tests-${{ github.workflow }}-${{ github.event_name }}-${{ github.head_ref || github.ref_name }}
|
||||
cancel-in-progress: true
|
||||
group: frontend-tests-${{ github.workflow }}-${{ github.event_name == 'pull_request' && github.event.pull_request.number || github.run_id }}
|
||||
cancel-in-progress: ${{ github.event_name == 'pull_request' }}
|
||||
|
||||
# Repository variables used as CI runner and credit controls:
|
||||
# - FRONTEND_CI_STANDARD_RUNNER: JSON runs-on value for format/build/unit jobs.
|
||||
# - FRONTEND_CI_E2E_RUNNER: JSON runs-on value for Playwright jobs.
|
||||
# - FRONTEND_CI_PR_E2E_MAX_PARALLEL: numeric Playwright PR job parallelism.
|
||||
# - FRONTEND_CI_FULL_E2E_MAX_PARALLEL: numeric full-suite job parallelism.
|
||||
# GitHub-hosted example: ["ubuntu-22.04"], with PR parallelism 2 and full parallelism 1.
|
||||
jobs:
|
||||
format-tests:
|
||||
# CI runs on the repository's self-hosted runner pool.
|
||||
runs-on: [self-hosted, Linux, X64, pleno, frontend]
|
||||
runs-on: ${{ fromJSON(vars.FRONTEND_CI_STANDARD_RUNNER || '["self-hosted","Linux","X64","pleno","frontend"]') }}
|
||||
timeout-minutes: 15
|
||||
steps:
|
||||
- name: Repair self-hosted workspace permissions
|
||||
if: ${{ contains(vars.FRONTEND_CI_STANDARD_RUNNER || 'self-hosted', 'self-hosted') }}
|
||||
shell: bash
|
||||
run: |
|
||||
if [[ -d "$GITHUB_WORKSPACE" ]]; then
|
||||
@@ -55,10 +96,11 @@ jobs:
|
||||
|
||||
build-and-unit:
|
||||
needs: format-tests
|
||||
runs-on: [self-hosted, Linux, X64, pleno, frontend]
|
||||
runs-on: ${{ fromJSON(vars.FRONTEND_CI_STANDARD_RUNNER || '["self-hosted","Linux","X64","pleno","frontend"]') }}
|
||||
timeout-minutes: 30
|
||||
steps:
|
||||
- name: Repair self-hosted workspace permissions
|
||||
if: ${{ contains(vars.FRONTEND_CI_STANDARD_RUNNER || 'self-hosted', 'self-hosted') }}
|
||||
shell: bash
|
||||
run: |
|
||||
if [[ -d "$GITHUB_WORKSPACE" ]]; then
|
||||
@@ -97,23 +139,206 @@ jobs:
|
||||
env:
|
||||
VITEST_BATCH_SIZE: 5
|
||||
|
||||
e2e-pr:
|
||||
if: github.event_name != 'schedule'
|
||||
e2e-targeted:
|
||||
if: >
|
||||
github.event_name == 'workflow_dispatch' &&
|
||||
(inputs.mode == 'targeted' || inputs.mode == 'targeted-then-full')
|
||||
needs: build-and-unit
|
||||
name: E2E-pr-${{ matrix.suite }}-${{ matrix.project }}
|
||||
runs-on: [self-hosted, Linux, X64, pleno, frontend, docker]
|
||||
timeout-minutes: 30
|
||||
name: E2E-targeted-${{ matrix.project }}
|
||||
# Use GitHub-hosted runners to avoid self-hosted desktop contention and sleep/power events.
|
||||
runs-on: ubuntu-24.04
|
||||
timeout-minutes: 35
|
||||
strategy:
|
||||
fail-fast: false
|
||||
max-parallel: 4
|
||||
matrix:
|
||||
project: ${{ fromJSON(inputs.target_projects || '["chromium-desktop"]') }}
|
||||
env:
|
||||
MATRIX_PROJECT: ${{ matrix.project }}
|
||||
PLAYWRIGHT_ARTIFACT_NAMESPACE: e2e-targeted-${{ matrix.project }}
|
||||
PLAYWRIGHT_REPORTER_MODE: line-html
|
||||
PLAYWRIGHT_WORKERS: 1
|
||||
PLAYWRIGHT_VIDEO_MODE: on-first-retry
|
||||
TARGET_GREP: ${{ inputs.target_grep }}
|
||||
TARGET_SPECS: ${{ inputs.target_specs }}
|
||||
RUN_ID: ${{ github.run_id }}
|
||||
steps:
|
||||
- name: Normalize workspace permissions
|
||||
shell: bash
|
||||
run: |
|
||||
if [[ -d "$GITHUB_WORKSPACE" ]]; then
|
||||
sudo -n chown -R "$(id -u):$(id -g)" "$GITHUB_WORKSPACE" 2>/dev/null || true
|
||||
foreign_entry="$(find "$GITHUB_WORKSPACE" -mindepth 1 -maxdepth 2 ! -user "$(id -u)" -print -quit 2>/dev/null || true)"
|
||||
if [[ -n "$foreign_entry" ]]; then
|
||||
trash="$GITHUB_WORKSPACE/../_workspace-trash-$GITHUB_RUN_ID-$GITHUB_JOB"
|
||||
rm -rf "$trash" 2>/dev/null || true
|
||||
mv "$GITHUB_WORKSPACE" "$trash" 2>/dev/null || true
|
||||
mkdir -p "$GITHUB_WORKSPACE"
|
||||
fi
|
||||
fi
|
||||
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v5
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v5
|
||||
with:
|
||||
node-version: 22
|
||||
|
||||
- name: Run targeted Playwright specs in container
|
||||
shell: bash
|
||||
run: |
|
||||
set -euo pipefail
|
||||
case "$MATRIX_PROJECT" in
|
||||
chromium-mobile) project_offset=1 ;;
|
||||
chromium-desktop) project_offset=2 ;;
|
||||
chromium-tablet) project_offset=3 ;;
|
||||
webkit-mobile) project_offset=31 ;;
|
||||
webkit-desktop) project_offset=32 ;;
|
||||
webkit-tablet) project_offset=33 ;;
|
||||
firefox-mobile) project_offset=61 ;;
|
||||
firefox-desktop) project_offset=62 ;;
|
||||
firefox-tablet) project_offset=63 ;;
|
||||
*) echo "Unsupported Playwright project: $MATRIX_PROJECT" >&2; exit 1 ;;
|
||||
esac
|
||||
port_seed=$((20000 + (RUN_ID % 20000) + project_offset))
|
||||
lock_root="${PLAYWRIGHT_PORT_LOCK_ROOT:-/tmp/pleno-playwright-port-locks}"
|
||||
mkdir -p "$lock_root"
|
||||
chmod 1777 "$lock_root" 2>/dev/null || true
|
||||
find "$lock_root" -mindepth 1 -maxdepth 1 -type d -mmin +360 -exec rmdir {} \; 2>/dev/null || true
|
||||
playwright_port_lock=""
|
||||
playwright_dev_port=""
|
||||
for ((candidate = port_seed; candidate < port_seed + 1000; candidate += 1)); do
|
||||
lock_dir="${lock_root}/${candidate}.lock"
|
||||
if ! mkdir "$lock_dir" 2>/dev/null; then
|
||||
continue
|
||||
fi
|
||||
if ss -H -ltn "sport = :${candidate}" 2>/dev/null | grep -q .; then
|
||||
rmdir "$lock_dir" || true
|
||||
continue
|
||||
fi
|
||||
playwright_port_lock="$lock_dir"
|
||||
playwright_dev_port="$candidate"
|
||||
break
|
||||
done
|
||||
if [[ -z "$playwright_dev_port" ]]; then
|
||||
echo "Unable to find a free Playwright dev-server port." >&2
|
||||
exit 1
|
||||
fi
|
||||
trap 'if [[ -n "${playwright_port_lock:-}" ]]; then rmdir "$playwright_port_lock" 2>/dev/null || true; fi' EXIT
|
||||
if docker info >/dev/null 2>&1; then
|
||||
docker_cmd=(docker)
|
||||
elif sudo -n docker info >/dev/null 2>&1; then
|
||||
docker_cmd=(sudo docker)
|
||||
else
|
||||
echo "Docker is not available to the runner user, and sudo docker is not available." >&2
|
||||
exit 1
|
||||
fi
|
||||
mkdir -p output/playwright
|
||||
scripts/ci/runner-diagnostics.sh "before targeted Playwright ${MATRIX_PROJECT}" -- "${docker_cmd[@]}"
|
||||
SYSTEMD_INHIBIT_REASON="Frontend targeted Playwright ${MATRIX_PROJECT}" \
|
||||
scripts/ci/with-systemd-inhibit.sh "${docker_cmd[@]}" run --rm --ipc=host --network host \
|
||||
--volume "$PWD:/source:ro" \
|
||||
--volume "$PWD/output/playwright:/work/output/playwright" \
|
||||
--workdir /work \
|
||||
--env HOME=/tmp \
|
||||
--env CI="${CI:-}" \
|
||||
--env PLAYWRIGHT_ARTIFACT_NAMESPACE="$PLAYWRIGHT_ARTIFACT_NAMESPACE" \
|
||||
--env PLAYWRIGHT_REPORTER_MODE="$PLAYWRIGHT_REPORTER_MODE" \
|
||||
--env PLAYWRIGHT_WORKERS="$PLAYWRIGHT_WORKERS" \
|
||||
--env PLAYWRIGHT_VIDEO_MODE="$PLAYWRIGHT_VIDEO_MODE" \
|
||||
--env PLAYWRIGHT_DEV_PORT="$playwright_dev_port" \
|
||||
--env MATRIX_PROJECT="$MATRIX_PROJECT" \
|
||||
--env TARGET_GREP="$TARGET_GREP" \
|
||||
--env TARGET_SPECS="$TARGET_SPECS" \
|
||||
mcr.microsoft.com/playwright:v1.58.2-noble \
|
||||
bash -lc '
|
||||
set -euo pipefail
|
||||
tar --exclude=./output/playwright -C /source -cf - . | tar -C /work -xf -
|
||||
git config --global --add safe.directory /work
|
||||
install_dependencies() {
|
||||
local attempt
|
||||
for attempt in 1 2 3; do
|
||||
if npm ci --legacy-peer-deps --fetch-retries=5 --fetch-retry-mintimeout=20000 --fetch-retry-maxtimeout=120000; then
|
||||
return 0
|
||||
fi
|
||||
if [[ "$attempt" == "3" ]]; then
|
||||
return 1
|
||||
fi
|
||||
echo "npm ci failed on attempt ${attempt}; retrying..." >&2
|
||||
sleep 20
|
||||
done
|
||||
}
|
||||
install_dependencies
|
||||
ulimit -n 16384 || true
|
||||
mapfile -t spec_args < <(printf "%s\n" "$TARGET_SPECS" | tr "," "\n" | sed "s/^[[:space:]]*//;s/[[:space:]]*$//;/^$/d")
|
||||
if [[ "${#spec_args[@]}" -eq 0 && -z "${TARGET_GREP:-}" ]]; then
|
||||
echo "Provide at least one spec path or grep pattern." >&2
|
||||
exit 1
|
||||
fi
|
||||
for spec_path in "${spec_args[@]}"; do
|
||||
if [[ "$spec_path" == /* || "$spec_path" == *".."* || "$spec_path" != tests/e2e/* ]]; then
|
||||
echo "Targeted spec must stay under tests/e2e: $spec_path" >&2
|
||||
exit 1
|
||||
fi
|
||||
if [[ ! -f "$spec_path" ]]; then
|
||||
echo "Targeted spec does not exist: $spec_path" >&2
|
||||
exit 1
|
||||
fi
|
||||
done
|
||||
args=("${spec_args[@]}")
|
||||
if [[ -n "${TARGET_GREP:-}" ]]; then
|
||||
args+=(--grep "$TARGET_GREP")
|
||||
fi
|
||||
args+=(--project="$MATRIX_PROJECT")
|
||||
npx playwright test "${args[@]}"
|
||||
'
|
||||
|
||||
- name: Runner diagnostics after Playwright failure
|
||||
if: failure() || cancelled()
|
||||
continue-on-error: true
|
||||
run: scripts/ci/runner-diagnostics.sh "after targeted Playwright ${{ matrix.project }}"
|
||||
|
||||
- name: Upload Playwright report
|
||||
if: failure() || cancelled()
|
||||
continue-on-error: true
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: playwright-report-targeted-${{ matrix.project }}
|
||||
path: |
|
||||
output/playwright/${{ env.PLAYWRIGHT_ARTIFACT_NAMESPACE }}-*
|
||||
output/playwright/${{ env.PLAYWRIGHT_ARTIFACT_NAMESPACE }}
|
||||
if-no-files-found: ignore
|
||||
retention-days: 1
|
||||
|
||||
e2e-pr:
|
||||
if: >
|
||||
always() &&
|
||||
github.event_name != 'schedule' &&
|
||||
needs.build-and-unit.result == 'success' &&
|
||||
!(github.event_name == 'workflow_dispatch' && inputs.mode == 'targeted') &&
|
||||
(
|
||||
github.event_name != 'workflow_dispatch' ||
|
||||
inputs.mode == 'full' ||
|
||||
needs.e2e-targeted.result == 'success'
|
||||
)
|
||||
needs: [build-and-unit, e2e-targeted]
|
||||
name: E2E-pr-${{ matrix.suite }}-${{ matrix.project }}
|
||||
runs-on: ${{ fromJSON(vars.FRONTEND_CI_E2E_RUNNER || '["self-hosted","Linux","X64","pleno","frontend","docker"]') }}
|
||||
timeout-minutes: 45
|
||||
strategy:
|
||||
fail-fast: false
|
||||
max-parallel: ${{ fromJSON(vars.FRONTEND_CI_PR_E2E_MAX_PARALLEL || '2') }}
|
||||
matrix:
|
||||
suite: [core, changed]
|
||||
project: [chromium-desktop, chromium-mobile]
|
||||
env:
|
||||
PLAYWRIGHT_ARTIFACT_NAMESPACE: e2e-pr-${{ matrix.suite }}-${{ matrix.project }}
|
||||
PLAYWRIGHT_REPORTER_MODE: line-html
|
||||
PLAYWRIGHT_WORKERS: 1
|
||||
PLAYWRIGHT_VIDEO_MODE: on-first-retry
|
||||
steps:
|
||||
- name: Repair self-hosted workspace permissions
|
||||
if: ${{ contains(vars.FRONTEND_CI_E2E_RUNNER || 'self-hosted', 'self-hosted') }}
|
||||
shell: bash
|
||||
run: |
|
||||
if [[ -d "$GITHUB_WORKSPACE" ]]; then
|
||||
@@ -140,20 +365,29 @@ jobs:
|
||||
EVENT_NAME: ${{ github.event_name }}
|
||||
HEAD_SHA: ${{ github.sha }}
|
||||
PR_BASE_SHA: ${{ github.event.pull_request.base.sha }}
|
||||
PR_HEAD_SHA: ${{ github.event.pull_request.head.sha }}
|
||||
PUSH_BEFORE_SHA: ${{ github.event.before }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
zero_sha="0000000000000000000000000000000000000000"
|
||||
if [[ "$EVENT_NAME" == "pull_request" && -n "$PR_BASE_SHA" ]]; then
|
||||
base_ref="$PR_BASE_SHA"
|
||||
head_ref="$PR_HEAD_SHA"
|
||||
elif [[ -z "$PUSH_BEFORE_SHA" || "$PUSH_BEFORE_SHA" == "$zero_sha" ]]; then
|
||||
git fetch --no-tags --prune origin "$DEFAULT_BRANCH"
|
||||
base_ref="origin/$DEFAULT_BRANCH"
|
||||
head_ref="$HEAD_SHA"
|
||||
else
|
||||
base_ref="$PUSH_BEFORE_SHA"
|
||||
head_ref="$HEAD_SHA"
|
||||
fi
|
||||
if [[ "$EVENT_NAME" == "pull_request" && -n "$PR_HEAD_SHA" ]]; then
|
||||
head_ref="$PR_HEAD_SHA"
|
||||
else
|
||||
head_ref="$HEAD_SHA"
|
||||
fi
|
||||
echo "base=$base_ref" >> "$GITHUB_OUTPUT"
|
||||
echo "head=$HEAD_SHA" >> "$GITHUB_OUTPUT"
|
||||
echo "head=$head_ref" >> "$GITHUB_OUTPUT"
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v5
|
||||
@@ -181,28 +415,30 @@ jobs:
|
||||
*) echo "Unsupported Playwright PR project: $MATRIX_PROJECT" >&2; exit 1 ;;
|
||||
esac
|
||||
port_seed=$((20000 + (RUN_ID % 20000) + suite_offset + project_offset))
|
||||
playwright_dev_port="$(
|
||||
PORT_SEED="$port_seed" node - <<'NODE'
|
||||
const net = require("node:net");
|
||||
const start = Number(process.env.PORT_SEED || 20000);
|
||||
const candidates = Array.from({ length: 200 }, (_, index) => start + index);
|
||||
function tryPort(index) {
|
||||
if (index >= candidates.length) {
|
||||
console.error("Unable to find a free Playwright dev-server port.");
|
||||
process.exit(1);
|
||||
}
|
||||
const port = candidates[index];
|
||||
const server = net.createServer();
|
||||
server.once("error", () => tryPort(index + 1));
|
||||
server.listen(port, "127.0.0.1", () => {
|
||||
server.close(() => {
|
||||
console.log(port);
|
||||
});
|
||||
});
|
||||
}
|
||||
tryPort(0);
|
||||
NODE
|
||||
)"
|
||||
lock_root="${PLAYWRIGHT_PORT_LOCK_ROOT:-/tmp/pleno-playwright-port-locks}"
|
||||
mkdir -p "$lock_root"
|
||||
chmod 1777 "$lock_root" 2>/dev/null || true
|
||||
find "$lock_root" -mindepth 1 -maxdepth 1 -type d -mmin +360 -exec rmdir {} \; 2>/dev/null || true
|
||||
playwright_port_lock=""
|
||||
playwright_dev_port=""
|
||||
for ((candidate = port_seed; candidate < port_seed + 1000; candidate += 1)); do
|
||||
lock_dir="${lock_root}/${candidate}.lock"
|
||||
if ! mkdir "$lock_dir" 2>/dev/null; then
|
||||
continue
|
||||
fi
|
||||
if ss -H -ltn "sport = :${candidate}" 2>/dev/null | grep -q .; then
|
||||
rmdir "$lock_dir" || true
|
||||
continue
|
||||
fi
|
||||
playwright_port_lock="$lock_dir"
|
||||
playwright_dev_port="$candidate"
|
||||
break
|
||||
done
|
||||
if [[ -z "$playwright_dev_port" ]]; then
|
||||
echo "Unable to find a free Playwright dev-server port." >&2
|
||||
exit 1
|
||||
fi
|
||||
trap 'if [[ -n "${playwright_port_lock:-}" ]]; then rmdir "$playwright_port_lock" 2>/dev/null || true; fi' EXIT
|
||||
if docker info >/dev/null 2>&1; then
|
||||
docker_cmd=(docker)
|
||||
elif sudo -n docker info >/dev/null 2>&1; then
|
||||
@@ -212,7 +448,9 @@ jobs:
|
||||
exit 1
|
||||
fi
|
||||
mkdir -p output/playwright
|
||||
"${docker_cmd[@]}" run --rm --ipc=host --network host \
|
||||
scripts/ci/runner-diagnostics.sh "before Playwright PR ${MATRIX_SUITE}/${MATRIX_PROJECT}" -- "${docker_cmd[@]}"
|
||||
SYSTEMD_INHIBIT_REASON="Frontend Playwright PR ${MATRIX_SUITE}/${MATRIX_PROJECT}" \
|
||||
scripts/ci/with-systemd-inhibit.sh "${docker_cmd[@]}" run --rm --ipc=host --network host \
|
||||
--volume "$PWD:/source:ro" \
|
||||
--volume "$PWD/output/playwright:/work/output/playwright" \
|
||||
--workdir /work \
|
||||
@@ -220,6 +458,8 @@ jobs:
|
||||
--env CI="${CI:-}" \
|
||||
--env PLAYWRIGHT_ARTIFACT_NAMESPACE="$PLAYWRIGHT_ARTIFACT_NAMESPACE" \
|
||||
--env PLAYWRIGHT_REPORTER_MODE="$PLAYWRIGHT_REPORTER_MODE" \
|
||||
--env PLAYWRIGHT_WORKERS="$PLAYWRIGHT_WORKERS" \
|
||||
--env PLAYWRIGHT_VIDEO_MODE="$PLAYWRIGHT_VIDEO_MODE" \
|
||||
--env PLAYWRIGHT_DEV_PORT="$playwright_dev_port" \
|
||||
--env MATRIX_SUITE="$MATRIX_SUITE" \
|
||||
--env MATRIX_PROJECT="$MATRIX_PROJECT" \
|
||||
@@ -230,9 +470,23 @@ jobs:
|
||||
set -euo pipefail
|
||||
tar --exclude=./output/playwright -C /source -cf - . | tar -C /work -xf -
|
||||
git config --global --add safe.directory /work
|
||||
npm ci --legacy-peer-deps
|
||||
install_dependencies() {
|
||||
local attempt
|
||||
for attempt in 1 2 3; do
|
||||
if npm ci --legacy-peer-deps --fetch-retries=5 --fetch-retry-mintimeout=20000 --fetch-retry-maxtimeout=120000; then
|
||||
return 0
|
||||
fi
|
||||
if [[ "$attempt" == "3" ]]; then
|
||||
return 1
|
||||
fi
|
||||
echo "npm ci failed on attempt ${attempt}; retrying..." >&2
|
||||
sleep 20
|
||||
done
|
||||
}
|
||||
install_dependencies
|
||||
ulimit -n 16384 || true
|
||||
if [[ "$MATRIX_SUITE" == "core" ]]; then
|
||||
PLAYWRIGHT_ARTIFACT_NAMESPACE="${PLAYWRIGHT_ARTIFACT_NAMESPACE}-ct" npm run test:ct -- --project="$MATRIX_PROJECT"
|
||||
npx playwright test --grep @smoke --project="$MATRIX_PROJECT"
|
||||
npm run test:e2e:pr -- --core-only --project="$MATRIX_PROJECT"
|
||||
else
|
||||
@@ -240,6 +494,11 @@ jobs:
|
||||
fi
|
||||
'
|
||||
|
||||
- name: Runner diagnostics after Playwright failure
|
||||
if: failure() || cancelled()
|
||||
continue-on-error: true
|
||||
run: scripts/ci/runner-diagnostics.sh "after Playwright PR ${{ matrix.suite }}/${{ matrix.project }}"
|
||||
|
||||
- name: Upload Playwright report
|
||||
if: failure() || cancelled()
|
||||
continue-on-error: true
|
||||
@@ -252,38 +511,72 @@ jobs:
|
||||
if-no-files-found: ignore
|
||||
retention-days: 1
|
||||
|
||||
required-ci:
|
||||
if: ${{ always() && (github.event_name == 'pull_request' || github.event_name == 'push') }}
|
||||
name: Required CI
|
||||
needs: [format-tests, build-and-unit, e2e-pr]
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 5
|
||||
steps:
|
||||
- name: Verify required jobs succeeded
|
||||
shell: bash
|
||||
env:
|
||||
FORMAT_TESTS_RESULT: ${{ needs.format-tests.result }}
|
||||
BUILD_AND_UNIT_RESULT: ${{ needs.build-and-unit.result }}
|
||||
E2E_PR_RESULT: ${{ needs.e2e-pr.result }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
failed=0
|
||||
for required_job in FORMAT_TESTS_RESULT BUILD_AND_UNIT_RESULT E2E_PR_RESULT; do
|
||||
result="${!required_job:-missing}"
|
||||
if [[ "$result" != "success" ]]; then
|
||||
echo "${required_job}=${result}" >&2
|
||||
failed=1
|
||||
fi
|
||||
done
|
||||
exit "$failed"
|
||||
|
||||
e2e-full:
|
||||
if: >
|
||||
always() &&
|
||||
(github.event_name == 'schedule' || github.event_name == 'workflow_dispatch' || github.ref_name == github.event.repository.default_branch) &&
|
||||
!(github.event_name == 'workflow_dispatch' && inputs.mode == 'targeted') &&
|
||||
needs.build-and-unit.result == 'success' &&
|
||||
(github.event_name == 'schedule' || needs.e2e-pr.result == 'success')
|
||||
needs: [build-and-unit, e2e-pr]
|
||||
name: E2E-full-${{ matrix.role }}-${{ matrix.browser_label }}-${{ matrix.device }}
|
||||
runs-on: [self-hosted, Linux, X64, pleno, frontend, docker]
|
||||
(github.event_name == 'schedule' || needs.e2e-pr.result == 'success') &&
|
||||
(
|
||||
github.event_name != 'workflow_dispatch' ||
|
||||
inputs.mode == 'full' ||
|
||||
needs.e2e-targeted.result == 'success'
|
||||
)
|
||||
needs: [build-and-unit, e2e-pr, e2e-targeted]
|
||||
name: E2E-full-${{ matrix.browser_label }}-${{ matrix.device }}-${{ matrix.role }}
|
||||
runs-on: ${{ fromJSON(vars.FRONTEND_CI_E2E_RUNNER || '["self-hosted","Linux","X64","pleno","frontend","docker"]') }}
|
||||
timeout-minutes: 60
|
||||
strategy:
|
||||
fail-fast: false
|
||||
max-parallel: 4
|
||||
max-parallel: ${{ fromJSON(vars.FRONTEND_CI_FULL_E2E_MAX_PARALLEL || '1') }}
|
||||
matrix:
|
||||
role: [customer, subuser, admin, superuser]
|
||||
browser: [chromium, firefox, webkit]
|
||||
device: [mobile, tablet, desktop]
|
||||
browser: [chromium, webkit, firefox]
|
||||
device: [mobile, desktop, tablet]
|
||||
role: [superuser, admin, customer, subuser]
|
||||
include:
|
||||
- browser: chromium
|
||||
browser_label: Chromium
|
||||
browser_install: chromium
|
||||
- browser: firefox
|
||||
browser_label: Firefox
|
||||
browser_install: firefox
|
||||
- browser: webkit
|
||||
browser_label: WebKit
|
||||
browser_install: webkit
|
||||
- browser: firefox
|
||||
browser_label: Firefox
|
||||
browser_install: firefox
|
||||
env:
|
||||
PLAYWRIGHT_ARTIFACT_NAMESPACE: e2e-full-${{ matrix.role }}-${{ matrix.browser }}-${{ matrix.device }}
|
||||
PLAYWRIGHT_ARTIFACT_NAMESPACE: e2e-full-${{ matrix.browser }}-${{ matrix.device }}-${{ matrix.role }}
|
||||
PLAYWRIGHT_REPORTER_MODE: line-html
|
||||
PLAYWRIGHT_WORKERS: 1
|
||||
PLAYWRIGHT_VIDEO_MODE: off
|
||||
steps:
|
||||
- name: Repair self-hosted workspace permissions
|
||||
if: ${{ contains(vars.FRONTEND_CI_E2E_RUNNER || 'self-hosted', 'self-hosted') }}
|
||||
shell: bash
|
||||
run: |
|
||||
if [[ -d "$GITHUB_WORKSPACE" ]]; then
|
||||
@@ -315,47 +608,49 @@ jobs:
|
||||
run: |
|
||||
set -euo pipefail
|
||||
case "$MATRIX_ROLE" in
|
||||
customer) role_offset=0 ;;
|
||||
subuser) role_offset=100 ;;
|
||||
admin) role_offset=200 ;;
|
||||
superuser) role_offset=300 ;;
|
||||
superuser) role_offset=0 ;;
|
||||
admin) role_offset=100 ;;
|
||||
customer) role_offset=200 ;;
|
||||
subuser) role_offset=300 ;;
|
||||
*) echo "Unsupported Playwright role: $MATRIX_ROLE" >&2; exit 1 ;;
|
||||
esac
|
||||
case "$MATRIX_BROWSER" in
|
||||
chromium) browser_offset=0 ;;
|
||||
firefox) browser_offset=30 ;;
|
||||
webkit) browser_offset=60 ;;
|
||||
webkit) browser_offset=30 ;;
|
||||
firefox) browser_offset=60 ;;
|
||||
*) echo "Unsupported Playwright browser: $MATRIX_BROWSER" >&2; exit 1 ;;
|
||||
esac
|
||||
case "$MATRIX_DEVICE" in
|
||||
mobile) device_offset=1 ;;
|
||||
tablet) device_offset=2 ;;
|
||||
desktop) device_offset=3 ;;
|
||||
desktop) device_offset=2 ;;
|
||||
tablet) device_offset=3 ;;
|
||||
*) echo "Unsupported Playwright device: $MATRIX_DEVICE" >&2; exit 1 ;;
|
||||
esac
|
||||
port_seed=$((20000 + (RUN_ID % 20000) + role_offset + browser_offset + device_offset))
|
||||
playwright_dev_port="$(
|
||||
PORT_SEED="$port_seed" node - <<'NODE'
|
||||
const net = require("node:net");
|
||||
const start = Number(process.env.PORT_SEED || 20000);
|
||||
const candidates = Array.from({ length: 200 }, (_, index) => start + index);
|
||||
function tryPort(index) {
|
||||
if (index >= candidates.length) {
|
||||
console.error("Unable to find a free Playwright dev-server port.");
|
||||
process.exit(1);
|
||||
}
|
||||
const port = candidates[index];
|
||||
const server = net.createServer();
|
||||
server.once("error", () => tryPort(index + 1));
|
||||
server.listen(port, "127.0.0.1", () => {
|
||||
server.close(() => {
|
||||
console.log(port);
|
||||
});
|
||||
});
|
||||
}
|
||||
tryPort(0);
|
||||
NODE
|
||||
)"
|
||||
lock_root="${PLAYWRIGHT_PORT_LOCK_ROOT:-/tmp/pleno-playwright-port-locks}"
|
||||
mkdir -p "$lock_root"
|
||||
chmod 1777 "$lock_root" 2>/dev/null || true
|
||||
find "$lock_root" -mindepth 1 -maxdepth 1 -type d -mmin +360 -exec rmdir {} \; 2>/dev/null || true
|
||||
playwright_port_lock=""
|
||||
playwright_dev_port=""
|
||||
for ((candidate = port_seed; candidate < port_seed + 1000; candidate += 1)); do
|
||||
lock_dir="${lock_root}/${candidate}.lock"
|
||||
if ! mkdir "$lock_dir" 2>/dev/null; then
|
||||
continue
|
||||
fi
|
||||
if ss -H -ltn "sport = :${candidate}" 2>/dev/null | grep -q .; then
|
||||
rmdir "$lock_dir" || true
|
||||
continue
|
||||
fi
|
||||
playwright_port_lock="$lock_dir"
|
||||
playwright_dev_port="$candidate"
|
||||
break
|
||||
done
|
||||
if [[ -z "$playwright_dev_port" ]]; then
|
||||
echo "Unable to find a free Playwright dev-server port." >&2
|
||||
exit 1
|
||||
fi
|
||||
trap 'if [[ -n "${playwright_port_lock:-}" ]]; then rmdir "$playwright_port_lock" 2>/dev/null || true; fi' EXIT
|
||||
if docker info >/dev/null 2>&1; then
|
||||
docker_cmd=(docker)
|
||||
elif sudo -n docker info >/dev/null 2>&1; then
|
||||
@@ -365,7 +660,9 @@ jobs:
|
||||
exit 1
|
||||
fi
|
||||
mkdir -p output/playwright
|
||||
"${docker_cmd[@]}" run --rm --ipc=host --network host \
|
||||
scripts/ci/runner-diagnostics.sh "before Playwright full ${MATRIX_BROWSER}/${MATRIX_DEVICE}/${MATRIX_ROLE}" -- "${docker_cmd[@]}"
|
||||
SYSTEMD_INHIBIT_REASON="Frontend Playwright full ${MATRIX_BROWSER}/${MATRIX_DEVICE}/${MATRIX_ROLE}" \
|
||||
scripts/ci/with-systemd-inhibit.sh "${docker_cmd[@]}" run --rm --ipc=host --network host \
|
||||
--volume "$PWD:/source:ro" \
|
||||
--volume "$PWD/output/playwright:/work/output/playwright" \
|
||||
--workdir /work \
|
||||
@@ -373,6 +670,8 @@ jobs:
|
||||
--env CI="${CI:-}" \
|
||||
--env PLAYWRIGHT_ARTIFACT_NAMESPACE="$PLAYWRIGHT_ARTIFACT_NAMESPACE" \
|
||||
--env PLAYWRIGHT_REPORTER_MODE="$PLAYWRIGHT_REPORTER_MODE" \
|
||||
--env PLAYWRIGHT_WORKERS="$PLAYWRIGHT_WORKERS" \
|
||||
--env PLAYWRIGHT_VIDEO_MODE="$PLAYWRIGHT_VIDEO_MODE" \
|
||||
--env PLAYWRIGHT_DEV_PORT="$playwright_dev_port" \
|
||||
--env MATRIX_ROLE="$MATRIX_ROLE" \
|
||||
--env MATRIX_BROWSER="$MATRIX_BROWSER" \
|
||||
@@ -382,20 +681,38 @@ jobs:
|
||||
set -euo pipefail
|
||||
tar --exclude=./output/playwright -C /source -cf - . | tar -C /work -xf -
|
||||
git config --global --add safe.directory /work
|
||||
npm ci --legacy-peer-deps
|
||||
install_dependencies() {
|
||||
local attempt
|
||||
for attempt in 1 2 3; do
|
||||
if npm ci --legacy-peer-deps --fetch-retries=5 --fetch-retry-mintimeout=20000 --fetch-retry-maxtimeout=120000; then
|
||||
return 0
|
||||
fi
|
||||
if [[ "$attempt" == "3" ]]; then
|
||||
return 1
|
||||
fi
|
||||
echo "npm ci failed on attempt ${attempt}; retrying..." >&2
|
||||
sleep 20
|
||||
done
|
||||
}
|
||||
install_dependencies
|
||||
ulimit -n 16384 || true
|
||||
npm run test:e2e:full:slice -- --role="$MATRIX_ROLE" --project="$MATRIX_BROWSER-$MATRIX_DEVICE"
|
||||
'
|
||||
|
||||
- name: Runner diagnostics after Playwright failure
|
||||
if: failure() || cancelled()
|
||||
continue-on-error: true
|
||||
run: scripts/ci/runner-diagnostics.sh "after Playwright full ${{ matrix.browser }}/${{ matrix.device }}/${{ matrix.role }}"
|
||||
|
||||
- name: Upload Playwright report
|
||||
if: failure() || cancelled()
|
||||
continue-on-error: true
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: playwright-report-full-${{ matrix.role }}-${{ matrix.browser }}-${{ matrix.device }}
|
||||
name: playwright-report-full-${{ matrix.browser }}-${{ matrix.device }}-${{ matrix.role }}
|
||||
path: |
|
||||
output/playwright/${{ env.PLAYWRIGHT_ARTIFACT_NAMESPACE }}/report
|
||||
output/playwright/${{ env.PLAYWRIGHT_ARTIFACT_NAMESPACE }}/test-results
|
||||
output/playwright/test-lists/${{ matrix.role }}-${{ matrix.browser }}-${{ matrix.device }}.txt
|
||||
output/playwright/test-lists/${{ matrix.browser }}-${{ matrix.device }}-${{ matrix.role }}.txt
|
||||
if-no-files-found: ignore
|
||||
retention-days: 1
|
||||
|
||||
@@ -8,12 +8,22 @@ pnpm-debug.log*
|
||||
lerna-debug.log*
|
||||
|
||||
node_modules
|
||||
node_modules.codex-permission-blocked-*
|
||||
.DS_Store
|
||||
dist
|
||||
dist-ssr
|
||||
coverage
|
||||
*.local
|
||||
dev-dist
|
||||
.playwright-cli/
|
||||
|
||||
# Mobile build and signing outputs
|
||||
/app/build/
|
||||
/app-release*.aab
|
||||
/app-release*.apk
|
||||
/app-release*.idsig
|
||||
*.keystore
|
||||
*.jks
|
||||
|
||||
/cypress/videos/
|
||||
/cypress/screenshots/
|
||||
|
||||
@@ -16,6 +16,16 @@ See [Vite Configuration Reference](https://vite.dev/config/).
|
||||
npm install
|
||||
```
|
||||
|
||||
## Contributing Changes
|
||||
|
||||
Create a scoped feature branch, push it, and open a pull request targeting
|
||||
`master`. Do not push directly to `master`. Merge only after the `Required CI`
|
||||
check succeeds, all review conversations are resolved, and the branch is up to
|
||||
date. Use squash merge so `master` retains linear history.
|
||||
|
||||
See [`.github/BRANCH_PROTECTION.md`](.github/BRANCH_PROTECTION.md) for the
|
||||
repository policy, rollout checks, and emergency bypass procedure.
|
||||
|
||||
### Compile and Hot-Reload for Development
|
||||
|
||||
```sh
|
||||
@@ -96,16 +106,22 @@ Artifacts and summaries:
|
||||
|
||||
## Playwright Full E2E
|
||||
|
||||
Run the permanent grouped full-suite entrypoint with a hard max of 5 total workers across:
|
||||
Run the permanent grouped full-suite entrypoint with a hard max of 5 total workers across the browser-engine groups:
|
||||
|
||||
- `chromium-(desktop|tablet|mobile)`
|
||||
- `firefox-(desktop|tablet|mobile)`
|
||||
- `webkit-(desktop|tablet|mobile)`
|
||||
- Chromium
|
||||
- WebKit
|
||||
- Firefox
|
||||
|
||||
```sh
|
||||
npm run test:e2e:ci
|
||||
```
|
||||
|
||||
The full CI matrix is ordered by browser engine, then device class, then user role:
|
||||
|
||||
- browsers: `chromium`, `webkit`, `firefox`
|
||||
- devices: `mobile`, `desktop`, `tablet`
|
||||
- roles: `superuser`, `admin`, `customer`, `subuser`
|
||||
|
||||
Run a single full-suite slice for one role and one Playwright project:
|
||||
|
||||
```sh
|
||||
@@ -117,7 +133,7 @@ Default worker allocation:
|
||||
```sh
|
||||
PLAYWRIGHT_PARALLEL_WORKERS_CHROMIUM=2
|
||||
PLAYWRIGHT_PARALLEL_WORKERS_FIREFOX=1
|
||||
PLAYWRIGHT_PARALLEL_WORKERS_WEBKIT=2
|
||||
PLAYWRIGHT_PARALLEL_WORKERS_WEBKIT=1
|
||||
```
|
||||
|
||||
Optional overrides:
|
||||
@@ -126,18 +142,69 @@ Optional overrides:
|
||||
PLAYWRIGHT_PARALLEL_BASE_PORT=5191
|
||||
PLAYWRIGHT_PARALLEL_WORKERS_CHROMIUM=2
|
||||
PLAYWRIGHT_PARALLEL_WORKERS_FIREFOX=1
|
||||
PLAYWRIGHT_PARALLEL_WORKERS_WEBKIT=2
|
||||
PLAYWRIGHT_PARALLEL_WORKERS_WEBKIT=1
|
||||
```
|
||||
|
||||
The runner fails fast if the combined worker count exceeds 5.
|
||||
|
||||
GitHub Actions keeps the full browser/device/role matrix stable by capping full-suite
|
||||
matrix parallelism at 2 jobs, running each full slice with `PLAYWRIGHT_WORKERS=1`,
|
||||
wrapping Docker Playwright runs with `systemd-inhibit` when available, and setting
|
||||
`PLAYWRIGHT_VIDEO_MODE=off` for the full matrix. The E2E network harness serves
|
||||
Font Awesome from local fixtures so WebKit page loads and visual snapshots do not
|
||||
depend on CDN/TLS availability. Traces and screenshots are still retained on
|
||||
failure.
|
||||
|
||||
Artifacts and summaries:
|
||||
|
||||
- `output/playwright/ci-parallel-report/index.html`
|
||||
- `output/playwright/ci-parallel-chromium/report/index.html`
|
||||
- `output/playwright/ci-parallel-firefox/report/index.html`
|
||||
- `output/playwright/ci-parallel-webkit/report/index.html`
|
||||
- `output/playwright/test-lists/<role>-<project>.txt`
|
||||
- `output/playwright/test-lists/<project>-<role>.txt`
|
||||
- `output/playwright/test-lists/<role>-<project>.txt` (legacy compatibility copy)
|
||||
|
||||
## Android App Icon
|
||||
|
||||
The Play Store Android package is built from the Capacitor project in `android/`.
|
||||
The legacy Bubblewrap/TWA project at the repository root is not used by
|
||||
`npm run mobile:android:bundle`.
|
||||
|
||||
The source image for the native launcher icon is:
|
||||
|
||||
```text
|
||||
public/favicons/web-app-manifest-512x512.png
|
||||
```
|
||||
|
||||
Regenerate the checked-in launcher assets after changing that source image:
|
||||
|
||||
```sh
|
||||
npm run mobile:android:icons
|
||||
```
|
||||
|
||||
Check that the generated Android launcher assets are current:
|
||||
|
||||
```sh
|
||||
npm run mobile:android:icons:check
|
||||
```
|
||||
|
||||
`npm run mobile:android:sync` runs the icon generator before building and syncing
|
||||
the Capacitor Android project. The generator updates `android/app/src/main/res`
|
||||
launcher assets, `public/icons/icon-192x192.png`, `public/icons/icon-512x512.png`,
|
||||
and `store_icon.png`.
|
||||
|
||||
## Mobile Store Releases
|
||||
|
||||
Signed Android and iOS store artifacts are built through the GitHub Actions
|
||||
`Mobile Store Artifacts` workflow. By default, current `master` after green
|
||||
`Automated Tests` uploads Android to Google Play production and uploads iOS to
|
||||
App Store Connect.
|
||||
|
||||
See `docs/mobile-artifacts.md` for workflow triggers, required secrets, and
|
||||
local mobile checks. See `docs/app-store-release.md` for App Store Connect
|
||||
release preparation and review notes. For a separate development-signed IPA
|
||||
that can be installed on an approved iPhone from Ubuntu over USB, see
|
||||
`docs/ios-device-debug.md`.
|
||||
|
||||
## Bubblewrap (TWA) Build and Install
|
||||
|
||||
|
||||
@@ -0,0 +1,101 @@
|
||||
# Using Android gitignore template: https://github.com/github/gitignore/blob/HEAD/Android.gitignore
|
||||
|
||||
# Built application files
|
||||
*.apk
|
||||
*.aar
|
||||
*.ap_
|
||||
*.aab
|
||||
|
||||
# Files for the ART/Dalvik VM
|
||||
*.dex
|
||||
|
||||
# Java class files
|
||||
*.class
|
||||
|
||||
# Generated files
|
||||
bin/
|
||||
gen/
|
||||
out/
|
||||
# Uncomment the following line in case you need and you don't have the release build type files in your app
|
||||
# release/
|
||||
|
||||
# Gradle files
|
||||
.gradle/
|
||||
build/
|
||||
|
||||
# Local configuration file (sdk path, etc)
|
||||
local.properties
|
||||
|
||||
# Proguard folder generated by Eclipse
|
||||
proguard/
|
||||
|
||||
# Log Files
|
||||
*.log
|
||||
|
||||
# Android Studio Navigation editor temp files
|
||||
.navigation/
|
||||
|
||||
# Android Studio captures folder
|
||||
captures/
|
||||
|
||||
# IntelliJ
|
||||
*.iml
|
||||
.idea/workspace.xml
|
||||
.idea/tasks.xml
|
||||
.idea/gradle.xml
|
||||
.idea/assetWizardSettings.xml
|
||||
.idea/dictionaries
|
||||
.idea/libraries
|
||||
# Android Studio 3 in .gitignore file.
|
||||
.idea/caches
|
||||
.idea/modules.xml
|
||||
# Comment next line if keeping position of elements in Navigation Editor is relevant for you
|
||||
.idea/navEditor.xml
|
||||
|
||||
# Keystore files
|
||||
# Uncomment the following lines if you do not want to check your keystore files in.
|
||||
#*.jks
|
||||
#*.keystore
|
||||
|
||||
# External native build folder generated in Android Studio 2.2 and later
|
||||
.externalNativeBuild
|
||||
.cxx/
|
||||
|
||||
# Google Services (e.g. APIs or Firebase)
|
||||
# google-services.json
|
||||
|
||||
# Freeline
|
||||
freeline.py
|
||||
freeline/
|
||||
freeline_project_description.json
|
||||
|
||||
# fastlane
|
||||
fastlane/report.xml
|
||||
fastlane/Preview.html
|
||||
fastlane/screenshots
|
||||
fastlane/test_output
|
||||
fastlane/readme.md
|
||||
|
||||
# Version control
|
||||
vcs.xml
|
||||
|
||||
# lint
|
||||
lint/intermediates/
|
||||
lint/generated/
|
||||
lint/outputs/
|
||||
lint/tmp/
|
||||
# lint/reports/
|
||||
|
||||
# Android Profiling
|
||||
*.hprof
|
||||
|
||||
# Cordova plugins for Capacitor
|
||||
capacitor-cordova-android-plugins
|
||||
|
||||
# Copied web assets
|
||||
app/src/main/assets/public
|
||||
|
||||
# Generated Config files
|
||||
app/src/main/assets/capacitor.config.json
|
||||
app/src/main/assets/capacitor.plugins.json
|
||||
app/src/main/res/xml/config.xml
|
||||
@@ -0,0 +1,2 @@
|
||||
/build/*
|
||||
!/build/.npmkeep
|
||||
@@ -0,0 +1,77 @@
|
||||
apply plugin: 'com.android.application'
|
||||
|
||||
def mobileEnv = { String name, String defaultValue = null ->
|
||||
return System.getenv(name) ?: (project.hasProperty(name) ? project.property(name).toString() : defaultValue)
|
||||
}
|
||||
|
||||
def releaseStoreFile = mobileEnv('ANDROID_KEYSTORE_FILE')
|
||||
def hasReleaseSigning = releaseStoreFile &&
|
||||
mobileEnv('ANDROID_KEYSTORE_PASSWORD') &&
|
||||
mobileEnv('ANDROID_KEY_ALIAS') &&
|
||||
mobileEnv('ANDROID_KEY_PASSWORD')
|
||||
|
||||
android {
|
||||
namespace = "io.truckwash.twa"
|
||||
compileSdk = rootProject.ext.compileSdkVersion
|
||||
defaultConfig {
|
||||
applicationId "io.truckwash.twa"
|
||||
minSdkVersion rootProject.ext.minSdkVersion
|
||||
targetSdkVersion rootProject.ext.targetSdkVersion
|
||||
versionCode Integer.parseInt(mobileEnv('MOBILE_VERSION_CODE', '1'))
|
||||
versionName mobileEnv('MOBILE_VERSION_NAME', '1.0')
|
||||
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
||||
aaptOptions {
|
||||
// Files and dirs to omit from the packaged assets dir, modified to accommodate modern web apps.
|
||||
// Default: https://android.googlesource.com/platform/frameworks/base/+/282e181b58cf72b6ca770dc7ca5f91f135444502/tools/aapt/AaptAssets.cpp#61
|
||||
ignoreAssetsPattern = '!.svn:!.git:!.ds_store:!*.scc:.*:!CVS:!thumbs.db:!picasa.ini:!*~'
|
||||
}
|
||||
}
|
||||
signingConfigs {
|
||||
release {
|
||||
if (hasReleaseSigning) {
|
||||
storeFile file(releaseStoreFile)
|
||||
storePassword mobileEnv('ANDROID_KEYSTORE_PASSWORD')
|
||||
keyAlias mobileEnv('ANDROID_KEY_ALIAS')
|
||||
keyPassword mobileEnv('ANDROID_KEY_PASSWORD')
|
||||
}
|
||||
}
|
||||
}
|
||||
buildTypes {
|
||||
release {
|
||||
minifyEnabled false
|
||||
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
|
||||
if (hasReleaseSigning) {
|
||||
signingConfig signingConfigs.release
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
repositories {
|
||||
flatDir{
|
||||
dirs '../capacitor-cordova-android-plugins/src/main/libs', 'libs'
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation fileTree(include: ['*.jar'], dir: 'libs')
|
||||
implementation "androidx.appcompat:appcompat:$androidxAppCompatVersion"
|
||||
implementation "androidx.coordinatorlayout:coordinatorlayout:$androidxCoordinatorLayoutVersion"
|
||||
implementation "androidx.core:core-splashscreen:$coreSplashScreenVersion"
|
||||
implementation project(':capacitor-android')
|
||||
testImplementation "junit:junit:$junitVersion"
|
||||
androidTestImplementation "androidx.test.ext:junit:$androidxJunitVersion"
|
||||
androidTestImplementation "androidx.test.espresso:espresso-core:$androidxEspressoCoreVersion"
|
||||
implementation project(':capacitor-cordova-android-plugins')
|
||||
}
|
||||
|
||||
apply from: 'capacitor.build.gradle'
|
||||
|
||||
try {
|
||||
def servicesJSON = file('google-services.json')
|
||||
if (servicesJSON.text) {
|
||||
apply plugin: 'com.google.gms.google-services'
|
||||
}
|
||||
} catch(Exception e) {
|
||||
logger.info("google-services.json not found, google-services plugin not applied. Push Notifications won't work")
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
// DO NOT EDIT THIS FILE! IT IS GENERATED EACH TIME "capacitor update" IS RUN
|
||||
|
||||
android {
|
||||
compileOptions {
|
||||
sourceCompatibility JavaVersion.VERSION_21
|
||||
targetCompatibility JavaVersion.VERSION_21
|
||||
}
|
||||
}
|
||||
|
||||
apply from: "../capacitor-cordova-android-plugins/cordova.variables.gradle"
|
||||
dependencies {
|
||||
implementation project(':capacitor-geolocation')
|
||||
|
||||
}
|
||||
|
||||
|
||||
if (hasProperty('postBuildExtras')) {
|
||||
postBuildExtras()
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
# Add project specific ProGuard rules here.
|
||||
# You can control the set of applied configuration files using the
|
||||
# proguardFiles setting in build.gradle.
|
||||
#
|
||||
# For more details, see
|
||||
# http://developer.android.com/guide/developing/tools/proguard.html
|
||||
|
||||
# If your project uses WebView with JS, uncomment the following
|
||||
# and specify the fully qualified class name to the JavaScript interface
|
||||
# class:
|
||||
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
|
||||
# public *;
|
||||
#}
|
||||
|
||||
# Uncomment this to preserve the line number information for
|
||||
# debugging stack traces.
|
||||
#-keepattributes SourceFile,LineNumberTable
|
||||
|
||||
# If you keep the line number information, uncomment this to
|
||||
# hide the original source file name.
|
||||
#-renamesourcefileattribute SourceFile
|
||||
@@ -0,0 +1,26 @@
|
||||
package io.truckwash.twa;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
import android.content.Context;
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
||||
import androidx.test.platform.app.InstrumentationRegistry;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
|
||||
/**
|
||||
* Instrumented test, which will execute on an Android device.
|
||||
*
|
||||
* @see <a href="http://d.android.com/tools/testing">Testing documentation</a>
|
||||
*/
|
||||
@RunWith(AndroidJUnit4.class)
|
||||
public class ExampleInstrumentedTest {
|
||||
|
||||
@Test
|
||||
public void useAppContext() throws Exception {
|
||||
// Context of the app under test.
|
||||
Context appContext = InstrumentationRegistry.getInstrumentation().getTargetContext();
|
||||
|
||||
assertEquals("io.truckwash.twa", appContext.getPackageName());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
|
||||
<application
|
||||
android:allowBackup="true"
|
||||
android:icon="@mipmap/ic_launcher"
|
||||
android:label="@string/app_name"
|
||||
android:roundIcon="@mipmap/ic_launcher_round"
|
||||
android:supportsRtl="true"
|
||||
android:theme="@style/AppTheme">
|
||||
|
||||
<activity
|
||||
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|locale|smallestScreenSize|screenLayout|uiMode|navigation|density"
|
||||
android:name=".MainActivity"
|
||||
android:label="@string/title_activity_main"
|
||||
android:theme="@style/AppTheme.NoActionBarLaunch"
|
||||
android:launchMode="singleTask"
|
||||
android:exported="true">
|
||||
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN" />
|
||||
<category android:name="android.intent.category.LAUNCHER" />
|
||||
</intent-filter>
|
||||
|
||||
</activity>
|
||||
|
||||
<provider
|
||||
android:name="androidx.core.content.FileProvider"
|
||||
android:authorities="${applicationId}.fileprovider"
|
||||
android:exported="false"
|
||||
android:grantUriPermissions="true">
|
||||
<meta-data
|
||||
android:name="android.support.FILE_PROVIDER_PATHS"
|
||||
android:resource="@xml/file_paths"></meta-data>
|
||||
</provider>
|
||||
</application>
|
||||
|
||||
<uses-feature
|
||||
android:name="android.hardware.camera"
|
||||
android:required="false" />
|
||||
<uses-feature
|
||||
android:name="android.hardware.camera.autofocus"
|
||||
android:required="false" />
|
||||
<uses-feature
|
||||
android:name="android.hardware.location.gps"
|
||||
android:required="false" />
|
||||
|
||||
<!-- Permissions -->
|
||||
<uses-permission android:name="android.permission.INTERNET" />
|
||||
<uses-permission android:name="android.permission.CAMERA" />
|
||||
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
|
||||
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
|
||||
</manifest>
|
||||
@@ -0,0 +1,5 @@
|
||||
package io.truckwash.twa;
|
||||
|
||||
import com.getcapacitor.BridgeActivity;
|
||||
|
||||
public class MainActivity extends BridgeActivity {}
|
||||
|
After Width: | Height: | Size: 7.5 KiB |
|
After Width: | Height: | Size: 3.9 KiB |
|
After Width: | Height: | Size: 9.0 KiB |
|
After Width: | Height: | Size: 14 KiB |
|
After Width: | Height: | Size: 17 KiB |
|
After Width: | Height: | Size: 7.7 KiB |
|
After Width: | Height: | Size: 4.0 KiB |
|
After Width: | Height: | Size: 9.6 KiB |
|
After Width: | Height: | Size: 13 KiB |
|
After Width: | Height: | Size: 17 KiB |
@@ -0,0 +1,34 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:aapt="http://schemas.android.com/aapt"
|
||||
android:width="108dp"
|
||||
android:height="108dp"
|
||||
android:viewportHeight="108"
|
||||
android:viewportWidth="108">
|
||||
<path
|
||||
android:fillType="evenOdd"
|
||||
android:pathData="M32,64C32,64 38.39,52.99 44.13,50.95C51.37,48.37 70.14,49.57 70.14,49.57L108.26,87.69L108,109.01L75.97,107.97L32,64Z"
|
||||
android:strokeColor="#00000000"
|
||||
android:strokeWidth="1">
|
||||
<aapt:attr name="android:fillColor">
|
||||
<gradient
|
||||
android:endX="78.5885"
|
||||
android:endY="90.9159"
|
||||
android:startX="48.7653"
|
||||
android:startY="61.0927"
|
||||
android:type="linear">
|
||||
<item
|
||||
android:color="#44000000"
|
||||
android:offset="0.0" />
|
||||
<item
|
||||
android:color="#00000000"
|
||||
android:offset="1.0" />
|
||||
</gradient>
|
||||
</aapt:attr>
|
||||
</path>
|
||||
<path
|
||||
android:fillColor="#FFFFFF"
|
||||
android:fillType="nonZero"
|
||||
android:pathData="M66.94,46.02L66.94,46.02C72.44,50.07 76,56.61 76,64L32,64C32,56.61 35.56,50.11 40.98,46.06L36.18,41.19C35.45,40.45 35.45,39.3 36.18,38.56C36.91,37.81 38.05,37.81 38.78,38.56L44.25,44.05C47.18,42.57 50.48,41.71 54,41.71C57.48,41.71 60.78,42.57 63.68,44.05L69.11,38.56C69.84,37.81 70.98,37.81 71.71,38.56C72.44,39.3 72.44,40.45 71.71,41.19L66.94,46.02ZM62.94,56.92C64.08,56.92 65,56.01 65,54.88C65,53.76 64.08,52.85 62.94,52.85C61.8,52.85 60.88,53.76 60.88,54.88C60.88,56.01 61.8,56.92 62.94,56.92ZM45.06,56.92C46.2,56.92 47.13,56.01 47.13,54.88C47.13,53.76 46.2,52.85 45.06,52.85C43.92,52.85 43,53.76 43,54.88C43,56.01 43.92,56.92 45.06,56.92Z"
|
||||
android:strokeColor="#00000000"
|
||||
android:strokeWidth="1" />
|
||||
</vector>
|
||||
@@ -0,0 +1,170 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="108dp"
|
||||
android:height="108dp"
|
||||
android:viewportHeight="108"
|
||||
android:viewportWidth="108">
|
||||
<path
|
||||
android:fillColor="#26A69A"
|
||||
android:pathData="M0,0h108v108h-108z" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M9,0L9,108"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M19,0L19,108"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M29,0L29,108"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M39,0L39,108"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M49,0L49,108"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M59,0L59,108"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M69,0L69,108"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M79,0L79,108"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M89,0L89,108"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M99,0L99,108"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,9L108,9"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,19L108,19"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,29L108,29"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,39L108,39"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,49L108,49"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,59L108,59"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,69L108,69"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,79L108,79"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,89L108,89"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,99L108,99"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M19,29L89,29"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M19,39L89,39"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M19,49L89,49"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M19,59L89,59"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M19,69L89,69"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M19,79L89,79"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M29,19L29,89"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M39,19L39,89"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M49,19L49,89"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M59,19L59,89"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M69,19L69,89"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M79,19L79,89"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8" />
|
||||
</vector>
|
||||
|
After Width: | Height: | Size: 3.9 KiB |
@@ -0,0 +1,12 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
tools:context=".MainActivity">
|
||||
|
||||
<WebView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent" />
|
||||
</androidx.coordinatorlayout.widget.CoordinatorLayout>
|
||||
@@ -0,0 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<background android:drawable="@color/ic_launcher_background"/>
|
||||
<foreground android:drawable="@mipmap/ic_launcher_foreground"/>
|
||||
</adaptive-icon>
|
||||
@@ -0,0 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<background android:drawable="@color/ic_launcher_background"/>
|
||||
<foreground android:drawable="@mipmap/ic_launcher_foreground"/>
|
||||
</adaptive-icon>
|
||||
|
After Width: | Height: | Size: 4.8 KiB |
|
After Width: | Height: | Size: 14 KiB |
|
After Width: | Height: | Size: 4.8 KiB |
|
After Width: | Height: | Size: 2.9 KiB |
|
After Width: | Height: | Size: 8.1 KiB |
|
After Width: | Height: | Size: 2.9 KiB |
|
After Width: | Height: | Size: 7.0 KiB |
|
After Width: | Height: | Size: 21 KiB |
|
After Width: | Height: | Size: 7.0 KiB |
|
After Width: | Height: | Size: 12 KiB |
|
After Width: | Height: | Size: 38 KiB |
|
After Width: | Height: | Size: 12 KiB |
|
After Width: | Height: | Size: 18 KiB |
|
After Width: | Height: | Size: 61 KiB |
|
After Width: | Height: | Size: 18 KiB |
@@ -0,0 +1,4 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<color name="ic_launcher_background">#0787BB</color>
|
||||
</resources>
|
||||
@@ -0,0 +1,7 @@
|
||||
<?xml version='1.0' encoding='utf-8'?>
|
||||
<resources>
|
||||
<string name="app_name">Truck Wash</string>
|
||||
<string name="title_activity_main">Truck Wash</string>
|
||||
<string name="package_name">io.truckwash.twa</string>
|
||||
<string name="custom_url_scheme">io.truckwash.twa</string>
|
||||
</resources>
|
||||
@@ -0,0 +1,22 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
|
||||
<!-- Base application theme. -->
|
||||
<style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar">
|
||||
<!-- Customize your theme here. -->
|
||||
<item name="colorPrimary">@color/colorPrimary</item>
|
||||
<item name="colorPrimaryDark">@color/colorPrimaryDark</item>
|
||||
<item name="colorAccent">@color/colorAccent</item>
|
||||
</style>
|
||||
|
||||
<style name="AppTheme.NoActionBar" parent="Theme.AppCompat.DayNight.NoActionBar">
|
||||
<item name="windowActionBar">false</item>
|
||||
<item name="windowNoTitle">true</item>
|
||||
<item name="android:background">@null</item>
|
||||
</style>
|
||||
|
||||
|
||||
<style name="AppTheme.NoActionBarLaunch" parent="Theme.SplashScreen">
|
||||
<item name="android:background">@drawable/splash</item>
|
||||
</style>
|
||||
</resources>
|
||||
@@ -0,0 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<paths xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<external-path name="my_images" path="." />
|
||||
<cache-path name="my_cache_images" path="." />
|
||||
</paths>
|
||||
@@ -0,0 +1,18 @@
|
||||
package io.truckwash.twa;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
/**
|
||||
* Example local unit test, which will execute on the development machine (host).
|
||||
*
|
||||
* @see <a href="http://d.android.com/tools/testing">Testing documentation</a>
|
||||
*/
|
||||
public class ExampleUnitTest {
|
||||
|
||||
@Test
|
||||
public void addition_isCorrect() throws Exception {
|
||||
assertEquals(4, 2 + 2);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
// Top-level build file where you can add configuration options common to all sub-projects/modules.
|
||||
|
||||
buildscript {
|
||||
|
||||
repositories {
|
||||
google()
|
||||
mavenCentral()
|
||||
}
|
||||
dependencies {
|
||||
classpath 'com.android.tools.build:gradle:8.13.0'
|
||||
classpath 'com.google.gms:google-services:4.4.4'
|
||||
|
||||
// NOTE: Do not place your application dependencies here; they belong
|
||||
// in the individual module build.gradle files
|
||||
}
|
||||
}
|
||||
|
||||
apply from: "variables.gradle"
|
||||
|
||||
allprojects {
|
||||
repositories {
|
||||
google()
|
||||
mavenCentral()
|
||||
}
|
||||
}
|
||||
|
||||
task clean(type: Delete) {
|
||||
delete rootProject.buildDir
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
// DO NOT EDIT THIS FILE! IT IS GENERATED EACH TIME "capacitor update" IS RUN
|
||||
include ':capacitor-android'
|
||||
project(':capacitor-android').projectDir = new File('../node_modules/@capacitor/android/capacitor')
|
||||
|
||||
include ':capacitor-geolocation'
|
||||
project(':capacitor-geolocation').projectDir = new File('../node_modules/@capacitor/geolocation/android')
|
||||
@@ -0,0 +1,22 @@
|
||||
# Project-wide Gradle settings.
|
||||
|
||||
# IDE (e.g. Android Studio) users:
|
||||
# Gradle settings configured through the IDE *will override*
|
||||
# any settings specified in this file.
|
||||
|
||||
# For more details on how to configure your build environment visit
|
||||
# http://www.gradle.org/docs/current/userguide/build_environment.html
|
||||
|
||||
# Specifies the JVM arguments used for the daemon process.
|
||||
# The setting is particularly useful for tweaking memory settings.
|
||||
org.gradle.jvmargs=-Xmx1536m
|
||||
|
||||
# When configured, Gradle will run in incubating parallel mode.
|
||||
# This option should only be used with decoupled projects. More details, visit
|
||||
# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
|
||||
# org.gradle.parallel=true
|
||||
|
||||
# AndroidX package structure to make it clearer which packages are bundled with the
|
||||
# Android operating system, and which are packaged with your app's APK
|
||||
# https://developer.android.com/topic/libraries/support-library/androidx-rn
|
||||
android.useAndroidX=true
|
||||
@@ -0,0 +1,7 @@
|
||||
distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.14.3-all.zip
|
||||
networkTimeout=10000
|
||||
validateDistributionUrl=true
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
zipStorePath=wrapper/dists
|
||||
@@ -0,0 +1,251 @@
|
||||
#!/bin/sh
|
||||
|
||||
#
|
||||
# Copyright © 2015-2021 the original authors.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# https://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
#
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
#
|
||||
|
||||
##############################################################################
|
||||
#
|
||||
# Gradle start up script for POSIX generated by Gradle.
|
||||
#
|
||||
# Important for running:
|
||||
#
|
||||
# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is
|
||||
# noncompliant, but you have some other compliant shell such as ksh or
|
||||
# bash, then to run this script, type that shell name before the whole
|
||||
# command line, like:
|
||||
#
|
||||
# ksh Gradle
|
||||
#
|
||||
# Busybox and similar reduced shells will NOT work, because this script
|
||||
# requires all of these POSIX shell features:
|
||||
# * functions;
|
||||
# * expansions «$var», «${var}», «${var:-default}», «${var+SET}»,
|
||||
# «${var#prefix}», «${var%suffix}», and «$( cmd )»;
|
||||
# * compound commands having a testable exit status, especially «case»;
|
||||
# * various built-in commands including «command», «set», and «ulimit».
|
||||
#
|
||||
# Important for patching:
|
||||
#
|
||||
# (2) This script targets any POSIX shell, so it avoids extensions provided
|
||||
# by Bash, Ksh, etc; in particular arrays are avoided.
|
||||
#
|
||||
# The "traditional" practice of packing multiple parameters into a
|
||||
# space-separated string is a well documented source of bugs and security
|
||||
# problems, so this is (mostly) avoided, by progressively accumulating
|
||||
# options in "$@", and eventually passing that to Java.
|
||||
#
|
||||
# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS,
|
||||
# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly;
|
||||
# see the in-line comments for details.
|
||||
#
|
||||
# There are tweaks for specific operating systems such as AIX, CygWin,
|
||||
# Darwin, MinGW, and NonStop.
|
||||
#
|
||||
# (3) This script is generated from the Groovy template
|
||||
# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
|
||||
# within the Gradle project.
|
||||
#
|
||||
# You can find Gradle at https://github.com/gradle/gradle/.
|
||||
#
|
||||
##############################################################################
|
||||
|
||||
# Attempt to set APP_HOME
|
||||
|
||||
# Resolve links: $0 may be a link
|
||||
app_path=$0
|
||||
|
||||
# Need this for daisy-chained symlinks.
|
||||
while
|
||||
APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path
|
||||
[ -h "$app_path" ]
|
||||
do
|
||||
ls=$( ls -ld "$app_path" )
|
||||
link=${ls#*' -> '}
|
||||
case $link in #(
|
||||
/*) app_path=$link ;; #(
|
||||
*) app_path=$APP_HOME$link ;;
|
||||
esac
|
||||
done
|
||||
|
||||
# This is normally unused
|
||||
# shellcheck disable=SC2034
|
||||
APP_BASE_NAME=${0##*/}
|
||||
# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036)
|
||||
APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit
|
||||
|
||||
# Use the maximum available, or set MAX_FD != -1 to use that value.
|
||||
MAX_FD=maximum
|
||||
|
||||
warn () {
|
||||
echo "$*"
|
||||
} >&2
|
||||
|
||||
die () {
|
||||
echo
|
||||
echo "$*"
|
||||
echo
|
||||
exit 1
|
||||
} >&2
|
||||
|
||||
# OS specific support (must be 'true' or 'false').
|
||||
cygwin=false
|
||||
msys=false
|
||||
darwin=false
|
||||
nonstop=false
|
||||
case "$( uname )" in #(
|
||||
CYGWIN* ) cygwin=true ;; #(
|
||||
Darwin* ) darwin=true ;; #(
|
||||
MSYS* | MINGW* ) msys=true ;; #(
|
||||
NONSTOP* ) nonstop=true ;;
|
||||
esac
|
||||
|
||||
CLASSPATH="\\\"\\\""
|
||||
|
||||
|
||||
# Determine the Java command to use to start the JVM.
|
||||
if [ -n "$JAVA_HOME" ] ; then
|
||||
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
|
||||
# IBM's JDK on AIX uses strange locations for the executables
|
||||
JAVACMD=$JAVA_HOME/jre/sh/java
|
||||
else
|
||||
JAVACMD=$JAVA_HOME/bin/java
|
||||
fi
|
||||
if [ ! -x "$JAVACMD" ] ; then
|
||||
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
|
||||
|
||||
Please set the JAVA_HOME variable in your environment to match the
|
||||
location of your Java installation."
|
||||
fi
|
||||
else
|
||||
JAVACMD=java
|
||||
if ! command -v java >/dev/null 2>&1
|
||||
then
|
||||
die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
||||
|
||||
Please set the JAVA_HOME variable in your environment to match the
|
||||
location of your Java installation."
|
||||
fi
|
||||
fi
|
||||
|
||||
# Increase the maximum file descriptors if we can.
|
||||
if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
|
||||
case $MAX_FD in #(
|
||||
max*)
|
||||
# In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked.
|
||||
# shellcheck disable=SC2039,SC3045
|
||||
MAX_FD=$( ulimit -H -n ) ||
|
||||
warn "Could not query maximum file descriptor limit"
|
||||
esac
|
||||
case $MAX_FD in #(
|
||||
'' | soft) :;; #(
|
||||
*)
|
||||
# In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked.
|
||||
# shellcheck disable=SC2039,SC3045
|
||||
ulimit -n "$MAX_FD" ||
|
||||
warn "Could not set maximum file descriptor limit to $MAX_FD"
|
||||
esac
|
||||
fi
|
||||
|
||||
# Collect all arguments for the java command, stacking in reverse order:
|
||||
# * args from the command line
|
||||
# * the main class name
|
||||
# * -classpath
|
||||
# * -D...appname settings
|
||||
# * --module-path (only if needed)
|
||||
# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables.
|
||||
|
||||
# For Cygwin or MSYS, switch paths to Windows format before running java
|
||||
if "$cygwin" || "$msys" ; then
|
||||
APP_HOME=$( cygpath --path --mixed "$APP_HOME" )
|
||||
CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" )
|
||||
|
||||
JAVACMD=$( cygpath --unix "$JAVACMD" )
|
||||
|
||||
# Now convert the arguments - kludge to limit ourselves to /bin/sh
|
||||
for arg do
|
||||
if
|
||||
case $arg in #(
|
||||
-*) false ;; # don't mess with options #(
|
||||
/?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath
|
||||
[ -e "$t" ] ;; #(
|
||||
*) false ;;
|
||||
esac
|
||||
then
|
||||
arg=$( cygpath --path --ignore --mixed "$arg" )
|
||||
fi
|
||||
# Roll the args list around exactly as many times as the number of
|
||||
# args, so each arg winds up back in the position where it started, but
|
||||
# possibly modified.
|
||||
#
|
||||
# NB: a `for` loop captures its iteration list before it begins, so
|
||||
# changing the positional parameters here affects neither the number of
|
||||
# iterations, nor the values presented in `arg`.
|
||||
shift # remove old arg
|
||||
set -- "$@" "$arg" # push replacement arg
|
||||
done
|
||||
fi
|
||||
|
||||
|
||||
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
|
||||
|
||||
# Collect all arguments for the java command:
|
||||
# * DEFAULT_JVM_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments,
|
||||
# and any embedded shellness will be escaped.
|
||||
# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be
|
||||
# treated as '${Hostname}' itself on the command line.
|
||||
|
||||
set -- \
|
||||
"-Dorg.gradle.appname=$APP_BASE_NAME" \
|
||||
-classpath "$CLASSPATH" \
|
||||
-jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \
|
||||
"$@"
|
||||
|
||||
# Stop when "xargs" is not available.
|
||||
if ! command -v xargs >/dev/null 2>&1
|
||||
then
|
||||
die "xargs is not available"
|
||||
fi
|
||||
|
||||
# Use "xargs" to parse quoted args.
|
||||
#
|
||||
# With -n1 it outputs one arg per line, with the quotes and backslashes removed.
|
||||
#
|
||||
# In Bash we could simply go:
|
||||
#
|
||||
# readarray ARGS < <( xargs -n1 <<<"$var" ) &&
|
||||
# set -- "${ARGS[@]}" "$@"
|
||||
#
|
||||
# but POSIX shell has neither arrays nor command substitution, so instead we
|
||||
# post-process each arg (as a line of input to sed) to backslash-escape any
|
||||
# character that might be a shell metacharacter, then use eval to reverse
|
||||
# that process (while maintaining the separation between arguments), and wrap
|
||||
# the whole thing up as a single "set" statement.
|
||||
#
|
||||
# This will of course break if any of these variables contains a newline or
|
||||
# an unmatched quote.
|
||||
#
|
||||
|
||||
eval "set -- $(
|
||||
printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" |
|
||||
xargs -n1 |
|
||||
sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' |
|
||||
tr '\n' ' '
|
||||
)" '"$@"'
|
||||
|
||||
exec "$JAVACMD" "$@"
|
||||
@@ -0,0 +1,94 @@
|
||||
@rem
|
||||
@rem Copyright 2015 the original author or authors.
|
||||
@rem
|
||||
@rem Licensed under the Apache License, Version 2.0 (the "License");
|
||||
@rem you may not use this file except in compliance with the License.
|
||||
@rem You may obtain a copy of the License at
|
||||
@rem
|
||||
@rem https://www.apache.org/licenses/LICENSE-2.0
|
||||
@rem
|
||||
@rem Unless required by applicable law or agreed to in writing, software
|
||||
@rem distributed under the License is distributed on an "AS IS" BASIS,
|
||||
@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
@rem See the License for the specific language governing permissions and
|
||||
@rem limitations under the License.
|
||||
@rem
|
||||
@rem SPDX-License-Identifier: Apache-2.0
|
||||
@rem
|
||||
|
||||
@if "%DEBUG%"=="" @echo off
|
||||
@rem ##########################################################################
|
||||
@rem
|
||||
@rem Gradle startup script for Windows
|
||||
@rem
|
||||
@rem ##########################################################################
|
||||
|
||||
@rem Set local scope for the variables with windows NT shell
|
||||
if "%OS%"=="Windows_NT" setlocal
|
||||
|
||||
set DIRNAME=%~dp0
|
||||
if "%DIRNAME%"=="" set DIRNAME=.
|
||||
@rem This is normally unused
|
||||
set APP_BASE_NAME=%~n0
|
||||
set APP_HOME=%DIRNAME%
|
||||
|
||||
@rem Resolve any "." and ".." in APP_HOME to make it shorter.
|
||||
for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
|
||||
|
||||
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||
set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
|
||||
|
||||
@rem Find java.exe
|
||||
if defined JAVA_HOME goto findJavaFromJavaHome
|
||||
|
||||
set JAVA_EXE=java.exe
|
||||
%JAVA_EXE% -version >NUL 2>&1
|
||||
if %ERRORLEVEL% equ 0 goto execute
|
||||
|
||||
echo. 1>&2
|
||||
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2
|
||||
echo. 1>&2
|
||||
echo Please set the JAVA_HOME variable in your environment to match the 1>&2
|
||||
echo location of your Java installation. 1>&2
|
||||
|
||||
goto fail
|
||||
|
||||
:findJavaFromJavaHome
|
||||
set JAVA_HOME=%JAVA_HOME:"=%
|
||||
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
|
||||
|
||||
if exist "%JAVA_EXE%" goto execute
|
||||
|
||||
echo. 1>&2
|
||||
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2
|
||||
echo. 1>&2
|
||||
echo Please set the JAVA_HOME variable in your environment to match the 1>&2
|
||||
echo location of your Java installation. 1>&2
|
||||
|
||||
goto fail
|
||||
|
||||
:execute
|
||||
@rem Setup the command line
|
||||
|
||||
set CLASSPATH=
|
||||
|
||||
|
||||
@rem Execute Gradle
|
||||
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %*
|
||||
|
||||
:end
|
||||
@rem End local scope for the variables with windows NT shell
|
||||
if %ERRORLEVEL% equ 0 goto mainEnd
|
||||
|
||||
:fail
|
||||
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
|
||||
rem the _cmd.exe /c_ return code!
|
||||
set EXIT_CODE=%ERRORLEVEL%
|
||||
if %EXIT_CODE% equ 0 set EXIT_CODE=1
|
||||
if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE%
|
||||
exit /b %EXIT_CODE%
|
||||
|
||||
:mainEnd
|
||||
if "%OS%"=="Windows_NT" endlocal
|
||||
|
||||
:omega
|
||||
@@ -0,0 +1,5 @@
|
||||
include ':app'
|
||||
include ':capacitor-cordova-android-plugins'
|
||||
project(':capacitor-cordova-android-plugins').projectDir = new File('./capacitor-cordova-android-plugins/')
|
||||
|
||||
apply from: 'capacitor.settings.gradle'
|
||||
@@ -0,0 +1,16 @@
|
||||
ext {
|
||||
minSdkVersion = 24
|
||||
compileSdkVersion = 36
|
||||
targetSdkVersion = 36
|
||||
androidxActivityVersion = '1.11.0'
|
||||
androidxAppCompatVersion = '1.7.1'
|
||||
androidxCoordinatorLayoutVersion = '1.3.0'
|
||||
androidxCoreVersion = '1.17.0'
|
||||
androidxFragmentVersion = '1.8.9'
|
||||
coreSplashScreenVersion = '1.2.0'
|
||||
androidxWebkitVersion = '1.14.0'
|
||||
junitVersion = '4.13.2'
|
||||
androidxJunitVersion = '1.3.0'
|
||||
androidxEspressoCoreVersion = '3.7.0'
|
||||
cordovaAndroidVersion = '14.0.1'
|
||||
}
|
||||
@@ -1,49 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
|
||||
<!-- Automatically generated file. DO NOT MODIFY -->
|
||||
|
||||
<!-- Value from default config. -->
|
||||
<bool name="enableNotification">true</bool>
|
||||
<!-- Value from default config. -->
|
||||
<bool name="enableSiteSettingsShortcut">true</bool>
|
||||
<!-- Value from default config. -->
|
||||
<color name="backgroundColor">#0787BB</color>
|
||||
<!-- Value from default config. -->
|
||||
<color name="colorPrimary">#063651</color>
|
||||
<!-- Value from default config. -->
|
||||
<color name="colorPrimaryDark">#000000</color>
|
||||
<!-- Value from default config. -->
|
||||
<color name="navigationColor">#000000</color>
|
||||
<!-- Value from default config. -->
|
||||
<color name="navigationColorDark">#000000</color>
|
||||
<!-- Value from default config. -->
|
||||
<color name="navigationDividerColor">#000000</color>
|
||||
<!-- Value from default config. -->
|
||||
<color name="navigationDividerColorDark">#000000</color>
|
||||
<!-- Value from default config. -->
|
||||
<integer name="splashScreenFadeOutDuration">300</integer>
|
||||
<!-- Value from default config. -->
|
||||
<string name="appName" translatable="false">Truck Wash</string>
|
||||
<!-- Value from default config. -->
|
||||
<string name="fallbackType" translatable="false">customtabs</string>
|
||||
<!-- Value from default config. -->
|
||||
<string name="fullScopeUrl" translatable="false">https://truckwash.io/</string>
|
||||
<!-- Value from default config. -->
|
||||
<string name="generatorApp" translatable="false">bubblewrap-cli</string>
|
||||
<!-- Value from default config. -->
|
||||
<string name="hostName" translatable="false">truckwash.io</string>
|
||||
<!-- Value from default config. -->
|
||||
<string name="launchHandlerClientMode" translatable="false">navigate-existing</string>
|
||||
<!-- Value from default config. -->
|
||||
<string name="launchUrl" translatable="false">https://truckwash.io/</string>
|
||||
<!-- Value from default config. -->
|
||||
<string name="launcherName" translatable="false">Truck Wash</string>
|
||||
<!-- Value from default config. -->
|
||||
<string name="orientation" translatable="false">portrait</string>
|
||||
<!-- Value from default config. -->
|
||||
<string name="providerAuthority" translatable="false">io.truckwash.twa.fileprovider</string>
|
||||
<!-- Value from default config. -->
|
||||
<string name="webManifestUrl" translatable="false">https://truckwash.io/manifest.webmanifest</string>
|
||||
|
||||
</resources>
|
||||
@@ -1,55 +0,0 @@
|
||||
-keep class androidx.core.app.CoreComponentFactory { <init>(); }
|
||||
-keep class androidx.core.content.FileProvider { <init>(); }
|
||||
-keep class androidx.profileinstaller.ProfileInstallReceiver { <init>(); }
|
||||
-keep class androidx.startup.InitializationProvider { <init>(); }
|
||||
-keep class com.google.android.gms.common.api.GoogleApiActivity { <init>(); }
|
||||
-keep class com.google.androidbrowserhelper.locationdelegation.PermissionRequestActivity { <init>(); }
|
||||
-keep class com.google.androidbrowserhelper.trusted.FocusActivity { <init>(); }
|
||||
-keep class com.google.androidbrowserhelper.trusted.ManageDataLauncherActivity { <init>(); }
|
||||
-keep class com.google.androidbrowserhelper.trusted.NotificationPermissionRequestActivity { <init>(); }
|
||||
-keep class com.google.androidbrowserhelper.trusted.WebViewFallbackActivity { <init>(); }
|
||||
-keep class io.truckwash.twa.Application { <init>(); }
|
||||
-keep class io.truckwash.twa.DelegationService { <init>(); }
|
||||
-keep class io.truckwash.twa.LauncherActivity { <init>(); }
|
||||
-keep class android.widget.Space { <init>(android.content.Context, android.util.AttributeSet); }
|
||||
|
||||
-keep class androidx.appcompat.app.AlertController$RecycleListView { <init>(android.content.Context, android.util.AttributeSet); }
|
||||
|
||||
-keep class androidx.appcompat.view.menu.ActionMenuItemView { <init>(android.content.Context, android.util.AttributeSet); }
|
||||
|
||||
-keep class androidx.appcompat.view.menu.ExpandedMenuView { <init>(android.content.Context, android.util.AttributeSet); }
|
||||
|
||||
-keep class androidx.appcompat.view.menu.ListMenuItemView { <init>(android.content.Context, android.util.AttributeSet); }
|
||||
|
||||
-keep class androidx.appcompat.widget.ActionBarContainer { <init>(android.content.Context, android.util.AttributeSet); }
|
||||
|
||||
-keep class androidx.appcompat.widget.ActionBarContextView { <init>(android.content.Context, android.util.AttributeSet); }
|
||||
|
||||
-keep class androidx.appcompat.widget.ActionBarOverlayLayout { <init>(android.content.Context, android.util.AttributeSet); }
|
||||
|
||||
-keep class androidx.appcompat.widget.ActionMenuView { <init>(android.content.Context, android.util.AttributeSet); }
|
||||
|
||||
-keep class androidx.appcompat.widget.ActivityChooserView$InnerLayout { <init>(android.content.Context, android.util.AttributeSet); }
|
||||
|
||||
-keep class androidx.appcompat.widget.AlertDialogLayout { <init>(android.content.Context, android.util.AttributeSet); }
|
||||
|
||||
-keep class androidx.appcompat.widget.ButtonBarLayout { <init>(android.content.Context, android.util.AttributeSet); }
|
||||
|
||||
-keep class androidx.appcompat.widget.ContentFrameLayout { <init>(android.content.Context, android.util.AttributeSet); }
|
||||
|
||||
-keep class androidx.appcompat.widget.DialogTitle { <init>(android.content.Context, android.util.AttributeSet); }
|
||||
|
||||
-keep class androidx.appcompat.widget.FitWindowsFrameLayout { <init>(android.content.Context, android.util.AttributeSet); }
|
||||
|
||||
-keep class androidx.appcompat.widget.FitWindowsLinearLayout { <init>(android.content.Context, android.util.AttributeSet); }
|
||||
|
||||
-keep class androidx.appcompat.widget.SearchView$SearchAutoComplete { <init>(android.content.Context, android.util.AttributeSet); }
|
||||
|
||||
-keep class androidx.appcompat.widget.Toolbar { <init>(android.content.Context, android.util.AttributeSet); }
|
||||
|
||||
-keep class androidx.appcompat.widget.ViewStubCompat { <init>(android.content.Context, android.util.AttributeSet); }
|
||||
|
||||
-keep class androidx.browser.browseractions.BrowserActionsFallbackMenuView { <init>(android.content.Context, android.util.AttributeSet); }
|
||||
|
||||
-keep class androidx.core.widget.NestedScrollView { <init>(android.content.Context, android.util.AttributeSet); }
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
{}
|
||||
@@ -1,2 +0,0 @@
|
||||
#- File Locator -
|
||||
listingFile=../../../../outputs/apk/release/output-metadata.json
|
||||
@@ -1,2 +0,0 @@
|
||||
appMetadataVersion=1.1
|
||||
androidGradlePluginVersion=8.9.1
|
||||
@@ -1,18 +0,0 @@
|
||||
{
|
||||
"version": 3,
|
||||
"artifactType": {
|
||||
"type": "BUNDLE",
|
||||
"kind": "RegularFile"
|
||||
},
|
||||
"applicationId": "io.truckwash.twa",
|
||||
"variantName": "release",
|
||||
"elements": [
|
||||
{
|
||||
"type": "SINGLE",
|
||||
"filters": [],
|
||||
"attributes": [],
|
||||
"outputFile": "../../../../outputs/bundle/release/app-release.aab"
|
||||
}
|
||||
],
|
||||
"elementType": "File"
|
||||
}
|
||||
@@ -1,2 +0,0 @@
|
||||
#- File Locator -
|
||||
listingFile=../../../bundle_ide_model/release/produceReleaseBundleIdeListingFile/output-metadata.json
|
||||
@@ -1,222 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
Copyright 2019 Google Inc. All Rights Reserved.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
-->
|
||||
<!--
|
||||
The "package" attribute is rewritten by the Gradle build with the value of applicationId.
|
||||
It is still required here, as it is used to derive paths, for instance when referring
|
||||
to an Activity by ".MyActivity" instead of the full name. If more Activities are added to the
|
||||
application, the package attribute will need to reflect the correct path in order to use
|
||||
the abbreviated format.
|
||||
-->
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="io.truckwash.twa"
|
||||
android:versionCode="6"
|
||||
android:versionName="6" >
|
||||
|
||||
<uses-sdk
|
||||
android:minSdkVersion="21"
|
||||
android:targetSdkVersion="35" />
|
||||
|
||||
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
|
||||
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
|
||||
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
|
||||
|
||||
<queries>
|
||||
<intent>
|
||||
<action android:name="android.intent.action.VIEW" />
|
||||
|
||||
<category android:name="android.intent.category.BROWSABLE" />
|
||||
|
||||
<data android:scheme="https" />
|
||||
</intent>
|
||||
</queries>
|
||||
|
||||
<permission
|
||||
android:name="io.truckwash.twa.DYNAMIC_RECEIVER_NOT_EXPORTED_PERMISSION"
|
||||
android:protectionLevel="signature" />
|
||||
|
||||
<uses-permission android:name="io.truckwash.twa.DYNAMIC_RECEIVER_NOT_EXPORTED_PERMISSION" />
|
||||
|
||||
<application
|
||||
android:name="io.truckwash.twa.Application"
|
||||
android:allowBackup="true"
|
||||
android:appComponentFactory="androidx.core.app.CoreComponentFactory"
|
||||
android:extractNativeLibs="true"
|
||||
android:icon="@mipmap/ic_launcher"
|
||||
android:label="@string/appName"
|
||||
android:manageSpaceActivity="com.google.androidbrowserhelper.trusted.ManageDataLauncherActivity"
|
||||
android:supportsRtl="true"
|
||||
android:theme="@android:style/Theme.Translucent.NoTitleBar" >
|
||||
<meta-data
|
||||
android:name="asset_statements"
|
||||
android:resource="@string/assetStatements" />
|
||||
<meta-data
|
||||
android:name="web_manifest_url"
|
||||
android:value="@string/webManifestUrl" />
|
||||
<meta-data
|
||||
android:name="twa_generator"
|
||||
android:value="@string/generatorApp" />
|
||||
|
||||
<activity android:name="com.google.androidbrowserhelper.trusted.ManageDataLauncherActivity" >
|
||||
<meta-data
|
||||
android:name="android.support.customtabs.trusted.MANAGE_SPACE_URL"
|
||||
android:value="@string/launchUrl" />
|
||||
</activity>
|
||||
<activity
|
||||
android:name="io.truckwash.twa.LauncherActivity"
|
||||
android:alwaysRetainTaskState="true"
|
||||
android:exported="true"
|
||||
android:label="@string/launcherName" >
|
||||
<meta-data
|
||||
android:name="android.support.customtabs.trusted.DEFAULT_URL"
|
||||
android:value="@string/launchUrl" />
|
||||
<meta-data
|
||||
android:name="android.support.customtabs.trusted.STATUS_BAR_COLOR"
|
||||
android:resource="@color/colorPrimary" />
|
||||
<meta-data
|
||||
android:name="android.support.customtabs.trusted.LAUNCH_HANDLER_CLIENT_MODE"
|
||||
android:value="@string/launchHandlerClientMode" />
|
||||
<meta-data
|
||||
android:name="android.support.customtabs.trusted.STATUS_BAR_COLOR_DARK"
|
||||
android:resource="@color/colorPrimaryDark" />
|
||||
<meta-data
|
||||
android:name="android.support.customtabs.trusted.NAVIGATION_BAR_COLOR"
|
||||
android:resource="@color/navigationColor" />
|
||||
<meta-data
|
||||
android:name="android.support.customtabs.trusted.NAVIGATION_BAR_COLOR_DARK"
|
||||
android:resource="@color/navigationColorDark" />
|
||||
<meta-data
|
||||
android:name="androix.browser.trusted.NAVIGATION_BAR_DIVIDER_COLOR"
|
||||
android:resource="@color/navigationDividerColor" />
|
||||
<meta-data
|
||||
android:name="androix.browser.trusted.NAVIGATION_BAR_DIVIDER_COLOR_DARK"
|
||||
android:resource="@color/navigationDividerColorDark" />
|
||||
<meta-data
|
||||
android:name="android.support.customtabs.trusted.SPLASH_IMAGE_DRAWABLE"
|
||||
android:resource="@drawable/splash" />
|
||||
<meta-data
|
||||
android:name="android.support.customtabs.trusted.SPLASH_SCREEN_BACKGROUND_COLOR"
|
||||
android:resource="@color/backgroundColor" />
|
||||
<meta-data
|
||||
android:name="android.support.customtabs.trusted.SPLASH_SCREEN_FADE_OUT_DURATION"
|
||||
android:value="@integer/splashScreenFadeOutDuration" />
|
||||
<meta-data
|
||||
android:name="android.support.customtabs.trusted.FILE_PROVIDER_AUTHORITY"
|
||||
android:value="@string/providerAuthority" />
|
||||
<meta-data
|
||||
android:name="android.app.shortcuts"
|
||||
android:resource="@xml/shortcuts" />
|
||||
<meta-data
|
||||
android:name="android.support.customtabs.trusted.FALLBACK_STRATEGY"
|
||||
android:value="@string/fallbackType" />
|
||||
<meta-data
|
||||
android:name="android.support.customtabs.trusted.SCREEN_ORIENTATION"
|
||||
android:value="@string/orientation" />
|
||||
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN" />
|
||||
|
||||
<category android:name="android.intent.category.LAUNCHER" />
|
||||
</intent-filter>
|
||||
<intent-filter android:autoVerify="true" >
|
||||
<action android:name="android.intent.action.VIEW" />
|
||||
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
<category android:name="android.intent.category.BROWSABLE" />
|
||||
|
||||
<data
|
||||
android:host="@string/hostName"
|
||||
android:scheme="https" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
<activity android:name="com.google.androidbrowserhelper.trusted.FocusActivity" />
|
||||
<activity
|
||||
android:name="com.google.androidbrowserhelper.trusted.WebViewFallbackActivity"
|
||||
android:configChanges="orientation|screenSize" />
|
||||
|
||||
<provider
|
||||
android:name="androidx.core.content.FileProvider"
|
||||
android:authorities="@string/providerAuthority"
|
||||
android:exported="false"
|
||||
android:grantUriPermissions="true" >
|
||||
<meta-data
|
||||
android:name="android.support.FILE_PROVIDER_PATHS"
|
||||
android:resource="@xml/filepaths" />
|
||||
</provider>
|
||||
|
||||
<service
|
||||
android:name="io.truckwash.twa.DelegationService"
|
||||
android:enabled="@bool/enableNotification"
|
||||
android:exported="@bool/enableNotification" >
|
||||
<meta-data
|
||||
android:name="android.support.customtabs.trusted.SMALL_ICON"
|
||||
android:resource="@drawable/ic_notification_icon" />
|
||||
|
||||
<intent-filter>
|
||||
<action android:name="android.support.customtabs.trusted.TRUSTED_WEB_ACTIVITY_SERVICE" />
|
||||
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
</intent-filter>
|
||||
</service>
|
||||
|
||||
<activity android:name="com.google.androidbrowserhelper.trusted.NotificationPermissionRequestActivity" />
|
||||
<activity android:name="com.google.androidbrowserhelper.locationdelegation.PermissionRequestActivity" />
|
||||
<activity
|
||||
android:name="com.google.android.gms.common.api.GoogleApiActivity"
|
||||
android:exported="false"
|
||||
android:theme="@android:style/Theme.Translucent.NoTitleBar" />
|
||||
|
||||
<meta-data
|
||||
android:name="com.google.android.gms.version"
|
||||
android:value="@integer/google_play_services_version" />
|
||||
|
||||
<provider
|
||||
android:name="androidx.startup.InitializationProvider"
|
||||
android:authorities="io.truckwash.twa.androidx-startup"
|
||||
android:exported="false" >
|
||||
<meta-data
|
||||
android:name="androidx.emoji2.text.EmojiCompatInitializer"
|
||||
android:value="androidx.startup" />
|
||||
<meta-data
|
||||
android:name="androidx.lifecycle.ProcessLifecycleInitializer"
|
||||
android:value="androidx.startup" />
|
||||
<meta-data
|
||||
android:name="androidx.profileinstaller.ProfileInstallerInitializer"
|
||||
android:value="androidx.startup" />
|
||||
</provider>
|
||||
|
||||
<receiver
|
||||
android:name="androidx.profileinstaller.ProfileInstallReceiver"
|
||||
android:directBootAware="false"
|
||||
android:enabled="true"
|
||||
android:exported="true"
|
||||
android:permission="android.permission.DUMP" >
|
||||
<intent-filter>
|
||||
<action android:name="androidx.profileinstaller.action.INSTALL_PROFILE" />
|
||||
</intent-filter>
|
||||
<intent-filter>
|
||||
<action android:name="androidx.profileinstaller.action.SKIP_FILE" />
|
||||
</intent-filter>
|
||||
<intent-filter>
|
||||
<action android:name="androidx.profileinstaller.action.SAVE_PROFILE" />
|
||||
</intent-filter>
|
||||
<intent-filter>
|
||||
<action android:name="androidx.profileinstaller.action.BENCHMARK_OPERATION" />
|
||||
</intent-filter>
|
||||
</receiver>
|
||||
</application>
|
||||
|
||||
</manifest>
|
||||
@@ -1,873 +0,0 @@
|
||||
Landroidx/activity/a;
|
||||
Landroidx/activity/ComponentActivity$4;
|
||||
Landroidx/lifecycle/j;
|
||||
Landroidx/lifecycle/k;
|
||||
HSPLandroidx/activity/ComponentActivity$4;->h(Landroidx/lifecycle/l;Landroidx/lifecycle/g$a;)V
|
||||
Landroidx/activity/b;
|
||||
Landroidx/core/app/b;
|
||||
Landroidx/lifecycle/l;
|
||||
Landroidx/lifecycle/E;
|
||||
Landroidx/lifecycle/f;
|
||||
LI/d;
|
||||
Landroidx/activity/c;
|
||||
HSPLandroidx/activity/c;-><init>(Z)V
|
||||
HSPLandroidx/activity/c;->a()V
|
||||
PLandroidx/activity/c;->b(Landroidx/activity/a;)V
|
||||
HSPLandroidx/activity/c;->c(Z)V
|
||||
Landroidx/activity/OnBackPressedDispatcher$LifecycleOnBackPressedCancellable;
|
||||
PLandroidx/activity/OnBackPressedDispatcher$LifecycleOnBackPressedCancellable;->d()V
|
||||
HSPLandroidx/activity/OnBackPressedDispatcher$LifecycleOnBackPressedCancellable;->h(Landroidx/lifecycle/l;Landroidx/lifecycle/g$a;)V
|
||||
Landroidx/activity/OnBackPressedDispatcher;
|
||||
HSPLandroidx/activity/OnBackPressedDispatcher;->a(Landroidx/activity/c;)Landroidx/activity/a;
|
||||
Lc/a;
|
||||
Lc/d;
|
||||
Lc/e;
|
||||
Lc/f;
|
||||
Lc/g;
|
||||
Lc/h;
|
||||
Lc/i;
|
||||
HSPLc/i;-><clinit>()V
|
||||
Ld/a;
|
||||
HSPLd/a;-><init>(II)V
|
||||
Le/a;
|
||||
Lg/b;
|
||||
Lh/a;
|
||||
HSPLh/a;-><init>(Landroid/content/Context;)V
|
||||
HSPLh/a;->a(Landroid/content/Context;)Lh/a;
|
||||
HSPLh/a;->b()I
|
||||
HSPLh/a;->c()I
|
||||
HSPLh/a;->d()Z
|
||||
Lh/b;
|
||||
HSPLh/b;-><clinit>()V
|
||||
HSPLh/b;-><init>(Landroid/content/Context;)V
|
||||
Li/a;
|
||||
Ls/b;
|
||||
HSPLi/a;-><init>(Landroid/content/Context;IIIILjava/lang/CharSequence;)V
|
||||
Landroidx/appcompat/view/menu/a;
|
||||
Landroidx/appcompat/view/menu/h;
|
||||
HSPLandroidx/appcompat/view/menu/a;-><init>(Landroid/content/Context;II)V
|
||||
HSPLandroidx/appcompat/view/menu/a;->k(Landroid/content/Context;Landroidx/appcompat/view/menu/d;)V
|
||||
PLandroidx/appcompat/view/menu/a;->a(Landroidx/appcompat/view/menu/d;Z)V
|
||||
HSPLandroidx/appcompat/view/menu/a;->f(Landroidx/appcompat/view/menu/h$a;)V
|
||||
HSPLandroidx/appcompat/view/menu/a;->j(Z)V
|
||||
Landroidx/appcompat/view/menu/d$a;
|
||||
Landroidx/appcompat/view/menu/d$b;
|
||||
Landroidx/appcompat/view/menu/d;
|
||||
Ls/a;
|
||||
HSPLandroidx/appcompat/view/menu/d;-><clinit>()V
|
||||
HSPLandroidx/appcompat/view/menu/d;-><init>(Landroid/content/Context;)V
|
||||
HSPLandroidx/appcompat/view/menu/d;->b(Landroidx/appcompat/view/menu/h;Landroid/content/Context;)V
|
||||
PLandroidx/appcompat/view/menu/d;->close()V
|
||||
PLandroidx/appcompat/view/menu/d;->d(Z)V
|
||||
HSPLandroidx/appcompat/view/menu/d;->h(Z)V
|
||||
HSPLandroidx/appcompat/view/menu/d;->q()V
|
||||
HSPLandroidx/appcompat/view/menu/d;->r()Ljava/util/ArrayList;
|
||||
HSPLandroidx/appcompat/view/menu/d;->v()Ljava/util/ArrayList;
|
||||
HSPLandroidx/appcompat/view/menu/d;->A()Ljava/util/ArrayList;
|
||||
HSPLandroidx/appcompat/view/menu/d;->hasVisibleItems()Z
|
||||
HSPLandroidx/appcompat/view/menu/d;->H(Z)V
|
||||
HSPLandroidx/appcompat/view/menu/d;->M(Landroidx/appcompat/view/menu/d$a;)V
|
||||
HSPLandroidx/appcompat/view/menu/d;->setQwertyMode(Z)V
|
||||
HSPLandroidx/appcompat/view/menu/d;->U(Z)V
|
||||
HSPLandroidx/appcompat/view/menu/d;->size()I
|
||||
HSPLandroidx/appcompat/view/menu/d;->V()V
|
||||
HSPLandroidx/appcompat/view/menu/d;->W()V
|
||||
Landroidx/appcompat/view/menu/h$a;
|
||||
Landroidx/appcompat/view/menu/i;
|
||||
Landroidx/appcompat/widget/a$a;
|
||||
HSPLandroidx/appcompat/widget/a$a;-><init>(Landroidx/appcompat/widget/a;)V
|
||||
Landroidx/appcompat/widget/a;
|
||||
HSPLandroidx/appcompat/widget/a;-><init>(Landroid/content/Context;Landroid/util/AttributeSet;I)V
|
||||
Landroidx/appcompat/widget/b;
|
||||
HSPLandroidx/appcompat/widget/b;-><init>(Landroidx/appcompat/widget/ActionBarContainer;)V
|
||||
HSPLandroidx/appcompat/widget/b;->draw(Landroid/graphics/Canvas;)V
|
||||
HSPLandroidx/appcompat/widget/b;->getOpacity()I
|
||||
HSPLandroidx/appcompat/widget/b;->getOutline(Landroid/graphics/Outline;)V
|
||||
Landroidx/appcompat/widget/ActionBarContainer;
|
||||
HSPLandroidx/appcompat/widget/ActionBarContainer;-><init>(Landroid/content/Context;Landroid/util/AttributeSet;)V
|
||||
HSPLandroidx/appcompat/widget/ActionBarContainer;->drawableStateChanged()V
|
||||
HSPLandroidx/appcompat/widget/ActionBarContainer;->jumpDrawablesToCurrentState()V
|
||||
HSPLandroidx/appcompat/widget/ActionBarContainer;->onFinishInflate()V
|
||||
HSPLandroidx/appcompat/widget/ActionBarContainer;->onLayout(ZIIII)V
|
||||
HSPLandroidx/appcompat/widget/ActionBarContainer;->onMeasure(II)V
|
||||
HSPLandroidx/appcompat/widget/ActionBarContainer;->setTabContainer(Landroidx/appcompat/widget/G;)V
|
||||
PLandroidx/appcompat/widget/ActionBarContainer;->verifyDrawable(Landroid/graphics/drawable/Drawable;)Z
|
||||
Landroidx/appcompat/widget/ActionBarContextView;
|
||||
HSPLandroidx/appcompat/widget/ActionBarContextView;-><init>(Landroid/content/Context;Landroid/util/AttributeSet;)V
|
||||
HSPLandroidx/appcompat/widget/ActionBarContextView;-><init>(Landroid/content/Context;Landroid/util/AttributeSet;I)V
|
||||
PLandroidx/appcompat/widget/ActionBarContextView;->onDetachedFromWindow()V
|
||||
Landroidx/appcompat/widget/ActionBarOverlayLayout$a;
|
||||
HSPLandroidx/appcompat/widget/ActionBarOverlayLayout$a;-><init>(Landroidx/appcompat/widget/ActionBarOverlayLayout;)V
|
||||
Landroidx/appcompat/widget/ActionBarOverlayLayout$b;
|
||||
HSPLandroidx/appcompat/widget/ActionBarOverlayLayout$b;-><init>(Landroidx/appcompat/widget/ActionBarOverlayLayout;)V
|
||||
Landroidx/appcompat/widget/ActionBarOverlayLayout$c;
|
||||
HSPLandroidx/appcompat/widget/ActionBarOverlayLayout$c;-><init>(Landroidx/appcompat/widget/ActionBarOverlayLayout;)V
|
||||
Landroidx/appcompat/widget/ActionBarOverlayLayout$d;
|
||||
Landroidx/appcompat/widget/ActionBarOverlayLayout$e;
|
||||
HSPLandroidx/appcompat/widget/ActionBarOverlayLayout$e;-><init>(Landroid/content/Context;Landroid/util/AttributeSet;)V
|
||||
Landroidx/appcompat/widget/ActionBarOverlayLayout;
|
||||
Landroidx/core/view/o;
|
||||
Landroidx/core/view/p;
|
||||
HSPLandroidx/appcompat/widget/ActionBarOverlayLayout;-><clinit>()V
|
||||
HSPLandroidx/appcompat/widget/ActionBarOverlayLayout;-><init>(Landroid/content/Context;Landroid/util/AttributeSet;)V
|
||||
HSPLandroidx/appcompat/widget/ActionBarOverlayLayout;->h(Landroid/view/View;Landroid/graphics/Rect;ZZZZ)Z
|
||||
HSPLandroidx/appcompat/widget/ActionBarOverlayLayout;->checkLayoutParams(Landroid/view/ViewGroup$LayoutParams;)Z
|
||||
HSPLandroidx/appcompat/widget/ActionBarOverlayLayout;->generateLayoutParams(Landroid/util/AttributeSet;)Landroid/view/ViewGroup$LayoutParams;
|
||||
HSPLandroidx/appcompat/widget/ActionBarOverlayLayout;->k(Landroid/util/AttributeSet;)Landroidx/appcompat/widget/ActionBarOverlayLayout$e;
|
||||
HSPLandroidx/appcompat/widget/ActionBarOverlayLayout;->l(Landroid/view/View;)Landroidx/appcompat/widget/u;
|
||||
PLandroidx/appcompat/widget/ActionBarOverlayLayout;->m()V
|
||||
HSPLandroidx/appcompat/widget/ActionBarOverlayLayout;->n(Landroid/content/Context;)V
|
||||
PLandroidx/appcompat/widget/ActionBarOverlayLayout;->onDetachedFromWindow()V
|
||||
HSPLandroidx/appcompat/widget/ActionBarOverlayLayout;->onLayout(ZIIII)V
|
||||
HSPLandroidx/appcompat/widget/ActionBarOverlayLayout;->onMeasure(II)V
|
||||
HSPLandroidx/appcompat/widget/ActionBarOverlayLayout;->onStartNestedScroll(Landroid/view/View;Landroid/view/View;I)Z
|
||||
HSPLandroidx/appcompat/widget/ActionBarOverlayLayout;->f(Landroid/view/View;Landroid/view/View;II)Z
|
||||
HSPLandroidx/appcompat/widget/ActionBarOverlayLayout;->onWindowVisibilityChanged(I)V
|
||||
HSPLandroidx/appcompat/widget/ActionBarOverlayLayout;->q()V
|
||||
HSPLandroidx/appcompat/widget/ActionBarOverlayLayout;->setActionBarVisibilityCallback(Landroidx/appcompat/widget/ActionBarOverlayLayout$d;)V
|
||||
HSPLandroidx/appcompat/widget/ActionBarOverlayLayout;->setHasNonEmbeddedTabs(Z)V
|
||||
HSPLandroidx/appcompat/widget/ActionBarOverlayLayout;->setWindowCallback(Landroid/view/Window$Callback;)V
|
||||
HSPLandroidx/appcompat/widget/ActionBarOverlayLayout;->setWindowTitle(Ljava/lang/CharSequence;)V
|
||||
HSPLandroidx/appcompat/widget/ActionBarOverlayLayout;->shouldDelayChildPressedState()Z
|
||||
Landroidx/appcompat/widget/c$d$a;
|
||||
Landroidx/appcompat/widget/y;
|
||||
HSPLandroidx/appcompat/widget/c$d$a;-><init>(Landroidx/appcompat/widget/c$d;Landroid/view/View;Landroidx/appcompat/widget/c;)V
|
||||
Landroidx/appcompat/widget/c$d;
|
||||
Landroidx/appcompat/widget/l;
|
||||
Landroidx/appcompat/widget/ActionMenuView$a;
|
||||
HSPLandroidx/appcompat/widget/c$d;-><init>(Landroidx/appcompat/widget/c;Landroid/content/Context;)V
|
||||
Landroidx/appcompat/widget/c$f;
|
||||
HSPLandroidx/appcompat/widget/c$f;-><init>(Landroidx/appcompat/widget/c;)V
|
||||
Landroidx/appcompat/widget/c;
|
||||
HSPLandroidx/appcompat/widget/c;-><init>(Landroid/content/Context;)V
|
||||
PLandroidx/appcompat/widget/c;->w()Z
|
||||
HSPLandroidx/appcompat/widget/c;->h()Z
|
||||
PLandroidx/appcompat/widget/c;->z()Z
|
||||
PLandroidx/appcompat/widget/c;->A()Z
|
||||
HSPLandroidx/appcompat/widget/c;->k(Landroid/content/Context;Landroidx/appcompat/view/menu/d;)V
|
||||
PLandroidx/appcompat/widget/c;->a(Landroidx/appcompat/view/menu/d;Z)V
|
||||
HSPLandroidx/appcompat/widget/c;->D(Z)V
|
||||
HSPLandroidx/appcompat/widget/c;->E(Landroidx/appcompat/widget/ActionMenuView;)V
|
||||
HSPLandroidx/appcompat/widget/c;->j(Z)V
|
||||
Landroidx/appcompat/widget/ActionMenuView$e;
|
||||
Landroidx/appcompat/widget/ActionMenuView;
|
||||
Landroidx/appcompat/widget/z;
|
||||
HSPLandroidx/appcompat/widget/ActionMenuView;-><init>(Landroid/content/Context;)V
|
||||
HSPLandroidx/appcompat/widget/ActionMenuView;-><init>(Landroid/content/Context;Landroid/util/AttributeSet;)V
|
||||
PLandroidx/appcompat/widget/ActionMenuView;->w()V
|
||||
HSPLandroidx/appcompat/widget/ActionMenuView;->C(Landroidx/appcompat/view/menu/d;)V
|
||||
PLandroidx/appcompat/widget/ActionMenuView;->onDetachedFromWindow()V
|
||||
HSPLandroidx/appcompat/widget/ActionMenuView;->onLayout(ZIIII)V
|
||||
HSPLandroidx/appcompat/widget/ActionMenuView;->onMeasure(II)V
|
||||
HSPLandroidx/appcompat/widget/ActionMenuView;->G()Landroidx/appcompat/view/menu/d;
|
||||
HSPLandroidx/appcompat/widget/ActionMenuView;->H(Landroidx/appcompat/view/menu/h$a;Landroidx/appcompat/view/menu/d$a;)V
|
||||
HSPLandroidx/appcompat/widget/ActionMenuView;->setOnMenuItemClickListener(Landroidx/appcompat/widget/ActionMenuView$e;)V
|
||||
HSPLandroidx/appcompat/widget/ActionMenuView;->setOverflowReserved(Z)V
|
||||
HSPLandroidx/appcompat/widget/ActionMenuView;->setPopupTheme(I)V
|
||||
HSPLandroidx/appcompat/widget/ActionMenuView;->setPresenter(Landroidx/appcompat/widget/c;)V
|
||||
Landroidx/appcompat/widget/e;
|
||||
HSPLandroidx/appcompat/widget/e;-><init>(Landroid/view/View;)V
|
||||
HSPLandroidx/appcompat/widget/e;->b()V
|
||||
HSPLandroidx/appcompat/widget/e;->e(Landroid/util/AttributeSet;I)V
|
||||
HSPLandroidx/appcompat/widget/e;->k()Z
|
||||
Landroidx/appcompat/widget/f$a;
|
||||
Landroidx/appcompat/widget/D$f;
|
||||
HSPLandroidx/appcompat/widget/f$a;-><init>()V
|
||||
HSPLandroidx/appcompat/widget/f$a;->f([II)Z
|
||||
HSPLandroidx/appcompat/widget/f$a;->a(Landroidx/appcompat/widget/D;Landroid/content/Context;I)Landroid/graphics/drawable/Drawable;
|
||||
HSPLandroidx/appcompat/widget/f$a;->b(Landroid/content/Context;I)Landroid/content/res/ColorStateList;
|
||||
HSPLandroidx/appcompat/widget/f$a;->d(Landroid/content/Context;ILandroid/graphics/drawable/Drawable;)Z
|
||||
HSPLandroidx/appcompat/widget/f$a;->c(Landroid/content/Context;ILandroid/graphics/drawable/Drawable;)Z
|
||||
Landroidx/appcompat/widget/f;
|
||||
HSPLandroidx/appcompat/widget/f;-><clinit>()V
|
||||
HSPLandroidx/appcompat/widget/f;-><init>()V
|
||||
HSPLandroidx/appcompat/widget/f;->a()Landroid/graphics/PorterDuff$Mode;
|
||||
HSPLandroidx/appcompat/widget/f;->b()Landroidx/appcompat/widget/f;
|
||||
HSPLandroidx/appcompat/widget/f;->e(Landroid/content/Context;I)Landroid/content/res/ColorStateList;
|
||||
HSPLandroidx/appcompat/widget/f;->f()V
|
||||
Landroidx/appcompat/widget/g;
|
||||
HSPLandroidx/appcompat/widget/g;-><init>(Landroid/widget/EditText;)V
|
||||
HSPLandroidx/appcompat/widget/g;->a(Landroid/text/method/KeyListener;)Landroid/text/method/KeyListener;
|
||||
HSPLandroidx/appcompat/widget/g;->c(Landroid/util/AttributeSet;I)V
|
||||
HSPLandroidx/appcompat/widget/g;->e(Z)V
|
||||
Landroidx/appcompat/widget/h;
|
||||
HSPLandroidx/appcompat/widget/h;-><init>(Landroid/widget/TextView;)V
|
||||
HSPLandroidx/appcompat/widget/h;->a([Landroid/text/InputFilter;)[Landroid/text/InputFilter;
|
||||
HSPLandroidx/appcompat/widget/h;->b(Landroid/util/AttributeSet;I)V
|
||||
HSPLandroidx/appcompat/widget/h;->d(Z)V
|
||||
Landroidx/appcompat/widget/j;
|
||||
HSPLandroidx/appcompat/widget/j;-><init>(Landroid/content/Context;Landroid/util/AttributeSet;I)V
|
||||
HSPLandroidx/appcompat/widget/j;->setBackgroundDrawable(Landroid/graphics/drawable/Drawable;)V
|
||||
HSPLandroidx/appcompat/widget/j;->setImageDrawable(Landroid/graphics/drawable/Drawable;)V
|
||||
Landroidx/appcompat/widget/k;
|
||||
HSPLandroidx/appcompat/widget/k;-><init>(Landroid/widget/ImageView;)V
|
||||
HSPLandroidx/appcompat/widget/k;->b()V
|
||||
HSPLandroidx/appcompat/widget/k;->c()V
|
||||
HSPLandroidx/appcompat/widget/k;->g(Landroid/util/AttributeSet;I)V
|
||||
HSPLandroidx/appcompat/widget/l;-><init>(Landroid/content/Context;Landroid/util/AttributeSet;I)V
|
||||
HSPLandroidx/appcompat/widget/l;->setBackgroundDrawable(Landroid/graphics/drawable/Drawable;)V
|
||||
HSPLandroidx/appcompat/widget/l;->setImageDrawable(Landroid/graphics/drawable/Drawable;)V
|
||||
Landroidx/appcompat/widget/n;
|
||||
HSPLandroidx/appcompat/widget/n;-><init>(Landroid/widget/TextView;)V
|
||||
Landroidx/appcompat/widget/o$a;
|
||||
Landroidx/core/content/res/h$e;
|
||||
HSPLandroidx/appcompat/widget/o$a;-><init>(Landroidx/appcompat/widget/o;IILjava/lang/ref/WeakReference;)V
|
||||
HSPLandroidx/appcompat/widget/o$a;->f(I)V
|
||||
Landroidx/appcompat/widget/o;
|
||||
HSPLandroidx/appcompat/widget/o;-><init>(Landroid/widget/TextView;)V
|
||||
HSPLandroidx/appcompat/widget/o;->b()V
|
||||
HSPLandroidx/appcompat/widget/o;->d(Landroid/content/Context;Landroidx/appcompat/widget/f;I)Landroidx/appcompat/widget/J;
|
||||
HSPLandroidx/appcompat/widget/o;->m(Landroid/util/AttributeSet;I)V
|
||||
HSPLandroidx/appcompat/widget/o;->o(ZIIII)V
|
||||
HSPLandroidx/appcompat/widget/o;->q(Landroid/content/Context;I)V
|
||||
HSPLandroidx/appcompat/widget/o;->y(Landroid/graphics/drawable/Drawable;Landroid/graphics/drawable/Drawable;Landroid/graphics/drawable/Drawable;Landroid/graphics/drawable/Drawable;Landroid/graphics/drawable/Drawable;Landroid/graphics/drawable/Drawable;)V
|
||||
HSPLandroidx/appcompat/widget/o;->C(Landroid/content/Context;Landroidx/appcompat/widget/L;)V
|
||||
Landroidx/appcompat/widget/p;
|
||||
Landroidx/core/widget/h;
|
||||
HSPLandroidx/appcompat/widget/p;-><init>(Landroid/content/Context;)V
|
||||
HSPLandroidx/appcompat/widget/p;-><init>(Landroid/content/Context;Landroid/util/AttributeSet;)V
|
||||
HSPLandroidx/appcompat/widget/p;-><init>(Landroid/content/Context;Landroid/util/AttributeSet;I)V
|
||||
HSPLandroidx/appcompat/widget/p;->r()V
|
||||
HSPLandroidx/appcompat/widget/p;->drawableStateChanged()V
|
||||
HSPLandroidx/appcompat/widget/p;->getEmojiTextViewHelper()Landroidx/appcompat/widget/h;
|
||||
HSPLandroidx/appcompat/widget/p;->getText()Ljava/lang/CharSequence;
|
||||
HSPLandroidx/appcompat/widget/p;->onLayout(ZIIII)V
|
||||
HSPLandroidx/appcompat/widget/p;->onMeasure(II)V
|
||||
HSPLandroidx/appcompat/widget/p;->onTextChanged(Ljava/lang/CharSequence;III)V
|
||||
HSPLandroidx/appcompat/widget/p;->setCompoundDrawables(Landroid/graphics/drawable/Drawable;Landroid/graphics/drawable/Drawable;Landroid/graphics/drawable/Drawable;Landroid/graphics/drawable/Drawable;)V
|
||||
HSPLandroidx/appcompat/widget/p;->setCompoundDrawablesWithIntrinsicBounds(Landroid/graphics/drawable/Drawable;Landroid/graphics/drawable/Drawable;Landroid/graphics/drawable/Drawable;Landroid/graphics/drawable/Drawable;)V
|
||||
HSPLandroidx/appcompat/widget/p;->setFilters([Landroid/text/InputFilter;)V
|
||||
HSPLandroidx/appcompat/widget/p;->setTextAppearance(Landroid/content/Context;I)V
|
||||
HSPLandroidx/appcompat/widget/p;->setTypeface(Landroid/graphics/Typeface;I)V
|
||||
Landroidx/appcompat/widget/q$b;
|
||||
Landroidx/appcompat/widget/q$d;
|
||||
HSPLandroidx/appcompat/widget/q$b;-><init>()V
|
||||
Landroidx/appcompat/widget/q$c;
|
||||
HSPLandroidx/appcompat/widget/q$c;-><init>()V
|
||||
HSPLandroidx/appcompat/widget/q$d;-><init>()V
|
||||
Landroidx/appcompat/widget/q;
|
||||
HSPLandroidx/appcompat/widget/q;-><clinit>()V
|
||||
HSPLandroidx/appcompat/widget/q;-><init>(Landroid/widget/TextView;)V
|
||||
HSPLandroidx/appcompat/widget/q;->j()I
|
||||
HSPLandroidx/appcompat/widget/q;->o(Landroid/util/AttributeSet;I)V
|
||||
HSPLandroidx/appcompat/widget/q;->y()Z
|
||||
Landroidx/appcompat/widget/ContentFrameLayout$a;
|
||||
Landroidx/appcompat/widget/ContentFrameLayout;
|
||||
HSPLandroidx/appcompat/widget/ContentFrameLayout;-><init>(Landroid/content/Context;Landroid/util/AttributeSet;)V
|
||||
HSPLandroidx/appcompat/widget/ContentFrameLayout;-><init>(Landroid/content/Context;Landroid/util/AttributeSet;I)V
|
||||
HSPLandroidx/appcompat/widget/ContentFrameLayout;->getMinWidthMajor()Landroid/util/TypedValue;
|
||||
HSPLandroidx/appcompat/widget/ContentFrameLayout;->getMinWidthMinor()Landroid/util/TypedValue;
|
||||
HSPLandroidx/appcompat/widget/ContentFrameLayout;->onAttachedToWindow()V
|
||||
PLandroidx/appcompat/widget/ContentFrameLayout;->onDetachedFromWindow()V
|
||||
HSPLandroidx/appcompat/widget/ContentFrameLayout;->onMeasure(II)V
|
||||
HSPLandroidx/appcompat/widget/ContentFrameLayout;->setAttachListener(Landroidx/appcompat/widget/ContentFrameLayout$a;)V
|
||||
Landroidx/appcompat/widget/u;
|
||||
Landroidx/appcompat/widget/v;
|
||||
HSPLandroidx/appcompat/widget/y;-><init>(Landroid/view/View;)V
|
||||
HSPLandroidx/appcompat/widget/z;-><init>(Landroid/content/Context;Landroid/util/AttributeSet;)V
|
||||
HSPLandroidx/appcompat/widget/z;-><init>(Landroid/content/Context;Landroid/util/AttributeSet;I)V
|
||||
HSPLandroidx/appcompat/widget/z;->getVirtualChildCount()I
|
||||
HSPLandroidx/appcompat/widget/z;->p(IIII)V
|
||||
HSPLandroidx/appcompat/widget/z;->s(II)V
|
||||
HSPLandroidx/appcompat/widget/z;->onInitializeAccessibilityNodeInfo(Landroid/view/accessibility/AccessibilityNodeInfo;)V
|
||||
HSPLandroidx/appcompat/widget/z;->onLayout(ZIIII)V
|
||||
HSPLandroidx/appcompat/widget/z;->onMeasure(II)V
|
||||
HSPLandroidx/appcompat/widget/z;->setBaselineAligned(Z)V
|
||||
HSPLandroidx/appcompat/widget/z;->setDividerDrawable(Landroid/graphics/drawable/Drawable;)V
|
||||
Landroidx/appcompat/widget/D$c;
|
||||
Lp/h;
|
||||
Landroidx/appcompat/widget/D;
|
||||
Landroidx/appcompat/widget/E;
|
||||
Landroidx/appcompat/widget/F;
|
||||
HSPLandroidx/appcompat/widget/F;-><init>()V
|
||||
HSPLandroidx/appcompat/widget/F;->a()I
|
||||
HSPLandroidx/appcompat/widget/F;->d()I
|
||||
HSPLandroidx/appcompat/widget/F;->e(II)V
|
||||
HSPLandroidx/appcompat/widget/F;->f(Z)V
|
||||
HSPLandroidx/appcompat/widget/F;->g(II)V
|
||||
Landroidx/appcompat/widget/H;
|
||||
HSPLandroidx/appcompat/widget/H;-><clinit>()V
|
||||
HSPLandroidx/appcompat/widget/H;->a(Landroid/view/View;Landroid/content/Context;)V
|
||||
Landroidx/appcompat/widget/I;
|
||||
HSPLandroidx/appcompat/widget/I;-><clinit>()V
|
||||
HSPLandroidx/appcompat/widget/I;->a(Landroid/content/Context;)Z
|
||||
HSPLandroidx/appcompat/widget/I;->b(Landroid/content/Context;)Landroid/content/Context;
|
||||
Landroidx/appcompat/widget/K;
|
||||
Landroidx/appcompat/widget/L;
|
||||
HSPLandroidx/appcompat/widget/L;-><init>(Landroid/content/Context;Landroid/content/res/TypedArray;)V
|
||||
HSPLandroidx/appcompat/widget/L;->a(IZ)Z
|
||||
HSPLandroidx/appcompat/widget/L;->b(II)I
|
||||
HSPLandroidx/appcompat/widget/L;->c(I)Landroid/content/res/ColorStateList;
|
||||
HSPLandroidx/appcompat/widget/L;->d(II)I
|
||||
HSPLandroidx/appcompat/widget/L;->e(II)I
|
||||
HSPLandroidx/appcompat/widget/L;->f(I)Landroid/graphics/drawable/Drawable;
|
||||
HSPLandroidx/appcompat/widget/L;->g(IF)F
|
||||
HSPLandroidx/appcompat/widget/L;->h(IILandroidx/core/content/res/h$e;)Landroid/graphics/Typeface;
|
||||
HSPLandroidx/appcompat/widget/L;->i(II)I
|
||||
HSPLandroidx/appcompat/widget/L;->j(II)I
|
||||
HSPLandroidx/appcompat/widget/L;->k(II)I
|
||||
HSPLandroidx/appcompat/widget/L;->l(II)I
|
||||
HSPLandroidx/appcompat/widget/L;->m(I)Ljava/lang/String;
|
||||
HSPLandroidx/appcompat/widget/L;->n(I)Ljava/lang/CharSequence;
|
||||
HSPLandroidx/appcompat/widget/L;->o()Landroid/content/res/TypedArray;
|
||||
HSPLandroidx/appcompat/widget/L;->p(I)Z
|
||||
HSPLandroidx/appcompat/widget/L;->q(Landroid/content/Context;I[I)Landroidx/appcompat/widget/L;
|
||||
HSPLandroidx/appcompat/widget/L;->r(Landroid/content/Context;Landroid/util/AttributeSet;[I)Landroidx/appcompat/widget/L;
|
||||
HSPLandroidx/appcompat/widget/L;->s(Landroid/content/Context;Landroid/util/AttributeSet;[III)Landroidx/appcompat/widget/L;
|
||||
HSPLandroidx/appcompat/widget/L;->u()V
|
||||
Landroidx/appcompat/widget/Toolbar$a;
|
||||
HSPLandroidx/appcompat/widget/Toolbar$a;-><init>(Landroidx/appcompat/widget/Toolbar;)V
|
||||
Landroidx/appcompat/widget/Toolbar$b;
|
||||
HSPLandroidx/appcompat/widget/Toolbar$b;-><init>(Landroidx/appcompat/widget/Toolbar;)V
|
||||
Landroidx/appcompat/widget/Toolbar$f;
|
||||
HSPLandroidx/appcompat/widget/Toolbar$f;-><init>(Landroidx/appcompat/widget/Toolbar;)V
|
||||
HSPLandroidx/appcompat/widget/Toolbar$f;->h()Z
|
||||
HSPLandroidx/appcompat/widget/Toolbar$f;->k(Landroid/content/Context;Landroidx/appcompat/view/menu/d;)V
|
||||
PLandroidx/appcompat/widget/Toolbar$f;->a(Landroidx/appcompat/view/menu/d;Z)V
|
||||
HSPLandroidx/appcompat/widget/Toolbar$f;->j(Z)V
|
||||
Landroidx/appcompat/widget/Toolbar$g;
|
||||
HSPLandroidx/appcompat/widget/Toolbar$g;-><init>(II)V
|
||||
Landroidx/appcompat/widget/Toolbar;
|
||||
HSPLandroidx/appcompat/widget/Toolbar;-><init>(Landroid/content/Context;Landroid/util/AttributeSet;)V
|
||||
HSPLandroidx/appcompat/widget/Toolbar;-><init>(Landroid/content/Context;Landroid/util/AttributeSet;I)V
|
||||
HSPLandroidx/appcompat/widget/Toolbar;->b(Ljava/util/List;I)V
|
||||
HSPLandroidx/appcompat/widget/Toolbar;->c(Landroid/view/View;Z)V
|
||||
HSPLandroidx/appcompat/widget/Toolbar;->checkLayoutParams(Landroid/view/ViewGroup$LayoutParams;)Z
|
||||
HSPLandroidx/appcompat/widget/Toolbar;->f()V
|
||||
HSPLandroidx/appcompat/widget/Toolbar;->i()V
|
||||
HSPLandroidx/appcompat/widget/Toolbar;->j()V
|
||||
HSPLandroidx/appcompat/widget/Toolbar;->k()Landroidx/appcompat/widget/Toolbar$g;
|
||||
HSPLandroidx/appcompat/widget/Toolbar;->o(Landroid/view/View;I)I
|
||||
HSPLandroidx/appcompat/widget/Toolbar;->p(I)I
|
||||
HSPLandroidx/appcompat/widget/Toolbar;->getContentInsetEnd()I
|
||||
HSPLandroidx/appcompat/widget/Toolbar;->getContentInsetStart()I
|
||||
HSPLandroidx/appcompat/widget/Toolbar;->getCurrentContentInsetEnd()I
|
||||
HSPLandroidx/appcompat/widget/Toolbar;->getCurrentContentInsetLeft()I
|
||||
HSPLandroidx/appcompat/widget/Toolbar;->getCurrentContentInsetRight()I
|
||||
HSPLandroidx/appcompat/widget/Toolbar;->getCurrentContentInsetStart()I
|
||||
HSPLandroidx/appcompat/widget/Toolbar;->q(Landroid/view/View;)I
|
||||
HSPLandroidx/appcompat/widget/Toolbar;->getNavigationContentDescription()Ljava/lang/CharSequence;
|
||||
HSPLandroidx/appcompat/widget/Toolbar;->getNavigationIcon()Landroid/graphics/drawable/Drawable;
|
||||
HSPLandroidx/appcompat/widget/Toolbar;->getSubtitle()Ljava/lang/CharSequence;
|
||||
HSPLandroidx/appcompat/widget/Toolbar;->getTitle()Ljava/lang/CharSequence;
|
||||
HSPLandroidx/appcompat/widget/Toolbar;->r(Landroid/view/View;)I
|
||||
HSPLandroidx/appcompat/widget/Toolbar;->s(Ljava/util/List;[I)I
|
||||
HSPLandroidx/appcompat/widget/Toolbar;->getWrapper()Landroidx/appcompat/widget/u;
|
||||
PLandroidx/appcompat/widget/Toolbar;->t()Z
|
||||
HSPLandroidx/appcompat/widget/Toolbar;->w(Landroid/view/View;)Z
|
||||
HSPLandroidx/appcompat/widget/Toolbar;->z(Landroid/view/View;I[II)I
|
||||
HSPLandroidx/appcompat/widget/Toolbar;->A(Landroid/view/View;IIII[I)I
|
||||
HSPLandroidx/appcompat/widget/Toolbar;->B(Landroid/view/View;IIIII)V
|
||||
PLandroidx/appcompat/widget/Toolbar;->onDetachedFromWindow()V
|
||||
HSPLandroidx/appcompat/widget/Toolbar;->onLayout(ZIIII)V
|
||||
HSPLandroidx/appcompat/widget/Toolbar;->onMeasure(II)V
|
||||
HSPLandroidx/appcompat/widget/Toolbar;->onRtlPropertiesChanged(I)V
|
||||
HSPLandroidx/appcompat/widget/Toolbar;->setCollapsible(Z)V
|
||||
HSPLandroidx/appcompat/widget/Toolbar;->F(II)V
|
||||
HSPLandroidx/appcompat/widget/Toolbar;->setNavigationContentDescription(Ljava/lang/CharSequence;)V
|
||||
HSPLandroidx/appcompat/widget/Toolbar;->setNavigationIcon(Landroid/graphics/drawable/Drawable;)V
|
||||
HSPLandroidx/appcompat/widget/Toolbar;->setNavigationOnClickListener(Landroid/view/View$OnClickListener;)V
|
||||
HSPLandroidx/appcompat/widget/Toolbar;->setPopupTheme(I)V
|
||||
HSPLandroidx/appcompat/widget/Toolbar;->setSubtitle(Ljava/lang/CharSequence;)V
|
||||
HSPLandroidx/appcompat/widget/Toolbar;->G(Landroid/content/Context;I)V
|
||||
HSPLandroidx/appcompat/widget/Toolbar;->setTitle(Ljava/lang/CharSequence;)V
|
||||
HSPLandroidx/appcompat/widget/Toolbar;->H(Landroid/content/Context;I)V
|
||||
HSPLandroidx/appcompat/widget/Toolbar;->I()Z
|
||||
HSPLandroidx/appcompat/widget/Toolbar;->J(Landroid/view/View;)Z
|
||||
Landroidx/appcompat/widget/P$a;
|
||||
HSPLandroidx/appcompat/widget/P$a;-><init>(Landroidx/appcompat/widget/P;)V
|
||||
Landroidx/appcompat/widget/P;
|
||||
HSPLandroidx/appcompat/widget/P;-><init>(Landroidx/appcompat/widget/Toolbar;Z)V
|
||||
HSPLandroidx/appcompat/widget/P;-><init>(Landroidx/appcompat/widget/Toolbar;ZII)V
|
||||
HSPLandroidx/appcompat/widget/P;->e()Landroid/content/Context;
|
||||
HSPLandroidx/appcompat/widget/P;->g(I)V
|
||||
HSPLandroidx/appcompat/widget/P;->h(I)V
|
||||
HSPLandroidx/appcompat/widget/P;->l(Landroid/graphics/drawable/Drawable;)V
|
||||
HSPLandroidx/appcompat/widget/P;->o(Ljava/lang/CharSequence;)V
|
||||
HSPLandroidx/appcompat/widget/P;->c(Landroid/view/Window$Callback;)V
|
||||
HSPLandroidx/appcompat/widget/P;->b(Ljava/lang/CharSequence;)V
|
||||
HSPLandroidx/appcompat/widget/P;->q()V
|
||||
Landroidx/appcompat/widget/Q;
|
||||
HSPLandroidx/appcompat/widget/Q;->a(Landroid/view/View;Ljava/lang/CharSequence;)V
|
||||
Landroidx/appcompat/widget/W;
|
||||
HSPLandroidx/appcompat/widget/W;-><clinit>()V
|
||||
HSPLandroidx/appcompat/widget/W;->b()Z
|
||||
HSPLandroidx/appcompat/widget/W;->c()Z
|
||||
Landroidx/appcompat/widget/X;
|
||||
HSPLandroidx/appcompat/widget/X;-><clinit>()V
|
||||
HSPLandroidx/appcompat/widget/X;->a(Landroid/view/View;)Z
|
||||
LE/b;
|
||||
LE/c;
|
||||
HSPLE/c;-><clinit>()V
|
||||
Landroidx/fragment/app/a;
|
||||
Landroidx/fragment/app/y;
|
||||
Landroidx/fragment/app/q$h;
|
||||
HSPLandroidx/fragment/app/a;-><init>(Landroidx/fragment/app/q;)V
|
||||
HSPLandroidx/fragment/app/a;->i(I)V
|
||||
HSPLandroidx/fragment/app/a;->g(ILandroidx/fragment/app/Fragment;Ljava/lang/String;I)V
|
||||
HSPLandroidx/fragment/app/a;->k()V
|
||||
HSPLandroidx/fragment/app/a;->m(Ljava/util/ArrayList;Landroidx/fragment/app/Fragment;)Landroidx/fragment/app/Fragment;
|
||||
HSPLandroidx/fragment/app/a;->a(Ljava/util/ArrayList;Ljava/util/ArrayList;)Z
|
||||
HSPLandroidx/fragment/app/a;->n()V
|
||||
Landroidx/fragment/app/d;
|
||||
Landroidx/fragment/app/E;
|
||||
HSPLandroidx/fragment/app/d;-><init>(Landroid/view/ViewGroup;)V
|
||||
Landroidx/fragment/app/Fragment$a;
|
||||
HSPLandroidx/fragment/app/Fragment$a;-><init>(Landroidx/fragment/app/Fragment;)V
|
||||
Landroidx/fragment/app/Fragment$c;
|
||||
Landroidx/fragment/app/g;
|
||||
HSPLandroidx/fragment/app/Fragment$c;-><init>(Landroidx/fragment/app/Fragment;)V
|
||||
Landroidx/fragment/app/Fragment$d;
|
||||
HSPLandroidx/fragment/app/Fragment$d;-><init>()V
|
||||
Landroidx/fragment/app/Fragment$e;
|
||||
PLandroidx/fragment/app/Fragment$e;->a(Landroid/view/View;)V
|
||||
Landroidx/fragment/app/Fragment;
|
||||
HSPLandroidx/fragment/app/Fragment;-><clinit>()V
|
||||
HSPLandroidx/fragment/app/Fragment;-><init>()V
|
||||
HSPLandroidx/fragment/app/Fragment;->h()Landroidx/fragment/app/g;
|
||||
HSPLandroidx/fragment/app/Fragment;->i()Landroidx/fragment/app/Fragment$d;
|
||||
HSPLandroidx/fragment/app/Fragment;->equals(Ljava/lang/Object;)Z
|
||||
HSPLandroidx/fragment/app/Fragment;->j()Landroidx/fragment/app/e;
|
||||
HSPLandroidx/fragment/app/Fragment;->n()Landroidx/fragment/app/q;
|
||||
HSPLandroidx/fragment/app/Fragment;->o()Landroid/content/Context;
|
||||
HSPLandroidx/fragment/app/Fragment;->v()Landroid/view/View;
|
||||
HSPLandroidx/fragment/app/Fragment;->w(Landroid/os/Bundle;)Landroid/view/LayoutInflater;
|
||||
HSPLandroidx/fragment/app/Fragment;->g()Landroidx/lifecycle/g;
|
||||
HSPLandroidx/fragment/app/Fragment;->x()I
|
||||
HSPLandroidx/fragment/app/Fragment;->z()Landroidx/fragment/app/Fragment;
|
||||
HSPLandroidx/fragment/app/Fragment;->A()Landroidx/fragment/app/q;
|
||||
HSPLandroidx/fragment/app/Fragment;->E()F
|
||||
HSPLandroidx/fragment/app/Fragment;->b()Landroidx/savedstate/a;
|
||||
HSPLandroidx/fragment/app/Fragment;->M()Landroid/view/View;
|
||||
HSPLandroidx/fragment/app/Fragment;->c()Landroidx/lifecycle/D;
|
||||
HSPLandroidx/fragment/app/Fragment;->N()V
|
||||
PLandroidx/fragment/app/Fragment;->O()V
|
||||
HSPLandroidx/fragment/app/Fragment;->P()Z
|
||||
HSPLandroidx/fragment/app/Fragment;->S()Z
|
||||
HSPLandroidx/fragment/app/Fragment;->U(Landroid/os/Bundle;)V
|
||||
HSPLandroidx/fragment/app/Fragment;->V(Landroid/os/Bundle;)V
|
||||
PLandroidx/fragment/app/Fragment;->b0()V
|
||||
PLandroidx/fragment/app/Fragment;->c0()V
|
||||
HSPLandroidx/fragment/app/Fragment;->d0(Landroid/os/Bundle;)Landroid/view/LayoutInflater;
|
||||
HSPLandroidx/fragment/app/Fragment;->f0(Landroid/content/Context;Landroid/util/AttributeSet;Landroid/os/Bundle;)V
|
||||
PLandroidx/fragment/app/Fragment;->h0()V
|
||||
HSPLandroidx/fragment/app/Fragment;->j0(Z)V
|
||||
HSPLandroidx/fragment/app/Fragment;->k0()V
|
||||
HSPLandroidx/fragment/app/Fragment;->m0()V
|
||||
PLandroidx/fragment/app/Fragment;->n0()V
|
||||
HSPLandroidx/fragment/app/Fragment;->o0(Landroid/view/View;Landroid/os/Bundle;)V
|
||||
HSPLandroidx/fragment/app/Fragment;->p0(Landroid/os/Bundle;)V
|
||||
HSPLandroidx/fragment/app/Fragment;->q0(Landroid/os/Bundle;)V
|
||||
HSPLandroidx/fragment/app/Fragment;->r0()V
|
||||
HSPLandroidx/fragment/app/Fragment;->t0(Landroid/os/Bundle;)V
|
||||
HSPLandroidx/fragment/app/Fragment;->u0(Landroid/view/Menu;Landroid/view/MenuInflater;)Z
|
||||
HSPLandroidx/fragment/app/Fragment;->v0(Landroid/view/LayoutInflater;Landroid/view/ViewGroup;Landroid/os/Bundle;)V
|
||||
PLandroidx/fragment/app/Fragment;->w0()V
|
||||
PLandroidx/fragment/app/Fragment;->x0()V
|
||||
HSPLandroidx/fragment/app/Fragment;->y0(Landroid/os/Bundle;)Landroid/view/LayoutInflater;
|
||||
PLandroidx/fragment/app/Fragment;->B0()V
|
||||
HSPLandroidx/fragment/app/Fragment;->C0(Landroid/view/Menu;)Z
|
||||
HSPLandroidx/fragment/app/Fragment;->D0()V
|
||||
HSPLandroidx/fragment/app/Fragment;->E0()V
|
||||
HSPLandroidx/fragment/app/Fragment;->G0()V
|
||||
PLandroidx/fragment/app/Fragment;->H0()V
|
||||
HSPLandroidx/fragment/app/Fragment;->I0()V
|
||||
HSPLandroidx/fragment/app/Fragment;->L0()Landroid/content/Context;
|
||||
HSPLandroidx/fragment/app/Fragment;->M0()Landroid/view/View;
|
||||
HSPLandroidx/fragment/app/Fragment;->N0(Landroid/os/Bundle;)V
|
||||
HSPLandroidx/fragment/app/Fragment;->O0()V
|
||||
HSPLandroidx/fragment/app/Fragment;->P0(Landroid/os/Bundle;)V
|
||||
HSPLandroidx/fragment/app/Fragment;->Q0(IIII)V
|
||||
HSPLandroidx/fragment/app/Fragment;->R0(Landroid/view/View;)V
|
||||
HSPLandroidx/fragment/app/Fragment;->S0(I)V
|
||||
HSPLandroidx/fragment/app/Fragment;->T0(Z)V
|
||||
HSPLandroidx/fragment/app/Fragment;->U0(F)V
|
||||
HSPLandroidx/fragment/app/Fragment;->V0(Ljava/util/ArrayList;Ljava/util/ArrayList;)V
|
||||
HSPLandroidx/fragment/app/Fragment;->toString()Ljava/lang/String;
|
||||
Landroidx/fragment/app/e;
|
||||
HSPLandroidx/fragment/app/g;-><init>()V
|
||||
Landroidx/fragment/app/h;
|
||||
PLandroidx/fragment/app/h;->a(Landroid/view/View;)V
|
||||
HSPLandroidx/fragment/app/h;->addView(Landroid/view/View;ILandroid/view/ViewGroup$LayoutParams;)V
|
||||
HSPLandroidx/fragment/app/h;->dispatchDraw(Landroid/graphics/Canvas;)V
|
||||
HSPLandroidx/fragment/app/h;->drawChild(Landroid/graphics/Canvas;Landroid/view/View;J)Z
|
||||
PLandroidx/fragment/app/h;->removeView(Landroid/view/View;)V
|
||||
Landroidx/fragment/app/i;
|
||||
HSPLandroidx/fragment/app/i;-><clinit>()V
|
||||
HSPLandroidx/fragment/app/i;-><init>()V
|
||||
HSPLandroidx/fragment/app/i;->b(Ljava/lang/ClassLoader;Ljava/lang/String;)Z
|
||||
HSPLandroidx/fragment/app/i;->c(Ljava/lang/ClassLoader;Ljava/lang/String;)Ljava/lang/Class;
|
||||
Landroidx/fragment/app/j;
|
||||
Landroidx/fragment/app/k;
|
||||
HSPLandroidx/fragment/app/k;-><init>(Landroidx/fragment/app/q;)V
|
||||
HSPLandroidx/fragment/app/k;->onCreateView(Landroid/view/View;Ljava/lang/String;Landroid/content/Context;Landroid/util/AttributeSet;)Landroid/view/View;
|
||||
Landroidx/fragment/app/l;
|
||||
HSPLandroidx/fragment/app/l;-><init>(Landroidx/fragment/app/q;)V
|
||||
HSPLandroidx/fragment/app/l;->a(Landroidx/fragment/app/Fragment;Landroid/os/Bundle;Z)V
|
||||
HSPLandroidx/fragment/app/l;->b(Landroidx/fragment/app/Fragment;Z)V
|
||||
HSPLandroidx/fragment/app/l;->c(Landroidx/fragment/app/Fragment;Landroid/os/Bundle;Z)V
|
||||
PLandroidx/fragment/app/l;->d(Landroidx/fragment/app/Fragment;Z)V
|
||||
PLandroidx/fragment/app/l;->e(Landroidx/fragment/app/Fragment;Z)V
|
||||
HSPLandroidx/fragment/app/l;->f(Landroidx/fragment/app/Fragment;Z)V
|
||||
HSPLandroidx/fragment/app/l;->g(Landroidx/fragment/app/Fragment;Landroid/os/Bundle;Z)V
|
||||
HSPLandroidx/fragment/app/l;->h(Landroidx/fragment/app/Fragment;Z)V
|
||||
HSPLandroidx/fragment/app/l;->j(Landroidx/fragment/app/Fragment;Z)V
|
||||
PLandroidx/fragment/app/l;->k(Landroidx/fragment/app/Fragment;Z)V
|
||||
HSPLandroidx/fragment/app/l;->l(Landroidx/fragment/app/Fragment;Landroid/view/View;Landroid/os/Bundle;Z)V
|
||||
PLandroidx/fragment/app/l;->m(Landroidx/fragment/app/Fragment;Z)V
|
||||
Landroidx/fragment/app/q$a;
|
||||
HSPLandroidx/fragment/app/q$a;-><init>(Landroidx/fragment/app/q;Z)V
|
||||
Landroidx/fragment/app/q$b;
|
||||
Landroidx/core/view/l;
|
||||
HSPLandroidx/fragment/app/q$b;-><init>(Landroidx/fragment/app/q;)V
|
||||
Landroidx/fragment/app/q$c;
|
||||
HSPLandroidx/fragment/app/q$c;-><init>(Landroidx/fragment/app/q;)V
|
||||
Landroidx/fragment/app/q$d;
|
||||
Landroidx/fragment/app/F;
|
||||
HSPLandroidx/fragment/app/q$d;-><init>(Landroidx/fragment/app/q;)V
|
||||
Landroidx/fragment/app/FragmentManager$6;
|
||||
Landroidx/fragment/app/q$f;
|
||||
Landroidx/fragment/app/u;
|
||||
Landroidx/fragment/app/q;
|
||||
HSPLandroidx/fragment/app/q;-><clinit>()V
|
||||
HSPLandroidx/fragment/app/q;-><init>()V
|
||||
HSPLandroidx/fragment/app/q;->h(Landroidx/fragment/app/Fragment;)Landroidx/fragment/app/w;
|
||||
HSPLandroidx/fragment/app/q;->i(Landroidx/fragment/app/u;)V
|
||||
HSPLandroidx/fragment/app/q;->j(Landroidx/fragment/app/j;Landroidx/fragment/app/g;Landroidx/fragment/app/Fragment;)V
|
||||
HSPLandroidx/fragment/app/q;->l()Landroidx/fragment/app/y;
|
||||
HSPLandroidx/fragment/app/q;->m()Z
|
||||
HSPLandroidx/fragment/app/q;->n()V
|
||||
PLandroidx/fragment/app/q;->o()V
|
||||
HSPLandroidx/fragment/app/q;->p()Ljava/util/Set;
|
||||
HSPLandroidx/fragment/app/q;->q(Ljava/util/ArrayList;II)Ljava/util/Set;
|
||||
HSPLandroidx/fragment/app/q;->r(Landroidx/fragment/app/Fragment;)Landroidx/fragment/app/w;
|
||||
HSPLandroidx/fragment/app/q;->t()V
|
||||
HSPLandroidx/fragment/app/q;->v()V
|
||||
HSPLandroidx/fragment/app/q;->w(Landroid/view/Menu;Landroid/view/MenuInflater;)Z
|
||||
PLandroidx/fragment/app/q;->x()V
|
||||
PLandroidx/fragment/app/q;->y()V
|
||||
HSPLandroidx/fragment/app/q;->C(Landroidx/fragment/app/Fragment;)V
|
||||
PLandroidx/fragment/app/q;->D()V
|
||||
HSPLandroidx/fragment/app/q;->E(Landroid/view/Menu;)Z
|
||||
HSPLandroidx/fragment/app/q;->F()V
|
||||
HSPLandroidx/fragment/app/q;->G()V
|
||||
HSPLandroidx/fragment/app/q;->H()V
|
||||
HSPLandroidx/fragment/app/q;->I(I)V
|
||||
PLandroidx/fragment/app/q;->J()V
|
||||
HSPLandroidx/fragment/app/q;->K()V
|
||||
HSPLandroidx/fragment/app/q;->L()V
|
||||
PLandroidx/fragment/app/q;->M()V
|
||||
HSPLandroidx/fragment/app/q;->N(Z)V
|
||||
HSPLandroidx/fragment/app/q;->O(Z)Z
|
||||
HSPLandroidx/fragment/app/q;->Q(Ljava/util/ArrayList;Ljava/util/ArrayList;II)V
|
||||
HSPLandroidx/fragment/app/q;->R(Ljava/util/ArrayList;Ljava/util/ArrayList;II)V
|
||||
HSPLandroidx/fragment/app/q;->S(Ljava/lang/String;)Landroidx/fragment/app/Fragment;
|
||||
HSPLandroidx/fragment/app/q;->T(I)Landroidx/fragment/app/Fragment;
|
||||
HSPLandroidx/fragment/app/q;->Y(Ljava/util/ArrayList;Ljava/util/ArrayList;)Z
|
||||
HSPLandroidx/fragment/app/q;->Z()I
|
||||
HSPLandroidx/fragment/app/q;->a0(Landroidx/fragment/app/Fragment;)Landroidx/fragment/app/t;
|
||||
HSPLandroidx/fragment/app/q;->b0()Landroidx/fragment/app/g;
|
||||
HSPLandroidx/fragment/app/q;->c0(Landroidx/fragment/app/Fragment;)Landroid/view/ViewGroup;
|
||||
HSPLandroidx/fragment/app/q;->d0()Landroidx/fragment/app/i;
|
||||
HSPLandroidx/fragment/app/q;->e0()Landroidx/fragment/app/j;
|
||||
HSPLandroidx/fragment/app/q;->f0()Landroidx/fragment/app/l;
|
||||
HSPLandroidx/fragment/app/q;->g0()Landroidx/fragment/app/Fragment;
|
||||
HSPLandroidx/fragment/app/q;->h0()Landroidx/fragment/app/Fragment;
|
||||
HSPLandroidx/fragment/app/q;->i0()Landroidx/fragment/app/F;
|
||||
HSPLandroidx/fragment/app/q;->j0()LF/c$c;
|
||||
HSPLandroidx/fragment/app/q;->k0(Landroid/view/View;)Landroidx/fragment/app/Fragment;
|
||||
HSPLandroidx/fragment/app/q;->l0(Landroidx/fragment/app/Fragment;)Landroidx/lifecycle/D;
|
||||
PLandroidx/fragment/app/q;->o0()Z
|
||||
HSPLandroidx/fragment/app/q;->p0(I)Z
|
||||
HSPLandroidx/fragment/app/q;->q0(Landroidx/fragment/app/Fragment;)Z
|
||||
HSPLandroidx/fragment/app/q;->t0(Landroidx/fragment/app/Fragment;)Z
|
||||
HSPLandroidx/fragment/app/q;->u0(Landroidx/fragment/app/Fragment;)Z
|
||||
HSPLandroidx/fragment/app/q;->v0(I)Z
|
||||
HSPLandroidx/fragment/app/q;->w0()Z
|
||||
HSPLandroidx/fragment/app/q;->x0(IZ)V
|
||||
HSPLandroidx/fragment/app/q;->y0()V
|
||||
HSPLandroidx/fragment/app/q;->A0(Landroidx/fragment/app/w;)V
|
||||
HSPLandroidx/fragment/app/q;->C0(Ljava/util/ArrayList;Ljava/util/ArrayList;)V
|
||||
HSPLandroidx/fragment/app/q;->H0(Landroidx/fragment/app/Fragment;Z)V
|
||||
HSPLandroidx/fragment/app/q;->J0(Landroidx/fragment/app/Fragment;)V
|
||||
HSPLandroidx/fragment/app/q;->M0()V
|
||||
HSPLandroidx/fragment/app/q;->N0()V
|
||||
Landroidx/fragment/app/r;
|
||||
HSPLandroidx/fragment/app/r;-><init>()V
|
||||
Landroidx/fragment/app/t$a;
|
||||
Landroidx/lifecycle/A$b;
|
||||
HSPLandroidx/fragment/app/t$a;-><init>()V
|
||||
HSPLandroidx/fragment/app/t$a;->b(Ljava/lang/Class;)Landroidx/lifecycle/z;
|
||||
Landroidx/fragment/app/t;
|
||||
Landroidx/lifecycle/z;
|
||||
HSPLandroidx/fragment/app/t;-><clinit>()V
|
||||
HSPLandroidx/fragment/app/t;-><init>(Z)V
|
||||
PLandroidx/fragment/app/t;->f(Landroidx/fragment/app/Fragment;)V
|
||||
PLandroidx/fragment/app/t;->g(Ljava/lang/String;)V
|
||||
HSPLandroidx/fragment/app/t;->i(Landroidx/fragment/app/Fragment;)Landroidx/fragment/app/t;
|
||||
HSPLandroidx/fragment/app/t;->k(Landroidx/fragment/app/Fragment;)Landroidx/lifecycle/D;
|
||||
PLandroidx/fragment/app/t;->d()V
|
||||
HSPLandroidx/fragment/app/t;->m(Z)V
|
||||
PLandroidx/fragment/app/t;->n(Landroidx/fragment/app/Fragment;)Z
|
||||
Landroidx/fragment/app/w$a;
|
||||
HSPLandroidx/fragment/app/w$a;-><init>(Landroidx/fragment/app/w;Landroid/view/View;)V
|
||||
HSPLandroidx/fragment/app/w$a;->onViewAttachedToWindow(Landroid/view/View;)V
|
||||
Landroidx/fragment/app/w$b;
|
||||
HSPLandroidx/fragment/app/w$b;-><clinit>()V
|
||||
Landroidx/fragment/app/w;
|
||||
HSPLandroidx/fragment/app/w;-><init>(Landroidx/fragment/app/l;Landroidx/fragment/app/x;Landroidx/fragment/app/Fragment;)V
|
||||
HSPLandroidx/fragment/app/w;->a()V
|
||||
HSPLandroidx/fragment/app/w;->b()V
|
||||
HSPLandroidx/fragment/app/w;->c()V
|
||||
HSPLandroidx/fragment/app/w;->d()I
|
||||
HSPLandroidx/fragment/app/w;->e()V
|
||||
HSPLandroidx/fragment/app/w;->f()V
|
||||
PLandroidx/fragment/app/w;->g()V
|
||||
PLandroidx/fragment/app/w;->h()V
|
||||
PLandroidx/fragment/app/w;->i()V
|
||||
HSPLandroidx/fragment/app/w;->j()V
|
||||
HSPLandroidx/fragment/app/w;->k()Landroidx/fragment/app/Fragment;
|
||||
HSPLandroidx/fragment/app/w;->m()V
|
||||
PLandroidx/fragment/app/w;->n()V
|
||||
HSPLandroidx/fragment/app/w;->o()V
|
||||
PLandroidx/fragment/app/w;->r()V
|
||||
HSPLandroidx/fragment/app/w;->s(I)V
|
||||
HSPLandroidx/fragment/app/w;->t()V
|
||||
PLandroidx/fragment/app/w;->u()V
|
||||
Landroidx/fragment/app/x;
|
||||
HSPLandroidx/fragment/app/x;-><init>()V
|
||||
HSPLandroidx/fragment/app/x;->a(Landroidx/fragment/app/Fragment;)V
|
||||
HSPLandroidx/fragment/app/x;->b()V
|
||||
HSPLandroidx/fragment/app/x;->c(Ljava/lang/String;)Z
|
||||
HSPLandroidx/fragment/app/x;->d(I)V
|
||||
HSPLandroidx/fragment/app/x;->e(Ljava/lang/String;)Landroidx/fragment/app/Fragment;
|
||||
HSPLandroidx/fragment/app/x;->f(I)Landroidx/fragment/app/Fragment;
|
||||
HSPLandroidx/fragment/app/x;->h(Landroidx/fragment/app/Fragment;)I
|
||||
HSPLandroidx/fragment/app/x;->i()Ljava/util/List;
|
||||
HSPLandroidx/fragment/app/x;->j()Ljava/util/List;
|
||||
HSPLandroidx/fragment/app/x;->l(Ljava/lang/String;)Landroidx/fragment/app/w;
|
||||
HSPLandroidx/fragment/app/x;->m()Ljava/util/List;
|
||||
PLandroidx/fragment/app/x;->n()Landroidx/fragment/app/t;
|
||||
HSPLandroidx/fragment/app/x;->p(Landroidx/fragment/app/w;)V
|
||||
PLandroidx/fragment/app/x;->q(Landroidx/fragment/app/w;)V
|
||||
HSPLandroidx/fragment/app/x;->r()V
|
||||
HSPLandroidx/fragment/app/x;->y(Landroidx/fragment/app/t;)V
|
||||
Landroidx/fragment/app/y$a;
|
||||
HSPLandroidx/fragment/app/y$a;-><init>(ILandroidx/fragment/app/Fragment;)V
|
||||
HSPLandroidx/fragment/app/y$a;-><init>(ILandroidx/fragment/app/Fragment;Z)V
|
||||
HSPLandroidx/fragment/app/y;-><init>(Landroidx/fragment/app/i;Ljava/lang/ClassLoader;)V
|
||||
HSPLandroidx/fragment/app/y;->d(Landroidx/fragment/app/y$a;)V
|
||||
HSPLandroidx/fragment/app/y;->g(ILandroidx/fragment/app/Fragment;Ljava/lang/String;I)V
|
||||
HSPLandroidx/fragment/app/y;->h(Z)Landroidx/fragment/app/y;
|
||||
Landroidx/fragment/app/C;
|
||||
HSPLandroidx/fragment/app/C;-><init>(Landroidx/fragment/app/Fragment;Landroidx/lifecycle/D;)V
|
||||
HSPLandroidx/fragment/app/C;->g()Landroidx/lifecycle/g;
|
||||
HSPLandroidx/fragment/app/C;->b()Landroidx/savedstate/a;
|
||||
HSPLandroidx/fragment/app/C;->d(Landroidx/lifecycle/g$a;)V
|
||||
HSPLandroidx/fragment/app/C;->e()V
|
||||
HSPLandroidx/fragment/app/C;->h(Landroid/os/Bundle;)V
|
||||
PLandroidx/fragment/app/C;->i(Landroid/os/Bundle;)V
|
||||
Landroidx/fragment/app/E$a;
|
||||
HSPLandroidx/fragment/app/E$a;-><init>(Landroidx/fragment/app/E;Landroidx/fragment/app/E$d;)V
|
||||
HSPLandroidx/fragment/app/E$a;->run()V
|
||||
Landroidx/fragment/app/E$b;
|
||||
HSPLandroidx/fragment/app/E$b;-><init>(Landroidx/fragment/app/E;Landroidx/fragment/app/E$d;)V
|
||||
HSPLandroidx/fragment/app/E$b;->run()V
|
||||
Landroidx/fragment/app/E$c;
|
||||
HSPLandroidx/fragment/app/E$c;-><clinit>()V
|
||||
Landroidx/fragment/app/E$d;
|
||||
Landroidx/fragment/app/E$e;
|
||||
HSPLandroidx/fragment/app/E$d;-><init>(Landroidx/fragment/app/E$e$c;Landroidx/fragment/app/E$e$b;Landroidx/fragment/app/w;Landroidx/core/os/b;)V
|
||||
HSPLandroidx/fragment/app/E$d;->c()V
|
||||
HSPLandroidx/fragment/app/E$d;->l()V
|
||||
Landroidx/fragment/app/E$e$a;
|
||||
Landroidx/core/os/b$a;
|
||||
HSPLandroidx/fragment/app/E$e$a;-><init>(Landroidx/fragment/app/E$e;)V
|
||||
Landroidx/fragment/app/E$e$b;
|
||||
HSPLandroidx/fragment/app/E$e$b;-><clinit>()V
|
||||
HSPLandroidx/fragment/app/E$e$b;-><init>(Ljava/lang/String;I)V
|
||||
HSPLandroidx/fragment/app/E$e$b;->values()[Landroidx/fragment/app/E$e$b;
|
||||
Landroidx/fragment/app/E$e$c;
|
||||
HSPLandroidx/fragment/app/E$e$c;-><clinit>()V
|
||||
HSPLandroidx/fragment/app/E$e$c;-><init>(Ljava/lang/String;I)V
|
||||
HSPLandroidx/fragment/app/E$e$c;->a(Landroid/view/View;)V
|
||||
HSPLandroidx/fragment/app/E$e$c;->b(I)Landroidx/fragment/app/E$e$c;
|
||||
HSPLandroidx/fragment/app/E$e$c;->values()[Landroidx/fragment/app/E$e$c;
|
||||
HSPLandroidx/fragment/app/E$e;-><init>(Landroidx/fragment/app/E$e$c;Landroidx/fragment/app/E$e$b;Landroidx/fragment/app/Fragment;Landroidx/core/os/b;)V
|
||||
HSPLandroidx/fragment/app/E$e;->a(Ljava/lang/Runnable;)V
|
||||
HSPLandroidx/fragment/app/E$e;->b()V
|
||||
HSPLandroidx/fragment/app/E$e;->c()V
|
||||
HSPLandroidx/fragment/app/E$e;->e()Landroidx/fragment/app/E$e$c;
|
||||
HSPLandroidx/fragment/app/E$e;->f()Landroidx/fragment/app/Fragment;
|
||||
HSPLandroidx/fragment/app/E$e;->g()Landroidx/fragment/app/E$e$b;
|
||||
HSPLandroidx/fragment/app/E$e;->h()Z
|
||||
HSPLandroidx/fragment/app/E$e;->k(Landroidx/fragment/app/E$e$c;Landroidx/fragment/app/E$e$b;)V
|
||||
HSPLandroidx/fragment/app/E;-><init>(Landroid/view/ViewGroup;)V
|
||||
HSPLandroidx/fragment/app/E;->a(Landroidx/fragment/app/E$e$c;Landroidx/fragment/app/E$e$b;Landroidx/fragment/app/w;)V
|
||||
HSPLandroidx/fragment/app/E;->b(Landroidx/fragment/app/E$e$c;Landroidx/fragment/app/w;)V
|
||||
PLandroidx/fragment/app/E;->d(Landroidx/fragment/app/w;)V
|
||||
HSPLandroidx/fragment/app/E;->g()V
|
||||
HSPLandroidx/fragment/app/E;->h(Landroidx/fragment/app/Fragment;)Landroidx/fragment/app/E$e;
|
||||
HSPLandroidx/fragment/app/E;->i(Landroidx/fragment/app/Fragment;)Landroidx/fragment/app/E$e;
|
||||
HSPLandroidx/fragment/app/E;->j()V
|
||||
HSPLandroidx/fragment/app/E;->l(Landroidx/fragment/app/w;)Landroidx/fragment/app/E$e$b;
|
||||
HSPLandroidx/fragment/app/E;->n(Landroid/view/ViewGroup;Landroidx/fragment/app/q;)Landroidx/fragment/app/E;
|
||||
HSPLandroidx/fragment/app/E;->o(Landroid/view/ViewGroup;Landroidx/fragment/app/F;)Landroidx/fragment/app/E;
|
||||
HSPLandroidx/fragment/app/E;->p()V
|
||||
HSPLandroidx/fragment/app/E;->q()V
|
||||
HSPLandroidx/fragment/app/E;->r(Z)V
|
||||
LF/c$a;
|
||||
HSPLF/c$a;->a()[LF/c$a;
|
||||
HSPLF/c$a;-><clinit>()V
|
||||
HSPLF/c$a;-><init>(Ljava/lang/String;I)V
|
||||
LF/c$c$a;
|
||||
HSPLF/c$c$a;-><init>()V
|
||||
HSPLF/c$c$a;-><init>(Lt0/d;)V
|
||||
LF/c$c;
|
||||
HSPLF/c$c;-><clinit>()V
|
||||
HSPLF/c$c;-><init>(Ljava/util/Set;LF/c$b;Ljava/util/Map;)V
|
||||
HSPLF/c$c;->a()Ljava/util/Set;
|
||||
LF/c;
|
||||
HSPLF/c;-><clinit>()V
|
||||
HSPLF/c;-><init>()V
|
||||
HSPLF/c;->b(Landroidx/fragment/app/Fragment;)LF/c$c;
|
||||
HSPLF/c;->e(LF/d;)V
|
||||
LF/d;
|
||||
HSPLF/d;-><init>(Landroidx/fragment/app/Fragment;Ljava/lang/String;)V
|
||||
Landroidx/lifecycle/d;
|
||||
HSPLandroidx/lifecycle/d;-><init>()V
|
||||
HSPLandroidx/lifecycle/d;->onActivityCreated(Landroid/app/Activity;Landroid/os/Bundle;)V
|
||||
PLandroidx/lifecycle/d;->onActivityDestroyed(Landroid/app/Activity;)V
|
||||
PLandroidx/lifecycle/d;->onActivityPaused(Landroid/app/Activity;)V
|
||||
HSPLandroidx/lifecycle/d;->onActivityResumed(Landroid/app/Activity;)V
|
||||
HSPLandroidx/lifecycle/d;->onActivityStarted(Landroid/app/Activity;)V
|
||||
PLandroidx/lifecycle/d;->onActivityStopped(Landroid/app/Activity;)V
|
||||
Landroidx/lifecycle/i$a;
|
||||
HSPLandroidx/lifecycle/i$a;-><init>()V
|
||||
HSPLandroidx/lifecycle/i$a;->onActivityCreated(Landroid/app/Activity;Landroid/os/Bundle;)V
|
||||
Landroidx/lifecycle/i;
|
||||
HSPLandroidx/lifecycle/i;-><clinit>()V
|
||||
HSPLandroidx/lifecycle/i;->a(Landroid/content/Context;)V
|
||||
Landroidx/lifecycle/m$b;
|
||||
HSPLandroidx/lifecycle/m$b;-><init>(Landroidx/lifecycle/k;Landroidx/lifecycle/g$b;)V
|
||||
HSPLandroidx/lifecycle/m$b;->a(Landroidx/lifecycle/l;Landroidx/lifecycle/g$a;)V
|
||||
Landroidx/lifecycle/m;
|
||||
Landroidx/lifecycle/g;
|
||||
HSPLandroidx/lifecycle/m;-><init>(Landroidx/lifecycle/l;)V
|
||||
HSPLandroidx/lifecycle/m;-><init>(Landroidx/lifecycle/l;Z)V
|
||||
HSPLandroidx/lifecycle/m;->a(Landroidx/lifecycle/k;)V
|
||||
HPLandroidx/lifecycle/m;->d(Landroidx/lifecycle/l;)V
|
||||
HSPLandroidx/lifecycle/m;->e(Landroidx/lifecycle/k;)Landroidx/lifecycle/g$b;
|
||||
HSPLandroidx/lifecycle/m;->f(Ljava/lang/String;)V
|
||||
HSPLandroidx/lifecycle/m;->g(Landroidx/lifecycle/l;)V
|
||||
HSPLandroidx/lifecycle/m;->b()Landroidx/lifecycle/g$b;
|
||||
HSPLandroidx/lifecycle/m;->h(Landroidx/lifecycle/g$a;)V
|
||||
HSPLandroidx/lifecycle/m;->i()Z
|
||||
HSPLandroidx/lifecycle/m;->j(Landroidx/lifecycle/g$b;)V
|
||||
HSPLandroidx/lifecycle/m;->k()V
|
||||
HSPLandroidx/lifecycle/m;->l(Landroidx/lifecycle/g$b;)V
|
||||
HSPLandroidx/lifecycle/m;->c(Landroidx/lifecycle/k;)V
|
||||
HSPLandroidx/lifecycle/m;->m()V
|
||||
Landroidx/lifecycle/LiveData$a;
|
||||
HSPLandroidx/lifecycle/LiveData$a;-><init>(Landroidx/lifecycle/LiveData;)V
|
||||
HSPLandroidx/lifecycle/LiveData$a;->run()V
|
||||
Landroidx/lifecycle/LiveData$LifecycleBoundObserver;
|
||||
Landroidx/lifecycle/LiveData$b;
|
||||
PLandroidx/lifecycle/LiveData$LifecycleBoundObserver;->i()V
|
||||
HSPLandroidx/lifecycle/LiveData$LifecycleBoundObserver;->h(Landroidx/lifecycle/l;Landroidx/lifecycle/g$a;)V
|
||||
HSPLandroidx/lifecycle/LiveData$LifecycleBoundObserver;->j()Z
|
||||
HSPLandroidx/lifecycle/LiveData$b;->d(Z)V
|
||||
HSPLandroidx/lifecycle/LiveData$b;->i()V
|
||||
Landroidx/lifecycle/LiveData;
|
||||
HSPLandroidx/lifecycle/LiveData;-><clinit>()V
|
||||
HSPLandroidx/lifecycle/LiveData;-><init>()V
|
||||
HSPLandroidx/lifecycle/LiveData;->a(Ljava/lang/String;)V
|
||||
HSPLandroidx/lifecycle/LiveData;->b(I)V
|
||||
HSPLandroidx/lifecycle/LiveData;->c(Landroidx/lifecycle/LiveData$b;)V
|
||||
HSPLandroidx/lifecycle/LiveData;->d(Landroidx/lifecycle/LiveData$b;)V
|
||||
HSPLandroidx/lifecycle/LiveData;->e()V
|
||||
HSPLandroidx/lifecycle/LiveData;->f()V
|
||||
HSPLandroidx/lifecycle/LiveData;->g(Landroidx/lifecycle/q;)V
|
||||
HSPLandroidx/lifecycle/LiveData;->h(Ljava/lang/Object;)V
|
||||
Landroidx/lifecycle/p;
|
||||
HSPLandroidx/lifecycle/p;-><init>()V
|
||||
HSPLandroidx/lifecycle/p;->h(Ljava/lang/Object;)V
|
||||
Landroidx/lifecycle/ProcessLifecycleInitializer;
|
||||
LJ/a;
|
||||
HSPLandroidx/lifecycle/ProcessLifecycleInitializer;-><init>()V
|
||||
HSPLandroidx/lifecycle/ProcessLifecycleInitializer;->c(Landroid/content/Context;)Landroidx/lifecycle/l;
|
||||
HSPLandroidx/lifecycle/ProcessLifecycleInitializer;->b(Landroid/content/Context;)Ljava/lang/Object;
|
||||
HSPLandroidx/lifecycle/ProcessLifecycleInitializer;->a()Ljava/util/List;
|
||||
Landroidx/lifecycle/t;
|
||||
HSPLandroidx/lifecycle/t;-><clinit>()V
|
||||
HSPLandroidx/lifecycle/t;-><init>()V
|
||||
HSPLandroidx/lifecycle/t;->g()Landroidx/lifecycle/g;
|
||||
Landroidx/lifecycle/u$c;
|
||||
HSPLandroidx/lifecycle/u$c;-><init>()V
|
||||
HSPLandroidx/lifecycle/u$c;->onActivityCreated(Landroid/app/Activity;Landroid/os/Bundle;)V
|
||||
PLandroidx/lifecycle/u$c;->onActivityDestroyed(Landroid/app/Activity;)V
|
||||
PLandroidx/lifecycle/u$c;->onActivityPaused(Landroid/app/Activity;)V
|
||||
HSPLandroidx/lifecycle/u$c;->onActivityPostCreated(Landroid/app/Activity;Landroid/os/Bundle;)V
|
||||
HSPLandroidx/lifecycle/u$c;->onActivityPostResumed(Landroid/app/Activity;)V
|
||||
HSPLandroidx/lifecycle/u$c;->onActivityPostStarted(Landroid/app/Activity;)V
|
||||
PLandroidx/lifecycle/u$c;->onActivityPreDestroyed(Landroid/app/Activity;)V
|
||||
PLandroidx/lifecycle/u$c;->onActivityPrePaused(Landroid/app/Activity;)V
|
||||
PLandroidx/lifecycle/u$c;->onActivityPreStopped(Landroid/app/Activity;)V
|
||||
HSPLandroidx/lifecycle/u$c;->onActivityResumed(Landroid/app/Activity;)V
|
||||
HSPLandroidx/lifecycle/u$c;->onActivityStarted(Landroid/app/Activity;)V
|
||||
PLandroidx/lifecycle/u$c;->onActivityStopped(Landroid/app/Activity;)V
|
||||
HSPLandroidx/lifecycle/u$c;->registerIn(Landroid/app/Activity;)V
|
||||
Landroidx/lifecycle/u;
|
||||
HSPLandroidx/lifecycle/u;-><init>()V
|
||||
HSPLandroidx/lifecycle/u;->a(Landroidx/lifecycle/g$a;)V
|
||||
HSPLandroidx/lifecycle/u;->b(Landroidx/lifecycle/u$a;)V
|
||||
HSPLandroidx/lifecycle/u;->c(Landroidx/lifecycle/u$a;)V
|
||||
HSPLandroidx/lifecycle/u;->d(Landroidx/lifecycle/u$a;)V
|
||||
HSPLandroidx/lifecycle/u;->onActivityCreated(Landroid/os/Bundle;)V
|
||||
PLandroidx/lifecycle/u;->onDestroy()V
|
||||
PLandroidx/lifecycle/u;->onPause()V
|
||||
HSPLandroidx/lifecycle/u;->onResume()V
|
||||
HSPLandroidx/lifecycle/u;->onStart()V
|
||||
PLandroidx/lifecycle/u;->onStop()V
|
||||
HSPLandroidx/lifecycle/z;-><init>()V
|
||||
PLandroidx/lifecycle/z;->a()V
|
||||
PLandroidx/lifecycle/z;->d()V
|
||||
Landroidx/lifecycle/A;
|
||||
HSPLandroidx/lifecycle/A;-><init>(Landroidx/lifecycle/D;Landroidx/lifecycle/A$b;)V
|
||||
HSPLandroidx/lifecycle/A;->a(Ljava/lang/Class;)Landroidx/lifecycle/z;
|
||||
HSPLandroidx/lifecycle/A;->b(Ljava/lang/String;Ljava/lang/Class;)Landroidx/lifecycle/z;
|
||||
Landroidx/lifecycle/D;
|
||||
HSPLandroidx/lifecycle/D;-><init>()V
|
||||
PLandroidx/lifecycle/D;->a()V
|
||||
HSPLandroidx/lifecycle/D;->b(Ljava/lang/String;)Landroidx/lifecycle/z;
|
||||
HSPLandroidx/lifecycle/D;->d(Ljava/lang/String;Landroidx/lifecycle/z;)V
|
||||
Landroidx/lifecycle/F;
|
||||
HSPLandroidx/lifecycle/F;->a(Landroid/view/View;Landroidx/lifecycle/l;)V
|
||||
Landroidx/lifecycle/G;
|
||||
HSPLandroidx/lifecycle/G;->a(Landroid/view/View;Landroidx/lifecycle/E;)V
|
||||
Landroidx/startup/a;
|
||||
HSPLandroidx/startup/a;-><clinit>()V
|
||||
HSPLandroidx/startup/a;-><init>(Landroid/content/Context;)V
|
||||
HSPLandroidx/startup/a;->a()V
|
||||
HSPLandroidx/startup/a;->b(Landroid/os/Bundle;)V
|
||||
HSPLandroidx/startup/a;->c(Ljava/lang/Class;)Ljava/lang/Object;
|
||||
HSPLandroidx/startup/a;->d(Ljava/lang/Class;Ljava/util/Set;)Ljava/lang/Object;
|
||||
HSPLandroidx/startup/a;->e(Landroid/content/Context;)Landroidx/startup/a;
|
||||
HSPLandroidx/startup/a;->f(Ljava/lang/Class;)Ljava/lang/Object;
|
||||
HSPLandroidx/startup/a;->g(Ljava/lang/Class;)Z
|
||||
Landroidx/appcompat/widget/N;
|
||||
HSPLandroidx/appcompat/widget/N;-><init>(Landroidx/appcompat/widget/Toolbar;)V
|
||||
HSPLandroidx/appcompat/widget/N;->run()V
|
||||
Landroidx/fragment/app/m;
|
||||
Lw/a;
|
||||
HSPLandroidx/fragment/app/m;-><init>(Landroidx/fragment/app/q;)V
|
||||
Landroidx/fragment/app/n;
|
||||
HSPLandroidx/fragment/app/n;-><init>(Landroidx/fragment/app/q;)V
|
||||
Landroidx/fragment/app/o;
|
||||
HSPLandroidx/fragment/app/o;-><init>(Landroidx/fragment/app/q;)V
|
||||
Landroidx/fragment/app/p;
|
||||
HSPLandroidx/fragment/app/p;-><init>(Landroidx/fragment/app/q;)V
|
||||
Landroidx/lifecycle/s;
|
||||
HSPLandroidx/lifecycle/s;-><init>(Landroidx/lifecycle/t;)V
|
||||
Lh/c;
|
||||
HSPLh/c;->a(Ljava/lang/Object;)V
|
||||
@@ -1,10 +0,0 @@
|
||||
{
|
||||
"version": 3,
|
||||
"artifactType": {
|
||||
"type": "COMPATIBLE_SCREEN_MANIFEST",
|
||||
"kind": "Directory"
|
||||
},
|
||||
"applicationId": "io.truckwash.twa",
|
||||
"variantName": "release",
|
||||
"elements": []
|
||||
}
|
||||
@@ -1,89 +0,0 @@
|
||||
# This is a configuration file for ProGuard.
|
||||
# http://proguard.sourceforge.net/index.html#manual/usage.html
|
||||
#
|
||||
# Starting with version 2.2 of the Android plugin for Gradle, this file is distributed together with
|
||||
# the plugin and unpacked at build-time. The files in $ANDROID_HOME are no longer maintained and
|
||||
# will be ignored by new version of the Android plugin for Gradle.
|
||||
|
||||
# Optimizations: If you don't want to optimize, use the proguard-android.txt configuration file
|
||||
# instead of this one, which turns off the optimization flags.
|
||||
-allowaccessmodification
|
||||
|
||||
# Preserve some attributes that may be required for reflection.
|
||||
-keepattributes AnnotationDefault,
|
||||
EnclosingMethod,
|
||||
InnerClasses,
|
||||
RuntimeVisibleAnnotations,
|
||||
RuntimeVisibleParameterAnnotations,
|
||||
RuntimeVisibleTypeAnnotations,
|
||||
Signature
|
||||
|
||||
-keep public class com.google.vending.licensing.ILicensingService
|
||||
-keep public class com.android.vending.licensing.ILicensingService
|
||||
-keep public class com.google.android.vending.licensing.ILicensingService
|
||||
-dontnote com.android.vending.licensing.ILicensingService
|
||||
-dontnote com.google.vending.licensing.ILicensingService
|
||||
-dontnote com.google.android.vending.licensing.ILicensingService
|
||||
|
||||
# For native methods, see https://www.guardsquare.com/manual/configuration/examples#native
|
||||
-keepclasseswithmembernames,includedescriptorclasses class * {
|
||||
native <methods>;
|
||||
}
|
||||
|
||||
# Keep setters in Views so that animations can still work.
|
||||
-keepclassmembers public class * extends android.view.View {
|
||||
void set*(***);
|
||||
*** get*();
|
||||
}
|
||||
|
||||
# We want to keep methods in Activity that could be used in the XML attribute onClick.
|
||||
-keepclassmembers class * extends android.app.Activity {
|
||||
public void *(android.view.View);
|
||||
}
|
||||
|
||||
# For enumeration classes, see https://www.guardsquare.com/manual/configuration/examples#enumerations
|
||||
-keepclassmembers enum * {
|
||||
public static **[] values();
|
||||
public static ** valueOf(java.lang.String);
|
||||
}
|
||||
|
||||
-keepclassmembers class * implements android.os.Parcelable {
|
||||
public static final ** CREATOR;
|
||||
}
|
||||
|
||||
# Preserve annotated Javascript interface methods.
|
||||
-keepclassmembers class * {
|
||||
@android.webkit.JavascriptInterface <methods>;
|
||||
}
|
||||
|
||||
# The support libraries contains references to newer platform versions.
|
||||
# Don't warn about those in case this app is linking against an older
|
||||
# platform version. We know about them, and they are safe.
|
||||
-dontnote android.support.**
|
||||
-dontnote androidx.**
|
||||
-dontwarn android.support.**
|
||||
-dontwarn androidx.**
|
||||
|
||||
# Understand the @Keep support annotation.
|
||||
-keep class android.support.annotation.Keep
|
||||
|
||||
-keep @android.support.annotation.Keep class * {*;}
|
||||
|
||||
-keepclasseswithmembers class * {
|
||||
@android.support.annotation.Keep <methods>;
|
||||
}
|
||||
|
||||
-keepclasseswithmembers class * {
|
||||
@android.support.annotation.Keep <fields>;
|
||||
}
|
||||
|
||||
-keepclasseswithmembers class * {
|
||||
@android.support.annotation.Keep <init>(...);
|
||||
}
|
||||
|
||||
# These classes are duplicated between android.jar and org.apache.http.legacy.jar.
|
||||
-dontnote org.apache.http.**
|
||||
-dontnote android.net.http.**
|
||||
|
||||
# These classes are duplicated between android.jar and core-lambda-stubs.jar.
|
||||
-dontnote java.lang.invoke.**
|
||||
@@ -1,95 +0,0 @@
|
||||
# This is a configuration file for ProGuard.
|
||||
# http://proguard.sourceforge.net/index.html#manual/usage.html
|
||||
#
|
||||
# Starting with version 2.2 of the Android plugin for Gradle, this file is distributed together with
|
||||
# the plugin and unpacked at build-time. The files in $ANDROID_HOME are no longer maintained and
|
||||
# will be ignored by new version of the Android plugin for Gradle.
|
||||
|
||||
# Optimization is turned off by default. Dex does not like code run
|
||||
# through the ProGuard optimize steps (and performs some
|
||||
# of these optimizations on its own).
|
||||
# Note that if you want to enable optimization, you cannot just
|
||||
# include optimization flags in your own project configuration file;
|
||||
# instead you will need to point to the
|
||||
# "proguard-android-optimize.txt" file instead of this one from your
|
||||
# project.properties file.
|
||||
-dontoptimize
|
||||
|
||||
# Preserve some attributes that may be required for reflection.
|
||||
-keepattributes AnnotationDefault,
|
||||
EnclosingMethod,
|
||||
InnerClasses,
|
||||
RuntimeVisibleAnnotations,
|
||||
RuntimeVisibleParameterAnnotations,
|
||||
RuntimeVisibleTypeAnnotations,
|
||||
Signature
|
||||
|
||||
-keep public class com.google.vending.licensing.ILicensingService
|
||||
-keep public class com.android.vending.licensing.ILicensingService
|
||||
-keep public class com.google.android.vending.licensing.ILicensingService
|
||||
-dontnote com.android.vending.licensing.ILicensingService
|
||||
-dontnote com.google.vending.licensing.ILicensingService
|
||||
-dontnote com.google.android.vending.licensing.ILicensingService
|
||||
|
||||
# For native methods, see https://www.guardsquare.com/manual/configuration/examples#native
|
||||
-keepclasseswithmembernames,includedescriptorclasses class * {
|
||||
native <methods>;
|
||||
}
|
||||
|
||||
# Keep setters in Views so that animations can still work.
|
||||
-keepclassmembers public class * extends android.view.View {
|
||||
void set*(***);
|
||||
*** get*();
|
||||
}
|
||||
|
||||
# We want to keep methods in Activity that could be used in the XML attribute onClick.
|
||||
-keepclassmembers class * extends android.app.Activity {
|
||||
public void *(android.view.View);
|
||||
}
|
||||
|
||||
# For enumeration classes, see https://www.guardsquare.com/manual/configuration/examples#enumerations
|
||||
-keepclassmembers enum * {
|
||||
public static **[] values();
|
||||
public static ** valueOf(java.lang.String);
|
||||
}
|
||||
|
||||
-keepclassmembers class * implements android.os.Parcelable {
|
||||
public static final ** CREATOR;
|
||||
}
|
||||
|
||||
# Preserve annotated Javascript interface methods.
|
||||
-keepclassmembers class * {
|
||||
@android.webkit.JavascriptInterface <methods>;
|
||||
}
|
||||
|
||||
# The support libraries contains references to newer platform versions.
|
||||
# Don't warn about those in case this app is linking against an older
|
||||
# platform version. We know about them, and they are safe.
|
||||
-dontnote android.support.**
|
||||
-dontnote androidx.**
|
||||
-dontwarn android.support.**
|
||||
-dontwarn androidx.**
|
||||
|
||||
# Understand the @Keep support annotation.
|
||||
-keep class android.support.annotation.Keep
|
||||
|
||||
-keep @android.support.annotation.Keep class * {*;}
|
||||
|
||||
-keepclasseswithmembers class * {
|
||||
@android.support.annotation.Keep <methods>;
|
||||
}
|
||||
|
||||
-keepclasseswithmembers class * {
|
||||
@android.support.annotation.Keep <fields>;
|
||||
}
|
||||
|
||||
-keepclasseswithmembers class * {
|
||||
@android.support.annotation.Keep <init>(...);
|
||||
}
|
||||
|
||||
# These classes are duplicated between android.jar and org.apache.http.legacy.jar.
|
||||
-dontnote org.apache.http.**
|
||||
-dontnote android.net.http.**
|
||||
|
||||
# These classes are duplicated between android.jar and core-lambda-stubs.jar.
|
||||
-dontnote java.lang.invoke.**
|
||||
@@ -1,89 +0,0 @@
|
||||
# This is a configuration file for ProGuard.
|
||||
# http://proguard.sourceforge.net/index.html#manual/usage.html
|
||||
#
|
||||
# Starting with version 2.2 of the Android plugin for Gradle, this file is distributed together with
|
||||
# the plugin and unpacked at build-time. The files in $ANDROID_HOME are no longer maintained and
|
||||
# will be ignored by new version of the Android plugin for Gradle.
|
||||
|
||||
# Optimizations can be turned on and off in the 'postProcessing' DSL block.
|
||||
# The configuration below is applied if optimizations are enabled.
|
||||
-allowaccessmodification
|
||||
|
||||
# Preserve some attributes that may be required for reflection.
|
||||
-keepattributes AnnotationDefault,
|
||||
EnclosingMethod,
|
||||
InnerClasses,
|
||||
RuntimeVisibleAnnotations,
|
||||
RuntimeVisibleParameterAnnotations,
|
||||
RuntimeVisibleTypeAnnotations,
|
||||
Signature
|
||||
|
||||
-keep public class com.google.vending.licensing.ILicensingService
|
||||
-keep public class com.android.vending.licensing.ILicensingService
|
||||
-keep public class com.google.android.vending.licensing.ILicensingService
|
||||
-dontnote com.android.vending.licensing.ILicensingService
|
||||
-dontnote com.google.vending.licensing.ILicensingService
|
||||
-dontnote com.google.android.vending.licensing.ILicensingService
|
||||
|
||||
# For native methods, see https://www.guardsquare.com/manual/configuration/examples#native
|
||||
-keepclasseswithmembernames,includedescriptorclasses class * {
|
||||
native <methods>;
|
||||
}
|
||||
|
||||
# Keep setters in Views so that animations can still work.
|
||||
-keepclassmembers public class * extends android.view.View {
|
||||
void set*(***);
|
||||
*** get*();
|
||||
}
|
||||
|
||||
# We want to keep methods in Activity that could be used in the XML attribute onClick.
|
||||
-keepclassmembers class * extends android.app.Activity {
|
||||
public void *(android.view.View);
|
||||
}
|
||||
|
||||
# For enumeration classes, see https://www.guardsquare.com/manual/configuration/examples#enumerations
|
||||
-keepclassmembers enum * {
|
||||
public static **[] values();
|
||||
public static ** valueOf(java.lang.String);
|
||||
}
|
||||
|
||||
-keepclassmembers class * implements android.os.Parcelable {
|
||||
public static final ** CREATOR;
|
||||
}
|
||||
|
||||
# Preserve annotated Javascript interface methods.
|
||||
-keepclassmembers class * {
|
||||
@android.webkit.JavascriptInterface <methods>;
|
||||
}
|
||||
|
||||
# The support libraries contains references to newer platform versions.
|
||||
# Don't warn about those in case this app is linking against an older
|
||||
# platform version. We know about them, and they are safe.
|
||||
-dontnote android.support.**
|
||||
-dontnote androidx.**
|
||||
-dontwarn android.support.**
|
||||
-dontwarn androidx.**
|
||||
|
||||
# Understand the @Keep support annotation.
|
||||
-keep class android.support.annotation.Keep
|
||||
|
||||
-keep @android.support.annotation.Keep class * {*;}
|
||||
|
||||
-keepclasseswithmembers class * {
|
||||
@android.support.annotation.Keep <methods>;
|
||||
}
|
||||
|
||||
-keepclasseswithmembers class * {
|
||||
@android.support.annotation.Keep <fields>;
|
||||
}
|
||||
|
||||
-keepclasseswithmembers class * {
|
||||
@android.support.annotation.Keep <init>(...);
|
||||
}
|
||||
|
||||
# These classes are duplicated between android.jar and org.apache.http.legacy.jar.
|
||||
-dontnote org.apache.http.**
|
||||
-dontnote android.net.http.**
|
||||
|
||||
# These classes are duplicated between android.jar and core-lambda-stubs.jar.
|
||||
-dontnote java.lang.invoke.**
|
||||
@@ -1,5 +0,0 @@
|
||||
31=0/.dm
|
||||
2147483647=0/.dm
|
||||
28=1/.dm
|
||||
29=1/.dm
|
||||
30=1/.dm
|
||||
@@ -1,2 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<merger version="3"><dataSet config="main" ignore_pattern="!.svn:!.git:!.ds_store:!*.scc:.*:<dir>_*:!CVS:!thumbs.db:!picasa.ini:!*~"><source path="C:\Users\2jepp\WebstormProjects\pleno-vue\app\src\main\assets"/></dataSet><dataSet config="release" ignore_pattern="!.svn:!.git:!.ds_store:!*.scc:.*:<dir>_*:!CVS:!thumbs.db:!picasa.ini:!*~"><source path="C:\Users\2jepp\WebstormProjects\pleno-vue\app\src\release\assets"/></dataSet><dataSet config="generated" ignore_pattern="!.svn:!.git:!.ds_store:!*.scc:.*:<dir>_*:!CVS:!thumbs.db:!picasa.ini:!*~"><source path="C:\Users\2jepp\WebstormProjects\pleno-vue\app\build\intermediates\shader_assets\release\compileReleaseShaders\out"/></dataSet></merger>
|
||||
@@ -1,2 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<merger version="3"><dataSet config="main" ignore_pattern="!.svn:!.git:!.ds_store:!*.scc:.*:<dir>_*:!CVS:!thumbs.db:!picasa.ini:!*~"><source path="C:\Users\2jepp\WebstormProjects\pleno-vue\app\src\main\jniLibs"/></dataSet><dataSet config="release" ignore_pattern="!.svn:!.git:!.ds_store:!*.scc:.*:<dir>_*:!CVS:!thumbs.db:!picasa.ini:!*~"><source path="C:\Users\2jepp\WebstormProjects\pleno-vue\app\src\release\jniLibs"/></dataSet></merger>
|
||||
@@ -1,2 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<merger version="3"><dataSet config="main" ignore_pattern="!.svn:!.git:!.ds_store:!*.scc:.*:<dir>_*:!CVS:!thumbs.db:!picasa.ini:!*~"><source path="C:\Users\2jepp\WebstormProjects\pleno-vue\app\src\main\shaders"/></dataSet><dataSet config="release" ignore_pattern="!.svn:!.git:!.ds_store:!*.scc:.*:<dir>_*:!CVS:!thumbs.db:!picasa.ini:!*~"><source path="C:\Users\2jepp\WebstormProjects\pleno-vue\app\src\release\shaders"/></dataSet></merger>
|
||||
@@ -1,4 +0,0 @@
|
||||
#Thu Jan 22 13:48:32 CET 2026
|
||||
base.0=C\:\\Users\\2jepp\\WebstormProjects\\pleno-vue\\app\\build\\intermediates\\dex\\release\\minifyReleaseWithR8\\classes.dex
|
||||
renamed.0=classes.dex
|
||||
path.0=classes.dex
|
||||