deeplink-doctor 1.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +24 -0
- package/LICENSE +21 -0
- package/README.md +188 -0
- package/dist/checks/calculateExitCode.d.ts +4 -0
- package/dist/checks/calculateExitCode.js +4 -0
- package/dist/checks/calculateExitCode.js.map +1 -0
- package/dist/checks/checkAasaAppIds.d.ts +2 -0
- package/dist/checks/checkAasaAppIds.js +15 -0
- package/dist/checks/checkAasaAppIds.js.map +1 -0
- package/dist/checks/checkAppIdentifiers.d.ts +2 -0
- package/dist/checks/checkAppIdentifiers.js +13 -0
- package/dist/checks/checkAppIdentifiers.js.map +1 -0
- package/dist/checks/checkAssetlinks.d.ts +2 -0
- package/dist/checks/checkAssetlinks.js +21 -0
- package/dist/checks/checkAssetlinks.js.map +1 -0
- package/dist/checks/checkAssociatedDomains.d.ts +3 -0
- package/dist/checks/checkAssociatedDomains.js +7 -0
- package/dist/checks/checkAssociatedDomains.js.map +1 -0
- package/dist/checks/checkAutoVerifyHosts.d.ts +2 -0
- package/dist/checks/checkAutoVerifyHosts.js +6 -0
- package/dist/checks/checkAutoVerifyHosts.js.map +1 -0
- package/dist/checks/checkConfig.d.ts +2 -0
- package/dist/checks/checkConfig.js +11 -0
- package/dist/checks/checkConfig.js.map +1 -0
- package/dist/checks/checkDeadLinks.d.ts +2 -0
- package/dist/checks/checkDeadLinks.js +9 -0
- package/dist/checks/checkDeadLinks.js.map +1 -0
- package/dist/checks/checkRemote.d.ts +2 -0
- package/dist/checks/checkRemote.js +4 -0
- package/dist/checks/checkRemote.js.map +1 -0
- package/dist/checks/checkScheme.d.ts +3 -0
- package/dist/checks/checkScheme.js +13 -0
- package/dist/checks/checkScheme.js.map +1 -0
- package/dist/checks/checkUnreachableRoutes.d.ts +2 -0
- package/dist/checks/checkUnreachableRoutes.js +11 -0
- package/dist/checks/checkUnreachableRoutes.js.map +1 -0
- package/dist/checks/runChecks.d.ts +2 -0
- package/dist/checks/runChecks.js +8 -0
- package/dist/checks/runChecks.js.map +1 -0
- package/dist/checks/targetMatchesRoute.d.ts +2 -0
- package/dist/checks/targetMatchesRoute.js +14 -0
- package/dist/checks/targetMatchesRoute.js.map +1 -0
- package/dist/commands/check.d.ts +21 -0
- package/dist/commands/check.js +42 -0
- package/dist/commands/check.js.map +1 -0
- package/dist/config/findingExplanations.d.ts +2 -0
- package/dist/config/findingExplanations.js +19 -0
- package/dist/config/findingExplanations.js.map +1 -0
- package/dist/config/findingSeverities.d.ts +2 -0
- package/dist/config/findingSeverities.js +16 -0
- package/dist/config/findingSeverities.js.map +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +59 -0
- package/dist/index.js.map +1 -0
- package/dist/lib/generateFinding.d.ts +5 -0
- package/dist/lib/generateFinding.js +8 -0
- package/dist/lib/generateFinding.js.map +1 -0
- package/dist/links/extractDomains.d.ts +2 -0
- package/dist/links/extractDomains.js +11 -0
- package/dist/links/extractDomains.js.map +1 -0
- package/dist/links/extractLinkConfig.d.ts +2 -0
- package/dist/links/extractLinkConfig.js +15 -0
- package/dist/links/extractLinkConfig.js.map +1 -0
- package/dist/links/extractLinkTargets.d.ts +2 -0
- package/dist/links/extractLinkTargets.js +25 -0
- package/dist/links/extractLinkTargets.js.map +1 -0
- package/dist/parsers/expoConfig.d.ts +4 -0
- package/dist/parsers/expoConfig.js +16 -0
- package/dist/parsers/expoConfig.js.map +1 -0
- package/dist/parsers/routes.d.ts +2 -0
- package/dist/parsers/routes.js +23 -0
- package/dist/parsers/routes.js.map +1 -0
- package/dist/report/human.d.ts +3 -0
- package/dist/report/human.js +72 -0
- package/dist/report/human.js.map +1 -0
- package/dist/report/json.d.ts +3 -0
- package/dist/report/json.js +19 -0
- package/dist/report/json.js.map +1 -0
- package/dist/routes/parseRoutePath.d.ts +3 -0
- package/dist/routes/parseRoutePath.js +53 -0
- package/dist/routes/parseRoutePath.js.map +1 -0
- package/dist/routes/routeHandlesPath.d.ts +2 -0
- package/dist/routes/routeHandlesPath.js +12 -0
- package/dist/routes/routeHandlesPath.js.map +1 -0
- package/dist/routes/routeHandlesPrefix.d.ts +2 -0
- package/dist/routes/routeHandlesPrefix.js +15 -0
- package/dist/routes/routeHandlesPrefix.js.map +1 -0
- package/dist/sources/aasa.d.ts +2 -0
- package/dist/sources/aasa.js +16 -0
- package/dist/sources/aasa.js.map +1 -0
- package/dist/sources/assetlinks.d.ts +2 -0
- package/dist/sources/assetlinks.js +13 -0
- package/dist/sources/assetlinks.js.map +1 -0
- package/dist/sources/httpFetcher.d.ts +2 -0
- package/dist/sources/httpFetcher.js +5 -0
- package/dist/sources/httpFetcher.js.map +1 -0
- package/dist/sources/loadAssociations.d.ts +2 -0
- package/dist/sources/loadAssociations.js +49 -0
- package/dist/sources/loadAssociations.js.map +1 -0
- package/dist/suppressions/applySuppressions.d.ts +5 -0
- package/dist/suppressions/applySuppressions.js +38 -0
- package/dist/suppressions/applySuppressions.js.map +1 -0
- package/dist/suppressions/parseSuppressionConfig.d.ts +2 -0
- package/dist/suppressions/parseSuppressionConfig.js +28 -0
- package/dist/suppressions/parseSuppressionConfig.js.map +1 -0
- package/dist/suppressions/readSuppressionConfig.d.ts +5 -0
- package/dist/suppressions/readSuppressionConfig.js +32 -0
- package/dist/suppressions/readSuppressionConfig.js.map +1 -0
- package/dist/types.d.ts +92 -0
- package/dist/types.js +15 -0
- package/dist/types.js.map +1 -0
- package/package.json +83 -0
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
## [1.0.1](https://github.com/KMavr/deeplink-doctor/compare/deeplink-doctor-v1.0.0...deeplink-doctor-v1.0.1) (2026-06-25)
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
### Bug Fixes
|
|
7
|
+
|
|
8
|
+
* add repository metadata for provenance ([#16](https://github.com/KMavr/deeplink-doctor/issues/16)) ([7bbb22c](https://github.com/KMavr/deeplink-doctor/commit/7bbb22cbf1d4273cc849738111caa9abc028270c))
|
|
9
|
+
|
|
10
|
+
## 1.0.0 (2026-06-25)
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
### Features
|
|
14
|
+
|
|
15
|
+
* parse the expo-router route tree from the app/ directory ([#1](https://github.com/KMavr/deeplink-doctor/issues/1)) ([a574747](https://github.com/KMavr/deeplink-doctor/commit/a574747b7dbdff78020d92c0057cbecf278e77ec))
|
|
16
|
+
* read native deep-link config from `expo config --json` ([#2](https://github.com/KMavr/deeplink-doctor/issues/2)) ([43f4ad5](https://github.com/KMavr/deeplink-doctor/commit/43f4ad51ab9f32f92fe902555caefe119e913d23))
|
|
17
|
+
* reconcile routes against native config — DL001/DL002 + check command ([#3](https://github.com/KMavr/deeplink-doctor/issues/3)) ([8ce30cd](https://github.com/KMavr/deeplink-doctor/commit/8ce30cd30f3b30015dfdf789da8b64e29cab3ef4))
|
|
18
|
+
* add config-hygiene checks DL003 and DL101–DL104 ([#4](https://github.com/KMavr/deeplink-doctor/issues/4)) ([1cce5cc](https://github.com/KMavr/deeplink-doctor/commit/1cce5cc1287345843bfcfda6c14f4a5ea5eac69d))
|
|
19
|
+
* add governed suppressions with DL901/DL902 accountability checks ([#5](https://github.com/KMavr/deeplink-doctor/issues/5)) ([ae7d789](https://github.com/KMavr/deeplink-doctor/commit/ae7d7893ed3949bcb8fb2bdfa53959255b585bf1))
|
|
20
|
+
* verify hosted association files over the network — DL2xx via --remote ([#6](https://github.com/KMavr/deeplink-doctor/issues/6)) ([2ce500f](https://github.com/KMavr/deeplink-doctor/commit/2ce500f3c28c710a7c7f634b7b3f8eca5dc0c9fa))
|
|
21
|
+
* add --config flag for suppression config path ([#7](https://github.com/KMavr/deeplink-doctor/issues/7)) ([cd581fe](https://github.com/KMavr/deeplink-doctor/commit/cd581fe6743af5e7b729ab6f9ac08e3a1d043ae1))
|
|
22
|
+
* add --explain to append finding explanations ([#12](https://github.com/KMavr/deeplink-doctor/issues/12)) ([c0bc95c](https://github.com/KMavr/deeplink-doctor/commit/c0bc95c9d9eb1f5a58db9d48fc06ab308bcbb3e3))
|
|
23
|
+
* add --silent flag to hide warnings ([#11](https://github.com/KMavr/deeplink-doctor/issues/11)) ([3055f94](https://github.com/KMavr/deeplink-doctor/commit/3055f94c73c7d2d65501a28ee1353ef800de35b8))
|
|
24
|
+
* include suppressed findings in JSON output ([#8](https://github.com/KMavr/deeplink-doctor/issues/8)) ([1eb1f13](https://github.com/KMavr/deeplink-doctor/commit/1eb1f13d0f1818bcb164ad4cf95ed1dd36b74e39))
|
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Konstantinos Mavrikas
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
# deeplink-doctor
|
|
2
|
+
|
|
3
|
+
[](https://github.com/KMavr/deeplink-doctor/actions/workflows/ci.yml)
|
|
4
|
+
[](https://www.npmjs.com/package/deeplink-doctor)
|
|
5
|
+
[](https://www.npmjs.com/package/deeplink-doctor)
|
|
6
|
+
|
|
7
|
+
> Statically reconcile an Expo project's **route tree**, its **native deep-link
|
|
8
|
+
> config**, and (optionally) its **hosted association files** — and report where
|
|
9
|
+
> they disagree.
|
|
10
|
+
|
|
11
|
+
Deep links break in ways nothing catches until a user taps one in production: an
|
|
12
|
+
`intentFilter` advertises a path no screen handles, a screen can never be reached
|
|
13
|
+
by any link, or the `apple-app-site-association` file you hosted names the wrong
|
|
14
|
+
bundle. Apple's validator and Android's `pm verify-app-links` check the _hosted
|
|
15
|
+
file_ or _device state_ — none of them read your repo, so none can tell you the
|
|
16
|
+
repo and the config disagree. That repo-aware cross-check is the whole point of
|
|
17
|
+
this tool.
|
|
18
|
+
|
|
19
|
+
Zero config, no setup, runs over `npx`.
|
|
20
|
+
|
|
21
|
+
## Quickstart
|
|
22
|
+
|
|
23
|
+
```sh
|
|
24
|
+
# from the root of an Expo (expo-router) project
|
|
25
|
+
npx deeplink-doctor
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
That runs every static check — no network, safe on every commit. Add `--remote`
|
|
29
|
+
to also fetch and validate your hosted association files.
|
|
30
|
+
|
|
31
|
+
Requires Node 18+ and an `expo-router` project (it reads your `app/` directory
|
|
32
|
+
and `npx expo config --json`).
|
|
33
|
+
|
|
34
|
+
## What it catches
|
|
35
|
+
|
|
36
|
+
A project can look perfectly configured locally yet be dead in production. Static
|
|
37
|
+
checks run offline:
|
|
38
|
+
|
|
39
|
+
```sh
|
|
40
|
+
$ npx deeplink-doctor
|
|
41
|
+
DL001 error No route handles deep link target "/promo"
|
|
42
|
+
|
|
43
|
+
1 issue (1 error, 0 warnings)
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
…and the hosted files are the other half of the handshake. `--remote` fetches
|
|
47
|
+
them and confirms they name _your_ app:
|
|
48
|
+
|
|
49
|
+
```sh
|
|
50
|
+
$ npx deeplink-doctor check --remote --explain
|
|
51
|
+
DL202 error AASA appID does not match ios.bundleIdentifier "com.example.app"
|
|
52
|
+
The hosted AASA lists no appID matching your ios.bundleIdentifier (an
|
|
53
|
+
appID is <teamId>.<bundleIdentifier>). iOS will not associate the domain
|
|
54
|
+
with your app. Fix: add your team + bundle to applinks.details[].appID(s)
|
|
55
|
+
in the hosted AASA.
|
|
56
|
+
|
|
57
|
+
DL203 error assetlinks.json has no entry for android.package "com.example.app"
|
|
58
|
+
The hosted assetlinks.json has no statement for your android.package.
|
|
59
|
+
Android will not verify your App Links. Fix: add a statement whose
|
|
60
|
+
target.package_name is your package.
|
|
61
|
+
|
|
62
|
+
2 issues (2 errors, 0 warnings)
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
Green offline, red against the live world — the classic "works in dev, broken in
|
|
66
|
+
prod." That's exactly the gap `--remote` closes.
|
|
67
|
+
|
|
68
|
+
## How it works
|
|
69
|
+
|
|
70
|
+
It reconciles up to three sources of truth:
|
|
71
|
+
|
|
72
|
+
1. **Route tree** — parses your `app/` directory the way expo-router does
|
|
73
|
+
(dynamic `[id]`, catch-all `[...rest]`, `(groups)`, `_layout`, `+`-special and
|
|
74
|
+
API routes).
|
|
75
|
+
2. **Native config** — reads the _resolved_ config from `npx expo config --json`
|
|
76
|
+
(`scheme`, `ios.associatedDomains`/`bundleIdentifier`, `android.package`/
|
|
77
|
+
`intentFilters`).
|
|
78
|
+
3. **Hosted association files** _(opt-in, `--remote`)_ — fetches
|
|
79
|
+
`https://<domain>/.well-known/apple-app-site-association` and
|
|
80
|
+
`assetlinks.json` and checks they point back at your app.
|
|
81
|
+
|
|
82
|
+
## Commands
|
|
83
|
+
|
|
84
|
+
| Command | Description |
|
|
85
|
+
| ------------------------------------------- | ----------------------------------------------- |
|
|
86
|
+
| `deeplink-doctor` / `deeplink-doctor check` | Run the checks and report mismatches (default). |
|
|
87
|
+
| `deeplink-doctor routes` | Print the parsed route tree (debug). |
|
|
88
|
+
|
|
89
|
+
### Flags (for `check`)
|
|
90
|
+
|
|
91
|
+
| Flag | Effect |
|
|
92
|
+
| ----------------- | -------------------------------------------------------------------------- |
|
|
93
|
+
| `--remote` | Also fetch and validate hosted association files (makes network requests). |
|
|
94
|
+
| `--domain <host>` | Override the domain(s) probed by `--remote`. |
|
|
95
|
+
| `--strict` | Promote warnings to failures (non-zero exit on any finding). |
|
|
96
|
+
| `--silent` | Hide warnings (errors only). Ignored when `--strict` is set. |
|
|
97
|
+
| `--explain` | Append a long-form explanation to each finding. |
|
|
98
|
+
| `--config <path>` | Path to a `deeplink.config.json` (defaults to the project root). |
|
|
99
|
+
| `--json` | Emit a stable machine-readable schema instead of the human report. |
|
|
100
|
+
|
|
101
|
+
### Exit codes
|
|
102
|
+
|
|
103
|
+
| Code | Meaning |
|
|
104
|
+
| ---- | ------------------------------------------------------------------ |
|
|
105
|
+
| `0` | Clean (or only warnings, without `--strict`). |
|
|
106
|
+
| `1` | One or more findings at failure severity. |
|
|
107
|
+
| `2` | Tool or usage error (e.g. not an Expo project, unreadable config). |
|
|
108
|
+
|
|
109
|
+
## Checks
|
|
110
|
+
|
|
111
|
+
Severity is the default; `--strict` promotes every warning to a failure.
|
|
112
|
+
|
|
113
|
+
| Code | Severity | Meaning |
|
|
114
|
+
| ------- | -------- | ------------------------------------------------------------------- |
|
|
115
|
+
| `DL001` | error | A path advertised by the native config matches no route. |
|
|
116
|
+
| `DL002` | warn | A deep-linkable route is reachable by no configured link. |
|
|
117
|
+
| `DL003` | error | A custom-scheme link is configured but `scheme` is missing. |
|
|
118
|
+
| `DL101` | error | An `associatedDomains` entry is missing its `applinks:` prefix. |
|
|
119
|
+
| `DL102` | error | `ios.bundleIdentifier` missing while iOS deep links are configured. |
|
|
120
|
+
| `DL103` | error | `android.package` missing while Android deep links are configured. |
|
|
121
|
+
| `DL104` | warn | `intentFilter` has `autoVerify` but no `data.host` to verify. |
|
|
122
|
+
| `DL201` | error | A hosted file is unreachable, not JSON, or served via a redirect. |
|
|
123
|
+
| `DL202` | error | The AASA names no appID matching `ios.bundleIdentifier`. |
|
|
124
|
+
| `DL203` | error | `assetlinks.json` has no entry for `android.package`. |
|
|
125
|
+
| `DL204` | warn | The matched `assetlinks` entry has empty cert fingerprints. |
|
|
126
|
+
| `DL901` | warn | A suppression rule is missing a `reason`/`owner`. |
|
|
127
|
+
| `DL902` | warn | A suppression rule targets an unknown code (likely a typo). |
|
|
128
|
+
|
|
129
|
+
Run `check --explain` to print the full why-and-fix for each finding you have.
|
|
130
|
+
|
|
131
|
+
## Suppressions
|
|
132
|
+
|
|
133
|
+
`DL002` in particular is false-positive-prone by design (admin-only or
|
|
134
|
+
intentionally unlinked screens). Suppress findings in a `deeplink.config.json` at
|
|
135
|
+
your project root — but suppressions are **governed**, not silent: each one needs
|
|
136
|
+
a `reason` and an `owner`, so an ignore is always accountable.
|
|
137
|
+
|
|
138
|
+
```json
|
|
139
|
+
{
|
|
140
|
+
"ignore": [
|
|
141
|
+
{
|
|
142
|
+
"code": "DL002",
|
|
143
|
+
"route": "/internal/debug",
|
|
144
|
+
"reason": "admin-only screen, never linked",
|
|
145
|
+
"owner": "your-handle",
|
|
146
|
+
"revisitWhen": "expo-router@>=6"
|
|
147
|
+
}
|
|
148
|
+
]
|
|
149
|
+
}
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
A rule missing `reason`/`owner` raises `DL901`; a rule targeting a code the tool
|
|
153
|
+
doesn't emit raises `DL902`. Suppressed findings stay out of the exit code but are
|
|
154
|
+
reported as a count (and listed in full under `--json`).
|
|
155
|
+
|
|
156
|
+
## CI
|
|
157
|
+
|
|
158
|
+
`--json` emits a stable schema for pipelines:
|
|
159
|
+
|
|
160
|
+
```json
|
|
161
|
+
{
|
|
162
|
+
"summary": { "total": 2, "errors": 2, "warnings": 0, "suppressed": 1 },
|
|
163
|
+
"findings": [{ "code": "DL202", "severity": "error", "message": "...", "target": "..." }],
|
|
164
|
+
"suppressed": [{ "code": "DL002", "severity": "warn", "message": "...", "route": "..." }]
|
|
165
|
+
}
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
Gate a pull request with the static checks on every push, and the full
|
|
169
|
+
`--remote` run before a release:
|
|
170
|
+
|
|
171
|
+
```sh
|
|
172
|
+
npx deeplink-doctor --strict # fast, hermetic, every commit
|
|
173
|
+
npx deeplink-doctor --remote --strict # full, networked, pre-release
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
## Scope
|
|
177
|
+
|
|
178
|
+
v1 is a focused linter for **expo-router** projects. Out of scope (by design):
|
|
179
|
+
|
|
180
|
+
- **SHA-256 fingerprint verification** against real signing certs — that needs
|
|
181
|
+
EAS/Play credentials. `DL204` flags an empty fingerprint list; it does not
|
|
182
|
+
verify the values.
|
|
183
|
+
- **React Navigation `linking.config`** — expo-router is the v1 target.
|
|
184
|
+
- **Runtime/device testing** — `adb` and Apple's validator already own that.
|
|
185
|
+
|
|
186
|
+
## License
|
|
187
|
+
|
|
188
|
+
MIT © Konstantinos Mavrikas
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"calculateExitCode.js","sourceRoot":"","sources":["../../src/checks/calculateExitCode.ts"],"names":[],"mappings":"AAEA,MAAM,CAAC,MAAM,iBAAiB,GAAG,CAAC,QAAmB,EAAE,OAA4B,EAAS,EAAE,CAC5F,QAAQ,CAAC,IAAI,CAAC,CAAC,EAAE,QAAQ,EAAE,EAAE,EAAE,CAAC,QAAQ,KAAK,OAAO,IAAI,CAAC,QAAQ,KAAK,MAAM,IAAI,OAAO,CAAC,MAAM,CAAC,CAAC;IAC9F,CAAC,CAAC,CAAC;IACH,CAAC,CAAC,CAAC,CAAC"}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { generateFinding } from '../lib/generateFinding.js';
|
|
2
|
+
export const checkAasaAppIds = (aasa, config) => {
|
|
3
|
+
const bundleIdentifierConfig = config.ios.bundleIdentifier;
|
|
4
|
+
if (!bundleIdentifierConfig) {
|
|
5
|
+
return [];
|
|
6
|
+
}
|
|
7
|
+
const formattedAppIdsAasa = aasa.appIDs.map((appID) => appID.slice(appID.indexOf('.') + 1));
|
|
8
|
+
if (formattedAppIdsAasa.includes(bundleIdentifierConfig)) {
|
|
9
|
+
return [];
|
|
10
|
+
}
|
|
11
|
+
return [
|
|
12
|
+
generateFinding('DL202', `AASA appID does not match ios.bundleIdentifier "${bundleIdentifierConfig}"`),
|
|
13
|
+
];
|
|
14
|
+
};
|
|
15
|
+
//# sourceMappingURL=checkAasaAppIds.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"checkAasaAppIds.js","sourceRoot":"","sources":["../../src/checks/checkAasaAppIds.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,MAAM,2BAA2B,CAAC;AAG5D,MAAM,CAAC,MAAM,eAAe,GAAG,CAAC,IAAe,EAAE,MAAkB,EAAa,EAAE;IAChF,MAAM,sBAAsB,GAAG,MAAM,CAAC,GAAG,CAAC,gBAAgB,CAAC;IAC3D,IAAI,CAAC,sBAAsB,EAAE,CAAC;QAC5B,OAAO,EAAE,CAAC;IACZ,CAAC;IAED,MAAM,mBAAmB,GAAG,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,KAAK,CAAC,KAAK,CAAC,OAAO,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC;IAE5F,IAAI,mBAAmB,CAAC,QAAQ,CAAC,sBAAsB,CAAC,EAAE,CAAC;QACzD,OAAO,EAAE,CAAC;IACZ,CAAC;IACD,OAAO;QACL,eAAe,CACb,OAAO,EACP,mDAAmD,sBAAsB,GAAG,CAC7E;KACF,CAAC;AACJ,CAAC,CAAC"}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { generateFinding } from '../lib/generateFinding.js';
|
|
2
|
+
export const checkAppIdentifiers = (config) => {
|
|
3
|
+
const missingIosBundleIdentifier = config.ios.associatedDomains.length > 0 && !config.ios.bundleIdentifier
|
|
4
|
+
? [
|
|
5
|
+
generateFinding('DL102', 'ios.bundleIdentifier is required when associatedDomains are configured'),
|
|
6
|
+
]
|
|
7
|
+
: [];
|
|
8
|
+
const missingAndroidIntentFilters = config.android.intentFilters.length > 0 && !config.android.package
|
|
9
|
+
? [generateFinding('DL103', 'android.package is required when intentFilters are configured')]
|
|
10
|
+
: [];
|
|
11
|
+
return [...missingIosBundleIdentifier, ...missingAndroidIntentFilters];
|
|
12
|
+
};
|
|
13
|
+
//# sourceMappingURL=checkAppIdentifiers.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"checkAppIdentifiers.js","sourceRoot":"","sources":["../../src/checks/checkAppIdentifiers.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,MAAM,2BAA2B,CAAC;AAG5D,MAAM,CAAC,MAAM,mBAAmB,GAAG,CAAC,MAAkB,EAAa,EAAE;IACnE,MAAM,0BAA0B,GAC9B,MAAM,CAAC,GAAG,CAAC,iBAAiB,CAAC,MAAM,GAAG,CAAC,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,gBAAgB;QACrE,CAAC,CAAC;YACE,eAAe,CACb,OAAO,EACP,wEAAwE,CACzE;SACF;QACH,CAAC,CAAC,EAAE,CAAC;IAET,MAAM,2BAA2B,GAC/B,MAAM,CAAC,OAAO,CAAC,aAAa,CAAC,MAAM,GAAG,CAAC,IAAI,CAAC,MAAM,CAAC,OAAO,CAAC,OAAO;QAChE,CAAC,CAAC,CAAC,eAAe,CAAC,OAAO,EAAE,+DAA+D,CAAC,CAAC;QAC7F,CAAC,CAAC,EAAE,CAAC;IAET,OAAO,CAAC,GAAG,0BAA0B,EAAE,GAAG,2BAA2B,CAAC,CAAC;AACzE,CAAC,CAAC"}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { generateFinding } from '../lib/generateFinding.js';
|
|
2
|
+
export const checkAssetlinks = (assetlinks, config) => {
|
|
3
|
+
const pkg = config.android.package;
|
|
4
|
+
if (!pkg) {
|
|
5
|
+
return [];
|
|
6
|
+
}
|
|
7
|
+
if (assetlinks.every(({ packageName }) => packageName !== pkg)) {
|
|
8
|
+
return [generateFinding('DL203', `assetlinks.json has no entry for android.package "${pkg}"`)];
|
|
9
|
+
}
|
|
10
|
+
return assetlinks.flatMap(({ packageName, fingerprints }) => {
|
|
11
|
+
if (pkg === packageName) {
|
|
12
|
+
return fingerprints.length > 0
|
|
13
|
+
? []
|
|
14
|
+
: [
|
|
15
|
+
generateFinding('DL204', `assetlinks entry for "${pkg}" has an empty sha256_cert_fingerprints array`),
|
|
16
|
+
];
|
|
17
|
+
}
|
|
18
|
+
return [];
|
|
19
|
+
});
|
|
20
|
+
};
|
|
21
|
+
//# sourceMappingURL=checkAssetlinks.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"checkAssetlinks.js","sourceRoot":"","sources":["../../src/checks/checkAssetlinks.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,MAAM,2BAA2B,CAAC;AAG5D,MAAM,CAAC,MAAM,eAAe,GAAG,CAAC,UAA4B,EAAE,MAAkB,EAAa,EAAE;IAC7F,MAAM,GAAG,GAAG,MAAM,CAAC,OAAO,CAAC,OAAO,CAAC;IACnC,IAAI,CAAC,GAAG,EAAE,CAAC;QACT,OAAO,EAAE,CAAC;IACZ,CAAC;IAED,IAAI,UAAU,CAAC,KAAK,CAAC,CAAC,EAAE,WAAW,EAAE,EAAE,EAAE,CAAC,WAAW,KAAK,GAAG,CAAC,EAAE,CAAC;QAC/D,OAAO,CAAC,eAAe,CAAC,OAAO,EAAE,qDAAqD,GAAG,GAAG,CAAC,CAAC,CAAC;IACjG,CAAC;IAED,OAAO,UAAU,CAAC,OAAO,CAAC,CAAC,EAAE,WAAW,EAAE,YAAY,EAAE,EAAE,EAAE;QAC1D,IAAI,GAAG,KAAK,WAAW,EAAE,CAAC;YACxB,OAAO,YAAY,CAAC,MAAM,GAAG,CAAC;gBAC5B,CAAC,CAAC,EAAE;gBACJ,CAAC,CAAC;oBACE,eAAe,CACb,OAAO,EACP,yBAAyB,GAAG,+CAA+C,CAC5E;iBACF,CAAC;QACR,CAAC;QACD,OAAO,EAAE,CAAC;IACZ,CAAC,CAAC,CAAC;AACL,CAAC,CAAC"}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { generateFinding } from '../lib/generateFinding.js';
|
|
2
|
+
export const RECOGNIZED_DOMAIN_PREFIXES = ['applinks:', 'webcredentials:', 'activitycontinuation:'];
|
|
3
|
+
export const checkAssociatedDomains = (config) => {
|
|
4
|
+
const invalidAssociatedDomains = config.ios.associatedDomains.filter((domain) => RECOGNIZED_DOMAIN_PREFIXES.every((prefix) => !domain.startsWith(prefix)));
|
|
5
|
+
return invalidAssociatedDomains.map((domain) => generateFinding('DL101', `associatedDomains entry "${domain}" is missing a scheme prefix (e.g. "${RECOGNIZED_DOMAIN_PREFIXES.join('", "')}")`));
|
|
6
|
+
};
|
|
7
|
+
//# sourceMappingURL=checkAssociatedDomains.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"checkAssociatedDomains.js","sourceRoot":"","sources":["../../src/checks/checkAssociatedDomains.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,MAAM,2BAA2B,CAAC;AAG5D,MAAM,CAAC,MAAM,0BAA0B,GAAG,CAAC,WAAW,EAAE,iBAAiB,EAAE,uBAAuB,CAAC,CAAC;AAEpG,MAAM,CAAC,MAAM,sBAAsB,GAAG,CAAC,MAAkB,EAAa,EAAE;IACtE,MAAM,wBAAwB,GAAa,MAAM,CAAC,GAAG,CAAC,iBAAiB,CAAC,MAAM,CAAC,CAAC,MAAM,EAAE,EAAE,CACxF,0BAA0B,CAAC,KAAK,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC,MAAM,CAAC,UAAU,CAAC,MAAM,CAAC,CAAC,CACzE,CAAC;IAEF,OAAO,wBAAwB,CAAC,GAAG,CAAC,CAAC,MAAM,EAAE,EAAE,CAC7C,eAAe,CACb,OAAO,EACP,4BAA4B,MAAM,uCAAuC,0BAA0B,CAAC,IAAI,CAAC,MAAM,CAAC,IAAI,CACrH,CACF,CAAC;AACJ,CAAC,CAAC"}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { generateFinding } from '../lib/generateFinding.js';
|
|
2
|
+
export const checkAutoVerifyHosts = (config) => {
|
|
3
|
+
const missingHostIntentFilters = config.android.intentFilters.filter((intentFilter) => intentFilter.autoVerify && !intentFilter.data.find((item) => item.host !== undefined));
|
|
4
|
+
return missingHostIntentFilters.map(() => generateFinding('DL104', 'intentFilter has autoVerify enabled but no data.host to verify'));
|
|
5
|
+
};
|
|
6
|
+
//# sourceMappingURL=checkAutoVerifyHosts.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"checkAutoVerifyHosts.js","sourceRoot":"","sources":["../../src/checks/checkAutoVerifyHosts.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,MAAM,2BAA2B,CAAC;AAG5D,MAAM,CAAC,MAAM,oBAAoB,GAAG,CAAC,MAAkB,EAAa,EAAE;IACpE,MAAM,wBAAwB,GAAG,MAAM,CAAC,OAAO,CAAC,aAAa,CAAC,MAAM,CAClE,CAAC,YAAY,EAAE,EAAE,CACf,YAAY,CAAC,UAAU,IAAI,CAAC,YAAY,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,IAAI,KAAK,SAAS,CAAC,CACxF,CAAC;IAEF,OAAO,wBAAwB,CAAC,GAAG,CAAC,GAAG,EAAE,CACvC,eAAe,CAAC,OAAO,EAAE,gEAAgE,CAAC,CAC3F,CAAC;AACJ,CAAC,CAAC"}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { checkAppIdentifiers } from './checkAppIdentifiers.js';
|
|
2
|
+
import { checkAssociatedDomains } from './checkAssociatedDomains.js';
|
|
3
|
+
import { checkAutoVerifyHosts } from './checkAutoVerifyHosts.js';
|
|
4
|
+
import { checkScheme } from './checkScheme.js';
|
|
5
|
+
export const checkConfig = (config) => [
|
|
6
|
+
...checkScheme(config),
|
|
7
|
+
...checkAssociatedDomains(config),
|
|
8
|
+
...checkAppIdentifiers(config),
|
|
9
|
+
...checkAutoVerifyHosts(config),
|
|
10
|
+
];
|
|
11
|
+
//# sourceMappingURL=checkConfig.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"checkConfig.js","sourceRoot":"","sources":["../../src/checks/checkConfig.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,mBAAmB,EAAE,MAAM,0BAA0B,CAAC;AAC/D,OAAO,EAAE,sBAAsB,EAAE,MAAM,6BAA6B,CAAC;AACrE,OAAO,EAAE,oBAAoB,EAAE,MAAM,2BAA2B,CAAC;AACjE,OAAO,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAE/C,MAAM,CAAC,MAAM,WAAW,GAAG,CAAC,MAAkB,EAAa,EAAE,CAAC;IAC5D,GAAG,WAAW,CAAC,MAAM,CAAC;IACtB,GAAG,sBAAsB,CAAC,MAAM,CAAC;IACjC,GAAG,mBAAmB,CAAC,MAAM,CAAC;IAC9B,GAAG,oBAAoB,CAAC,MAAM,CAAC;CAChC,CAAC"}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { generateFinding } from '../lib/generateFinding.js';
|
|
2
|
+
import { targetMatchesRoute } from './targetMatchesRoute.js';
|
|
3
|
+
export const checkDeadLinks = (routes, targets) => {
|
|
4
|
+
const deadTargets = targets.filter((target) => !routes.some((route) => targetMatchesRoute(route, target)));
|
|
5
|
+
return deadTargets.map((target) => generateFinding('DL001', `No route handles deep link target "${target.raw}"`, {
|
|
6
|
+
target: target.raw,
|
|
7
|
+
}));
|
|
8
|
+
};
|
|
9
|
+
//# sourceMappingURL=checkDeadLinks.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"checkDeadLinks.js","sourceRoot":"","sources":["../../src/checks/checkDeadLinks.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,MAAM,2BAA2B,CAAC;AAE5D,OAAO,EAAE,kBAAkB,EAAE,MAAM,yBAAyB,CAAC;AAE7D,MAAM,CAAC,MAAM,cAAc,GAAG,CAAC,MAAkB,EAAE,OAAqB,EAAa,EAAE;IACrF,MAAM,WAAW,GAAG,OAAO,CAAC,MAAM,CAChC,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,kBAAkB,CAAC,KAAK,EAAE,MAAM,CAAC,CAAC,CACvE,CAAC;IAEF,OAAO,WAAW,CAAC,GAAG,CAAC,CAAC,MAAM,EAAE,EAAE,CAChC,eAAe,CAAC,OAAO,EAAE,sCAAsC,MAAM,CAAC,GAAG,GAAG,EAAE;QAC5E,MAAM,EAAE,MAAM,CAAC,GAAG;KACnB,CAAC,CACH,CAAC;AACJ,CAAC,CAAC"}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import { checkAasaAppIds } from './checkAasaAppIds.js';
|
|
2
|
+
import { checkAssetlinks } from './checkAssetlinks.js';
|
|
3
|
+
export const checkRemote = (aasa, assetlinks, config) => [...checkAasaAppIds(aasa, config), ...checkAssetlinks(assetlinks, config)];
|
|
4
|
+
//# sourceMappingURL=checkRemote.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"checkRemote.js","sourceRoot":"","sources":["../../src/checks/checkRemote.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,eAAe,EAAE,MAAM,sBAAsB,CAAC;AACvD,OAAO,EAAE,eAAe,EAAE,MAAM,sBAAsB,CAAC;AAEvD,MAAM,CAAC,MAAM,WAAW,GAAG,CACzB,IAAe,EACf,UAA4B,EAC5B,MAAkB,EACP,EAAE,CAAC,CAAC,GAAG,eAAe,CAAC,IAAI,EAAE,MAAM,CAAC,EAAE,GAAG,eAAe,CAAC,UAAU,EAAE,MAAM,CAAC,CAAC,CAAC"}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { generateFinding } from '../lib/generateFinding.js';
|
|
2
|
+
export const WEB_SCHEMES = ['http', 'https'];
|
|
3
|
+
export const checkScheme = (config) => {
|
|
4
|
+
const hasNoSchemes = config.schemes.length === 0;
|
|
5
|
+
const hasCustomScheme = config.android.intentFilters.some(({ data }) => data.some((d) => d.scheme !== undefined && !WEB_SCHEMES.includes(d.scheme.toLowerCase())));
|
|
6
|
+
if (hasNoSchemes && hasCustomScheme) {
|
|
7
|
+
return [
|
|
8
|
+
generateFinding('DL003', 'A custom-scheme deep link is configured but no scheme is declared; set "scheme" in your Expo config'),
|
|
9
|
+
];
|
|
10
|
+
}
|
|
11
|
+
return [];
|
|
12
|
+
};
|
|
13
|
+
//# sourceMappingURL=checkScheme.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"checkScheme.js","sourceRoot":"","sources":["../../src/checks/checkScheme.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,MAAM,2BAA2B,CAAC;AAG5D,MAAM,CAAC,MAAM,WAAW,GAAG,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;AAE7C,MAAM,CAAC,MAAM,WAAW,GAAG,CAAC,MAAkB,EAAa,EAAE;IAC3D,MAAM,YAAY,GAAG,MAAM,CAAC,OAAO,CAAC,MAAM,KAAK,CAAC,CAAC;IACjD,MAAM,eAAe,GAAG,MAAM,CAAC,OAAO,CAAC,aAAa,CAAC,IAAI,CAAC,CAAC,EAAE,IAAI,EAAE,EAAE,EAAE,CACrE,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,MAAM,KAAK,SAAS,IAAI,CAAC,WAAW,CAAC,QAAQ,CAAC,CAAC,CAAC,MAAM,CAAC,WAAW,EAAE,CAAC,CAAC,CAC1F,CAAC;IACF,IAAI,YAAY,IAAI,eAAe,EAAE,CAAC;QACpC,OAAO;YACL,eAAe,CACb,OAAO,EACP,qGAAqG,CACtG;SACF,CAAC;IACJ,CAAC;IACD,OAAO,EAAE,CAAC;AACZ,CAAC,CAAC"}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { generateFinding } from '../lib/generateFinding.js';
|
|
2
|
+
import { isLinkableRoute } from '../routes/parseRoutePath.js';
|
|
3
|
+
import { targetMatchesRoute } from './targetMatchesRoute.js';
|
|
4
|
+
export const checkUnreachableRoutes = (routes, targets) => {
|
|
5
|
+
const linkableRoutes = routes.filter((route) => isLinkableRoute(route));
|
|
6
|
+
const unreachableRoutes = linkableRoutes.filter((route) => targets.every((target) => !targetMatchesRoute(route, target)));
|
|
7
|
+
return unreachableRoutes.map((route) => generateFinding('DL002', `Route "${route.pathname}" is not reachable by any configured link`, {
|
|
8
|
+
route: route.pathname,
|
|
9
|
+
}));
|
|
10
|
+
};
|
|
11
|
+
//# sourceMappingURL=checkUnreachableRoutes.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"checkUnreachableRoutes.js","sourceRoot":"","sources":["../../src/checks/checkUnreachableRoutes.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,MAAM,2BAA2B,CAAC;AAC5D,OAAO,EAAE,eAAe,EAAE,MAAM,6BAA6B,CAAC;AAE9D,OAAO,EAAE,kBAAkB,EAAE,MAAM,yBAAyB,CAAC;AAE7D,MAAM,CAAC,MAAM,sBAAsB,GAAG,CAAC,MAAkB,EAAE,OAAqB,EAAa,EAAE;IAC7F,MAAM,cAAc,GAAG,MAAM,CAAC,MAAM,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,eAAe,CAAC,KAAK,CAAC,CAAC,CAAC;IAExE,MAAM,iBAAiB,GAAG,cAAc,CAAC,MAAM,CAAC,CAAC,KAAK,EAAE,EAAE,CACxD,OAAO,CAAC,KAAK,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC,kBAAkB,CAAC,KAAK,EAAE,MAAM,CAAC,CAAC,CAC9D,CAAC;IAEF,OAAO,iBAAiB,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CACrC,eAAe,CAAC,OAAO,EAAE,UAAU,KAAK,CAAC,QAAQ,2CAA2C,EAAE;QAC5F,KAAK,EAAE,KAAK,CAAC,QAAQ;KACtB,CAAC,CACH,CAAC;AACJ,CAAC,CAAC"}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { checkDeadLinks } from './checkDeadLinks.js';
|
|
2
|
+
import { checkUnreachableRoutes } from './checkUnreachableRoutes.js';
|
|
3
|
+
export const runChecks = (routes, targets) => {
|
|
4
|
+
const deadLinks = checkDeadLinks(routes, targets);
|
|
5
|
+
const unreachableRoutes = checkUnreachableRoutes(routes, targets);
|
|
6
|
+
return [...deadLinks, ...unreachableRoutes];
|
|
7
|
+
};
|
|
8
|
+
//# sourceMappingURL=runChecks.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"runChecks.js","sourceRoot":"","sources":["../../src/checks/runChecks.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AACrD,OAAO,EAAE,sBAAsB,EAAE,MAAM,6BAA6B,CAAC;AAErE,MAAM,CAAC,MAAM,SAAS,GAAG,CAAC,MAAkB,EAAE,OAAqB,EAAa,EAAE;IAChF,MAAM,SAAS,GAAG,cAAc,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAClD,MAAM,iBAAiB,GAAG,sBAAsB,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAElE,OAAO,CAAC,GAAG,SAAS,EAAE,GAAG,iBAAiB,CAAC,CAAC;AAC9C,CAAC,CAAC"}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { routeHandlesPath } from '../routes/routeHandlesPath.js';
|
|
2
|
+
import { routeHandlesPrefix } from '../routes/routeHandlesPrefix.js';
|
|
3
|
+
export const targetMatchesRoute = (route, target) => {
|
|
4
|
+
if (target.kind === 'exact') {
|
|
5
|
+
return routeHandlesPath(route, target.segments);
|
|
6
|
+
}
|
|
7
|
+
if (target.kind === 'prefix') {
|
|
8
|
+
return routeHandlesPrefix(route, target.segments);
|
|
9
|
+
}
|
|
10
|
+
const globPatternIndex = target.segments.findIndex((s) => s.includes('.') || s.includes('*'));
|
|
11
|
+
const truncatedSegments = globPatternIndex === -1 ? target.segments : target.segments.slice(0, globPatternIndex);
|
|
12
|
+
return routeHandlesPrefix(route, truncatedSegments);
|
|
13
|
+
};
|
|
14
|
+
//# sourceMappingURL=targetMatchesRoute.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"targetMatchesRoute.js","sourceRoot":"","sources":["../../src/checks/targetMatchesRoute.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,gBAAgB,EAAE,MAAM,+BAA+B,CAAC;AACjE,OAAO,EAAE,kBAAkB,EAAE,MAAM,iCAAiC,CAAC;AAGrE,MAAM,CAAC,MAAM,kBAAkB,GAAG,CAAC,KAAe,EAAE,MAAkB,EAAW,EAAE;IACjF,IAAI,MAAM,CAAC,IAAI,KAAK,OAAO,EAAE,CAAC;QAC5B,OAAO,gBAAgB,CAAC,KAAK,EAAE,MAAM,CAAC,QAAQ,CAAC,CAAC;IAClD,CAAC;IACD,IAAI,MAAM,CAAC,IAAI,KAAK,QAAQ,EAAE,CAAC;QAC7B,OAAO,kBAAkB,CAAC,KAAK,EAAE,MAAM,CAAC,QAAQ,CAAC,CAAC;IACpD,CAAC;IAED,MAAM,gBAAgB,GAAG,MAAM,CAAC,QAAQ,CAAC,SAAS,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,QAAQ,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC;IAE9F,MAAM,iBAAiB,GACrB,gBAAgB,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC,CAAC,MAAM,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC,EAAE,gBAAgB,CAAC,CAAC;IAEzF,OAAO,kBAAkB,CAAC,KAAK,EAAE,iBAAiB,CAAC,CAAC;AACtD,CAAC,CAAC"}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { Associations, LinkConfig, PathData, SuppressionRule } from '../types.js';
|
|
2
|
+
type CheckDeps = {
|
|
3
|
+
readRoutes: (root: string) => PathData[];
|
|
4
|
+
readLinkConfig: (root: string) => LinkConfig;
|
|
5
|
+
readSuppressionConfig: (root: string, configPath?: string) => SuppressionRule[];
|
|
6
|
+
loadAssociations: (domain: string) => Promise<Associations>;
|
|
7
|
+
};
|
|
8
|
+
type CheckOptions = {
|
|
9
|
+
strict?: boolean;
|
|
10
|
+
json?: boolean;
|
|
11
|
+
remote?: boolean;
|
|
12
|
+
domain?: string;
|
|
13
|
+
config?: string;
|
|
14
|
+
silent?: boolean;
|
|
15
|
+
explain?: boolean;
|
|
16
|
+
};
|
|
17
|
+
export declare const runCheck: (root: string, deps: CheckDeps, options: CheckOptions) => Promise<{
|
|
18
|
+
report: string;
|
|
19
|
+
exitCode: 0 | 1 | 2;
|
|
20
|
+
}>;
|
|
21
|
+
export {};
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { calculateExitCode } from '../checks/calculateExitCode.js';
|
|
2
|
+
import { checkConfig } from '../checks/checkConfig.js';
|
|
3
|
+
import { checkRemote } from '../checks/checkRemote.js';
|
|
4
|
+
import { runChecks } from '../checks/runChecks.js';
|
|
5
|
+
import { extractDomains } from '../links/extractDomains.js';
|
|
6
|
+
import { extractLinkTargets } from '../links/extractLinkTargets.js';
|
|
7
|
+
import * as human from '../report/human.js';
|
|
8
|
+
import * as json from '../report/json.js';
|
|
9
|
+
import { applySuppressions } from '../suppressions/applySuppressions.js';
|
|
10
|
+
const remoteFindings = async (config, options, loadAssociations) => {
|
|
11
|
+
if (!options.remote) {
|
|
12
|
+
return [];
|
|
13
|
+
}
|
|
14
|
+
const domains = options.domain ? [options.domain] : extractDomains(config);
|
|
15
|
+
const associations = await Promise.all(domains.map((domain) => loadAssociations(domain)));
|
|
16
|
+
return associations.flatMap((association) => [
|
|
17
|
+
...association.findings,
|
|
18
|
+
...checkRemote(association.aasa, association.assetlinks, config),
|
|
19
|
+
]);
|
|
20
|
+
};
|
|
21
|
+
export const runCheck = async (root, deps, options) => {
|
|
22
|
+
try {
|
|
23
|
+
const config = deps.readLinkConfig(root);
|
|
24
|
+
const targets = extractLinkTargets(config);
|
|
25
|
+
const findings = [
|
|
26
|
+
...runChecks(deps.readRoutes(root), targets),
|
|
27
|
+
...checkConfig(config),
|
|
28
|
+
...(await remoteFindings(config, options, deps.loadAssociations)),
|
|
29
|
+
];
|
|
30
|
+
const { active, suppressed } = applySuppressions(findings, deps.readSuppressionConfig(root, options.config));
|
|
31
|
+
const silent = options.silent === true && options.strict !== true;
|
|
32
|
+
const reported = silent ? active.filter((finding) => finding.severity === 'error') : active;
|
|
33
|
+
const report = options.json
|
|
34
|
+
? json.renderFindings(reported, suppressed)
|
|
35
|
+
: human.renderFindings(reported, suppressed, options.explain);
|
|
36
|
+
return { report, exitCode: calculateExitCode(reported, { strict: options.strict ?? false }) };
|
|
37
|
+
}
|
|
38
|
+
catch (error) {
|
|
39
|
+
return { report: error instanceof Error ? error.message : String(error), exitCode: 2 };
|
|
40
|
+
}
|
|
41
|
+
};
|
|
42
|
+
//# sourceMappingURL=check.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"check.js","sourceRoot":"","sources":["../../src/commands/check.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
// Long-form help per finding code, surfaced by `check --explain`. Record<FindingCode, _>
|
|
2
|
+
// is exhaustive — a new code can't ship without an explanation. Prose drafted to
|
|
3
|
+
// say what the code means, why it matters, and how to fix it.
|
|
4
|
+
export const FINDING_EXPLANATIONS = {
|
|
5
|
+
DL001: 'A path advertised by your native config (an Android intentFilter path/pathPrefix/pathPattern, or a custom-scheme link) matches no route in your app/ tree. The link opens the app but lands nowhere. Fix: add a route that handles the path, or correct/remove the intentFilter entry.',
|
|
6
|
+
DL002: 'A deep-linkable route exists but no configured link pattern can reach it. This is often intentional (admin or internal-only screens) and is the most false-positive-prone check by design — suppress those in deeplink.config.json. Otherwise add an intentFilter path or associatedDomain so the screen is reachable by a link.',
|
|
7
|
+
DL003: 'A custom-scheme deep link is configured but no `scheme` is declared in your Expo config. Without a scheme, custom-scheme links cannot resolve. Fix: set `scheme` in app.json/app.config.',
|
|
8
|
+
DL101: 'An `ios.associatedDomains` entry is missing its service prefix (e.g. `applinks:`). iOS silently ignores malformed entries, so the domain is never associated. Fix: prefix the entry, e.g. `applinks:example.com`.',
|
|
9
|
+
DL102: 'iOS deep links are configured (associatedDomains present) but `ios.bundleIdentifier` is missing. Universal Links cannot be associated without it. Fix: set `ios.bundleIdentifier`.',
|
|
10
|
+
DL103: 'Android intent filters are configured but `android.package` is missing. App Links cannot be verified without the package name. Fix: set `android.package`.',
|
|
11
|
+
DL104: 'An intentFilter has `autoVerify: true` but no `data.host` entries, so Android has nothing to verify. Fix: add a `data.host`, or drop `autoVerify` if the filter is scheme-only.',
|
|
12
|
+
DL201: 'A hosted association file (AASA or assetlinks.json) is unreachable, not served as JSON, or served via a redirect. Apple and Google reject all three. Fix: serve the file at the exact /.well-known path, as application/json, over HTTPS, with no redirect.',
|
|
13
|
+
DL202: 'The hosted AASA lists no appID matching your `ios.bundleIdentifier` (an appID is `<teamId>.<bundleIdentifier>`). iOS will not associate the domain with your app. Fix: add your team + bundle to applinks.details[].appID(s) in the hosted AASA.',
|
|
14
|
+
DL203: 'The hosted assetlinks.json has no statement for your `android.package`. Android will not verify your App Links. Fix: add a statement whose target.package_name is your package.',
|
|
15
|
+
DL204: 'The matched assetlinks statement has an empty sha256_cert_fingerprints array, so Android has no signing certificate to verify against. Fix: add your app’s release signing SHA-256 fingerprint(s).',
|
|
16
|
+
DL901: 'A suppression rule is missing a `reason` and/or `owner`. Suppressions are governed — every ignore carries accountability so it can be revisited. Fix: add `reason` and `owner` to the rule in deeplink.config.json.',
|
|
17
|
+
DL902: 'A suppression rule targets a code this tool never emits — almost always a typo (e.g. DL2O2 instead of DL202). Fix: correct the code, or remove the stale rule.',
|
|
18
|
+
};
|
|
19
|
+
//# sourceMappingURL=findingExplanations.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"findingExplanations.js","sourceRoot":"","sources":["../../src/config/findingExplanations.ts"],"names":[],"mappings":"AAEA,yFAAyF;AACzF,iFAAiF;AACjF,8DAA8D;AAC9D,MAAM,CAAC,MAAM,oBAAoB,GAAgC;IAC/D,KAAK,EACH,wRAAwR;IAC1R,KAAK,EACH,kUAAkU;IACpU,KAAK,EACH,0LAA0L;IAC5L,KAAK,EACH,mNAAmN;IACrN,KAAK,EACH,oLAAoL;IACtL,KAAK,EACH,4JAA4J;IAC9J,KAAK,EACH,iLAAiL;IACnL,KAAK,EACH,6PAA6P;IAC/P,KAAK,EACH,kPAAkP;IACpP,KAAK,EACH,iLAAiL;IACnL,KAAK,EACH,oMAAoM;IACtM,KAAK,EACH,qNAAqN;IACvN,KAAK,EACH,gKAAgK;CACnK,CAAC"}
|