deeplink-parity 0.4.0 → 0.5.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.
Files changed (3) hide show
  1. package/README.md +98 -6
  2. package/dist/cli.js +35 -20
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -9,8 +9,7 @@
9
9
  Checks that what your app **declares** about deep links matches what is actually **hosted** — on both iOS and Android, from one command.
10
10
 
11
11
  ```bash
12
- # iOS and Android usually live in separate repositories — pass both
13
- npx deeplink-parity ./my-app-ios ./my-app-android
12
+ npx deeplink-parity .
14
13
  ```
15
14
 
16
15
  ```
@@ -30,6 +29,53 @@ WARN promo.example.com
30
29
 
31
30
  <br>
32
31
 
32
+ ## Quick start
33
+
34
+ Nothing to configure. Domains are read from your app and the matching files are fetched
35
+ from them.
36
+
37
+ **One repository** — a monorepo, Flutter, or React Native:
38
+
39
+ ```bash
40
+ npx deeplink-parity .
41
+ ```
42
+
43
+ **Two repositories** — native iOS and Android, which is the case worth checking:
44
+
45
+ ```bash
46
+ npx deeplink-parity ./my-app-ios ./my-app-android
47
+ ```
48
+
49
+ **On GitHub Actions**, one line:
50
+
51
+ ```yaml
52
+ - uses: actions/checkout@v4
53
+ - uses: camosss/deeplink-parity@v1
54
+ ```
55
+
56
+ Findings show up as annotations on the run. Add `sha256` to include the Android signing
57
+ key in the check, and `fail-on: never` to report without failing while an existing backlog
58
+ is cleared.
59
+
60
+ <br>
61
+
62
+ ## Contents
63
+
64
+ - [Why](#why)
65
+ - [What it checks](#what-it-checks)
66
+ - [Usage](#usage)
67
+ - [GitHub Action](#github-action)
68
+ - [In CI](#in-ci)
69
+ - [Run it on a schedule](#run-it-on-a-schedule)
70
+ - [Validate before deploying](#validate-before-deploying)
71
+ - [Native, Flutter and React Native](#native-flutter-and-react-native)
72
+ - [Hardened against real projects](#hardened-against-real-projects)
73
+ - [What it does not do](#what-it-does-not-do)
74
+ - [Development](#development)
75
+ - [License](#license)
76
+
77
+ <br>
78
+
33
79
  ## Why
34
80
 
35
81
  Deep links fail **silently**. There is no crash, no error log, no red test. A user taps a link, the browser opens instead of your app, and they shrug and move on. Nobody finds out for months.
@@ -81,7 +127,10 @@ npx deeplink-parity [path...] [options]
81
127
 
82
128
  --sha256 <fingerprint> Android signing fingerprint to look for in assetlinks.json
83
129
  --well-known <dir> Read well-known files from <dir>/<domain>/ instead of the network
84
- --json Machine-readable output
130
+ --json Machine-readable output on stdout
131
+ --output <file> Also write the JSON result to a file
132
+ --format github GitHub Actions annotations (auto-detected on Actions)
133
+ -h, --help Show this message
85
134
  ```
86
135
 
87
136
  Pass one path per checkout. A monorepo holding both platforms works as a single path,
@@ -97,6 +146,35 @@ npx deeplink-parity . --sha256 "AB:CD:…" # include the fingerpri
97
146
  The Android signing fingerprint comes from Play Console → App integrity → App signing.
98
147
  Without it the fingerprint check is skipped and reported as such.
99
148
 
149
+ ### GitHub Action
150
+
151
+ ```yaml
152
+ - uses: camosss/deeplink-parity@v1
153
+ with:
154
+ paths: ios android # one per checkout; omit for a single repo
155
+ sha256: ${{ secrets.ANDROID_SHA256 }}
156
+ ```
157
+
158
+ Findings appear as annotations on the run, and counts are available to later steps:
159
+
160
+ ```yaml
161
+ - uses: camosss/deeplink-parity@v1
162
+ id: links
163
+ with:
164
+ fail-on: never # report without blocking while a backlog is cleared
165
+ - run: echo "${{ steps.links.outputs.errors }} errors, ${{ steps.links.outputs.warnings }} warnings"
166
+ ```
167
+
168
+ | Input | Default | |
169
+ |---|---|---|
170
+ | `paths` | `.` | Checkout paths, whitespace separated |
171
+ | `sha256` | — | Android signing fingerprint |
172
+ | `well-known` | — | Read from disk instead of the network |
173
+ | `fail-on` | `error` | `never` to report without failing the step |
174
+ | `version` | pinned | npm version to run |
175
+
176
+ Outputs: `errors`, `warnings`, `notices`, `domains`, `report` (path to the JSON result).
177
+
100
178
  ### In CI
101
179
 
102
180
  | Exit code | Meaning |
@@ -110,7 +188,7 @@ writes nothing but JSON to stdout; progress notes go to stderr. On GitHub Action
110
188
  findings are also emitted as annotations, which appear on the run summary and against the
111
189
  file when one is involved — set `--format github` to force it elsewhere.
112
190
 
113
- ### Run it on a schedule — this is the point
191
+ ### Run it on a schedule
114
192
 
115
193
  Your deep-link configuration changes a few times a year. The things that break it do not live in your repo at all:
116
194
 
@@ -123,12 +201,26 @@ A pull-request check never sees any of this. **A daily scheduled run does.** See
123
201
 
124
202
  ### Validate before deploying
125
203
 
126
- `--well-known <dir>` reads the files from disk instead of the network, so the web team can check staged files before they ship — and CI can run with no egress.
204
+ `--well-known <dir>` reads the files from disk instead of the network, so the web team can
205
+ check staged files before they ship — and CI can run with no egress at all.
206
+
207
+ ```
208
+ well-known/
209
+ └── links.example.com/
210
+ ├── apple-app-site-association
211
+ └── assetlinks.json
212
+ ```
127
213
 
128
214
  ```bash
129
- npx deeplink-parity . --well-known ./staging/well-known
215
+ npx deeplink-parity . --well-known ./well-known
130
216
  ```
131
217
 
218
+ This answers a different question, though: whether the app matches the files you intend to
219
+ publish, not whether it matches what is live right now. The failures this tool exists to
220
+ catch — a deploy putting a redirect in front of `/.well-known/`, an expired domain, a
221
+ rotated key — only show up against the real host. Files hosted by an attribution vendor
222
+ cannot be checked offline at all.
223
+
132
224
  <br>
133
225
 
134
226
  ## Native, Flutter and React Native
package/dist/cli.js CHANGED
@@ -1,4 +1,5 @@
1
1
  #!/usr/bin/env node
2
+ import { writeFile } from 'node:fs/promises';
2
3
  import { resolve } from 'node:path';
3
4
  import { localSource, networkSource } from './fetch/wellKnown.js';
4
5
  import { exitCodeFor, printReport } from './report/console.js';
@@ -18,7 +19,8 @@ Usage
18
19
  Options
19
20
  --sha256 <fingerprint> Android signing fingerprint to look for in assetlinks.json
20
21
  --well-known <dir> Read well-known files from <dir>/<domain>/ instead of the network
21
- --json Machine-readable output
22
+ --json Machine-readable output on stdout
23
+ --output <file> Also write the JSON result to a file
22
24
  --format github GitHub Actions annotations (auto-detected on Actions)
23
25
  -h, --help Show this message
24
26
  `;
@@ -29,6 +31,7 @@ function parseArgs(argv) {
29
31
  let help = false;
30
32
  let sha256;
31
33
  let wellKnown;
34
+ let output;
32
35
  // Actions sets GITHUB_ACTIONS=true; annotate by default there
33
36
  let format = process.env.GITHUB_ACTIONS === 'true' ? 'github' : 'console';
34
37
  for (let i = 0; i < args.length; i++) {
@@ -43,6 +46,8 @@ function parseArgs(argv) {
43
46
  wellKnown = args[++i];
44
47
  else if (arg === '--format')
45
48
  format = args[++i];
49
+ else if (arg === '--output')
50
+ output = args[++i];
46
51
  else if (!arg.startsWith('-'))
47
52
  roots.push(arg);
48
53
  }
@@ -53,10 +58,11 @@ function parseArgs(argv) {
53
58
  sha256,
54
59
  wellKnown,
55
60
  format,
61
+ output,
56
62
  };
57
63
  }
58
64
  async function main() {
59
- const { roots, json, help, sha256, wellKnown, format } = parseArgs(process.argv);
65
+ const { roots, json, help, sha256, wellKnown, format, output } = parseArgs(process.argv);
60
66
  if (help) {
61
67
  console.log(USAGE);
62
68
  return;
@@ -78,27 +84,36 @@ async function main() {
78
84
  console.error('Expected a .entitlements file with applinks:, or an AndroidManifest.xml with intent-filters.');
79
85
  process.exit(2);
80
86
  }
87
+ const payload = {
88
+ ios: result.iosApps.map((a) => ({
89
+ entitlements: a.entitlementsPath,
90
+ teamId: a.teamId,
91
+ bundleId: a.bundleId,
92
+ domains: a.domains,
93
+ })),
94
+ android: result.androidApps.map((a) => ({
95
+ manifest: a.manifestPath,
96
+ packageIds: a.packageIds,
97
+ hosts: a.hosts.map((h) => h.host),
98
+ })),
99
+ summary: {
100
+ domains: result.domains.length,
101
+ error: result.findings.filter((f) => f.severity === 'error').length,
102
+ warn: result.findings.filter((f) => f.severity === 'warn').length,
103
+ info: result.findings.filter((f) => f.severity === 'info').length,
104
+ },
105
+ findings: result.findings,
106
+ };
107
+ // A file keeps stdout free, so annotations and the readable report can coexist with
108
+ // machine-readable output in the same run.
109
+ if (output)
110
+ await writeFile(output, `${JSON.stringify(payload, null, 2)}\n`);
81
111
  if (json) {
82
- console.log(JSON.stringify({
83
- ios: result.iosApps.map((a) => ({
84
- entitlements: a.entitlementsPath,
85
- teamId: a.teamId,
86
- bundleId: a.bundleId,
87
- domains: a.domains,
88
- })),
89
- android: result.androidApps.map((a) => ({
90
- manifest: a.manifestPath,
91
- packageIds: a.packageIds,
92
- hosts: a.hosts.map((h) => h.host),
93
- })),
94
- findings: result.findings,
95
- }, null, 2));
96
- }
97
- else if (format === 'github') {
98
- printGithubAnnotations(result.findings);
99
- printReport(result.findings, result.domains);
112
+ console.log(JSON.stringify(payload, null, 2));
100
113
  }
101
114
  else {
115
+ if (format === 'github')
116
+ printGithubAnnotations(result.findings);
102
117
  printReport(result.findings, result.domains);
103
118
  }
104
119
  process.exit(exitCodeFor(result.findings));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "deeplink-parity",
3
- "version": "0.4.0",
3
+ "version": "0.5.1",
4
4
  "description": "Checks that what your mobile app declares about deep links matches what is actually hosted — across iOS and Android.",
5
5
  "type": "module",
6
6
  "bin": {