deeplink-parity 0.6.1 → 0.6.3

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/dist/cli.js CHANGED
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- import { stat, writeFile } from 'node:fs/promises';
2
+ import { readFile, stat, writeFile } from 'node:fs/promises';
3
3
  import { resolve } from 'node:path';
4
4
  import { localSource, networkSource } from './fetch/wellKnown.js';
5
5
  import { exitCodeFor, printReport } from './report/console.js';
@@ -28,6 +28,7 @@ Options
28
28
  --output <file> Also write the JSON result to a file
29
29
  --format github GitHub Actions annotations (auto-detected on Actions)
30
30
  --yes init only: accept the top suggestion without prompting
31
+ -v, --version Print the version
31
32
  -h, --help Show this message
32
33
 
33
34
  The check reads the repositories and GETs two public well-known files per domain —
@@ -39,6 +40,7 @@ function parseArgs(argv) {
39
40
  const positional = [];
40
41
  let json = false;
41
42
  let help = false;
43
+ let version = false;
42
44
  let printRoutes = false;
43
45
  let yes = false;
44
46
  let sha256;
@@ -53,6 +55,8 @@ function parseArgs(argv) {
53
55
  json = true;
54
56
  else if (arg === '-h' || arg === '--help')
55
57
  help = true;
58
+ else if (arg === '-v' || arg === '--version')
59
+ version = true;
56
60
  else if (arg === '--print-routes')
57
61
  printRoutes = true;
58
62
  else if (arg === '--yes')
@@ -77,6 +81,7 @@ function parseArgs(argv) {
77
81
  roots: roots.length ? roots : [resolve('.')],
78
82
  json,
79
83
  help,
84
+ version,
80
85
  printRoutes,
81
86
  yes,
82
87
  sha256,
@@ -110,6 +115,11 @@ async function assertRootsExist(roots) {
110
115
  }
111
116
  async function main() {
112
117
  const opts = parseArgs(process.argv);
118
+ const pkg = JSON.parse(await readFile(new URL('../package.json', import.meta.url), 'utf8'));
119
+ if (opts.version) {
120
+ console.log(pkg.version);
121
+ return;
122
+ }
113
123
  if (opts.help) {
114
124
  console.log(USAGE);
115
125
  return;
@@ -150,6 +160,8 @@ async function main() {
150
160
  console.log();
151
161
  }
152
162
  const payload = {
163
+ version: pkg.version,
164
+ routeConfig: configPath,
153
165
  ios: result.iosApps.map((a) => ({
154
166
  entitlements: a.entitlementsPath,
155
167
  teamId: a.teamId,
@@ -181,13 +193,19 @@ async function main() {
181
193
  // machine-readable output in the same run.
182
194
  if (opts.output)
183
195
  await writeFile(opts.output, `${JSON.stringify(payload, null, 2)}\n`);
196
+ const routesLine = configPath
197
+ ? `routes: ${configPath}` +
198
+ (result.routes
199
+ ? ` (iOS ${result.routes.ios?.paths.length ?? 0} · Android ${result.routes.android?.paths.length ?? 0})`
200
+ : '')
201
+ : 'routes: no config found — run "deeplink-parity init" to compare route tables';
184
202
  if (opts.json) {
185
203
  console.log(JSON.stringify(payload, null, 2));
186
204
  }
187
205
  else {
188
206
  if (opts.format === 'github')
189
207
  printGithubAnnotations(result.findings);
190
- printReport(result.findings, result.domains);
208
+ printReport(result.findings, result.domains, { version: pkg.version, routesLine });
191
209
  }
192
210
  process.exit(exitCodeFor(result.findings));
193
211
  }
@@ -10,10 +10,14 @@ const LABEL = { error: 'ERROR', warn: 'WARN ', info: 'INFO ' };
10
10
  const ORDER = ['error', 'warn', 'info'];
11
11
  const useColor = process.stdout.isTTY && !process.env.NO_COLOR;
12
12
  const paint = (s, c) => (useColor ? `${c}${s}${RESET}` : s);
13
- export function printReport(findings, checkedDomains) {
13
+ export function printReport(findings, checkedDomains, meta) {
14
14
  // every escape goes through paint(), which is a no-op when stdout is not a terminal
15
15
  const separator = paint('·', DIM);
16
- console.log(`\n${paint('deeplink-parity', BOLD)} ${separator} ${checkedDomains.length} domain(s) checked\n`);
16
+ const version = meta ? ` ${paint(`v${meta.version}`, DIM)}` : '';
17
+ console.log(`\n${paint('deeplink-parity', BOLD)}${version} ${separator} ${checkedDomains.length} domain(s) checked`);
18
+ if (meta)
19
+ console.log(paint(meta.routesLine, DIM));
20
+ console.log();
17
21
  if (findings.length === 0) {
18
22
  console.log(paint('No problems found', ''));
19
23
  console.log();
@@ -53,8 +53,8 @@ const SHAPES = [
53
53
  { name: 'when/switch branch', pattern: String.raw `"(\/[^"\s]+)"\s*->` },
54
54
  { name: 'any path string', pattern: String.raw `"(\/[a-z0-9][a-z0-9\/_-]*)"` },
55
55
  ];
56
- export async function suggestRegex(file) {
57
- const content = await readFile(file, 'utf8');
56
+ export async function suggestRegex(files) {
57
+ const content = (await Promise.all(files.map((f) => readFile(f, 'utf8')))).join('\n');
58
58
  const suggestions = [];
59
59
  for (const shape of SHAPES) {
60
60
  const paths = [
@@ -13,32 +13,41 @@ function rootRelative(file, roots) {
13
13
  }
14
14
  return file;
15
15
  }
16
- async function pickFile(rl, platform, candidates, yes) {
16
+ async function pickFiles(rl, platform, candidates, yes) {
17
17
  const top = candidates.filter((c) => c.platform === platform).slice(0, 5);
18
18
  if (top.length === 0) {
19
19
  if (yes)
20
- return undefined;
20
+ return [];
21
21
  const manual = (await rl.question(`No ${platform} route-file candidates found. Enter a path, or leave blank to skip: `)).trim();
22
- return manual || undefined;
22
+ return manual ? [manual] : [];
23
23
  }
24
24
  if (yes)
25
- return top[0].file;
25
+ return [top[0].file];
26
26
  console.log(`\n${platform === 'ios' ? 'iOS' : 'Android'} route file candidates:`);
27
27
  top.forEach((c, i) => console.log(` ${i + 1}. ${c.file} (${c.pathStrings} path strings)`));
28
28
  console.log(` m. enter a path manually`);
29
29
  console.log(` s. skip ${platform}`);
30
- const answer = (await rl.question('> ')).trim().toLowerCase();
31
- if (answer === 's' || answer === '')
32
- return undefined;
33
- if (answer === 'm') {
34
- const manual = (await rl.question('path: ')).trim();
35
- return manual || undefined;
30
+ console.log(` (several numbers select several files, e.g. "1 2")`);
31
+ // an answer that cannot be interpreted is asked again — never silently narrowed
32
+ for (;;) {
33
+ const answer = (await rl.question('> ')).trim().toLowerCase();
34
+ if (answer === 's' || answer === '')
35
+ return [];
36
+ if (answer === 'm') {
37
+ const manual = (await rl.question('path: ')).trim();
38
+ return manual ? [manual] : [];
39
+ }
40
+ const tokens = answer.split(/[,\s]+/);
41
+ const indexes = tokens.map((t) => Number.parseInt(t, 10));
42
+ const valid = indexes.length > 0 &&
43
+ indexes.every((n, i) => Number.isInteger(n) && String(n) === tokens[i] && n >= 1 && n <= top.length);
44
+ if (valid)
45
+ return [...new Set(indexes)].map((n) => top[n - 1].file);
46
+ console.log(` Could not read "${answer}" — numbers 1-${top.length}, "m" or "s".`);
36
47
  }
37
- const index = Number.parseInt(answer, 10);
38
- return Number.isInteger(index) && index >= 1 && index <= top.length ? top[index - 1].file : undefined;
39
48
  }
40
- async function pickRegex(rl, file, yes) {
41
- const suggestions = await suggestRegex(file);
49
+ async function pickRegex(rl, files, yes) {
50
+ const suggestions = await suggestRegex(files);
42
51
  if (suggestions.length > 0) {
43
52
  const best = suggestions[0];
44
53
  const preview = best.paths.slice(0, PREVIEW).join(', ');
@@ -70,13 +79,15 @@ export async function runInit(roots, yes) {
70
79
  try {
71
80
  const picks = {};
72
81
  for (const platform of ['ios', 'android']) {
73
- const file = await pickFile(rl, platform, candidates, yes);
74
- if (!file)
82
+ const files = await pickFiles(rl, platform, candidates, yes);
83
+ if (files.length === 0)
75
84
  continue;
76
- const match = await pickRegex(rl, file, yes);
85
+ const match = await pickRegex(rl, files, yes);
77
86
  if (!match)
78
87
  continue;
79
- picks[platform] = { file: rootRelative(file, roots), match };
88
+ const relative = files.map((f) => rootRelative(f, roots));
89
+ picks[platform] =
90
+ relative.length === 1 ? { file: relative[0], match } : { files: relative, match };
80
91
  }
81
92
  if (!picks.ios && !picks.android) {
82
93
  console.log('\nNothing selected — no config written.');
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "deeplink-parity",
3
- "version": "0.6.1",
3
+ "version": "0.6.3",
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": {