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.
Files changed (112) hide show
  1. package/CHANGELOG.md +24 -0
  2. package/LICENSE +21 -0
  3. package/README.md +188 -0
  4. package/dist/checks/calculateExitCode.d.ts +4 -0
  5. package/dist/checks/calculateExitCode.js +4 -0
  6. package/dist/checks/calculateExitCode.js.map +1 -0
  7. package/dist/checks/checkAasaAppIds.d.ts +2 -0
  8. package/dist/checks/checkAasaAppIds.js +15 -0
  9. package/dist/checks/checkAasaAppIds.js.map +1 -0
  10. package/dist/checks/checkAppIdentifiers.d.ts +2 -0
  11. package/dist/checks/checkAppIdentifiers.js +13 -0
  12. package/dist/checks/checkAppIdentifiers.js.map +1 -0
  13. package/dist/checks/checkAssetlinks.d.ts +2 -0
  14. package/dist/checks/checkAssetlinks.js +21 -0
  15. package/dist/checks/checkAssetlinks.js.map +1 -0
  16. package/dist/checks/checkAssociatedDomains.d.ts +3 -0
  17. package/dist/checks/checkAssociatedDomains.js +7 -0
  18. package/dist/checks/checkAssociatedDomains.js.map +1 -0
  19. package/dist/checks/checkAutoVerifyHosts.d.ts +2 -0
  20. package/dist/checks/checkAutoVerifyHosts.js +6 -0
  21. package/dist/checks/checkAutoVerifyHosts.js.map +1 -0
  22. package/dist/checks/checkConfig.d.ts +2 -0
  23. package/dist/checks/checkConfig.js +11 -0
  24. package/dist/checks/checkConfig.js.map +1 -0
  25. package/dist/checks/checkDeadLinks.d.ts +2 -0
  26. package/dist/checks/checkDeadLinks.js +9 -0
  27. package/dist/checks/checkDeadLinks.js.map +1 -0
  28. package/dist/checks/checkRemote.d.ts +2 -0
  29. package/dist/checks/checkRemote.js +4 -0
  30. package/dist/checks/checkRemote.js.map +1 -0
  31. package/dist/checks/checkScheme.d.ts +3 -0
  32. package/dist/checks/checkScheme.js +13 -0
  33. package/dist/checks/checkScheme.js.map +1 -0
  34. package/dist/checks/checkUnreachableRoutes.d.ts +2 -0
  35. package/dist/checks/checkUnreachableRoutes.js +11 -0
  36. package/dist/checks/checkUnreachableRoutes.js.map +1 -0
  37. package/dist/checks/runChecks.d.ts +2 -0
  38. package/dist/checks/runChecks.js +8 -0
  39. package/dist/checks/runChecks.js.map +1 -0
  40. package/dist/checks/targetMatchesRoute.d.ts +2 -0
  41. package/dist/checks/targetMatchesRoute.js +14 -0
  42. package/dist/checks/targetMatchesRoute.js.map +1 -0
  43. package/dist/commands/check.d.ts +21 -0
  44. package/dist/commands/check.js +42 -0
  45. package/dist/commands/check.js.map +1 -0
  46. package/dist/config/findingExplanations.d.ts +2 -0
  47. package/dist/config/findingExplanations.js +19 -0
  48. package/dist/config/findingExplanations.js.map +1 -0
  49. package/dist/config/findingSeverities.d.ts +2 -0
  50. package/dist/config/findingSeverities.js +16 -0
  51. package/dist/config/findingSeverities.js.map +1 -0
  52. package/dist/index.d.ts +2 -0
  53. package/dist/index.js +59 -0
  54. package/dist/index.js.map +1 -0
  55. package/dist/lib/generateFinding.d.ts +5 -0
  56. package/dist/lib/generateFinding.js +8 -0
  57. package/dist/lib/generateFinding.js.map +1 -0
  58. package/dist/links/extractDomains.d.ts +2 -0
  59. package/dist/links/extractDomains.js +11 -0
  60. package/dist/links/extractDomains.js.map +1 -0
  61. package/dist/links/extractLinkConfig.d.ts +2 -0
  62. package/dist/links/extractLinkConfig.js +15 -0
  63. package/dist/links/extractLinkConfig.js.map +1 -0
  64. package/dist/links/extractLinkTargets.d.ts +2 -0
  65. package/dist/links/extractLinkTargets.js +25 -0
  66. package/dist/links/extractLinkTargets.js.map +1 -0
  67. package/dist/parsers/expoConfig.d.ts +4 -0
  68. package/dist/parsers/expoConfig.js +16 -0
  69. package/dist/parsers/expoConfig.js.map +1 -0
  70. package/dist/parsers/routes.d.ts +2 -0
  71. package/dist/parsers/routes.js +23 -0
  72. package/dist/parsers/routes.js.map +1 -0
  73. package/dist/report/human.d.ts +3 -0
  74. package/dist/report/human.js +72 -0
  75. package/dist/report/human.js.map +1 -0
  76. package/dist/report/json.d.ts +3 -0
  77. package/dist/report/json.js +19 -0
  78. package/dist/report/json.js.map +1 -0
  79. package/dist/routes/parseRoutePath.d.ts +3 -0
  80. package/dist/routes/parseRoutePath.js +53 -0
  81. package/dist/routes/parseRoutePath.js.map +1 -0
  82. package/dist/routes/routeHandlesPath.d.ts +2 -0
  83. package/dist/routes/routeHandlesPath.js +12 -0
  84. package/dist/routes/routeHandlesPath.js.map +1 -0
  85. package/dist/routes/routeHandlesPrefix.d.ts +2 -0
  86. package/dist/routes/routeHandlesPrefix.js +15 -0
  87. package/dist/routes/routeHandlesPrefix.js.map +1 -0
  88. package/dist/sources/aasa.d.ts +2 -0
  89. package/dist/sources/aasa.js +16 -0
  90. package/dist/sources/aasa.js.map +1 -0
  91. package/dist/sources/assetlinks.d.ts +2 -0
  92. package/dist/sources/assetlinks.js +13 -0
  93. package/dist/sources/assetlinks.js.map +1 -0
  94. package/dist/sources/httpFetcher.d.ts +2 -0
  95. package/dist/sources/httpFetcher.js +5 -0
  96. package/dist/sources/httpFetcher.js.map +1 -0
  97. package/dist/sources/loadAssociations.d.ts +2 -0
  98. package/dist/sources/loadAssociations.js +49 -0
  99. package/dist/sources/loadAssociations.js.map +1 -0
  100. package/dist/suppressions/applySuppressions.d.ts +5 -0
  101. package/dist/suppressions/applySuppressions.js +38 -0
  102. package/dist/suppressions/applySuppressions.js.map +1 -0
  103. package/dist/suppressions/parseSuppressionConfig.d.ts +2 -0
  104. package/dist/suppressions/parseSuppressionConfig.js +28 -0
  105. package/dist/suppressions/parseSuppressionConfig.js.map +1 -0
  106. package/dist/suppressions/readSuppressionConfig.d.ts +5 -0
  107. package/dist/suppressions/readSuppressionConfig.js +32 -0
  108. package/dist/suppressions/readSuppressionConfig.js.map +1 -0
  109. package/dist/types.d.ts +92 -0
  110. package/dist/types.js +15 -0
  111. package/dist/types.js.map +1 -0
  112. package/package.json +83 -0
@@ -0,0 +1,2 @@
1
+ import type { Finding, FindingCode } from '../types.js';
2
+ export declare const FINDING_SEVERITIES: Record<FindingCode, Finding['severity']>;
@@ -0,0 +1,16 @@
1
+ export const FINDING_SEVERITIES = {
2
+ DL001: 'error',
3
+ DL002: 'warn',
4
+ DL003: 'error',
5
+ DL101: 'error',
6
+ DL102: 'error',
7
+ DL103: 'error',
8
+ DL104: 'warn',
9
+ DL201: 'error',
10
+ DL202: 'error',
11
+ DL203: 'error',
12
+ DL204: 'warn',
13
+ DL901: 'warn',
14
+ DL902: 'warn',
15
+ };
16
+ //# sourceMappingURL=findingSeverities.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"findingSeverities.js","sourceRoot":"","sources":["../../src/config/findingSeverities.ts"],"names":[],"mappings":"AAEA,MAAM,CAAC,MAAM,kBAAkB,GAA6C;IAC1E,KAAK,EAAE,OAAO;IACd,KAAK,EAAE,MAAM;IACb,KAAK,EAAE,OAAO;IACd,KAAK,EAAE,OAAO;IACd,KAAK,EAAE,OAAO;IACd,KAAK,EAAE,OAAO;IACd,KAAK,EAAE,MAAM;IACb,KAAK,EAAE,OAAO;IACd,KAAK,EAAE,OAAO;IACd,KAAK,EAAE,OAAO;IACd,KAAK,EAAE,MAAM;IACb,KAAK,EAAE,MAAM;IACb,KAAK,EAAE,MAAM;CACd,CAAC"}
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export {};
package/dist/index.js ADDED
@@ -0,0 +1,59 @@
1
+ #!/usr/bin/env node
2
+ import { createRequire } from 'node:module';
3
+ import chalk from 'chalk';
4
+ import { Command } from 'commander';
5
+ import { runCheck } from './commands/check.js';
6
+ import { readLinkConfig } from './parsers/expoConfig.js';
7
+ import { parseRoutes } from './parsers/routes.js';
8
+ import * as human from './report/human.js';
9
+ import * as json from './report/json.js';
10
+ import { loadAssociations } from './sources/loadAssociations.js';
11
+ import { readSuppressionConfig } from './suppressions/readSuppressionConfig.js';
12
+ const require = createRequire(import.meta.url);
13
+ const pkg = require('../package.json');
14
+ const program = new Command();
15
+ program.name('deeplink-doctor').description(pkg.description).version(pkg.version, '-v, --version');
16
+ program
17
+ .command('routes')
18
+ .description('Print the route tree parsed from the app/ directory')
19
+ .option('--json', 'Emit machine-readable JSON; suppress human output')
20
+ .action((options) => {
21
+ try {
22
+ const routes = parseRoutes(process.cwd());
23
+ if (options.json) {
24
+ console.log(json.renderRoutes(routes));
25
+ }
26
+ else {
27
+ console.log(human.renderRoutes(routes));
28
+ }
29
+ }
30
+ catch (error) {
31
+ console.error(error instanceof Error ? error.message : String(error));
32
+ process.exitCode = 2;
33
+ }
34
+ });
35
+ program
36
+ .command('check', { isDefault: true })
37
+ .description('Reconcile routes against native deep-link config and report mismatches')
38
+ .option('--json', 'Emit machine-readable JSON; suppress human output')
39
+ .option('--strict', 'Promote warnings to failures (exit non-zero on any finding)')
40
+ .option('--remote', 'Also fetch and check hosted association files (AASA, assetlinks) — makes network requests')
41
+ .option('--domain <host>', 'Override the domain(s) probed by --remote')
42
+ .option('--config <path>', 'Path to a deeplink.config.json (defaults to the project root)')
43
+ .option('--silent', 'Hide warnings (errors only); ignored when --strict is set')
44
+ .option('--explain', 'Append a long-form explanation to each finding')
45
+ .action(async (options) => {
46
+ if (options.domain && !options.remote) {
47
+ console.warn(chalk.yellow('Warning: --domain has no effect without --remote; no association files will be fetched'));
48
+ }
49
+ const { report, exitCode } = await runCheck(process.cwd(), { readRoutes: parseRoutes, readLinkConfig, readSuppressionConfig, loadAssociations }, options);
50
+ if (exitCode === 2) {
51
+ console.error(report);
52
+ }
53
+ else {
54
+ console.log(report);
55
+ }
56
+ process.exitCode = exitCode;
57
+ });
58
+ program.parse(process.argv);
59
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,5 @@
1
+ import type { Finding, FindingCode } from '../types.js';
2
+ export declare const generateFinding: (code: FindingCode, message: string, extra?: {
3
+ route?: string;
4
+ target?: string;
5
+ }) => Finding;
@@ -0,0 +1,8 @@
1
+ import { FINDING_SEVERITIES } from '../config/findingSeverities.js';
2
+ export const generateFinding = (code, message, extra = {}) => ({
3
+ code,
4
+ severity: FINDING_SEVERITIES[code],
5
+ message,
6
+ ...extra,
7
+ });
8
+ //# sourceMappingURL=generateFinding.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"generateFinding.js","sourceRoot":"","sources":["../../src/lib/generateFinding.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,kBAAkB,EAAE,MAAM,gCAAgC,CAAC;AAGpE,MAAM,CAAC,MAAM,eAAe,GAAG,CAC7B,IAAiB,EACjB,OAAe,EACf,QAA6C,EAAE,EACtC,EAAE,CAAC,CAAC;IACb,IAAI;IACJ,QAAQ,EAAE,kBAAkB,CAAC,IAAI,CAAC;IAClC,OAAO;IACP,GAAG,KAAK;CACT,CAAC,CAAC"}
@@ -0,0 +1,2 @@
1
+ import type { LinkConfig } from '../types.js';
2
+ export declare const extractDomains: (config: LinkConfig) => string[];
@@ -0,0 +1,11 @@
1
+ export const extractDomains = (config) => {
2
+ const associatedDomains = config?.ios?.associatedDomains ?? [];
3
+ const intentFilters = config?.android?.intentFilters ?? [];
4
+ const iosDomains = associatedDomains
5
+ .filter((domain) => domain.startsWith('applinks:'))
6
+ .map((domain) => domain.slice(domain.indexOf(':') + 1, domain.indexOf('?') !== -1 ? domain.indexOf('?') : undefined));
7
+ const androidDomains = intentFilters.flatMap(({ data }) => data.flatMap((d) => (d?.host ? [d.host] : [])));
8
+ const domains = new Set([...iosDomains, ...androidDomains]);
9
+ return [...domains];
10
+ };
11
+ //# sourceMappingURL=extractDomains.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"extractDomains.js","sourceRoot":"","sources":["../../src/links/extractDomains.ts"],"names":[],"mappings":"AAEA,MAAM,CAAC,MAAM,cAAc,GAAG,CAAC,MAAkB,EAAY,EAAE;IAC7D,MAAM,iBAAiB,GAAG,MAAM,EAAE,GAAG,EAAE,iBAAiB,IAAI,EAAE,CAAC;IAC/D,MAAM,aAAa,GAAG,MAAM,EAAE,OAAO,EAAE,aAAa,IAAI,EAAE,CAAC;IAE3D,MAAM,UAAU,GAAG,iBAAiB;SACjC,MAAM,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,MAAM,CAAC,UAAU,CAAC,WAAW,CAAC,CAAC;SAClD,GAAG,CAAC,CAAC,MAAM,EAAE,EAAE,CACd,MAAM,CAAC,KAAK,CACV,MAAM,CAAC,OAAO,CAAC,GAAG,CAAC,GAAG,CAAC,EACvB,MAAM,CAAC,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,SAAS,CAC7D,CACF,CAAC;IAEJ,MAAM,cAAc,GAAG,aAAa,CAAC,OAAO,CAAC,CAAC,EAAE,IAAI,EAAE,EAAE,EAAE,CACxD,IAAI,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAC/C,CAAC;IAEF,MAAM,OAAO,GAAG,IAAI,GAAG,CAAC,CAAC,GAAG,UAAU,EAAE,GAAG,cAAc,CAAC,CAAC,CAAC;IAE5D,OAAO,CAAC,GAAG,OAAO,CAAC,CAAC;AACtB,CAAC,CAAC"}
@@ -0,0 +1,2 @@
1
+ import type { LinkConfig, ResolvedExpoConfig } from '../types.js';
2
+ export declare const extractLinkConfig: (config: ResolvedExpoConfig) => LinkConfig;
@@ -0,0 +1,15 @@
1
+ export const extractLinkConfig = (config) => {
2
+ const normalizedSchemes = typeof config.scheme === 'string' ? [config.scheme] : config.scheme;
3
+ return {
4
+ schemes: normalizedSchemes ?? [],
5
+ ios: {
6
+ ...(config.ios?.bundleIdentifier ? { bundleIdentifier: config.ios.bundleIdentifier } : {}),
7
+ associatedDomains: config.ios?.associatedDomains ?? [],
8
+ },
9
+ android: {
10
+ ...(config.android?.package ? { package: config.android.package } : {}),
11
+ intentFilters: config.android?.intentFilters ?? [],
12
+ },
13
+ };
14
+ };
15
+ //# sourceMappingURL=extractLinkConfig.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"extractLinkConfig.js","sourceRoot":"","sources":["../../src/links/extractLinkConfig.ts"],"names":[],"mappings":"AAEA,MAAM,CAAC,MAAM,iBAAiB,GAAG,CAAC,MAA0B,EAAc,EAAE;IAC1E,MAAM,iBAAiB,GAAG,OAAO,MAAM,CAAC,MAAM,KAAK,QAAQ,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,MAAM,CAAC;IAE9F,OAAO;QACL,OAAO,EAAE,iBAAiB,IAAI,EAAE;QAChC,GAAG,EAAE;YACH,GAAG,CAAC,MAAM,CAAC,GAAG,EAAE,gBAAgB,CAAC,CAAC,CAAC,EAAE,gBAAgB,EAAE,MAAM,CAAC,GAAG,CAAC,gBAAgB,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;YAC1F,iBAAiB,EAAE,MAAM,CAAC,GAAG,EAAE,iBAAiB,IAAI,EAAE;SACvD;QACD,OAAO,EAAE;YACP,GAAG,CAAC,MAAM,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC,CAAC,EAAE,OAAO,EAAE,MAAM,CAAC,OAAO,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;YACvE,aAAa,EAAE,MAAM,CAAC,OAAO,EAAE,aAAa,IAAI,EAAE;SACnD;KACF,CAAC;AACJ,CAAC,CAAC"}
@@ -0,0 +1,2 @@
1
+ import type { LinkConfig, LinkTarget } from '../types.js';
2
+ export declare const extractLinkTargets: (config: LinkConfig) => LinkTarget[];
@@ -0,0 +1,25 @@
1
+ const calculateKind = (pathPrefix, pathPattern) => {
2
+ if (pathPrefix) {
3
+ return 'prefix';
4
+ }
5
+ if (pathPattern) {
6
+ return 'pattern';
7
+ }
8
+ return 'exact';
9
+ };
10
+ export const extractLinkTargets = (config) => {
11
+ const intentFilters = config.android.intentFilters;
12
+ return intentFilters.flatMap(({ data }) => data.map(({ scheme, host, path, pathPrefix, pathPattern }) => {
13
+ const raw = path ?? pathPrefix ?? pathPattern ?? '/';
14
+ const kind = calculateKind(pathPrefix, pathPattern);
15
+ const segments = raw.split('/').filter(Boolean);
16
+ return {
17
+ raw,
18
+ kind,
19
+ segments,
20
+ ...(host !== undefined && { host }),
21
+ ...(scheme !== undefined && { scheme }),
22
+ };
23
+ }));
24
+ };
25
+ //# sourceMappingURL=extractLinkTargets.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"extractLinkTargets.js","sourceRoot":"","sources":["../../src/links/extractLinkTargets.ts"],"names":[],"mappings":"AAEA,MAAM,aAAa,GAAG,CAAC,UAAmB,EAAE,WAAoB,EAAE,EAAE;IAClE,IAAI,UAAU,EAAE,CAAC;QACf,OAAO,QAAQ,CAAC;IAClB,CAAC;IACD,IAAI,WAAW,EAAE,CAAC;QAChB,OAAO,SAAS,CAAC;IACnB,CAAC;IACD,OAAO,OAAO,CAAC;AACjB,CAAC,CAAC;AAEF,MAAM,CAAC,MAAM,kBAAkB,GAAG,CAAC,MAAkB,EAAgB,EAAE;IACrE,MAAM,aAAa,GAAG,MAAM,CAAC,OAAO,CAAC,aAAa,CAAC;IAEnD,OAAO,aAAa,CAAC,OAAO,CAAC,CAAC,EAAE,IAAI,EAAE,EAAE,EAAE,CACxC,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,MAAM,EAAE,IAAI,EAAE,IAAI,EAAE,UAAU,EAAE,WAAW,EAAE,EAAE,EAAE;QAC3D,MAAM,GAAG,GAAG,IAAI,IAAI,UAAU,IAAI,WAAW,IAAI,GAAG,CAAC;QACrD,MAAM,IAAI,GAAG,aAAa,CAAC,UAAU,EAAE,WAAW,CAAC,CAAC;QACpD,MAAM,QAAQ,GAAG,GAAG,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC;QAEhD,OAAO;YACL,GAAG;YACH,IAAI;YACJ,QAAQ;YACR,GAAG,CAAC,IAAI,KAAK,SAAS,IAAI,EAAE,IAAI,EAAE,CAAC;YACnC,GAAG,CAAC,MAAM,KAAK,SAAS,IAAI,EAAE,MAAM,EAAE,CAAC;SACxC,CAAC;IACJ,CAAC,CAAC,CACH,CAAC;AACJ,CAAC,CAAC"}
@@ -0,0 +1,4 @@
1
+ import type { LinkConfig } from '../types.js';
2
+ type ConfigRunner = (projectRoot: string) => string;
3
+ export declare const readLinkConfig: (projectRoot: string, run?: ConfigRunner) => LinkConfig;
4
+ export {};
@@ -0,0 +1,16 @@
1
+ import { execFileSync } from 'node:child_process';
2
+ import { extractLinkConfig } from '../links/extractLinkConfig.js';
3
+ const runExpoConfig = (projectRoot) => execFileSync('npx', ['expo', 'config', '--json'], { cwd: projectRoot, encoding: 'utf8' });
4
+ export const readLinkConfig = (projectRoot, run = runExpoConfig) => {
5
+ try {
6
+ const runOutput = run(projectRoot);
7
+ const parsed = JSON.parse(runOutput);
8
+ return extractLinkConfig(parsed);
9
+ }
10
+ catch (error) {
11
+ const reason = error instanceof Error ? error.message : String(error);
12
+ throw new Error(`Failed to read Expo config via \`npx expo config --json\` in "${projectRoot}": ${reason}. ` +
13
+ `Ensure this is an Expo project with expo installed.`, { cause: error });
14
+ }
15
+ };
16
+ //# sourceMappingURL=expoConfig.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"expoConfig.js","sourceRoot":"","sources":["../../src/parsers/expoConfig.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAClD,OAAO,EAAE,iBAAiB,EAAE,MAAM,+BAA+B,CAAC;AAKlE,MAAM,aAAa,GAAiB,CAAC,WAAW,EAAE,EAAE,CAClD,YAAY,CAAC,KAAK,EAAE,CAAC,MAAM,EAAE,QAAQ,EAAE,QAAQ,CAAC,EAAE,EAAE,GAAG,EAAE,WAAW,EAAE,QAAQ,EAAE,MAAM,EAAE,CAAC,CAAC;AAE5F,MAAM,CAAC,MAAM,cAAc,GAAG,CAC5B,WAAmB,EACnB,MAAoB,aAAa,EACrB,EAAE;IACd,IAAI,CAAC;QACH,MAAM,SAAS,GAAG,GAAG,CAAC,WAAW,CAAC,CAAC;QACnC,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,SAAS,CAAC,CAAC;QAErC,OAAO,iBAAiB,CAAC,MAA4B,CAAC,CAAC;IACzD,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,MAAM,MAAM,GAAG,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;QACtE,MAAM,IAAI,KAAK,CACb,iEAAiE,WAAW,MAAM,MAAM,IAAI;YAC1F,qDAAqD,EACvD,EAAE,KAAK,EAAE,KAAK,EAAE,CACjB,CAAC;IACJ,CAAC;AACH,CAAC,CAAC"}
@@ -0,0 +1,2 @@
1
+ import type { PathData } from '../types.js';
2
+ export declare const parseRoutes: (projectRoot: string) => PathData[];
@@ -0,0 +1,23 @@
1
+ import { existsSync, readdirSync } from 'node:fs';
2
+ import { join, relative, sep } from 'node:path';
3
+ import { parseRoutePath } from '../routes/parseRoutePath.js';
4
+ const ROUTE_FILE = /(?<!\.(d|test|stories|spec))\.(js|ts|jsx|tsx)$/;
5
+ const resolveAppDir = (projectRoot) => {
6
+ const appPath = join(projectRoot, 'app');
7
+ if (existsSync(appPath)) {
8
+ return appPath;
9
+ }
10
+ const srcAppPath = join(projectRoot, 'src', 'app');
11
+ if (existsSync(srcAppPath)) {
12
+ return srcAppPath;
13
+ }
14
+ throw new Error(`No expo-router app directory found in "${projectRoot}" (looked for ./app and ./src/app).`);
15
+ };
16
+ export const parseRoutes = (projectRoot) => {
17
+ const appDir = resolveAppDir(projectRoot);
18
+ return readdirSync(appDir, { recursive: true, withFileTypes: true })
19
+ .filter((entry) => entry.isFile() && ROUTE_FILE.test(entry.name))
20
+ .map(({ name, parentPath }) => parseRoutePath(relative(appDir, join(parentPath, name)).split(sep).join('/')))
21
+ .sort((a, b) => a.filePath.localeCompare(b.filePath));
22
+ };
23
+ //# sourceMappingURL=routes.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"routes.js","sourceRoot":"","sources":["../../src/parsers/routes.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,WAAW,EAAE,MAAM,SAAS,CAAC;AAClD,OAAO,EAAE,IAAI,EAAE,QAAQ,EAAE,GAAG,EAAE,MAAM,WAAW,CAAC;AAChD,OAAO,EAAE,cAAc,EAAE,MAAM,6BAA6B,CAAC;AAG7D,MAAM,UAAU,GAAG,gDAAgD,CAAC;AAEpE,MAAM,aAAa,GAAG,CAAC,WAAmB,EAAU,EAAE;IACpD,MAAM,OAAO,GAAG,IAAI,CAAC,WAAW,EAAE,KAAK,CAAC,CAAC;IACzC,IAAI,UAAU,CAAC,OAAO,CAAC,EAAE,CAAC;QACxB,OAAO,OAAO,CAAC;IACjB,CAAC;IACD,MAAM,UAAU,GAAG,IAAI,CAAC,WAAW,EAAE,KAAK,EAAE,KAAK,CAAC,CAAC;IACnD,IAAI,UAAU,CAAC,UAAU,CAAC,EAAE,CAAC;QAC3B,OAAO,UAAU,CAAC;IACpB,CAAC;IACD,MAAM,IAAI,KAAK,CACb,0CAA0C,WAAW,qCAAqC,CAC3F,CAAC;AACJ,CAAC,CAAC;AAEF,MAAM,CAAC,MAAM,WAAW,GAAG,CAAC,WAAmB,EAAc,EAAE;IAC7D,MAAM,MAAM,GAAG,aAAa,CAAC,WAAW,CAAC,CAAC;IAE1C,OAAO,WAAW,CAAC,MAAM,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,aAAa,EAAE,IAAI,EAAE,CAAC;SACjE,MAAM,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,MAAM,EAAE,IAAI,UAAU,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;SAChE,GAAG,CAAC,CAAC,EAAE,IAAI,EAAE,UAAU,EAAE,EAAE,EAAE,CAC5B,cAAc,CAAC,QAAQ,CAAC,MAAM,EAAE,IAAI,CAAC,UAAU,EAAE,IAAI,CAAC,CAAC,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAC9E;SACA,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,QAAQ,CAAC,aAAa,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC;AAC1D,CAAC,CAAC"}
@@ -0,0 +1,3 @@
1
+ import type { Finding, PathData } from '../types.js';
2
+ export declare const renderRoutes: (routes: PathData[]) => string;
3
+ export declare const renderFindings: (findings: Finding[], suppressed?: Finding[], explain?: boolean) => string;
@@ -0,0 +1,72 @@
1
+ import chalk from 'chalk';
2
+ import { FINDING_EXPLANATIONS } from '../config/findingExplanations.js';
3
+ import { isLinkableRoute } from '../routes/parseRoutePath.js';
4
+ // Greedy word-wrap into lines no wider than `width`, without mutation.
5
+ const wrap = (text, width) => text.split(' ').reduce((lines, word) => {
6
+ const last = lines.at(-1);
7
+ if (last !== undefined && `${last} ${word}`.length <= width) {
8
+ return [...lines.slice(0, -1), `${last} ${word}`];
9
+ }
10
+ return [...lines, word];
11
+ }, []);
12
+ const tagsFor = (route) => {
13
+ const tags = [
14
+ [route.segments.some((s) => s.kind === 'dynamic'), 'dynamic'],
15
+ [route.segments.some((s) => s.kind === 'catchall'), 'catchall'],
16
+ [route.inGroup, 'group'],
17
+ [route.isLayout, 'layout'],
18
+ [route.isSpecial, 'special'],
19
+ [route.isApi, 'api'],
20
+ ];
21
+ return tags.filter(([active]) => active).map(([, label]) => label);
22
+ };
23
+ export const renderRoutes = (routes) => {
24
+ if (routes.length === 0) {
25
+ return chalk.yellow('No routes found in app/.');
26
+ }
27
+ const width = Math.max(...routes.map((route) => route.pathname.length));
28
+ const renderRoute = (route) => {
29
+ const tags = tagsFor(route);
30
+ const suffix = tags.length > 0 ? chalk.dim(` [${tags.join(', ')}]`) : '';
31
+ return ` ${route.pathname.padEnd(width)} ${chalk.dim(route.filePath)}${suffix}`;
32
+ };
33
+ const linkable = routes.filter(isLinkableRoute);
34
+ const other = routes.filter((route) => !isLinkableRoute(route));
35
+ const sections = [chalk.bold(`Routes (${routes.length})`)];
36
+ if (linkable.length > 0) {
37
+ sections.push(linkable.map(renderRoute).join('\n'));
38
+ }
39
+ if (other.length > 0) {
40
+ const heading = chalk.dim('Not link targets (layouts, special files, API routes):');
41
+ sections.push(`${heading}\n${other.map(renderRoute).join('\n')}`);
42
+ }
43
+ return sections.join('\n\n');
44
+ };
45
+ const plural = (count, word) => `${word}${count === 1 ? '' : 's'}`;
46
+ const SEVERITY_LABEL = {
47
+ error: chalk.red('error'),
48
+ warn: chalk.yellow('warn'),
49
+ };
50
+ const renderFinding = (finding, explain) => {
51
+ const line = ` ${chalk.dim(finding.code)} ${SEVERITY_LABEL[finding.severity]} ${finding.message}`;
52
+ if (!explain) {
53
+ return line;
54
+ }
55
+ const explanation = wrap(FINDING_EXPLANATIONS[finding.code], 76)
56
+ .map((wrapped) => chalk.dim(` ${wrapped}`))
57
+ .join('\n');
58
+ return `${line}\n${explanation}`;
59
+ };
60
+ export const renderFindings = (findings, suppressed = [], explain = false) => {
61
+ const suppressedNote = suppressed.length > 0 ? chalk.dim(` (${suppressed.length} suppressed)`) : '';
62
+ if (findings.length === 0) {
63
+ return chalk.green('✓ No deep-link issues found.') + suppressedNote;
64
+ }
65
+ const body = findings.map((finding) => renderFinding(finding, explain));
66
+ const errors = findings.filter((finding) => finding.severity === 'error').length;
67
+ const warnings = findings.filter((finding) => finding.severity === 'warn').length;
68
+ const summary = chalk.bold(`${findings.length} ${plural(findings.length, 'issue')} ` +
69
+ `(${errors} ${plural(errors, 'error')}, ${warnings} ${plural(warnings, 'warning')})`) + suppressedNote;
70
+ return [body.join(explain ? '\n\n' : '\n'), '', summary].join('\n');
71
+ };
72
+ //# sourceMappingURL=human.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"human.js","sourceRoot":"","sources":["../../src/report/human.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,3 @@
1
+ import type { Finding, PathData } from '../types.js';
2
+ export declare const renderRoutes: (routes: PathData[]) => string;
3
+ export declare const renderFindings: (findings: Finding[], suppressed?: Finding[]) => string;
@@ -0,0 +1,19 @@
1
+ import { isLinkableRoute } from '../routes/parseRoutePath.js';
2
+ export const renderRoutes = (routes) => JSON.stringify({
3
+ summary: {
4
+ total: routes.length,
5
+ linkable: routes.filter(isLinkableRoute).length,
6
+ },
7
+ routes,
8
+ }, null, 2);
9
+ export const renderFindings = (findings, suppressed = []) => JSON.stringify({
10
+ summary: {
11
+ total: findings.length,
12
+ errors: findings.filter((finding) => finding.severity === 'error').length,
13
+ warnings: findings.filter((finding) => finding.severity === 'warn').length,
14
+ suppressed: suppressed.length,
15
+ },
16
+ findings,
17
+ suppressed,
18
+ }, null, 2);
19
+ //# sourceMappingURL=json.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"json.js","sourceRoot":"","sources":["../../src/report/json.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,MAAM,6BAA6B,CAAC;AAG9D,MAAM,CAAC,MAAM,YAAY,GAAG,CAAC,MAAkB,EAAU,EAAE,CACzD,IAAI,CAAC,SAAS,CACZ;IACE,OAAO,EAAE;QACP,KAAK,EAAE,MAAM,CAAC,MAAM;QACpB,QAAQ,EAAE,MAAM,CAAC,MAAM,CAAC,eAAe,CAAC,CAAC,MAAM;KAChD;IACD,MAAM;CACP,EACD,IAAI,EACJ,CAAC,CACF,CAAC;AAEJ,MAAM,CAAC,MAAM,cAAc,GAAG,CAAC,QAAmB,EAAE,aAAwB,EAAE,EAAU,EAAE,CACxF,IAAI,CAAC,SAAS,CACZ;IACE,OAAO,EAAE;QACP,KAAK,EAAE,QAAQ,CAAC,MAAM;QACtB,MAAM,EAAE,QAAQ,CAAC,MAAM,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,OAAO,CAAC,QAAQ,KAAK,OAAO,CAAC,CAAC,MAAM;QACzE,QAAQ,EAAE,QAAQ,CAAC,MAAM,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,OAAO,CAAC,QAAQ,KAAK,MAAM,CAAC,CAAC,MAAM;QAC1E,UAAU,EAAE,UAAU,CAAC,MAAM;KAC9B;IACD,QAAQ;IACR,UAAU;CACX,EACD,IAAI,EACJ,CAAC,CACF,CAAC"}
@@ -0,0 +1,3 @@
1
+ import type { PathData } from '../types.js';
2
+ export declare const parseRoutePath: (path: string) => PathData;
3
+ export declare const isLinkableRoute: (pathData: PathData) => boolean;
@@ -0,0 +1,53 @@
1
+ const INDEX_REGEX = /\/?index\.(js|ts|jsx|tsx)$/;
2
+ const LAYOUT_REGEX = /\/?_layout\.(js|ts|jsx|tsx)$/;
3
+ const API_REGEX = /\+api\.(js|ts)$/;
4
+ const SPECIAL_REGEX = /^\+(not-found|html|native-intent)\.(js|ts|jsx|tsx)$/;
5
+ const GROUP_REGEX = /\/?\([^()]+\)/g;
6
+ const stripFileType = (path) => path.replace(/\.(js|ts|jsx|tsx)$/, '');
7
+ const isGroupSegment = (segment) => segment.startsWith('(') && segment.endsWith(')');
8
+ const renderSegment = (segment) => {
9
+ if (segment.kind === 'dynamic') {
10
+ return `[${segment.value}]`;
11
+ }
12
+ if (segment.kind === 'catchall') {
13
+ return `[...${segment.value}]`;
14
+ }
15
+ return segment.value;
16
+ };
17
+ const formatSegment = (segmentPath) => {
18
+ const segment = stripFileType(segmentPath);
19
+ if (segment.startsWith('[') && segment.endsWith(']')) {
20
+ const isCatchall = segment.startsWith('[...');
21
+ const segmentName = segment.replace(/^\[(\.\.\.)?([^\]]+)\]$/, '$2');
22
+ return {
23
+ kind: isCatchall ? 'catchall' : 'dynamic',
24
+ value: segmentName,
25
+ };
26
+ }
27
+ return { kind: 'static', value: segment };
28
+ };
29
+ export const parseRoutePath = (path) => {
30
+ const segments = path.split('/');
31
+ const basename = segments.at(-1) ?? '';
32
+ const group = path.match(GROUP_REGEX);
33
+ const isLayout = LAYOUT_REGEX.test(basename);
34
+ const isApi = API_REGEX.test(basename);
35
+ const isSpecial = SPECIAL_REGEX.test(basename);
36
+ const formattedSegments = segments
37
+ .filter((s) => !s.match(INDEX_REGEX) && !s.match(LAYOUT_REGEX) && !isGroupSegment(s))
38
+ .map(formatSegment);
39
+ const pathname = `/${formattedSegments.map(renderSegment).join('/')}`;
40
+ return {
41
+ pathname,
42
+ filePath: path,
43
+ segments: formattedSegments,
44
+ isLayout,
45
+ inGroup: group !== null,
46
+ isApi,
47
+ isSpecial,
48
+ };
49
+ };
50
+ export const isLinkableRoute = (pathData) => {
51
+ return !pathData.isLayout && !pathData.isApi && !pathData.isSpecial;
52
+ };
53
+ //# sourceMappingURL=parseRoutePath.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"parseRoutePath.js","sourceRoot":"","sources":["../../src/routes/parseRoutePath.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ import type { PathData } from '../types.js';
2
+ export declare const routeHandlesPath: (route: PathData, pathSegments: string[]) => boolean;
@@ -0,0 +1,12 @@
1
+ export const routeHandlesPath = (route, pathSegments) => {
2
+ if (pathSegments.length < route.segments.length) {
3
+ return false;
4
+ }
5
+ if (pathSegments.length > route.segments.length) {
6
+ const nonCatchAllSegments = route.segments.filter(({ kind }) => kind !== 'catchall');
7
+ return (route.segments.at(-1)?.kind === 'catchall' &&
8
+ nonCatchAllSegments.every((s, i) => s.kind === 'static' ? s.value === pathSegments[i] : s.kind === 'dynamic'));
9
+ }
10
+ return route.segments.every((segment, i) => segment.kind === 'static' ? segment.value === pathSegments[i] : true);
11
+ };
12
+ //# sourceMappingURL=routeHandlesPath.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"routeHandlesPath.js","sourceRoot":"","sources":["../../src/routes/routeHandlesPath.ts"],"names":[],"mappings":"AAEA,MAAM,CAAC,MAAM,gBAAgB,GAAG,CAAC,KAAe,EAAE,YAAsB,EAAW,EAAE;IACnF,IAAI,YAAY,CAAC,MAAM,GAAG,KAAK,CAAC,QAAQ,CAAC,MAAM,EAAE,CAAC;QAChD,OAAO,KAAK,CAAC;IACf,CAAC;IAED,IAAI,YAAY,CAAC,MAAM,GAAG,KAAK,CAAC,QAAQ,CAAC,MAAM,EAAE,CAAC;QAChD,MAAM,mBAAmB,GAAG,KAAK,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC,EAAE,IAAI,EAAE,EAAE,EAAE,CAAC,IAAI,KAAK,UAAU,CAAC,CAAC;QACrF,OAAO,CACL,KAAK,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,EAAE,IAAI,KAAK,UAAU;YAC1C,mBAAmB,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CACjC,CAAC,CAAC,IAAI,KAAK,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC,KAAK,KAAK,YAAY,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,KAAK,SAAS,CACzE,CACF,CAAC;IACJ,CAAC;IAED,OAAO,KAAK,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC,OAAO,EAAE,CAAC,EAAE,EAAE,CACzC,OAAO,CAAC,IAAI,KAAK,QAAQ,CAAC,CAAC,CAAC,OAAO,CAAC,KAAK,KAAK,YAAY,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CACrE,CAAC;AACJ,CAAC,CAAC"}
@@ -0,0 +1,2 @@
1
+ import type { PathData } from '../types.js';
2
+ export declare const routeHandlesPrefix: (route: PathData, pathSegments: string[]) => boolean;
@@ -0,0 +1,15 @@
1
+ export const routeHandlesPrefix = (route, pathSegments) => {
2
+ if (pathSegments.length === 0) {
3
+ return true;
4
+ }
5
+ if (pathSegments.length < route.segments.length) {
6
+ return pathSegments.every((segment, i) => route.segments[i].kind === 'static' ? route.segments[i].value === segment : true);
7
+ }
8
+ if (pathSegments.length > route.segments.length) {
9
+ const nonCatchAllSegments = route.segments.filter(({ kind }) => kind !== 'catchall');
10
+ return (route.segments.at(-1)?.kind === 'catchall' &&
11
+ nonCatchAllSegments.every((s, i) => s.kind === 'static' ? s.value === pathSegments[i] : s.kind === 'dynamic'));
12
+ }
13
+ return route.segments.every((segment, i) => segment.kind === 'static' ? segment.value === pathSegments[i] : true);
14
+ };
15
+ //# sourceMappingURL=routeHandlesPrefix.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"routeHandlesPrefix.js","sourceRoot":"","sources":["../../src/routes/routeHandlesPrefix.ts"],"names":[],"mappings":"AAEA,MAAM,CAAC,MAAM,kBAAkB,GAAG,CAAC,KAAe,EAAE,YAAsB,EAAW,EAAE;IACrF,IAAI,YAAY,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QAC9B,OAAO,IAAI,CAAC;IACd,CAAC;IAED,IAAI,YAAY,CAAC,MAAM,GAAG,KAAK,CAAC,QAAQ,CAAC,MAAM,EAAE,CAAC;QAChD,OAAO,YAAY,CAAC,KAAK,CAAC,CAAC,OAAO,EAAE,CAAC,EAAE,EAAE,CACvC,KAAK,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,IAAI,KAAK,QAAQ,CAAC,CAAC,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,KAAK,KAAK,OAAO,CAAC,CAAC,CAAC,IAAI,CACjF,CAAC;IACJ,CAAC;IAED,IAAI,YAAY,CAAC,MAAM,GAAG,KAAK,CAAC,QAAQ,CAAC,MAAM,EAAE,CAAC;QAChD,MAAM,mBAAmB,GAAG,KAAK,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC,EAAE,IAAI,EAAE,EAAE,EAAE,CAAC,IAAI,KAAK,UAAU,CAAC,CAAC;QACrF,OAAO,CACL,KAAK,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,EAAE,IAAI,KAAK,UAAU;YAC1C,mBAAmB,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CACjC,CAAC,CAAC,IAAI,KAAK,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC,KAAK,KAAK,YAAY,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,KAAK,SAAS,CACzE,CACF,CAAC;IACJ,CAAC;IAED,OAAO,KAAK,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC,OAAO,EAAE,CAAC,EAAE,EAAE,CACzC,OAAO,CAAC,IAAI,KAAK,QAAQ,CAAC,CAAC,CAAC,OAAO,CAAC,KAAK,KAAK,YAAY,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CACrE,CAAC;AACJ,CAAC,CAAC"}
@@ -0,0 +1,2 @@
1
+ import type { AasaModel } from '../types.js';
2
+ export declare const parseAasa: (body: string) => AasaModel;
@@ -0,0 +1,16 @@
1
+ export const parseAasa = (body) => {
2
+ const data = JSON.parse(body);
3
+ const details = data?.applinks?.details;
4
+ if (!Array.isArray(details)) {
5
+ return { appIDs: [] };
6
+ }
7
+ const appIDs = details.flatMap((detail) => {
8
+ const { appID, appIDs } = (detail ?? {});
9
+ if (Array.isArray(appIDs)) {
10
+ return appIDs.filter((id) => typeof id === 'string');
11
+ }
12
+ return typeof appID === 'string' ? [appID] : [];
13
+ });
14
+ return { appIDs };
15
+ };
16
+ //# sourceMappingURL=aasa.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"aasa.js","sourceRoot":"","sources":["../../src/sources/aasa.ts"],"names":[],"mappings":"AAEA,MAAM,CAAC,MAAM,SAAS,GAAG,CAAC,IAAY,EAAa,EAAE;IACnD,MAAM,IAAI,GAAY,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;IAEvC,MAAM,OAAO,GAAI,IAA6C,EAAE,QAAQ,EAAE,OAAO,CAAC;IAClF,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,OAAO,CAAC,EAAE,CAAC;QAC5B,OAAO,EAAE,MAAM,EAAE,EAAE,EAAE,CAAC;IACxB,CAAC;IAED,MAAM,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC,MAAe,EAAE,EAAE;QACjD,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,GAAG,CAAC,MAAM,IAAI,EAAE,CAA0C,CAAC;QAClF,IAAI,KAAK,CAAC,OAAO,CAAC,MAAM,CAAC,EAAE,CAAC;YAC1B,OAAO,MAAM,CAAC,MAAM,CAAC,CAAC,EAAE,EAAgB,EAAE,CAAC,OAAO,EAAE,KAAK,QAAQ,CAAC,CAAC;QACrE,CAAC;QACD,OAAO,OAAO,KAAK,KAAK,QAAQ,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;IAClD,CAAC,CAAC,CAAC;IAEH,OAAO,EAAE,MAAM,EAAE,CAAC;AACpB,CAAC,CAAC"}
@@ -0,0 +1,2 @@
1
+ import type { AssetlinkEntry } from '../types.js';
2
+ export declare const parseAssetlinks: (body: string) => AssetlinkEntry[];
@@ -0,0 +1,13 @@
1
+ export const parseAssetlinks = (body) => {
2
+ const data = JSON.parse(body);
3
+ if (!Array.isArray(data)) {
4
+ return [];
5
+ }
6
+ return data
7
+ .map((statement) => ({
8
+ packageName: statement?.target?.package_name,
9
+ fingerprints: statement?.target?.sha256_cert_fingerprints ?? [],
10
+ }))
11
+ .filter((t) => !!t.packageName);
12
+ };
13
+ //# sourceMappingURL=assetlinks.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"assetlinks.js","sourceRoot":"","sources":["../../src/sources/assetlinks.ts"],"names":[],"mappings":"AAIA,MAAM,CAAC,MAAM,eAAe,GAAG,CAAC,IAAY,EAAoB,EAAE;IAChE,MAAM,IAAI,GAAY,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;IAEvC,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,CAAC;QACzB,OAAO,EAAE,CAAC;IACZ,CAAC;IAED,OAAO,IAAI;SACR,GAAG,CAAC,CAAC,SAAuB,EAAE,EAAE,CAAC,CAAC;QACjC,WAAW,EAAE,SAAS,EAAE,MAAM,EAAE,YAAY;QAC5C,YAAY,EAAE,SAAS,EAAE,MAAM,EAAE,wBAAwB,IAAI,EAAE;KAChE,CAAC,CAAC;SACF,MAAM,CAAC,CAAC,CAAC,EAAuB,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,WAAW,CAAC,CAAC;AACzD,CAAC,CAAC"}
@@ -0,0 +1,2 @@
1
+ import type { Fetcher } from '../types.js';
2
+ export declare const httpFetcher: Fetcher;
@@ -0,0 +1,5 @@
1
+ export const httpFetcher = async (url) => {
2
+ const res = await fetch(url);
3
+ return { status: res.status, redirected: res.redirected, body: await res.text() };
4
+ };
5
+ //# sourceMappingURL=httpFetcher.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"httpFetcher.js","sourceRoot":"","sources":["../../src/sources/httpFetcher.ts"],"names":[],"mappings":"AAEA,MAAM,CAAC,MAAM,WAAW,GAAY,KAAK,EAAE,GAAG,EAAE,EAAE;IAChD,MAAM,GAAG,GAAG,MAAM,KAAK,CAAC,GAAG,CAAC,CAAC;IAC7B,OAAO,EAAE,MAAM,EAAE,GAAG,CAAC,MAAM,EAAE,UAAU,EAAE,GAAG,CAAC,UAAU,EAAE,IAAI,EAAE,MAAM,GAAG,CAAC,IAAI,EAAE,EAAE,CAAC;AACpF,CAAC,CAAC"}
@@ -0,0 +1,2 @@
1
+ import type { Associations, Fetcher } from '../types.js';
2
+ export declare const loadAssociations: (domain: string, fetcher?: Fetcher) => Promise<Associations>;
@@ -0,0 +1,49 @@
1
+ import { generateFinding } from '../lib/generateFinding.js';
2
+ import { parseAasa } from './aasa.js';
3
+ import { parseAssetlinks } from './assetlinks.js';
4
+ import { httpFetcher } from './httpFetcher.js';
5
+ const createAasaUrl = (domain) => `https://${domain}/.well-known/apple-app-site-association`;
6
+ const createAssetlinksUrl = (domain) => `https://${domain}/.well-known/assetlinks.json`;
7
+ const associationFinding = (url, reason) => generateFinding('DL201', `${url} ${reason}`, { target: url });
8
+ const fetchFile = async (fetcher, url) => {
9
+ try {
10
+ const { status, redirected, body } = await fetcher(url);
11
+ if (redirected) {
12
+ return { body: null, finding: associationFinding(url, 'is served via a redirect') };
13
+ }
14
+ if (status < 200 || status >= 300) {
15
+ return { body: null, finding: associationFinding(url, `responded with status ${status}`) };
16
+ }
17
+ return { body, finding: null };
18
+ }
19
+ catch {
20
+ return { body: null, finding: associationFinding(url, 'is unreachable') };
21
+ }
22
+ };
23
+ const parseFile = (fetched, url, parse) => {
24
+ if (fetched.body === null) {
25
+ return { value: null, findings: [fetched.finding] };
26
+ }
27
+ try {
28
+ return { value: parse(fetched.body), findings: [] };
29
+ }
30
+ catch {
31
+ return { value: null, findings: [associationFinding(url, 'did not return valid JSON')] };
32
+ }
33
+ };
34
+ export const loadAssociations = async (domain, fetcher = httpFetcher) => {
35
+ const aasaUrl = createAasaUrl(domain);
36
+ const assetlinksUrl = createAssetlinksUrl(domain);
37
+ const [aasaFetch, assetlinksFetch] = await Promise.all([
38
+ fetchFile(fetcher, aasaUrl),
39
+ fetchFile(fetcher, assetlinksUrl),
40
+ ]);
41
+ const aasa = parseFile(aasaFetch, aasaUrl, parseAasa);
42
+ const assetlinks = parseFile(assetlinksFetch, assetlinksUrl, parseAssetlinks);
43
+ return {
44
+ aasa: aasa.value ?? { appIDs: [] },
45
+ assetlinks: assetlinks.value ?? [],
46
+ findings: [...aasa.findings, ...assetlinks.findings],
47
+ };
48
+ };
49
+ //# sourceMappingURL=loadAssociations.js.map